¿Cómo acceder a argumentos de eventos no indexados desde Go types.Log?

Los enlaces Go tienen un tipo llamado Logque tiene los siguientes campos

type Log struct {
    // Consensus fields:
    // address of the contract that generated the event
    Address common.Address `json:"address" gencodec:"required"`
    // list of topics provided by the contract.
    Topics []common.Hash `json:"topics" gencodec:"required"`
    // supplied by the contract, usually ABI-encoded
    Data []byte `json:"data" gencodec:"required"`

    [...]   
}

Digamos que tengo un evento como esteevent SomethingHappened(uint256 indexed id, address indexed participant1, address indexed participant2, uint256 value1, uint256 value2);

Hay 4 temas y se llenan de la siguiente manera, son del tipo common.Hash:

  1. El primer tema es el hash de la firma del evento:SHA3("SomethingHappened(uint256,address,address,uint256,uint256)")
  2. El segundo tema es el codificado uint256que se puede leer usando la gran biblioteca:new(big.Int).SetBytes(log.Topics[1].Bytes())
  3. y 4. son las dos direcciones de los participantes. Dado common.Hashque tiene una longitud de 32 bytes y common.Address20 bytes, puede obtener la dirección de esta manera: common.BytesToAddress(log.Topics[2].Bytes()[12:32]).

Ahora, eso no es tan conveniente de hacer, pero aún es posible. Sin embargo, cuando intentamos acceder a los dos últimos argumentos, me pierdo. Están codificados de alguna manera en el Data []bytecampo de la common.Logestructura. La documentación menciona que están "codificados con ABI", pero no ofrece ninguna forma de hacer nada con esos datos. abigen tampoco tiene soporte para variables de eventos, por lo que realmente no sé cómo acceder a esos valores.

Respuestas (1)

El Datacampo Tipo de registro contiene los argumentos de registro de eventos no indexados , por lo que todo lo que tiene que hacer es decodificarlos en tipos Go.

Entonces, por ejemplo, aquí hay un contrato inteligente simple que emite entradas de registro no indexadas:

pragma solidity ^0.4.24;

contract Store {
  event ItemSet(bytes32 key, bytes32 value);

  mapping (bytes32 => bytes32) public items;

  function setItem(bytes32 key, bytes32 value) external {
    items[key] = value;
    emit ItemSet(key, value);
  }
}

Luego, en su código, después de obtener los registros de eventos, llama al Unpackmétodo del contrato inteligente Go vinculante pasándole la estructura que contiene las propiedades del evento, el nombre del evento de registro del contrato inteligente y, por último, los datos de registro reales.

for _, vLog := range logs {
  event := struct {
    Key   [32]byte
    Value [32]byte
  }{}
  err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
  if err != nil {
    log.Fatal(err)
  }

  fmt.Println(string(event.Key[:]))   // foo
  fmt.Println(string(event.Value[:])) // bar
}

Este es el ejemplo completo de consulta y decodificación de los registros no indexados del contrato inteligente de ejemplo.

package main

import (
    "context"
    "fmt"
    "log"
    "math/big"
    "strings"

    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethclient"

    store "./contracts" // for demo
)

func main() {
    client, err := ethclient.Dial("wss://rinkeby.infura.io/ws")
    if err != nil {
        log.Fatal(err)
    }

    contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
    query := ethereum.FilterQuery{
        FromBlock: big.NewInt(2394201),
        ToBlock:   big.NewInt(2394201),
        Addresses: []common.Address{
            contractAddress,
        },
    }

    logs, err := client.FilterLogs(context.Background(), query)
    if err != nil {
        log.Fatal(err)
    }

    contractAbi, err := abi.JSON(strings.NewReader(string(store.StoreABI)))
    if err != nil {
        log.Fatal(err)
    }

    for _, vLog := range logs {
        event := struct {
            Key   [32]byte
            Value [32]byte
        }{}
        err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Println(string(event.Key[:]))   // foo
        fmt.Println(string(event.Value[:])) // bar

        var topics [4]string
        for i := range vLog.Topics {
            topics[i] = vLog.Topics[i].Hex()
        }

        fmt.Println(topics[0]) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4
    }

    eventSignature := []byte("ItemSet(bytes32,bytes32)")
    hash := crypto.Keccak256Hash(eventSignature)
    fmt.Println(hash.Hex()) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4
}

Para registros indexados, simplemente use log.Topics. El primer tema es siempre el ID del método, que es un hash de la firma de la función de registro de eventos (nombre del método y tipos de argumentos).

Consulte la guía Ethereum Development with Go para obtener más ejemplos.