Transactions

當交易隨機數太高時會發生什麼?

  • October 17, 2021

設計原理

賬戶範式的一個弱點是,為了防止重放攻擊,每筆交易都必須有一個“隨機數”,這樣賬戶就會跟踪使用的隨機數,並且只有在最後一次使用的隨機數之後其隨機數為 1 時才接受交易。

這個網站上有一些關於交易隨機數太低的問題。當交易隨機數太高時會發生什麼?

概括

  • 隨機數太低的交易會立即被拒絕。

  • 具有過高隨機數的事務被放置在事務池隊列中。

  • 如果發送具有填充最後一個有效隨機數和過高隨機數之間的間隙的隨機數的交易並且隨機數序列完成,則序列中的所有交易都將得到處理和探勘。

  • 當 geth 實例關閉並重新啟動時,事務池隊列中的事務就會消失。

  • 事務池隊列最多只能容納 64 個具有相同From:地址且隨機數亂序的事務。

  • geth 實例可以通過使用 64 個具有相同地址且隨機數亂序的事務填充事務池隊列來崩潰,例如From:

    • 用最少的乙太幣創建多個賬戶(在我的測試中為 0.05 ETH)
    • 從每個賬戶發送 64 筆交易,數據負載很大
    • 對於我在我的 geth 實例上設置的 4 Gb 記憶體限制,400 x 64 個事務和大約 4,500 字節的有效負載會使 geth 實例崩潰(無論如何,從我有限的測試來看)。
    • 這些隨機數太高的交易不會傳播到其他節點並導致乙太坊網路上的其他節點崩潰。
  • 乙太坊世界電腦不能因為隨機數太高的交易而崩潰。幹得好,開發人員!

詳情如下。


當交易隨機數太低時會發生什麼?

我在我的私人 –dev 網路上創建了兩個新帳戶,使用

geth -datadir ./data --dev account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat Passphrase: 
Address: {c18f2996c11ba48c7e14233710e5a8580c4fb9ee}

geth -datadir ./data --dev account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat Passphrase: 
Address: {e1fb110faa8850b4c6be5bdb3b7940d1ede87dfb}

我已經在一個單獨的視窗中啟動了一個挖礦 geth 實例,因此隨著新區塊的開採,第一個帳戶(coinbase)將存入更多資金。

geth --datadir ./data --dev --mine --minerthreads 1 console

在我的主視窗中,我啟動了一個附加到探勘實例的 geth 實例。

geth --datadir ./data --dev attach

我將第一筆交易從我的第一個賬戶發送到我的第二個賬戶

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether")})
Unlock account c18f2996c11ba48c7e14233710e5a8580c4fb9ee
Passphrase: 
"0xd17510a4c9880155b0237cac58423e05b61ad3f2d5ee90f72d3db2b7d4ea2d47"

以下是第一筆交易的交易詳情。已為此事務自動分配了 0 的隨機數。

> eth.getTransaction("0xd17510a4c9880155b0237cac58423e05b61ad3f2d5ee90f72d3db2b7d4ea2d47")
{
  blockHash: "0x519ddf8c3d1a094933d2975bb7c9cdf3680c9d66b880ba22b26627f70d90bb54",
  blockNumber: 88,
  from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
  gas: 90000,
  gasPrice: 20000000000,
  hash: "0xd17510a4c9880155b0237cac58423e05b61ad3f2d5ee90f72d3db2b7d4ea2d47",
  input: "0x",
  nonce: 0,
  to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
  transactionIndex: 0,
  value: 1000000000000000000
}

我從我的第一個帳戶向我的第二個帳戶發送第二筆交易,指定隨機數為 0,並得到“隨機數太低”的預期結果。

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:0})
Nonce too low
   at InvalidResponse (<anonymous>:-81662:-106)
   at send (<anonymous>:-156322:-106)
   at sendTransaction (<anonymous>:-133322:-106)
   at <anonymous>:1:1

當 Transaction Nonce 過高時會發生什麼?

