1. 程式人生 > >解決replacement transaction underpriced以太坊交易異常

解決replacement transaction underpriced以太坊交易異常

replacement transaction underpriced異常

問題概述

以太坊系列(ETH&ETC)在傳送交易有三個對應的RPC介面,分別是ethsendTransaction、ethsendRawTransaction和personal_sendTransaction。這三個介面傳送(或構造傳送內容時)都需要一個引數nonce。官方文件對此引數的解釋是:整數型別,允許使用相同隨機數覆蓋自己傳送的處於pending狀態的交易。

僅從官網的解釋,我們無法獲取到更多的有效的資訊。但在真實生成中我們會發現如果傳錯nonce欄位值,通過RPC介面呼叫傳送的交易很大可能將不會被確認。如果通過console命令來操作一般不會出現此問題,因為節點已經幫我們處理了。

如果繼續追蹤問題,會發現nonce傳遞錯誤的交易可以通過eth_getTransaction查詢得到相關資訊,但是它的blocknumber始終未null,也就說這邊交易始終未被確認。如果是在dev模式下,應該是很快就會被確認的。更進一步,通過txpool.content命令,會發現那筆交易一直處於queued佇列中,而未被消費。

在使用同一個地址連續傳送交易時,每筆交易往往不可能立即到賬, 當前交易還未到賬的情況下,下一筆交易無論是通過eth.getTransactionCount()獲取nonce值來設定,還是由節點自動從區塊中查詢,都會獲得和前一筆交易同樣的nonce值,這時節點就會報錯Error: replacement transaction underpriced

為了防止交易重播,ETH(ETC)節點要求每筆交易必須有一個nonce數值。每一個賬戶從同一個節點發起交易時,這個nonce值從0開始計數,傳送一筆nonce對應加1。當前面的nonce處理完成之後才會處理後面的nonce。注意這裡的前提條件是相同的地址在相同的節點發送交易。 以下是nonce使用的幾條規則:

● 當nonce太小(小於之前已經有交易使用的nonce值),交易會被直接拒絕。

● 當nonce太大,交易會一直處於佇列之中,這也就是導致我們上面描述的問題的原因;

● 當傳送一個比較大的nonce值,然後補齊開始nonce到那個值之間的nonce,那麼交易依舊可以被執行。

● 當交易處於queue中時停止geth客戶端,那麼交易queue中的交易會被清除掉。

如果系統中的熱點賬戶或普通賬戶發起交易時出現error: replacement transaction underpriced異常,那麼就需要考慮nonce使用是否正確。

引起此異常原因主要是當一個賬戶發起一筆交易,假設使用nonce為1,交易已經發送至節點中,但由於手續費不高或網路擁堵或nonce值過高,此交易處於queued中遲遲未被打包。

同時此地址再發起一筆交易,如果通過eth_getTransactionCount獲取的nonce值與上一個nonce值相同,用同樣的nonce值再發出交易時,如果手續費高於原來的交易,那麼第一筆交易將會被覆蓋,如果手續費低於原來的交易就會發生上面的異常。

通常發生此異常意味著: - 你的Ethereum客戶端中已經有一幣處於pending狀態的交易。 - 新的一筆交易擁有pending狀態交易相同的nonce值。

- 新的交易的gas price太小,無法覆蓋pending狀態的交易。

通常情況下,覆蓋掉一筆處於pending狀態的交易gas price需要高於原交易的110%。

經過上面的解釋追蹤,我們已經瞭解到了nonce的基本使用規則。那麼,在實際應該用中我們如何保障nonce值的可靠性呢?這裡有兩個思路,第一個思路就是由業務系統維護nonce值的遞增。如果交易傳送就出現問題,那麼該地址下一筆交易繼續使用這個nonce進行傳送交易。第二個思路就是使用現有的api查詢當前地址已經發送交易的nonce值,然後對其加1,再發送交易。對應的API介面為:eth_getTransactionCount,此方法由兩個引數,第一個引數為需要查詢nonce的地址,第二個引數為block的狀態:latest、earliest和pending。一般情況使用pending就可以查詢獲得最新已使用的nonce。其他狀態大家可以自行驗證。

如果該熱點賬戶的私鑰資訊等都存放在Ethereum客戶端中,那麼在傳送交易的時候不傳遞nonce值,Ethereum客戶端會幫你處理好此nonce值的排序。

當然,此方案有兩個弊端。第一個是安全性無法保障(未進行冷熱賬戶分離),第二,在熱點賬戶下如果想覆蓋掉一筆交易,需要先查詢一下該交易的資訊,從中獲取nonce值。

自行管理nonce適用於冷熱賬戶模式,也就是適用sendRawTransaction傳送已經簽名好的交易時,此時nonce值已經存在於交易中,並且已經被簽名。

