理解以太坊事件与日志

想看明白我在写什么,你最好对以太坊智能合约有基本的了解。

0x01 什么是事件

事件是以太坊提供的一种链内链外沟通的一种机制。通过触发事件,智能合约可以通知链外组件某个交易完成了什么事儿。
下面是一个 ERC20 合约里常见的 Transfer 事件定义,通过 event 关键字表明这是一个事件定义声明。

event Transfer(address indexed from, address indexed to, uint256 value);

在 transfer 方法的实现中,我们会像下面代码里展示的这样通过 emit 关键字触发事件的发生。

 function transfer(address _to, uint256 _value) public returns (bool) {
  ...
    emit Transfer(msg.sender, _to, _value);
    return true;
  }

0x02 什么是日志

在以太坊的语境里,日志代表对事件的存储。下面是我从 ropsten 测试网上读取的交易回执,在里面我们可以看到 logs 数据项,这个就是我们这里所说的日志,合约执行时没触发一次事件,在交易回执里的 logs 数据项数组里就会多一个日志条目出来。

> eth.getTransactionReceipt("0xe03fac05ff4dde83fc9267184fd8c08bd78599f950e817dbf7fa4a4d4d319ce2");
{
  blockHash: "0x7eaf6abe64592d10828e136635aa6be6f4d09da3bb5b9fddf87773ee152d657c",
  blockNumber: 4654718,
  contractAddress: null,
  cumulativeGasUsed: 52464,
  from: "0x076979a0b3c87334e5d72e3afcafaa80f7888cac",
  gasUsed: 52464,
  logs: [{
      address: "0x73c2a5b1a32fa8e33101a6ab119203f4417feae4",
      blockHash: "0x7eaf6abe64592d10828e136635aa6be6f4d09da3bb5b9fddf87773ee152d657c",
      blockNumber: 4654718,
      data: "0x0000000000000000000000000000000000000000000000056bc75e2d63100000",
      logIndex: 0,
      removed: false,
      topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000076979a0b3c87334e5d72e3afcafaa80f7888cac", "0x000000000000000000000000cd9f286ba6a3d2df7885f4a2be267fc524d32bd3"],
      transactionHash: "0xe03fac05ff4dde83fc9267184fd8c08bd78599f950e817dbf7fa4a4d4d319ce2",
      transactionIndex: 0
  }],
  logsBloom: "0x20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000008000000000400000000000000000000000000000000000000040000000000000000100000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000200000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000400",
  status: "0x1",
  to: "0x73c2a5b1a32fa8e33101a6ab119203f4417feae4",
  transactionHash: "0xe03fac05ff4dde83fc9267184fd8c08bd78599f950e817dbf7fa4a4d4d319ce2",
  transactionIndex: 0
}

在这个日志里,我们可以看到很多和事件触发上下文相关的信息,比如 合约地址 address、所在区块哈希 blockHash、所在区块号 blockNumber、所属交易哈希 transactionHash 等等。

这里面最核心的就两个数据:topics 和 data。我们这里看到 topics 是个数组,这个数组的第一个元素就代表所触发的事件,是个 256 位的数字,用 16 进制表示。只是看这么个字符串,我们并不能确定这是哪个事件,这时候就需要借助于合约的 ABI 文件。在 ABI 文件中找出 type 为 event 的那些元素,如下面所示:

{
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "name": "owner",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "spender",
          "type": "address"
        },
        {
          "indexed": false,
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Approval",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "name": "from",
          "type": "address"
        },
        {
          "indexed": true,
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Transfer",
      "type": "event"
    }

为了找出 topic 所对应的事件,我们需要计算每个事件的签名并找到匹配的签名。 签名是事件名和输入参数类型的 sha3 散列,参数名被忽略。 对于事件 Transfer(address indexed from, address indexed to, uint256 value),签名将是 sha3 ('Transfer(address,address,uint256)'),这些都是可以从 ABI 中获得。

> web3.sha3("Transfer(address,address,uint256)")
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
> web3.sha3("Approval(address,address,uint256)")
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"

很明显可以看出,"Transfer(address,address,uint256)" 这个事件的签名和上面提到过的 topics 的第一个数据元素是一致的,说明这个日志就是对应的 Transfer 事件。

["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000076979a0b3c87334e5d72e3afcafaa80f7888cac", "0x000000000000000000000000cd9f286ba6a3d2df7885f4a2be267fc524d32bd3"],

