nonce在區塊鏈中是一個非常重要的概念,從比特幣到以太坊都有nonce的身影。
在比特幣中,nonce主要用于調整pow挖礦的難度,而在以太坊中,除了調整挖礦難度外,在外部賬戶的每筆交易中也都存在一個nonce。這個nonce是一個連續的整數,在每個賬戶發送交易時所產生,其主要設計目的是為防止雙花。
web3中的sendTransaction方法,官方文檔是這樣寫的:
https://web3js.readthedocs.io/en/1.0/web3-eth.html#sendtransaction
可以看到,在構建交易時,有一個可選的nonce參數,可以覆蓋在交易池中的,pending列表中相同nonce的交易。
接下來我會在Geth搭建的私有鏈來進行以下實驗,來看看不同情況下nonce對應的交易會怎樣:
- 相同nonce下的兩筆交易
- 不連續nonce下的交易
- 不具體指定nonce的交易
說明:使用Geth搭建私有鏈詳見:http://www.lxweimin.com/p/4c3efd23a427
首先下方命令看到地址0xfa8d4ded7fe1fec96c1b10443bea261195f233bb
的總交易數是2,也就是說,nonce數值已累加到1(nonce值從0開始),新的交易nonce值將是2。
> eth.getTransactionCount("0xfa8d4ded7fe1fec96c1b10443bea261195f233bb")
2
接下來指定nonce為2,創建一筆交易,如下可看到,交易成功,并被打包到了1980區塊中。
> web3.eth.sendTransaction({from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb", to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53", value: "1000000000000000000", nonce: "2"})
"0x8a16b9a887928125071469332425896d33a1fa8c4e5a5deb50545f637ea1bf5d"
> eth.getTransaction("0x8a16b9a887928125071469332425896d33a1fa8c4e5a5deb50545f637ea1bf5d")
{
blockHash: "0x9ba0aad04c9b52ef9bc543d274ce1718e32e9fdbf4bff7f88904f14c03f41855",
blockNumber: 1980,
from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb",
gas: 90000,
gasPrice: 1000000000,
hash: "0x8a16b9a887928125071469332425896d33a1fa8c4e5a5deb50545f637ea1bf5d",
input: "0x",
nonce: 2,
r: "0xd530dc31288ff48f796763ce398a21cca9e9ab640f80890478b259dc247709e0",
s: "0x275aa77f51cb157e2088e90cc3859bd2e53dbcc5bf752a5380a8736796a2a383",
to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53",
transactionIndex: 0,
v: "0x557",
value: 1000000000000000000
}
如果此時我在發送一筆交易,并指定nonce還是2,運行結果如下所示。
會看到此時交易報錯,提示“nonce too low”
> web3.eth.sendTransaction({from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb", to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53", value: "1000000000000000000", nonce: "2"})
Error: nonce too low
at web3.js:3143:20
at web3.js:6347:15
at web3.js:5081:36
at <anonymous>:1:1
按照nonce的規則,接下來的新交易nonce值應該是3,我現在跳過3,直接指定nonce值為4,會發生什么,如下所示。
> web3.eth.sendTransaction({from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb", to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53", value: "1000000000000000000", nonce:"4"})
"0xcbd473fb1bcc396dfd98e2f1807dea5b78c77f713592c134e7249b3786025d6a"
可以看到,返回了交易hash,我們查看該筆交易,發現blockNumber
為null,并沒有加入到區塊中,如下所示:
> eth.getTransaction("0xcbd473fb1bcc396dfd98e2f1807dea5b78c77f713592c134e7249b3786025d6a")
{
blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
blockNumber: null,
from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb",
gas: 90000,
gasPrice: 1000000000,
hash: "0xcbd473fb1bcc396dfd98e2f1807dea5b78c77f713592c134e7249b3786025d6a",
input: "0x",
nonce: 4,
r: "0x657ceabd6907d50d1af9046ea58352d0804cc3812b290ff103bc32514bc491c5",
s: "0x2bc6b80ef7de0ed9a737ba78f404f825d58ab345c446019fbd67644c2f2b2a36",
to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53",
transactionIndex: 0,
v: "0x558",
value: 1000000000000000000
}
暫未被加入到區塊中的交易,會被放入到交易池中(txpool),交易池里會維護兩個列表,一個是待被打包的pending列表,一個是當前無法執行的交易queued列表。
從下方請求可看到0xcbd473fb1bcc396dfd98e2f1807dea5b78c77f713592c134e7249b3786025d6a
交易被放到了queued列表中。這是由于交易池中沒有找到地址0xfa8d4DEd7fE1feC96c1B10443bEa261195f233Bb
為3的nonce。
> web3.txpool.content.queued
{
0xfa8d4DEd7fE1feC96c1B10443bEa261195f233Bb: {
4: {
blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
blockNumber: null,
from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb",
gas: "0x15f90",
gasPrice: "0x3b9aca00",
hash: "0xcbd473fb1bcc396dfd98e2f1807dea5b78c77f713592c134e7249b3786025d6a",
input: "0x",
nonce: "0x4",
r: "0x657ceabd6907d50d1af9046ea58352d0804cc3812b290ff103bc32514bc491c5",
s: "0x2bc6b80ef7de0ed9a737ba78f404f825d58ab345c446019fbd67644c2f2b2a36",
to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53",
transactionIndex: "0x0",
v: "0x558",
value: "0xde0b6b3a7640000"
}
}
新建一筆交易,設置nonce為3,如下所示。
會看到提交交易后,queued列表中nonce為4的交易也被移出,同時待打包的pending列表中,有了nonce值為3和4的交易信息。挖礦后,這兩筆交易將會被寫入到區塊中。
> web3.eth.sendTransaction({from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb", to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53", value: "1000000000000000000", nonce:"3"})
"0xf9ed5af220997d8278e075ed87b391e6a28354b0c45726bb41ebae22fe5817b1"
> web3.txpool.content.pending
{
0xfa8d4DEd7fE1feC96c1B10443bEa261195f233Bb: {
3: {
blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
blockNumber: null,
from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb",
gas: "0x15f90",
gasPrice: "0x3b9aca00",
hash: "0xf9ed5af220997d8278e075ed87b391e6a28354b0c45726bb41ebae22fe5817b1",
input: "0x",
nonce: "0x7",
r: "0x258f9f382c3cd8c595cd610ad7df09cc5b0d6b7a6cd68a67a0d151a1d72d8c72",
s: "0x7c4fe98cbeb967f8a6d7ebb4c848c264521ddf98fc992f99571d4e2380e50a9a",
to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53",
transactionIndex: "0x0",
v: "0x557",
value: "0xde0b6b3a7640000"
},
4: {
blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
blockNumber: null,
from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb",
gas: "0x15f90",
gasPrice: "0x3b9aca00",
hash: "0xcbd473fb1bcc396dfd98e2f1807dea5b78c77f713592c134e7249b3786025d6a",
input: "0x",
nonce: "0x7",
r: "0x258f9f382c3cd8c595cd610ad7df09cc5b0d6b7a6cd68a67a0d151a1d72d8c72",
s: "0x7c4fe98cbeb967f8a6d7ebb4c848c264521ddf98fc992f99571d4e2380e50a9a",
to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53",
transactionIndex: "0x0",
v: "0x557",
value: "0xde0b6b3a7640000"
},
}
}
總結一下:
- 以太坊中有兩種nonce,一種是在區塊中的nonce,主要是調整挖礦難度;一種是每筆交易中nonce。
- 每個外部賬戶(私鑰控制的賬戶)都有一個nonce值,從0開始連續累加,每累加一次,代表一筆交易。
- 某一地址的某一交易的nonce值如果大于當前的nonce,該交易會被放到交易池的queued列表中,直到缺失的nonce被提交到交易池中。
- 地址的nonce值是一個連續的整數,起設計的主要目的是防止雙花。
- 在發生一筆交易時,如果不指定nonce值時,節點會根據當前交易池的交易自動計算該筆交易的nonce。有可能會出現節點A和節點B計算的nonce值不一樣的情況。
參考代碼:
一筆交易調用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”的錯誤。