帳戶隨機數的並發模式
帳戶隨機數是交易結構中唯一可以防止重放攻擊的元素(如這個非常好的答案中所解釋的)。它本質上是強制對賬戶的交易進行順序化。
當交易通過 web3 介面送出到乙太坊節點(例如 Geth、Parity)時,這通常不會導致問題,然後由該節點進行簽名。順序化由節點內部完成,因此對最終使用者是透明的。
在發送到節點(例如通過 web3.eth.sendRawTransaction)之前,由使用者應用程序(即不是 Geth 或 Parity)創建和簽署事務時,情況並非如此。
建構原始交易需要獲取帳戶的隨機數,這可以通過 web3.eth.getTransactionCount 完成,如本問題所述。當不涉及並發時,這可以正常工作。
但是,考慮必須並行簽署多個交易的最終使用者。事務順序化成為使用者應用程序的責任。
“web3.eth.getTransactionCount” 僅返回確認的交易計數(請參閱此問題)。因此,當交易被並行簽名時,除非我們以一種聰明的方式操縱計數,否則只有其中一個會成功。
我的問題是:處理這種情況有哪些好的模式?
候選1:每個使用者多個密鑰/帳戶
這確實允許並發事務。但是,它在使用者應用程序中引入了額外的複雜性。另外,它的可擴展性不太高:考慮是否需要數十或數百個並發事務。此外,鑑於分層確定性錢包不是乙太坊中的原生錢包(我知道有LightWallet),這聽起來不是一個簡單的解決方案。
候選2:循環重試
這會形成一個檢查交易發送結果的循環。如果返回結果表明重複的隨機數(例如 Geth 將返回“替換交易低估”),則使用新的隨機數重試。
此解決方案不可移植:不同的客戶端針對相同的錯誤返回不同的字元串。在實踐中,我似乎注意到 Geth 有時甚至不返回上述字元串,而是默默地丟棄其他事務(未確認,因為我還沒有找到可靠的重現方法)。結果,檢測不能可靠地工作。
候選人 3:單身“現時經理”
每個事務構造都涉及從全域“nonce manager”“申請”一個nonce。單例“nonce manager”可以有一些聰明的邏輯來分配升序的 nonce 數字。這利用了這裡討論的行為。
這不僅引入了單例(可以說是一種反模式),而且聽起來也不容易做到正確:如果低隨機數交易以某種方式失敗(例如,甚至沒有發送到網路),其餘的的交易將無限期停滯。一般來說,這是一個糟糕的解決方案,它引入了比試圖解決更多的複雜性和問題。
候選 4:委託給本地 Geth/Parity 節點
這並不是真正的解決方案。最明顯的問題是額外的依賴似乎是不合理的,如果它只是針對 nonce 欄位的話。
此外,如果使用者應用程序已經管理私鑰,這會引入額外的複雜性:私鑰將需要由 Geth 或 Parity 節點共同管理(或複制)。同樣,如果僅用於處理 nonce,複雜性(和潛在的漏洞)似乎是不合理的。
題外話。在我看來,帳戶 nonce 並不是乙太坊中最好的設計。它強制使用者應用程序要麼處理低級協議細節,要麼引入對節點的依賴(Geth/Parity)。無論哪種方式,它都會給使用者應用程序增加不成比例的體積。
我還沒有到可以測試任何這些東西的地步,但我的直覺是單例 Nonce 管理器將是可行的方法,並有一些改進:
- 使其成為單例交易發送者
- 每個“發件人”地址的最大緩衝區或待處理事務的“頭”匹配或小於對等方在其事務池隊列中每個發件人地址可以擁有的最大事務數。讓我們將這些待處理的交易稱為“槽”。
- 實現為滾動隊列,其中最舊的、已確認的事務從“尾部”中刪除。
- 每個新插槽都會獲得一個新的隨機數
- 每個新插槽都與對 sendRawTransaction 的非同步呼叫相關聯。在任何類型的失敗時,插槽將變為空閒狀態。
- 任何新交易都會添加到最低的空閒槽中。
- 如果在一個插槽因錯誤而被釋放後更高的插槽掛起超過 N 毫秒,則通過使用與最高插槽隨機數相同的隨機數向自身發送 0 eth 來取消最高事務,並在已釋放的插槽中重播該事務。
實現細節需要非常熟悉並發程式、鎖、互斥鎖、非同步等。基本上以上是一個猜測,因為我還沒有直接使用 RPC 呼叫並且無法對其進行測試,但我認為這是我要走的路線的基本原理。我看到隊列結構將提供一個內部 API,用於列出待處理的事務、已處理和支持其他 UI 操作作為獎勵。