nonce在区块链中是一个非常重要的概念,从比特币到以太坊都有nonce的身影。
在比特币中,nonce主要用于调整pow挖矿的难度,而在以太坊中,除了调整挖矿难度外,在外部账户的每笔交易中也都存在一个nonce。这个nonce是一个连续的整数,在每个账户发送交易时所产生,其主要设计目的是为防止双花。
web3中的sendTransaction方法,官方文档是这样写的:
https://web3js.readthedocs.io/en/1.0/web3-eth.html#sendtransaction
sendTransaction web3.eth.sendTransaction(transactionObject [, callback]) Sends a transaction to the network. Parameters Object - The transaction object to send: from - String|Number: The address for the sending account. Uses the web3.eth.defaultAccount property, if not specified. Or an address or index of a local wallet in web3.eth.accounts.wallet. to - String: (optional) The destination address of the message, left undefined for a contract-creation transaction. value - Number|String|BN|BigNumber: (optional) The value transferred for the transaction in wei, also the endowment if it’s a contract-creation transaction. gas - Number: (optional, default: To-Be-Determined) The amount of gas to use for the transaction (unused gas is refunded). gasPrice - Number|String|BN|BigNumber: (optional) The price of gas for this transaction in wei, defaults to web3.eth.gasPrice. data - String: (optional) Either a ABI byte string containing the data of the function call on a contract, or in the case of a contract-creation transaction the initialisation code. nonce - Number: (optional) Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce.
每笔交易nonce值的各个情况
可以看到,在构建交易时,有一个可选的nonce参数,可以覆盖在交易池中的,pending列表中相同nonce的交易。
接下来我会在Geth搭建的私有链来进行以下实验,来看看不同情况下nonce对应的交易会怎样:
- 相同nonce下的两笔交易
- 不连续nonce下的交易
- 不具体指定nonce的交易
首先下方命令看到地址0xfa8d4ded7fe1fec96c1b10443beaf233bb的总交易数是2,也就是说,nonce数值已累加到2(nonce值从0开始),新的交易nonce值将是3。
> eth.getTransactionCount("0xac965f9832efd7684bbbd7ceed5891a337bca302") 3
接下来指定nonce为3,创建一笔交易,如下可看到,交易成功,并被打包到了区块中。
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "00000000", nonce: "3"}) "0x497c21a80dd96ca8f40a70bcb7013b4d11019d75b522fa1b46a82d7" > eth.getTransaction("0x497c21a80dd96ca8f40a70bcb7013b4d11019d75b522fa1b46a82d7") { blockHash: "0xc1aad9012d8bfc34a5003d73f7507bfe562cbad81f0050476ca698a455", blockNumber: , from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", gas: 21000, gasPrice: , hash: "0x497c21a80dd96ca8f40a70bcb7013b4d11019d75b522fa1b46a82d7", input: "0x", nonce: 3, r: "0xddde68ee95d14273b1b45ce6df5a40bd079357cb7eb1281d7edebb88799ed554", s: "0x7584f6cd4e8cad3c3691ca3ec1d671c9096f7acabaaec481e937f8cc", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", transactionIndex: 0, type: "0x0", v: "0x539bb", value: 00000000 }
如果此时我在发送一笔交易,并指定nonce还是3,运行结果如下所示。
会看到此时交易报错,提示“nonce too low”
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "00000000", nonce: "3"}) Error: nonce too low at web3.js:6347:37(47) at web3.js:5081:62(37) at
:1:25(13)
按照nonce的规则,接下来的新交易nonce值应该是4,我现在跳过4,直接指定nonce值为5,会发生什么,如下所示。
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "00000000", nonce: "5"}) "0x8e8967d1cd199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9"
可以看到,返回了交易hash,我们查看该笔交易,发现blockNumber为null,并没有加入到区块中,如下所示:
> eth.getTransaction("0x8e8967d1cd199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9") { blockHash: null, blockNumber: null, from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", gas: 21000, gasPrice: , hash: "0x8e8967d1cd199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9", input: "0x", nonce: 5, r: "0x6499ec326d5d9f1e5035fc4e7612b1e52c0c1bc60baf6f4068eaf790afe03f70", s: "0x2e66bb3e394dc8f9e6be9ad597c7144aaafdb5894e5ac8ce46a39bb", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", transactionIndex: null, type: "0x0", v: "0x539bb", value: 00000000 }
暂未被加入到区块中的交易,会被放入到交易池中(txpool),交易池里会维护两个列表,一个是待被打包的pending列表,一个是当前无法执行的交易queued列表。
从下方请求可看到0x8e8967d1cd199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9交易被放到了queued列表中。这是由于交易池中没有找到地址0xac965f9832efd7684bbbd7ceed5891a337bca302为4的nonce。
当交易处于queue中时停止geth客户端,那么交易queue中的交易会被清除掉。
> web3.txpool.content.queued { 0xaC965f9832eFD7684BBBd7cEED5891a337bCA302: { 5: { blockHash: null, blockNumber: null, from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", gas: "0x5208", gasPrice: "0x3b9aca00", hash: "0x8e8967d1cd199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9", input: "0x", nonce: "0x5", r: "0x6499ec326d5d9f1e5035fc4e7612b1e52c0c1bc60baf6f4068eaf790afe03f70", s: "0x2e66bb3e394dc8f9e6be9ad597c7144aaafdb5894e5ac8ce46a39bb", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", transactionIndex: null, type: "0x0", v: "0x539bb", value: "0x29a2241af62c0000" } } }
新建一笔交易,设置nonce为4,如下所示。
会看到提交交易后,queued列表中nonce为5的交易也被移出,同时待打包的pending列表中,有了nonce值为4和5的交易信息。挖矿后,这两笔交易将会被写入到区块中。
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "00000000", nonce: "4"}) "0xd8610ad5962e88f2eaeb8fd7f18da1dfa73b6e180cd34751dc32c1c975" > web3.txpool.content.pending { 0xaC965f9832eFD7684BBBd7cEED5891a337bCA302: { 4: { blockHash: null, blockNumber: null, from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", gas: "0x5208", gasPrice: "0x3b9aca00", hash: "0xd8610ad5962e88f2eaeb8fd7f18da1dfa73b6e180cd34751dc32c1c975", input: "0x", nonce: "0x4", r: "0xef4b52cd0a8ff5d4d119b43f2ebfbca3ed0785a19df2a90e9b9c4a9a39b07ddf", s: "0x191d51ccad9184cd2cf09f2fde7438d71bb6c19ea", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", transactionIndex: null, type: "0x0", v: "0x539bb", value: "0xec" }, 5: { blockHash: null, blockNumber: null, from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", gas: "0x5208", gasPrice: "0x3b9aca00", hash: "0x8e8967d1cd199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9", input: "0x", nonce: "0x5", r: "0x6499ec326d5d9f1e5035fc4e7612b1e52c0c1bc60baf6f4068eaf790afe03f70", s: "0x2e66bb3e394dc8f9e6be9ad597c7144aaafdb5894e5ac8ce46a39bb", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", transactionIndex: null, type: "0x0", v: "0x539bb", value: "0x29a2241af62c0000" } } }
那么如果当我们发起一笔交易,假设当前nonce为11,交易已经发送至节点中,但由于手续费不高或网络拥堵或nonce值过高,此交易处于queued中迟迟未被打包。
同时此地址再发起一笔交易,并且nonce值与上一个nonce值相同,用同样的nonce值再发出交易时,就会有两种情况:
如果手续费低于原来的交易就会发生异常:replacement transaction underpriced。
// 发生一笔nonce为11,gasPrice为78 gwei的交易 web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "00000000",gas:"21000",gasPrice:"",nonce: "11"}) "0x3aaf3fe591cca759ee72d46461c145d4496bde488bcf59c7c5643dd05d" // 当以上交易还没有上链时,再次发生一笔nonce为11,gasPrice为75 gwei的交易,则会报错 > web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "00000000",gas:"21000",gasPrice:"",nonce: "11"}) Error: replacement transaction underpriced at web3.js:6347:37(47) at web3.js:5081:62(37) at
:1:25(17)
如果手续费高于原来的交易,那么第一笔交易将会被覆盖。
// 发生一笔nonce为13,gasPrice为78 gwei的交易 > web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "00000000",gas:"21000",gasPrice:"",nonce: "13"}) "0x757cccf40ab253a69c3bad6fad71c097d8b0e69ead2dacdb6b75e03c23" // 当以上交易还没有上链时,再次发生一笔nonce为13,gasPrice为88 gwei的交易,发现交易成功了 > web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "00000000",gas:"21000",gasPrice:"",nonce: "13"}) "0xab538aaded6f1232b2198b3b508bc73af20cfd2d0ad0117faa6b8aadaf" // 最后查询第一笔交易,发现查询为空 > eth.getTransaction("0x757cccf40ab253a69c3bad6fad71c097d8b0e69ead2dacdb6b75e03c23") null // 然后查询第二笔交易,发现交易上链,nonce值为13 > eth.getTransaction("0xab538aaded6f1232b2198b3b508bc73af20cfd2d0ad0117faa6b8aadaf") { blockHash: "0x16ae951ccb6abc63edfed1dcc8292f43f41aa7c6597a92fe4da08de281", blockNumber: , from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", gas: 21000, gasPrice: , hash: "0xab538aaded6f1232b2198b3b508bc73af20cfd2d0ad0117faa6b8aadaf", input: "0x", nonce: 13, r: "0xfe5cd567b5c372a9187fde81bdc85554d0626a64dee00b", s: "0x3642a005f1a2c7801b8ac635fce0c7995c4d41a758cf80fef3dd292afe", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", transactionIndex: 0, type: "0x0", v: "0x539bb", value: 00000000 }
总结
总结一下:
- 1、以太坊中有两种nonce,一种是在区块中的nonce,主要是调整挖矿难度;一种是每笔交易中nonce。
- 2、每个外部账户(私钥控制的账户)都有一个nonce值,从0开始连续累加,每累加一次,代表一笔交易。
- 3、某一地址的某一交易的nonce值如果大于当前的nonce,该交易会被放到交易池的queued列表中,直到缺失的nonce被提交到交易池中。
- 4、地址的nonce值是一个连续的整数,设计的主要目的是防止双花。
- 5、在发生一笔交易时,如果不指定nonce值时,节点会根据当前交易池的交易自动计算该笔交易的nonce。有可能会出现节点A和节点B计算的nonce值不一样的情况。
- 6、当交易暂未上链时,可通过提高手续费的方式,覆盖同样nonce值的交易
- 7、通常情况下,覆盖掉一笔处于pending状态的交易gas price需要高于原交易的110%。
关于Nonce的保管
依赖节点
- 优点:
如果该热点账户的私钥信息等都存放在Ethereum客户端中,那么在发送交易的时候不传递nonce值,Ethereum客户端会帮你处理好此nonce值的排序。
- 缺点:
当然,此方案有两个弊端。第一个是安全性无法保障(未进行冷热账户分离),第二,在热点账户下如果想覆盖掉一笔交易,需要先查询一下该交易的信息,从中获取nonce值。
自行管理nonce
- 优点:
通过数据库进行管理nonce,在每一次发布成功的交易都做一次++操作,并且在数据库保存对当前这笔交易的nonce保管,以方便自己追踪当前交易的nonce。
- 缺点:
此种方案也有限制条件。第一,由于nonce统一进行维护,那么这个地址必须是内部地址,而且发起交易必须通过统一维护的nonce作为出口,否则在其他地方发起交易,原有维护的nonce将会出现混乱。第二,一旦已经发出的交易发生异常,异常交易的nonce未被使用,那么异常交易的nonce需要重新被使用之后它后面的nonce才会生效。
注:可获取当前地址发起交易的eth_getTransactionCount 参数为地址,以及Pending或者lastest,选用pending就行, 就可以获取你当前地址的最大nonce数。 但是这种情况需要确保你中间的nonce没有中断过。
例如:
# curl -H Content-Type:application/json -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0xac965f9832efd7684bbbd7ceed5891a337bca302","pending"],"id":1}' http://127.0.0.1:8545 {"jsonrpc":"2.0","id":1,"result":"0xe"}
参考代码
https://github.com/ethereum/go-ethereum/blob/86e77900c53ebcea39cbca38eb4d62fdf/core/tx_pool.go
一笔交易调用add(ctx context.Context, tx *types.Transaction)方法将交易信息加入到交易池的pending列表中,需要对交易信息进行验证,验证方法是validateTx,如下所示:
// validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { // Heuristic limit, reject transactions over 32KB to prevent DOS attacks if tx.Size() > 32*1024 { return ErrOversizedData } ... // Ensure the transaction adheres to nonce ordering if pool.currentState.GetNonce(from) > tx.Nonce() { return ErrNonceTooLow } ... }
使用GetNonce方法获取当前地址在交易池中的nonce值,如果当前交易的nonce比交易池中的nonce值小,就会报“nonce too low”的错误。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/217254.html原文链接:https://javaforall.net
