概述: 用Go读取智能合约事件教程。

读取事件日志

智能合约可以可选地释放“事件”,其作为交易收据的一部分存储日志。读取这些事件相当简单。首先我们需要构造一个过滤查询。我们从go-ethereum包中导入FilterQuery结构体并用过滤选项初始化它。我们告诉它我们想过滤的区块范围并指定从中读取此日志的合约地址。在示例中,我们将从在智能合约章节)创建的智能合约中读取特定区块所有日志。

  1. query := ethereum.FilterQuery{
  2. FromBlock: big.NewInt(2394201),
  3. ToBlock: big.NewInt(2394201),
  4. Addresses: []common.Address{
  5. contractAddress,
  6. },
  7. }

下一步是调用ethclient的FilterLogs,它接收我们的查询并将返回所有的匹配事件日志。

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

返回的所有日志将是ABI编码,因此它们本身不会非常易读。为了解码日志,我们需要导入我们智能合约的ABI。为此,我们导入编译好的智能合约Go包,它将包含名称格式为<Contract>ABI的外部属性。之后,我们使用go-ethereum中的accounts/abi包的abi.JSON函数返回一个我们可以在Go应用程序中使用的解析过的ABI接口。

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

现在我们可以通过日志进行迭代并将它们解码为我么可以使用的类型。若您回忆起我们的样例合约释放的日志在Solidity中是类型为bytes32,那么Go中的等价物将是[32]byte。我们可以使用这些类型创建一个匿名结构体,并将指针作为第一个参数传递给解析后的ABI接口的Unpack函数,以解码原始的日志数据。第二个参数是我们尝试解码的事件名称,最后一个参数是编码的日志数据。

  1. for _, vLog := range logs {
  2. event := struct {
  3. Key [32]byte
  4. Value [32]byte
  5. }{}
  6. err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
  7. if err != nil {
  8. log.Fatal(err)
  9. }
  10. fmt.Println(string(event.Key[:])) // foo
  11. fmt.Println(string(event.Value[:])) // bar
  12. }

此外,日志结构体包含附加信息,例如,区块摘要,区块号和交易摘要。

  1. fmt.Println(vLog.BlockHash.Hex()) // 0x3404b8c050aa0aacd0223e91b5c32fee6400f357764771d0684fa7b3f448f1a8
  2. fmt.Println(vLog.BlockNumber) // 2394201
  3. fmt.Println(vLog.TxHash.Hex()) // 0x280201eda63c9ff6f305fcee51d5eb86167fab40ca3108ec784e8652a0e2b1a6

主题(Topics)

若您的solidity事件包含indexed事件类型,那么它们将成为主题而不是日志的数据属性的一部分。在solidity中您最多只能有4个主题,但只有3个可索引的事件类型。第一个主题总是事件的签名。我们的示例合约不包含可索引的事件,但如果它确实包含,这是如何读取事件主题。

  1. var topics [4]string
  2. for i := range vLog.Topics {
  3. topics[i] = vLog.Topics[i].Hex()
  4. }
  5. fmt.Println(topics[0]) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4

正如您所见,首个主题只是被哈希过的事件签名。

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

这就是阅读和解析日志的全部内容。要学习如何订阅日志,阅读上个章节)。

完整代码

命令

  1. solc --abi Store.sol
  2. solc --bin Store.sol
  3. abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

Store.sol

  1. pragma solidity ^0.4.24;
  2. contract Store {
  3. event ItemSet(bytes32 key, bytes32 value);
  4. string public version;
  5. mapping (bytes32 => bytes32) public items;
  6. constructor(string _version) public {
  7. version = _version;
  8. }
  9. function setItem(bytes32 key, bytes32 value) external {
  10. items[key] = value;
  11. emit ItemSet(key, value);
  12. }
  13. }

event_read.go

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "math/big"
  7. "strings"
  8. "github.com/ethereum/go-ethereum"
  9. "github.com/ethereum/go-ethereum/accounts/abi"
  10. "github.com/ethereum/go-ethereum/common"
  11. "github.com/ethereum/go-ethereum/crypto"
  12. "github.com/ethereum/go-ethereum/ethclient"
  13. store "./contracts" // for demo
  14. )
  15. func main() {
  16. client, err := ethclient.Dial("wss://rinkeby.infura.io/ws")
  17. if err != nil {
  18. log.Fatal(err)
  19. }
  20. contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
  21. query := ethereum.FilterQuery{
  22. FromBlock: big.NewInt(2394201),
  23. ToBlock: big.NewInt(2394201),
  24. Addresses: []common.Address{
  25. contractAddress,
  26. },
  27. }
  28. logs, err := client.FilterLogs(context.Background(), query)
  29. if err != nil {
  30. log.Fatal(err)
  31. }
  32. contractAbi, err := abi.JSON(strings.NewReader(string(store.StoreABI)))
  33. if err != nil {
  34. log.Fatal(err)
  35. }
  36. for _, vLog := range logs {
  37. fmt.Println(vLog.BlockHash.Hex()) // 0x3404b8c050aa0aacd0223e91b5c32fee6400f357764771d0684fa7b3f448f1a8
  38. fmt.Println(vLog.BlockNumber) // 2394201
  39. fmt.Println(vLog.TxHash.Hex()) // 0x280201eda63c9ff6f305fcee51d5eb86167fab40ca3108ec784e8652a0e2b1a6
  40. event := struct {
  41. Key [32]byte
  42. Value [32]byte
  43. }{}
  44. err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
  45. if err != nil {
  46. log.Fatal(err)
  47. }
  48. fmt.Println(string(event.Key[:])) // foo
  49. fmt.Println(string(event.Value[:])) // bar
  50. var topics [4]string
  51. for i := range vLog.Topics {
  52. topics[i] = vLog.Topics[i].Hex()
  53. }
  54. fmt.Println(topics[0]) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4
  55. }
  56. eventSignature := []byte("ItemSet(bytes32,bytes32)")
  57. hash := crypto.Keccak256Hash(eventSignature)
  58. fmt.Println(hash.Hex()) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4
  59. }
  1. $ solc --version
  2. 0.4.24+commit.e67f0147.Emscripten.clang