现在我们知道了,日志里面 topics 数组里的第一个数据元素就是事件的签名。那么 topics 里其它的元素是什么呢?会看上面截取的 ABI,我们可以看到在事件元素的 inputs 数据项中,有的 indexed 的值为 true,有的为 false,我们在声明一个事件时,也可以指定事件的参数是否 indexed,比如下面这个 Transfer 事件的声明,from 就是表明为 indexed,表明是被索引收录的。

event Transfer(address indexed from, address indexed to, uint256 value);

topics 里的其它数据元素就是被索引收录的事件参数值,所有在 topics 里的内容,都是被索引收录,可以通过 bloom filter 进行过滤的。

日志里还有一个关键内容就是 data,这个比较容易理解,就是触发事件的时候传给事件的实际参数值,值得注意的是这里面只包含未索引的数据项。

0x03 有什么作用

最大的作用就是进行检索了,以太坊提供了基于 http 的 JSON-RPC基于 websockets 事件订阅监听。我们可以提供相应的 topics 对某个事件或事件数据进行检索。这在对 dApp 开发是很有帮助的。

值得注意的时,当我们对事件进行监听的时候,如果是基于 http,这种监听本质上仍然是种定期轮询机制。

0x04 web3j 对 websockets 的支持

在最新发布的 web3j 4.0.x 中,web3j 刚刚加入了对 websockets 的支持。

而被广泛使用的 Infura 节点服务仅支持通过 websockets 对事件进行监听,正是由于这个原因,很多使用 web3j 的团队不得不自己维护以太坊全节点,通过轮询的方式去监听合约事件的发生。
使用最新版本的 web3j,通过下面的示例代码,就可以对合约事件进行监听了。

// 注意 infura websockets 服务的路径和 http rpc 服务的路径是有所不同的
final WebSocketClient webSocketClient = new WebSocketClient(new URI("wss://ropsten.infura.io/ws/v3/<你的id>"));
final boolean includeRawResponses = false;
final WebSocketService webSocketService = new WebSocketService(webSocketClient, includeRawResponses);
// 这个没有出现在官方文档中,但必须要加上,否则会有 WebsocketNotConnectedException。
webSocketService.connect();
final Web3j web3j = Web3j.build(webSocketService);
System.out.println(event.getParams()));
// 0x73C2a5B1A32fa8E33101A6ab119203f4417feAE4 为合约地址
final Flowable<LogNotification> logNotifications = web3j.logsNotifications(Arrays.asList("0x73C2a5B1A32fa8E33101A6ab119203f4417feAE4"), 
// EventEncoder 这里主要为了生成事件签名
Arrays.asList(EventEncoder.encode(DfttToken.TRANSFER_EVENT)));
// 对数据进行监听
logNotifications.subscribe(event -> {
        Log log = event.getParams().getResult();
        List<Type> eventsData = FunctionReturnDecoder.decode(log.getData(), DfttToken.TRANSFER_EVENT.getNonIndexedParameters());
       for (Type eventDataItem : eventsData) {
                System.out.println(eventDataItem.getTypeAsString() + ": " + eventDataItem.getValue());
            }
        });

0x05 总结

本文根据自己对以太坊的事件和日志进行了简单介绍,希望能对你理解相关内容有所帮助,如有更多疑问,欢迎留言或加我微信: Ashton2011.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • 事件 智能合约具有在执行期间“发出”事件的能力。 事件在以太坊中也称为“日志”。 事件的输出存储在日志部分下的事务...
    Chole121阅读 5,810评论 1 13
  • (注:本文是在原文的基础上,根据个人的理解,修改部分内容并添加了一些注释) 买卖部分代码未调试通过 基础版的代币合...
    中v中阅读 2,892评论 0 2
  • 合约调用的时候回产生event log(事件日志),这个event log会记录在一个调用合约的交易的receip...
    小朴同学阅读 6,910评论 2 4
  • 【传智播客.黑马程序员训练营成都中心】 一、前言 ​​ 1.1 预备知识 (1) 以太坊相关概念 (2) 熟悉智...
    OpenCoder阅读 2,033评论 0 3
  • 文/王漫 导语: 生容易,活容易,生活不容易!人生是一本书,等你来解读。生活不光是柴米油盐酱醋茶,更应拥有琴棋书画...
    武商路漫漫阅读 640评论 10 23