我現在將第三筆交易從我的第一個賬戶發送到我的第二個賬戶,指定一個隨機數為 10000,並獲得一個交易雜湊,表明該交易已發送到交易池。

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:10000})
"0x5b09270d6bcd33297527a1f6b08fa1528deec01e82a100c7e62ee93fbdcd1f7d"

在探勘視窗中,一條消息顯示已收到交易。然而,該交易從未被探勘。

I0409 15:25:07.699859   10726 worker.go:569] commit new work on block 95 with 0 txs & 0 uncles. Took 289.587µs
I0409 15:25:08.493883   10726 xeth.go:1028] Tx(0x5b09270d6bcd33297527a1f6b08fa1528deec01e82a100c7e62ee93fbdcd1f7d) to: 0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb
> I0409 15:26:13.472919   10726 worker.go:348] 🔨  Mined block (#95 / 7fe1ada0). Wait 5 blocks for confirmation
I0409 15:26:13.473634   10726 worker.go:569] commit new work on block 96 with 0 txs & 0 uncles. Took 630.605µs
I0409 15:26:13.473707   10726 worker.go:447] 🔨 🔗  Mined 5 blocks back: block #90
I0409 15:26:13.474252   10726 worker.go:569] commit new work on block 96 with 0 txs & 0 uncles. Took 447.451µs
I0409 15:26:18.921404   10726 worker.go:348] 🔨  Mined block (#96 / 760e117c). Wait 5 blocks for confirmation
I0409 15:26:18.922033   10726 worker.go:569] commit new work on block 97 with 0 txs & 0 uncles. Took 547.204µs
I0409 15:26:18.922096   10726 worker.go:447] 🔨 🔗  Mined 5 blocks back: block #91

我嘗試檢索第三筆交易的交易詳情。blockHash 和 blockNumber 永遠保持為空。

> eth.getTransaction("0x5b09270d6bcd33297527a1f6b08fa1528deec01e82a100c7e62ee93fbdcd1f7d")
{
 blockHash: null,
 blockNumber: null,
 from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
 gas: 90000,
 gasPrice: 20000000000,
 hash: "0x5b09270d6bcd33297527a1f6b08fa1528deec01e82a100c7e62ee93fbdcd1f7d",
 input: "0x",
 nonce: 10000,
 to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
 transactionIndex: null,
 value: 1000000000000000000
}

我檢查了事務池狀態,並假設隨機數為 10000 的事務在隊列中。

> txpool.status
{
 pending: 0,
 queued: 1
}

我嘗試發送隨機數為 1 的第四筆交易。該交易被開採。

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:1})
"0x545af0a0276e154a8669921373de8904a330b829318a7c83f5bd9f9771e71ff8"
> eth.getTransaction("0x545af0a0276e154a8669921373de8904a330b829318a7c83f5bd9f9771e71ff8")
{
 blockHash: "0xc125f5da96e36ac87728a35eae8ff8046bcc08c6242825daa4b6bb1e7b460a01",
 blockNumber: 101,
 from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
 gas: 90000,
 gasPrice: 20000000000,
 hash: "0x545af0a0276e154a8669921373de8904a330b829318a7c83f5bd9f9771e71ff8",
 input: "0x",
 nonce: 1,
 to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
 transactionIndex: 0,
 value: 1000000000000000000
}

所以我嘗試發送一個隨機數為 3 的第五個交易(現在有一個間隙,因為最後一個有效的隨機數是 1)。事務進入事務池隊列並且不會被探勘。

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:3})
"0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461"
> eth.getTransaction("0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461")
{
 blockHash: null,
 blockNumber: null,
 from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
 gas: 90000,
 gasPrice: 20000000000,
 hash: "0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461",
 input: "0x",
 nonce: 3,
 to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
 transactionIndex: null,
 value: 1000000000000000000
}

我發送了一個隨機數為 2 的第六筆交易(這填補了最後一個有效隨機數為 1 和排隊交易的隨機數為 3 之間的空白)。

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:2})
"0xea7a6350d6f7aa61a5f515452021de905917338d3b4d354e19fc53d8bd4982f4"

