以太坊智能合约中,有一类特殊的回调函数,没有函数体,以大写字母开头,一般用来记录函数状态,这类回调函数称为事件event。事件event由合约函数调用,web3.js可以轻松监听此类event,并返回需要的数据。在网上搜索到的相关文章都是关于用web3.js来实现监听event的,今天我们就来从c++代码层面来看看怎么实现这个功能。
我们先来看看一个简单的智能合约例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| contract Contract { event Incremented(bool indexed odd, uint x, uint y); function Contract() { x = 69; y = 20; } function inc() { ++x; ++y; Incremented(x % 2 == 0, x, y); } uint x; uint y; }
|
这个合约只有两个变量x, y,一个函数inc()
和一个事件Incremented
,如果我们调用Contract
中的inc()
函数,则事件Incremented
会被执行,我们的目的就是监听这个事件。
首先我们在私有链上来部署这个合约,部署的过程就不赘述了,简单说就是执行一笔交易,交易的data为合约的二进制代码。
同样调用智能合约的函数也是向智能合约发送一笔交易,将函数以及相关参数放到交易的data中,也就是调用Client::submitTransaction()
这个函数,返回值是h256
类型,为交易的hash值,也就是交易的id。如果此笔交易在区块链上得到确认,则智能合约中的函数会被执行,函数中的事件会被触发,事件触发的结果记录在交易的日志中。那么我们去哪里找交易的日志呢?
当一笔交易得到确认,我们可以根据交易id去区块链上找到该笔交易的收据(receipt),receipt中记录交易的执行情况。
执行以下代码可以得到交易收据
1
| toJson(m_pClient->localisedTransactionReceipt(h)).toStyledString();
|
其中m_pClient
是dev::eth::Client
类实例,h
是交易id。
我们执行inc()
后,得到的收据内容为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| { "blockHash" : "0x862b329c8f5e8310ed5cc6decc18ccc61a96c984aeb3003627abdd9789bd73d9", "blockNumber" : 798, "contractAddress" : "0x46f74069282ce80249112a50c5fdea621436c2e7", "cumulativeGasUsed" : "0x85ba", "from" : "0x8fc41de977db1d49cff206ad8b25605e2b63e56e", "gasUsed" : "0x85ba", "logs" : [ { "address" : "0x59a1181c37245f8b4d7c7a5cec19d22dd5da4331", "blockHash" : "0x862b329c8f5e8310ed5cc6decc18ccc61a96c984aeb3003627abdd9789bd73d9", "blockNumber" : 798, "data" : "0x00000000000000000000000000000000000000000000000000000000000000460000000000000000000000000000000000000000000000000000000000000015", "logIndex" : 0, "polarity" : false, "topics" : [ "0x9b44895fef8929cca514f54bb4c52d35b5f403b960a39478ed7f4408e46eb69e", "0x0000000000000000000000000000000000000000000000000000000000000001" ], "transactionHash" : "0xc5659816ee1239f48cba7d99ea7defeb7b927dff8a9d074f618aa308fb1e9eb4", "transactionIndex" : 0, "type" : "mined" } ], "logsBloom" : "0x00000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000010000000040000000000000800000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000008000000000000000000000000000000000000000000000000000040000020000000000000000000000000000000000000000000000000000000000000", "stateRoot" : "0x2ff5b94c86fb021d6c354f827278ec48bd037a9b3981253f383717fe8f022eda", "to" : "0x59a1181c37245f8b4d7c7a5cec19d22dd5da4331", "transactionHash" : "0xc5659816ee1239f48cba7d99ea7defeb7b927dff8a9d074f618aa308fb1e9eb4", "transactionIndex" : 0 }
|
我们要从日志从寻找需要的东西,因此我们关注logs
这部分内容。再看topics
,这是一个数组,第一行的内容应该为事件名称的hash值,我们来计算一下:
1 2
| auto sh = dev::sha3("Incremented(bool,uint256,uint256)"); std::cout << "0x" << sh.hex() << std::endl;
|
结果正是0x9b44895fef8929cca514f54bb4c52d35b5f403b960a39478ed7f4408e46eb69e
,因此我们就知道这个事件得到了触发。
再来看这个事件的参数,第一个参数是x % 2 == 0的值,应该true, 也就是1,正是topics
中第二行的值,注意到合约代码中Incremented
第一个参数是indexed
的,也就是这个标志的参数都会记录在topics
的第2-n行中。那么没有这个标志的参数在哪呢?注意到上面还有个data
,内容为0x00000000000000000000000000000000000000000000000000000000000000460000000000000000000000000000000000000000000000000000000000000015
,这个可以分为两部分:
0x0000000000000000000000000000000000000000000000000000000000000046
和0x0000000000000000000000000000000000000000000000000000000000000015
,正是后面两个参数x和y的值,因此通过解析交易收据receipt,我们就能够得到事件触发的完整信息,也就是能够监听这个事件了。