文章目錄
- 每筆交易nonce值的各個情況
- 總結
- 關于Nonce的保管
- 依賴節點
- 自行管理nonce
- 參考代碼
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的交易
首先下方命令看到地址0xfa8d4ded7fe1fec96c1b10443bea261195f233bb的總交易數是2,也就是說,nonce數值已累加到2(nonce值從0開始),新的交易nonce值將是3,
> eth.getTransactionCount("0xac965f9832efd7684bbbd7ceed5891a337bca302")
3
接下來指定nonce為3,創建一筆交易,如下可看到,交易成功,并被打包到了3485668區塊中,
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "3000000000000000000", nonce: "3"})
"0x497c21a80d821634116d96ca8f40a70bcb7013b4d11019d75b522fa1b46a82d7"
> eth.getTransaction("0x497c21a80d821634116d96ca8f40a70bcb7013b4d11019d75b522fa1b46a82d7")
{
blockHash: "0xc1aad9012d8bfc34a5003d73f7507bfe562cba306223d81f0050476ca698a455",
blockNumber: 3485668,
from: "0xac965f9832efd7684bbbd7ceed5891a337bca302",
gas: 21000,
gasPrice: 1000000000,
hash: "0x497c21a80d821634116d96ca8f40a70bcb7013b4d11019d75b522fa1b46a82d7",
input: "0x",
nonce: 3,
r: "0xddde68ee95d14273b1b45ce6df5a40bd079357cb7eb1281d7edebb88799ed554",
s: "0x7584f6cd4e8cad3c3691ca3ec1d671c9096f7aca61386665baaec481e937f8cc",
to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2",
transactionIndex: 0,
type: "0x0",
v: "0x539bb",
value: 3000000000000000000
}
如果此時我在發送一筆交易,并指定nonce還是3,運行結果如下所示,
會看到此時交易報錯,提示“nonce too low”
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "3000000000000000000", nonce: "3"})
Error: nonce too low
at web3.js:6347:37(47)
at web3.js:5081:62(37)
at <eval>:1:25(13)
按照nonce的規則,接下來的新交易nonce值應該是4,我現在跳過4,直接指定nonce值為5,會發生什么,如下所示,
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "3000000000000000000", nonce: "5"})
"0x8e8967d1c6122006d199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9"
可以看到,回傳了交易hash,我們查看該筆交易,發現blockNumber為null,并沒有加入到區塊中,如下所示:
> eth.getTransaction("0x8e8967d1c6122006d199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9")
{
blockHash: null,
blockNumber: null,
from: "0xac965f9832efd7684bbbd7ceed5891a337bca302",
gas: 21000,
gasPrice: 1000000000,
hash: "0x8e8967d1c6122006d199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9",
input: "0x",
nonce: 5,
r: "0x6499ec326d5d9f1e5035fc4e7612b1e52c0c1bc60baf6f4068eaf790afe03f70",
s: "0x2e66b705148769b3e394dc8f9e6be9ad597c7144aaafdb5894e5ac8ce46a39bb",
to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2",
transactionIndex: null,
type: "0x0",
v: "0x539bb",
value: 3000000000000000000
}
暫未被加入到區塊中的交易,會被放入到交易池中(txpool),交易池里會維護兩個串列,一個是待被打包的pending串列,一個是當前無法執行的交易queued串列,
從下方請求可看到0x8e8967d1c6122006d199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9交易被放到了queued串列中,這是由于交易池中沒有找到地址0xac965f9832efd7684bbbd7ceed5891a337bca302為4的nonce,
當交易處于queue中時停止geth客戶端,那么交易queue中的交易會被清除掉,
> web3.txpool.content.queued
{
0xaC965f9832eFD7684BBBd7cEED5891a337bCA302: {
5: {
blockHash: null,
blockNumber: null,
from: "0xac965f9832efd7684bbbd7ceed5891a337bca302",
gas: "0x5208",
gasPrice: "0x3b9aca00",
hash: "0x8e8967d1c6122006d199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9",
input: "0x",
nonce: "0x5",
r: "0x6499ec326d5d9f1e5035fc4e7612b1e52c0c1bc60baf6f4068eaf790afe03f70",
s: "0x2e66b705148769b3e394dc8f9e6be9ad597c7144aaafdb5894e5ac8ce46a39bb",
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: "6000000000000000000", nonce: "4"})
"0xd8610ad5962e88f2eaeb8fd7f18da1df978389a73b6e180cd34751dc32c1c975"
> web3.txpool.content.pending
{
0xaC965f9832eFD7684BBBd7cEED5891a337bCA302: {
4: {
blockHash: null,
blockNumber: null,
from: "0xac965f9832efd7684bbbd7ceed5891a337bca302",
gas: "0x5208",
gasPrice: "0x3b9aca00",
hash: "0xd8610ad5962e88f2eaeb8fd7f18da1df978389a73b6e180cd34751dc32c1c975",
input: "0x",
nonce: "0x4",
r: "0xef4b52cd0a8ff5d4d119b43f2ebfbca3ed0785a19df2a90e9b9c4a9a39b07ddf",
s: "0x191d51cca73168957d9184cd2cf09f2fd713298139e7438d71bb6c19ea606603",
to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2",
transactionIndex: null,
type: "0x0",
v: "0x539bb",
value: "0x53444835ec580000"
},
5: {
blockHash: null,
blockNumber: null,
from: "0xac965f9832efd7684bbbd7ceed5891a337bca302",
gas: "0x5208",
gasPrice: "0x3b9aca00",
hash: "0x8e8967d1c6122006d199f375f96e9100c8810b2c97848a1006b2eba14c54a8d9",
input: "0x",
nonce: "0x5",
r: "0x6499ec326d5d9f1e5035fc4e7612b1e52c0c1bc60baf6f4068eaf790afe03f70",
s: "0x2e66b705148769b3e394dc8f9e6be9ad597c7144aaafdb5894e5ac8ce46a39bb",
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: "3000000000000000000",gas:"21000",gasPrice:"78000000000",nonce: "11"})
"0x3aaf3f302464e591cca759ee72d46461c145d4496bde488bcf59c7c5643dd05d"
// 當以上交易還沒有上鏈時,再次發生一筆nonce為11,gasPrice為75 gwei的交易,則會報錯
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "3000000000000000000",gas:"21000",gasPrice:"75000000000",nonce: "11"})
Error: replacement transaction underpriced
at web3.js:6347:37(47)
at web3.js:5081:62(37)
at <eval>:1:25(17)
如果手續費高于原來的交易,那么第一筆交易將會被覆寫,
// 發生一筆nonce為13,gasPrice為78 gwei的交易
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "3000000000000000000",gas:"21000",gasPrice:"78000000000",nonce: "13"})
"0x757cccf40ab253a69c3b314993ad6fad71c097d8b0e69ead2dacdb6b75e03c23"
// 當以上交易還沒有上鏈時,再次發生一筆nonce為13,gasPrice為88 gwei的交易,發現交易成功了
> web3.eth.sendTransaction({from: "0xac965f9832efd7684bbbd7ceed5891a337bca302", to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2", value: "3000000000000000000",gas:"21000",gasPrice:"88000000000",nonce: "13"})
"0xab538aaded6f1232b2198b3b508b910367c73af20cfd2d0ad0117faa6b8aadaf"
// 最后查詢第一筆交易,發現查詢為空
> eth.getTransaction("0x757cccf40ab253a69c3b314993ad6fad71c097d8b0e69ead2dacdb6b75e03c23")
null
// 然后查詢第二筆交易,發現交易上鏈,nonce值為13
> eth.getTransaction("0xab538aaded6f1232b2198b3b508b910367c73af20cfd2d0ad0117faa6b8aadaf")
{
blockHash: "0x16ae951cc721901b6abc63edfed1dcc8292f43f41aa7c6597a92fe4da08de281",
blockNumber: 3486174,
from: "0xac965f9832efd7684bbbd7ceed5891a337bca302",
gas: 21000,
gasPrice: 88000000000,
hash: "0xab538aaded6f1232b2198b3b508b910367c73af20cfd2d0ad0117faa6b8aadaf",
input: "0x",
nonce: 13,
r: "0xfe5cd567b5c372a9187fde81bd82690423c85554d0626a64dee007793341774b",
s: "0x3642a005f1a2c7801b8ac635fce0c7995c4d41a758cf80fef3d727506d292afe",
to: "0x6e60f5243e1a3f0be3f407b5afe9e5395ee82aa2",
transactionIndex: 0,
type: "0x0",
v: "0x539bb",
value: 3000000000000000000
}
總結
總結一下:
- 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/86e77900c53ebce3309099a39cbca38eb4d62fdf/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://www.uj5u.com/qukuanlian/282163.html
標籤:區塊鏈
上一篇:TLSNotary中心化預言機(3) 下一代技術----PADVA
下一篇:vue-router路由使用