現在,nonce 為 2 和 3 的交易都被探勘了。

> eth.getTransaction("0xea7a6350d6f7aa61a5f515452021de905917338d3b4d354e19fc53d8bd4982f4")
{
 blockHash: "0xabcfea8140fdbe3d04bab05cb0232a8c73de4a6bc2307907ede9d45ad58d7107",
 blockNumber: 170,
 from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
 gas: 90000,
 gasPrice: 20000000000,
 hash: "0xea7a6350d6f7aa61a5f515452021de905917338d3b4d354e19fc53d8bd4982f4",
 input: "0x",
 nonce: 2,
 to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
 transactionIndex: 0,
 value: 1000000000000000000
}
> eth.getTransaction("0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461")
{
 blockHash: "0xabcfea8140fdbe3d04bab05cb0232a8c73de4a6bc2307907ede9d45ad58d7107",
 blockNumber: 170,
 from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
 gas: 90000,
 gasPrice: 20000000000,
 hash: "0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461",
 input: "0x",
 nonce: 3,
 to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
 transactionIndex: 1,
 value: 1000000000000000000
}

我檢查了交易池狀態,nonce 10000 的交易仍在隊列中,並且將永遠保留。

> txpool.status
{
 pending: 0,
 queued: 1
}

我關閉了我的挖礦 geth 實例和我附加的 geth 實例,然後重新啟動它們。我現在檢查交易池狀態,nonce 10000 的交易已經消失。

> txpool.status
{
 pending: 0,
 queued: 0
}

交易池源碼

查看core/tx_pool.go (#48-50),對於每個發送地址具有無序隨機數序列的事務,最大隊列大小為 64 個事務。

const (
   maxQueued = 64 // max limit of queued txs per address
)

core/tx_pool.go (#436-456)顯示瞭如果隊列太滿則刪除事務的程式碼:

for i, entry := range promote {
   // If we reached a gap in the nonces, enforce transaction limit and stop
   if entry.Nonce() > guessedNonce {
       if len(promote)-i > maxQueued {
           if glog.V(logger.Debug) {
               glog.Infof("Queued tx limit exceeded for %s. Tx %s removed\n", common.PP(address[:]), common.PP(entry.hash[:]))
           }
           for _, drop := range promote[i+maxQueued:] {
               delete(txs, drop.hash)
           }
       }
       break
   }
   // Otherwise promote the transaction and move the guess nonce if needed
   pool.addTx(entry.hash, address, entry.Transaction)
   delete(txs, entry.hash)

   if entry.Nonce() == guessedNonce {
       guessedNonce++
   }
}

使用過高的隨機數對 Geth 進行崩潰測試

  • 對於我的測試,我在私有開發網路上創建了一個帶有點對點連接的非挖礦 geth 實例的挖礦 geth 實例。
  • 我通過關閉我的交換文件(sudo swapoff -a在 Linux 上執行)並執行其他記憶體佔用程序來將探勘 geth 實例限制為 4Gb。
  • 我創建了一個 Perl 腳本以迭代方式(1 .. 20,000)在我的挖礦 geth 實例上創建新賬戶,並將 0.05 ETH 從我的 coinbase 轉移到新賬戶中。
  • 我創建了另一個 Perl 腳本以迭代方式(1 .. 20,000)解鎖探勘 geth 實例中的每個新帳戶,並發送 64 筆交易,其中隨機數設置得太高,數據有效負載約為 4,500 字節。
  • 隨機數過高的事務最終填滿了挖礦 geth 實例的事務池隊列,並在大約第 400 次迭代時使 geth 崩潰。我的電腦也關機了。
  • 非挖礦 geth 實例沒有收到任何隨機數過高的交易。
  • 為了確認nonce太高的事務不會在節點之間傳播,我還在非挖礦geth實例上手動創建了一個nonce太高的事務,並且只有非挖礦geth實例的事務池隊列被填滿.

引用自:https://ethereum.stackexchange.com/questions/2808