前面几节课讲了很多内容,但是没有说nonce的几个坑,今天我就来好好说说。
关于nonce的使用建议可以参考下面群友发的内容:

然后我们就说说nonce。
如果跑过前面几课的代码,你会发现跑我的例子都能成功。当你写一个批量交易的脚本就会出问题,尤其是同一个地址发送多次请求时会遇到各种各样的问题,大部分原因就是nonce产生的问题。
我们都知道获取nonce的方法就是:`web3.eth.getTransactionCount(address)`
这句方法就是取你的地址下一个nonce的值是多少,但是这个方法取出来的值会有几个坑。
坑1:当你的上一次交易还没有打包完,你取到的nonce跟上一次的值是一样。
坑2:上一次交易已经成功打包了,得到的nonce值居然还跟上一次的一样。
坑3:上一次交易已经成功打包了,得到的nonce值居然还小于上一次的值。
这些坑产生的具体原因不在这里讨论了,其实我也没有去深入去查,但是有一些解决办法跟大家分享一下。
我在我的代码中,都把发送交易的方法封装了,因为都是通用的代码。在里面我加上了一步,就是如果交易成功被矿工打包上链了,再返回。
这就相当于作了一种同步方式,因为这种同步的方式比较方便查错,虽然效率低了,但是开发过程比较顺畅。
首先是查询交易状态的代码:
web3.eth.getTransactionReceipt(tx_hash)['status']
这句话可以根据tx来查询状态,1:完成 0:失败 其他:等待
我们判定这个返回值为1,再继续,可以用循环这种比较笨的方法改一下:
def send_transaction(func, params):
tx = func.buildTransaction(params)
signed_tx = self.web3.eth.account.sign_transaction(tx, private_key=wallet_key)
tx_hash = self.web3.eth.sendRawTransaction(signed_tx.rawTransaction)
while True:
status = get_transaction_status(tx_hash)
if status == 1:
break
elif status == 0:
print('失败')
exit()
time.sleep(1)
return tx_hash
经过这样的改造,你的send_transaction方法就变成同步了,只有矿工打包之后才会返回tx。
然后我们再处理nonce,在构造params之前,你可以这样处理一下:
global old_nonce
nonce = self.web3.eth.getTransactionCount(self.wallet_address)
num = 0
while True:
if num > 20:
break
if nonce <= old_nonce and nonce != 0:
nonce = self.web3.eth.getTransactionCount(self.wallet_address)
print(f'发现nonce[{old_nonce}]重复,已重新获取。[{nonce}]')
time.sleep(1)
else:
break
num = num + 1
params = {
...
}
old_nonce = nonce
我在这里存了上一次的nonce为old_nonce,每次会判断一下是否大于小于上一次的值,如果不对继续取。注意,我这里加了num为20,是只去重试20次,20次都取不到就是别的问题了。
一般如果你把发送交易方法改成了同步,后面遇到nonce这个问题就会少很多。
上面这几个方法都比较民科,如果有更科学的方式欢迎跟我讨论~
上面的这些方法我也经过了一些时间的验证,解决问题肯定是可以的!
这周我多发了一篇文章哦,如果下周我鸽了也很正常~
