从以太坊交易日志中监听智能合约事件event

以太坊智能合约中,有一类特殊的回调函数,没有函数体,以大写字母开头,一般用来记录函数状态,这类回调函数称为事件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_pClientdev::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,这个可以分为两部分:
0x00000000000000000000000000000000000000000000000000000000000000460x0000000000000000000000000000000000000000000000000000000000000015,正是后面两个参数x和y的值,因此通过解析交易收据receipt,我们就能够得到事件触发的完整信息,也就是能够监听这个事件了。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×