项目场景
在一个使用以太坊区块链技术的项目中,需要监听智能合约的事件,以便在事件触发时能够及时响应。项目中使用了web3.js
库的4.x版本,节点使用Geth启动,并通过HTTP与节点进行通信。
问题描述
合约DataStorage.sol
文件已经定义好了如下如下两个事件
通过hardhat
项目进行编译部署得到合约,在编译得到的ABI
中同样存在事件,编译部署全都正常
此时启动一个nodejs
后端服务去用web3
调用监听事件,首先通过geth
结点实例化一个web3
对象
web3.eth.Contract(ABI, ADDRESS)
实例化合约,以下为监听事件的代码:
但是当触发该事件后,并没有进行监听,控制台也没出现任何打印或是其它信息
原因分析
经过查阅资料🚪及web3官方文档🚪后我发现原因为如下两个:
-
事件监听必须使用
WebSocket
,HTTP
协议单向无法监听到事件
WebSocket vs HTTP:
HTTP
协议只能单向通信,客户端需要不断发送请求以轮询获取更新,这在实时性要求高的场景下效率较低。
WebSocket
协议支持双向通信,服务端可以主动推送消息到客户端,客户端也可以主动向服务器发送信息(注意Express
服务在与节点程序通信时,节点是作为服务端角色)适合事件监听这种需要实时响应的场景。 -
web3.js
4.x版本下监听事件已经不支持用回调函数,得通过返回的对象来.on
监听对象'data'
或者'error'
web3.js 4.x版本变化:
在web3.js
4.x版本中,监听事件时不再支持通过回调函数的方式,而是需要使用返回的对象并通过on
方法来监听data
和error
事件。这种方式更符合现代JavaScript的事件处理模式,也更具可读性和维护性。
解决方案
1. 启动WebSocket服务
如果是使用 geth
节点需要使用选项 --ws
开启服务,开发使用的Ganache
默认开启了WebSocket服务。
geth --datadir "." --dev --dev.period 2 --http --http.api eth,web3,net --http.corsdomain "http://remix.ethereum.org" --password password.txt --http.port 8888 --ws --ws.api eth,web3,net --ws.origins "*" --ws.addr "127.0.0.1" --ws.port 8546
2. 初始化Web3并使用WebSocketProvider
在初始化web3时,使用了 WebsocketProvider
来初始化Web3实例,通过 WebSocket
通信协议与节点通信
const { Web3 } = require('web3');
const web3 = new Web3(new Web3.providers.WebsocketProvider('ws://127.0.0.1:8546'));
3. 定义和监听智能合约事件
每个事件监听器应该独立定义,监听事件时,不再使用回调函数,而是使用 on
方法来监听 data
和 error
事件。
const contract = new web3.eth.Contract(contractABI, contractAddress);// 监听合约事件并记录日志
const dataStoredSubscription = contract.events.DataStored({fromBlock: 'latest'
});dataStoredSubscription.on('data', event => {console.log('DataStored event:', event.returnValues);
});
dataStoredSubscription.on('error', error => {console.error('Error on DataStored event:', error);
});
通过以上步骤,可以实现对智能合约事件的实时监听,并且能够处理可能发生的错误。这样既提高了代码的可读性和维护性,又确保了事件监听的实时性和可靠性。