Как получить доступ к неиндексированным аргументам событий из Go types.Log?

Привязки Go имеют тип Log, который имеет следующие поля:

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"`

    [...]   
}

Допустим, у меня есть такое событиеevent SomethingHappened(uint256 indexed id, address indexed participant1, address indexed participant2, uint256 value1, uint256 value2);

Есть 4 темы и заполняются они следующим образом, они типа common.Hash:

  1. Первая тема — это хеш сигнатуры события:SHA3("SomethingHappened(uint256,address,address,uint256,uint256)")
  2. Второй топик — закодированный uint256, который можно прочитать с помощью большой библиотеки:new(big.Int).SetBytes(log.Topics[1].Bytes())
  3. и 4. два адреса участников. Поскольку common.Hashимеет длину 32 байта и common.Address20 байтов, вы можете получить адрес следующим образом: common.BytesToAddress(log.Topics[2].Bytes()[12:32]).

Теперь это не так удобно делать, но все же возможно. Однако когда мы пытаемся получить доступ к последним двум аргументам, я теряюсь. Они как-то закодированы в Data []byteполе common.Logструктуры. В документации упоминается, что они «закодированы с помощью ABI», но не предлагает способа что-либо сделать с этими данными. abigen также не поддерживает переменные событий, поэтому я действительно не понимаю, как получить доступ к этим значениям.

Ответы (1)

Поле DataТип журнала содержит неиндексированные аргументы журнала событий, поэтому все, что вам нужно сделать, это декодировать их в типы Go.

Так, например, вот простой смарт-контракт, который создает неиндексированные записи журнала:

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);
  }
}

Затем в вашем коде, после извлечения журналов событий, вы вызываете Unpackметод из смарт-контракта Go, передавая ему структуру, содержащую свойства события, имя события журнала смарт-контракта и, наконец, фактические данные журнала.

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
}

Вот полный пример запроса и декодирования неиндексированных журналов примера смарт-контракта.

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
}

Для индексированных журналов вы просто используете log.Topics. Первым разделом всегда является идентификатор метода, который представляет собой хэш сигнатуры функции журнала событий (имя метода и типы аргументов).

Дополнительные примеры см. в руководстве по разработке Ethereum с Go .