Wie greife ich auf nicht indizierte Ereignisargumente von Go types.Log zu?

Die Go-Bindungen haben einen Typ namens Logmit den folgenden Feldern

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

    [...]   
}

Nehmen wir an, ich habe eine Veranstaltung wie dieseevent SomethingHappened(uint256 indexed id, address indexed participant1, address indexed participant2, uint256 value1, uint256 value2);

Es gibt 4 Themen und sie sind wie folgt gefüllt, sie sind vom Typ common.Hash:

  1. Das erste Thema ist der Hash der Ereignissignatur:SHA3("SomethingHappened(uint256,address,address,uint256,uint256)")
  2. Das zweite Thema ist die Codierung uint256, die mit der großen Bibliothek gelesen werden kann:new(big.Int).SetBytes(log.Topics[1].Bytes())
  3. und 4. sind die beiden Adressen der Teilnehmer. Da common.Hashes 32 Bytes lang common.Addressist und 20 Bytes beträgt, können Sie die Adresse wie folgt erhalten: common.BytesToAddress(log.Topics[2].Bytes()[12:32]).

Nun, das ist nicht so bequem, aber es ist immer noch möglich. Wenn wir jedoch versuchen, auf die letzten beiden Argumente zuzugreifen, verliere ich mich. Sie sind irgendwie im Data []byteFeld der Struktur codiert common.Log. Die Dokumentation erwähnt, dass sie "ABI-kodiert" sind, bietet aber keine Möglichkeit, etwas mit diesen Daten zu tun. abigen hat auch keine Unterstützung für Ereignisvariablen, daher bin ich wirklich verloren, wie ich auf diese Werte zugreifen kann.

Antworten (1)

Das DataFeld Protokolltyp enthält die Argumente des nicht indizierten Ereignisprotokolls, sodass Sie sie nur in Go-Typen decodieren müssen.

Hier ist zum Beispiel ein einfacher Smart Contract, der nicht indizierte Protokolleinträge ausgibt:

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

Dann rufen Sie in Ihrem Code nach dem Abrufen der Ereignisprotokolle die UnpackMethode aus der Smart-Contract-Go-Bindung auf und übergeben ihr die Struktur mit den Ereigniseigenschaften, dem Namen des Smart-Contract-Protokollereignisses und schließlich den eigentlichen Protokolldaten.

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
}

Hier ist das vollständige Beispiel für das Abfragen und Decodieren der nicht indizierten Protokolle des Beispiel-Smart-Vertrags.

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
}

Für indizierte Protokolle verwenden Sie einfach log.Topics. Das erste Thema ist immer die Methoden-ID, die ein Hash der Signatur der Ereignisprotokollfunktion ist (Methodenname und Argumenttypen).

Weitere Beispiele finden Sie im Handbuch Ethereum Development with Go .