nonce在區(qū)塊鏈中是一個(gè)非常重要的概念,從比特幣到以太坊都有nonce的身影勉躺。
在比特幣中癌瘾,nonce主要用于調(diào)整pow挖礦的難度,而在以太坊中饵溅,除了調(diào)整挖礦難度外妨退,在外部賬戶的每筆交易中也都存在一個(gè)nonce。這個(gè)nonce是一個(gè)連續(xù)的整數(shù)蜕企,在每個(gè)賬戶發(fā)送交易時(shí)所產(chǎn)生咬荷,其主要設(shè)計(jì)目的是為防止雙花。
web3中的sendTransaction方法轻掩,官方文檔是這樣寫的:
https://web3js.readthedocs.io/en/1.0/web3-eth.html#sendtransaction
可以看到幸乒,在構(gòu)建交易時(shí),有一個(gè)可選的nonce參數(shù)放典,可以覆蓋在交易池中的逝变,pending列表中相同nonce的交易。
接下來我會(huì)在Geth搭建的私有鏈來進(jìn)行以下實(shí)驗(yàn)奋构,來看看不同情況下nonce對(duì)應(yīng)的交易會(huì)怎樣:
- 相同nonce下的兩筆交易
- 不連續(xù)nonce下的交易
- 不具體指定nonce的交易
說明:使用Geth搭建私有鏈詳見:http://www.reibang.com/p/4c3efd23a427
首先下方命令看到地址0xfa8d4ded7fe1fec96c1b10443bea261195f233bb
的總交易數(shù)是2壳影,也就是說,nonce數(shù)值已累加到1(nonce值從0開始)弥臼,新的交易nonce值將是2宴咧。
> eth.getTransactionCount("0xfa8d4ded7fe1fec96c1b10443bea261195f233bb")
2
接下來指定nonce為2,創(chuàng)建一筆交易径缅,如下可看到掺栅,交易成功,并被打包到了1980區(qū)塊中纳猪。
> 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
}
如果此時(shí)我在發(fā)送一筆交易氧卧,并指定nonce還是2,運(yùn)行結(jié)果如下所示氏堤。
會(huì)看到此時(shí)交易報(bào)錯(cuò)沙绝,提示“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的規(guī)則,接下來的新交易nonce值應(yīng)該是3,我現(xiàn)在跳過3闪檬,直接指定nonce值為4星著,會(huì)發(fā)生什么,如下所示粗悯。
> web3.eth.sendTransaction({from: "0xfa8d4ded7fe1fec96c1b10443bea261195f233bb", to: "0xf3756e74c9c409fdf4fa6d44c492fbb1edf36f53", value: "1000000000000000000", nonce:"4"})
"0xcbd473fb1bcc396dfd98e2f1807dea5b78c77f713592c134e7249b3786025d6a"
可以看到虚循,返回了交易hash,我們查看該筆交易样傍,發(fā)現(xiàn)blockNumber
為null横缔,并沒有加入到區(qū)塊中,如下所示:
> 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
}
暫未被加入到區(qū)塊中的交易铭乾,會(huì)被放入到交易池中(txpool)剪廉,交易池里會(huì)維護(hù)兩個(gè)列表,一個(gè)是待被打包的pending列表炕檩,一個(gè)是當(dāng)前無法執(zhí)行的交易queued列表。
從下方請(qǐng)求可看到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"
}
}
新建一筆交易,設(shè)置nonce為3捞蚂,如下所示妇押。
會(huì)看到提交交易后,queued列表中nonce為4的交易也被移出姓迅,同時(shí)待打包的pending列表中敲霍,有了nonce值為3和4的交易信息。挖礦后丁存,這兩筆交易將會(huì)被寫入到區(qū)塊中肩杈。
> 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"
},
}
}
總結(jié)一下:
- 以太坊中有兩種nonce,一種是在區(qū)塊中的nonce解寝,主要是調(diào)整挖礦難度扩然;一種是每筆交易中nonce。
- 每個(gè)外部賬戶(私鑰控制的賬戶)都有一個(gè)nonce值聋伦,從0開始連續(xù)累加夫偶,每累加一次,代表一筆交易觉增。
- 某一地址的某一交易的nonce值如果大于當(dāng)前的nonce兵拢,該交易會(huì)被放到交易池的queued列表中,直到缺失的nonce被提交到交易池中逾礁。
- 地址的nonce值是一個(gè)連續(xù)的整數(shù)说铃,起設(shè)計(jì)的主要目的是防止雙花。
- 在發(fā)生一筆交易時(shí),如果不指定nonce值時(shí)截汪,節(jié)點(diǎn)會(huì)根據(jù)當(dāng)前交易池的交易自動(dòng)計(jì)算該筆交易的nonce疾牲。有可能會(huì)出現(xiàn)節(jié)點(diǎn)A和節(jié)點(diǎn)B計(jì)算的nonce值不一樣的情況。
參考代碼:
一筆交易調(diào)用add(ctx context.Context, tx *types.Transaction)
方法將交易信息加入到交易池的pending列表中衙解,需要對(duì)交易信息進(jìn)行驗(yàn)證阳柔,驗(yàn)證方法是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
方法獲取當(dāng)前地址在交易池中的nonce值,如果當(dāng)前交易的nonce比交易池中的nonce值小蚓峦,就會(huì)報(bào)“nonce too low”的錯(cuò)誤舌剂。