ETH (武汉链)交易的随机数(nonce)
最近在使用在使用BSN开放联盟链 · 武汉链 做交易的过程用遇到了一些问题,自己记录一下。
-
首先说下以太坊(Ethereum)和武汉链有什么关系,武汉链是BSN开放联盟链其中一条基于(Ethereum)的共用链,肯定还有人问BSN是谁,下面是我从百度百科上摘抄的一段。
BSN是由[国家信息中心]牵头,会同中国移动、中国银联等单位联合发起并建立。BSN是基于区块链技术和共识机制的全球性基础设施网络,是面向工业、企业、政府应用的可信、可控、可扩展的联盟链,致力于改变联盟链应用的局域网架构高成本问题,以互联网理念为开发者提供公共区块链资源环境,极大降低区块链应用的开发、部署、运维、互通和监管成本,从而使区块链技术得到快速普及和发展。
-
我自己为什么不直接使用ETH公链,而使用中国自己的武汉链,有两点原因:
-
公链在国内不合法,数据无法被监管
-
ETH公链的价格太高,我这小户实在承受不起 哈哈
-
-
下面文档的数据都是我基于武汉链的测试结果。
什么是交易(Transactions)
-
先说下什么是交易
以太坊交易是指由外部持有账户(EOA) 发起的行动,换句话说,是指由人管理而不是智能合约管理的账户。 例如,如果 Bob 发送 Alice 1 ETH,则 Bob 的帐户必须减少 1 ETH,而 Alice 的账户必须增加 1 ETH,看待交易的另一种方式是,它是唯一能触发状态改或是或促使合约在EVM 中执行的东西。以太坊是一个全域单例(global singleton) 的状态机(state machine),交易是唯一能够转动这状态机去改变状态的方法。合约无法自行运行。以太坊不会在后台运行。所有的状态改变与执行皆始于交易。
-
交易对象看起就像:
{ "from":"55935178000b12f5746230e17b352b8716277777", "to":"91f715616d0366f5670134c6e244190072798888", "gas":"0xC350", "gasLimit":"0xC950", "gasPrice":"0xc1b720800", "value":"0x1", "nonce":"0x2" }
-
交易的随机数(nonce)
nonce
– 显示从帐户发送的交易数量的计数器。 这将确保交易只处理一次。 在合约帐户中,这个数字代表该帐户创建的合约数量
交易的随机数(nonce)
先了解下nonce在节点上是怎么管理的。
-
所有的交易不管是本地交易
AddLocals
还是其他节点广播AddRemotes
过来的交易终都会调用方法addTxs
→addTxsLocked
→add
,经过一系列校验validateTx
进入队列queue
。【此时节点上账户address
的 nonce还未更新】 -
源码中的调用如下:
// This method is used to add transactions from the RPC API and performs synchronous pool // reorganization and event propagation. func (pool *TxPool) AddLocals(txs []*types.Transaction) []error { return pool.addTxs(txs, !pool.config.NoLocals, true) } // This method is used to add transactions from the p2p network and does not wait for pool // reorganization and internal event propagation. func (pool *TxPool) AddRemotes(txs []*types.Transaction) []error { return pool.addTxs(txs, false, false) } // addTxs attempts to queue a batch of transactions if they are valid. func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { ... newErrs, dirtyAddrs := pool.addTxsLocked(news, local) ... // Reorg the pool internals if needed and return done := pool.requestPromoteExecutables(dirtyAddrs) } // addTxsLocked attempts to queue a batch of transactions if they are valid. // The transaction pool lock must be held. func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) { ... replaced, err := pool.add(tx, local) } // add validates a transaction and inserts it into the non-executable queue for later // pending promotion and execution. If the transaction is a replacement for an already // pending or queued one, it overwrites the previous transaction if its price is higher. func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err error) {...}
-
交易进入队列后此时就轮到了
scheduleReorgLoop
上场。-
上面
addTxs
()中的pool.requestPromoteExecutables(dirtyAddrs)
,发送请求对给定地址进行交易升级检查至通道reqPromoteCh
-
通道
pool.reqPromoteCh
该通道接收到消息后scheduleReorgLoop
开始后台运行pool.runReorg
// runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop.
-
pool.runReorg
处理过程 -
已成为可处理的从未来队列到待处理事务集的事件。 在此过程中,所有无效交易(低随机数、低余额)都将被删除。pool.promoteExecutables(promoteAddrs)
-
交易提成为pending状态,并更新
address
的nonce
值promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction)
// Set the potentially new pending nonce and notify any subsystems of the new tx pool.pendingNonces.set(addr, tx.Nonce()+1)
-
至此交易状态提升为
pending
状态时address
的nonce
才更新(+1) -
源码中的调用如下:
// scheduleReorgLoop schedules runs of reset and promoteExecutables. Code above should not // call those methods directly, but request them being run using requestReset and // requestPromoteExecutables instead. func (pool *TxPool) scheduleReorgLoop() { ... // Run the background reorg and announcements go pool.runReorg(nextDone, reset, dirtyAccounts, queuedEvents) } // runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop. func (pool *TxPool) runReorg(...) { // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) } // promoteExecutables moves transactions that have become processable from the // future queue to the set of pending transactions. During this process, all // invalidated transactions (low nonce, low balance) are deleted. func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Transaction { ... if pool.promoteTx(addr, hash, tx) { promoted = append(promoted, tx) } } // promoteTx adds a transaction to the pending (processable) list of transactions // and returns whether it was inserted or an older was better. // // Note, this method assumes the pool lock is held! func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool { // Set the potentially new pending nonce and notify any subsystems of the new tx pool.pendingNonces.set(addr, tx.Nonce()+1) }
-
武汉链的使用过程
上面了解了nonce
的管理流程下面看下我在调用武汉链时遇到的问题。
我通过BSN网关调用武汉链做交易,由于我的交易量比较大,总是会有失败的交易,我就把经常遇到失败的的原因以及解决办法列举下。
-
nonce too low
// ErrNonceTooLow is returned if the nonce of a transaction is lower than the // one present in the local chain.
使用已经被旷工打包过的
nonce
,也就是该nonce
已经被使用过了。通过调用RPC接口查询最新的
nonce
{ "jsonrpc": "2.0", "method": "eth_getTransactionCount", "params": [ "91f715616d0366f5670134c6e244190072798888", "pending" // 整数块编号,或字符串"latest"、"earliest"或"pending" ], "id": 1 }
-
already known
// ErrAlreadyKnown is returned if the transactions is already contained // within the pool.
节点交易池中已经有相同
nonce
的交易,因为节点上交易处理慢导致nonce
更新不及时,通过RPC接口获取到的nonce
值是相同的。如果瞬时交易量大就需要考虑本地维护
nonce
值,本地nonce
管理可以参考web3j的TransactionManager。 -
replacement transaction underpriced
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced // with a different one without the required price bump.
该账户在节点上有非可执行的交易也是就有交易在对列中
queued
,然后使用相同的nonce
和相同的gasPrice
发送交易、- 交易使用的低于节点限制的gasPrice,该笔交易不会进入pending,只能在queued中等待。
- 交易使用了过高的
nonce
,该笔交易不会进入pending,只能在queued中等待。
首先需要查清楚是因为
gasPrice
还是nonce
导致。-
如果是
gasPrice
低于节点的最低限制(武汉链的gasPrice
最低1Gwei
),通过RPC接口(eth_gasPrice
)获取最新的gasPrice
,提交gasPrice
(gasPrice需要高于原交易的110%
)后使用该笔交易的nonce
重新发送交易替换队列中的那笔交易。 -
如果是使用了过高的
nonce
,使用RPC接口(eth.getTransactionCount(address, "latest
"))获取最新的【不是pending状态下的】nonce
,发送交易补全至pending
的nonce
后该笔交易会被自动执行。 -
覆盖交易原交易hash就会作废。
-
intrinsic gas too low
// ErrIntrinsicGas is returned if the transaction is specified to use less gas // than required to start the invocation.
该笔交易,
gasLimit
设置 低于该笔交易的使用量可以通过RPC接口(
eth_estimateGas
)估算gasLimit
,提高gasLimit
后重新发送交易。 -
tx fee (*** ether) exceeds the configured cap (* ether)
// checkTxFee is an internal function used to check whether the fee of // the given transaction is _reasonable_(under the cap).
gasLimit
超过配置上限 (1.00 ether)降低
gasLimit
即可 -
insufficient funds for gas * price + value
// ErrInsufficientFunds is returned if the total cost of executing a transaction // is higher than the balance of the user's account.
该账户能量值不足了,账户的gas余额不足
在BSN门户中购买即可
降低
gasLimit
即可
如有错误欢迎指正交流😊