這種模式下,需要在業務系統中維護nonce的自增序列,適用一個nonce之後,在業務系統中對nonce進行加一處理。

此種方案也有限制條件。第一,由於nonce統一進行維護,那麼這個地址必須是內部地址,而且發起交易必須通過統一維護的nonce作為出口,否則在其他地方發起交易,原有維護的nonce將會出現混亂。第二,一旦已經發出的交易發生異常,異常交易的nonce未被使用,那麼異常交易的nonce需要重新被使用之後它後面的nonce才會生效。

在構建一筆新的交易時,在交易資料結構中會產生一個nonce值, nonce是當前區塊鏈下,傳送者(from地址)發出的交易(成功記錄進區塊的)總數, 再加上1。例如新構建一筆從A發往B的交易,A地址之前的交易次數為10,那麼這筆交易中的nonce則會設定成11, 節點驗證通過後則會放入交易池(txPool),並向其他節點廣播,該筆交易等待礦工將其打包進新的區塊。

那麼,如果在先構建併發送了一筆從地址A發出的,nonce為11的交易,在該交易未打包進區塊之前, 再次構建一筆從A發出的交易,並將它傳送到節點,不管是先通過web3的eth.getTransactionCount(A)獲取到的過往的交易數量,還是由節點自行填寫nonce, 後面的這筆交易的nonce同樣是11, 此時就出現了問題:

  1. 後面的這筆交易手續費給得更高, 那麼節點會前面的那筆交易從交易池中剔除,轉而放入後面構建的這筆交易
  2. 如果後面的這筆交易給得不夠高, 就會被廢棄掉, 如果通過web3這樣的sdk來向節點發送交易時,會收到錯誤資訊

實際場景中,會有批量從一個地址傳送交易的需求,首先這些操作可能也應該是並行的,我們不會等待一筆交易成功寫入區塊後再發起第二筆交易,那麼此時有什麼好的解決辦法呢?先來看看geth節點中交易池對交易的處理流程

如之前所說,構建一筆交易時如果不手動設定nonce值,geth節點會預設計算髮起地址此前最大nonce數(寫入區塊的才算數),然後將其加上1, 然後將這筆交易放入節點交易池中的pending佇列,等到節點將其打包進區塊。

構建交易時,nonce值是可以手動設定的,如果當前的nonce本應該設定成11, 但是我手動設定成了13, 在節點收到這筆交易時, 發現pending佇列中並沒有改地址下nonce為11及12的交易, 就會將這筆nonce為13的交易放入交易池的queued佇列中。只有當前面的nonce補齊(nonce為11及12的交易被發現並放入pending佇列)之後,才會將它放入pending佇列中等待打包。

我們把pending佇列中的交易視為可執行的,因為它們可能被礦工打包進最新的區塊。 而queue佇列因為前面的nonce存在缺失,暫時無法被礦工打包,稱為不可執行交易。

那麼實際開發中,批量從一個地址傳送交易時,應該怎麼辦呢?

方案一:那麼在批量從一個地址傳送交易時, 可以持久化一個本地的nonce,構建交易時用本地的nonce去累加,逐一填充到後面的交易。(要注意本地的nonce可能會出現偏差,可能需要定期從區塊中重新獲取nonce,更新至本地)。這個方法也有一定的侷限性,適合內部地址(即只有這個服務會使用該地址傳送交易)。

說到這裡還有個坑,許多人認為通過eth.getTransactionCount(address, "pending"),第二個引數為pending, 就能獲得包含本地交易池pending佇列的nonce值,但是實際情況並不是這樣, 這裡的pending只包含待放入打包區塊的交易, 假設已寫入交易區塊的數量為20, 又傳送了nonce為21,22,23的交易, 通過上面方法取得nonce可能是21(前面的21,22,23均未放入待打包區塊), 也可能是22(前面的21放入待打包區塊了,但是22,23還未放入)。

方案二是每次構建交易時,從geth節點的pending佇列取到最後一筆可執行交易的nonce, 在此基礎上加1,再發送給節點。可以通過txpool.contenttxpool.inspect來獲得交易池列表,裡面可以看到pending及queue的交易列表。

啟動節點時,是可以設定交易池中的每個地址的pending佇列的容量上限,queue佇列的上容量上限, 以及整個交易池的pending佇列和queue佇列的容量上限。所以高併發的批量交易中,需要增加節點的交易池容量。

當然,除了擴大交易池,控制傳送頻率,更要設定合理的交易手續費,eth上交易寫入區塊的速度取決於手續費及eth網路的擁堵狀況,傳送每筆交易時,設定合理的礦工費用,避免大量的交易積壓在交易池。