以太坊交易中存在一个特殊的值nonce
,此nonce
并非计算block
难度的nonce
,此nonce
仅仅表示发送账号发送交易的次数,从0开始,每发送一次交易+1,那么第一次发送nonce
为0,第二次为1,以此类推。nonce
的存在可以用来防止重放攻击,也就是同一个交易只能被发送一次,下次发送同一个交易时,因为nonce
值和最新的nonce
不同,会被区块链拒绝。
我们来从代码层面看看这个nonce
的生成和检测。
我们可以从一张图来看这个nonce
的来龙去脉。
可以看到这张图上存在一个关键性的三角关系。
- 三角形上面顶点是区块链
blockchain
。 - 左边顶点是
postseal()
中的nonce
,postseal()
是从区块链获取到的最新的区块,那么左边顶点表示当前区块链中最新区块该发送账号的nonce
。 - 右边顶点是交易队列中的
nonce
当我们提交一个交易时,交易的nonce
取值是左右两个顶点的nonce
值中取最大值。然后再与blockchain
中最新块的nonce
进行比较,如果不同,则区块链拒绝此交易。那么问题来了,postseal()
中不就是最新块的nonce
吗?为什么还需要再次比较?这是因为postseal()
并不是一直与区块链同步的,只有满足某些条件才会同步。另外当交易成功提交后,该交易在正式被区块链确认前,是被存放在交易队列中的,此时右顶点的nonce
值为该交易的nonce
+1。
我们来一步一步拆开来看:
第一步,假设该发送者从来没有发送过交易,那么他的nonce
值应该为0,上面的图会变成:
此时blockchain
和postseal()
中均没有该账号的信息,nonce
值为初始值0。 交易队列中没有该发送账号的交易,因此nonce
值也是0,那么该发送者第一次提交交易时,nonce
值会被设置为max(0, 0)
,也就是0。然后再比较0与0是相等的,那么此交易被正确发送。
第二步,交易被发送到交易队列,这张图变成:
此时上顶点和左顶点的nonce
值不变,右顶点因为交易队列中已有一个该发送者发送的交易,那么nonce
+1,变成1。
此时如果该发送者想再发一个交易,那么新交易的nonce
会被设置为max(0, 1)
,也就是1。然后再与上顶点nonce
比较,得出不相等的结论,此交易被拒绝,提交失败!
第三步,第一个交易被区块链确认,这张图变成:
交易被确认后,上顶点blockchain
的nonce
变成1,左顶点因为同步,nonce
也变成1,而右顶点交易队列会删除掉已确认的交易,所以没有该发送者的交易了,nonce
就为0。此时如果该发送者再发一个交易,新交易的nonce
会被设置为max(1, 0)
,也就是1。然后再与上顶点nonce
比较,结果相等,该交易被成功发出!
交易被发出后被发到交易队列,流程就同第二步了。
下面我们来看看具体涉及到的代码实现:
1 | TransactionSkeleton Client::populateTransactionWithDefaults(TransactionSkeleton const& _t) const |
这段代码是提交交易时设置交易的一些参数,其中
1 | ret.nonce = max<u256>(postSeal().transactionsFrom(ret.from), m_tq.maxNonce(ret.from)); |
就是设置交易的nonce
,是左顶点和右顶点的nonce
值的最大值。
再来看交易队列中的nonce
:
1 | u256 TransactionQueue::maxNonce(Address const& _a) const |
注意那个+1,这个表示当前队列中该发送者最大的nonce
再+1。
而postseal()
从区块链同步的代码在:
1 | void Client::restartMining() |
省略了一些次要代码,从这里可以看出,先是从区块链同步最新信息到newPreMine
,然后拷贝到m_postSeal
,也就是postseal()
。
那么Client::restartMining()
是从哪里调用的呢?
有两个地方,但是一般都是从Client::resyncStateFromChain()
这里调用的:
1 | void Client::resyncStateFromChain() |
Client::resyncStateFromChain()
函数又是从哪里调用呢?
也有两个地方,一处是Client::onChainChanged()
表示区块链同步新块的时候,另一处是Client::syncTransactionQueue()
表示同步交易队列中交易的时候。
由此可见,以太坊通过这些机制保证nonce
与发送账号的发送次数严格对应,如果发送交易不遵守这条规则,则发送可能会失败,错误码为InvalidNonce