前言
區塊鏈是一個以 " 去中心化 "、" 去信任化 " 方式集體維護的分散式賬本,這裡的 " 分散式 " 不僅體現在資料的分散式儲存,也體現在資料的分散式記錄,即由系統參與者共同維護,作為 " 賬本 " 的區塊鏈自然少不了記賬,而交易自然而然的成為了重中之重。知道創宇區塊鏈安全實驗室 將從原始碼視角對以太坊交易池資料結構、交易費用設定、交易構建、交易入池、交易簽名、交易驗證等邏輯設計進行簡要淺析,並透過對以太坊交易安全機制設計來研究公鏈安全機制設計。
基本概念
交易流程示意圖大致如下所示:
流程說明:
首先由使用者透過網路發起交易請求,並使用自己的私鑰對交易進行簽名,之後進行交易廣播,進而將交易新增到交易池中,礦工從交易池中獲取交易資訊,然後將其進行打包並生成區塊,之後透過進行共識出塊,最後向全網廣播交易區塊。
資料流向:
- 交易池的資料來源:
本地提交,第三方應用透過呼叫本地以太坊節點的 rpc 服務提交交易;
遠端同步,透過廣播同步的形式,將其他以太坊節點的交易資料同步至本地節點。 - 交易池的資料去向:由 miner(礦工) 獲取並驗證,用於挖礦,挖礦成功後寫進區塊被廣播,交易被寫入規範鏈後會從交易池中進行刪除,如果交易被寫進分叉則交易池中的交易不會減少,之後等待重新打包。
資料結構
首先來看一下 txpoolconfig 的配置資訊:
預設配置如下:
txpool 資料結構如下所示:
基礎配置
在分析交易執行我們首先需要來看一些基本的配置,例如:交易手續費是有有最大的上限 / 下限、交易池配置、交易最大資訊檢索數量等,在這裡我們僅對一些關鍵的點進行檢視:
01 交易手續費
02 交易池配置
03 交易檢索數量
初始化池
交易池的初始化透過 newtxpool 來實現,具體程式碼如下所示:
在這裡首先呼叫 sanitize 函式 對配置引數進行校驗,以規避設定不合理的 gas prices。
之後使用預設配置初始化一個交易池 ( txpool ):
之後初始化本地賬戶並將配置的本地賬戶地址加到交易池:
之後建立更加 gasprices 排序的交易:
具體實現程式碼如下所示:
之後呼叫 reset 更新交易池:
reset 具體實現如下:
之後啟動 reorg 迴圈,使其能夠處理日誌載入期間生成的請求:
schedulereorgloop 具體實現程式碼如下所示,該函式主要用於 reset 和 promoteexecutable 的執行計劃。
此時如果本地交易開啟那麼從本地磁碟載入本地交易。
之後訂閱相關交易事件並開啟主迴圈:
主迴圈 loop 具體實現程式碼如下,它是 txpool 的一個 goroutine,也是主要的事件迴圈,它主要用於等待和響應外部區塊鏈事件以及各種報告和交易驅逐事件 :
構建交易
交易有使用者發起,使得資產從一方轉移至另一方,即所謂的價值轉移,我們最直觀的交易構建就是透過錢包來進行轉賬,在這裡我們直接以 eth_sendtransaction 這一個 rpc 為例進行分析交易的構建流程,eth_sendtransaction 請求示例如下:
引數示例:
- from: data,20 位元組 - 傳送交易的源地址
- to: data,20 位元組 - 交易的目標地址,當建立新合約時可選
- gas: quantity - 交易執行可用 gas 量,可選整數,預設值 90000,未用 gas 將返還
- gasprice: quantity - gas 價格,可選,預設值:待定 (to-be-determined)
- value: quantity - 交易傳送的金額,可選整數
- data: data - 合約的編譯帶啊或被呼叫方法的簽名及編碼引數
- nonce: quantity - nonce,可選,可以使用同一個 nonce 來實現掛起的交易的重寫
響應示例:
下面我們來跟蹤一下 eth_sendtransaction 這一個 rpc 的執行過程,在這裡首先檢索賬戶是否存在,之後檢查 nonce 是否為空,緊接著呼叫 singtx 進行簽名操作,之後呼叫 submittransaction 來提交交易:
signtx 實現程式碼如下所示,在這裡會繼續呼叫 signtx 進行簽名操作,這裡不再深入,後續的 " 交易簽名 " 會進行纖細分析:
簽名之後返回 sendtransaction 中去呼叫 submittransaction 來提交簽名,在這裡會首先檢查交易費用是否足夠,之後呼叫 sendtx 來傳送交易:
sendtx 的具體實現如下,在這裡會呼叫 addlocal 來新增交易到交易池中去,這裡不再深入後續會有 " 新增交易 " 這一個分析單元模組:
之後檢查接受地址是否為空,如果為空則建立一個地址 (一般在合約建立時出現),之後列印一份完整的 tx 詳細資訊的日誌便於後續手動調查分析,之後返回交易的 hash 值:
交易入池
我們知道交易的來源有兩個方面:一個方面是本地提交的,另一個方面是遠端提交的,這兩個的具體實現程式碼分別為 addlocals 和 addremotes,這兩個函式在新增交易到交易池時都是透過呼叫 addtxs 來實現的:
addtxs 程式碼如下所示:
首先會對交易進行過濾,檢查是否是一個已知的交易 (即新增過或廣播過的),之後呼叫 send 函式校驗透過 secp256k1 橢圓曲線從簽名 (v,r,s) 派生的地址,如果派生失敗或簽名不正確,則返回錯誤 :
之後將交易新增到交易池中去 (注意 : 這裡有事務鎖)
addtxslocked 的具體實現如下所示,它會將有效的交易進行排隊處理,同時呼叫 pool.add 函式將交易新增到交易佇列中去:
add 函式的具體實現如下所示:
在這裡會首先檢查當前的交易是否已經知曉 (即被廣播過或者新增到池子裡過),如果已知曉則直接丟棄:
之後鑑別交易是本地提交還是遠端提交,並呼叫函式 validatetx 來驗證交易,如果驗證不透過則直接丟棄:
之後檢查交易池是否滿了,如果滿了則放棄交易佇列中定價過低的交易,globalslots 和 globalqueue 為 pending 和 queue 的最大容量:
之後判斷當前交易在 pending 佇列中是否存在 nonce 值相同的交易,如果存在則判斷當前交易所設定的 gasprice 是否超過設定的 pricebump 百分比,超過則替換覆蓋已存在的交易,否則報錯返回替換交易 gasprice 過低,並且把它扔到 queue 佇列中 ( enqueuetx ):
之後呼叫 enqueuetx 將新增到交易佇列中去,同時檢查 from 賬戶是否為本地地址,如果是則新增到交易池本地地址中去:
enqueuetx 程式碼如下所示,該函式主要將新的交易插入到交易佇列中去:
最後會到 addtx 函式中在這裡會呼叫 requestpromoteexecutables 函式進行一次交易提升請求操作,它主要將交易從 queue 投放到 pending 中去 :
交易簽名
交易簽名主要透過函式 signtx 來實現,首先檢查錢包是否關閉,之後檢查錢包賬戶中是否包含發情交易請求的賬戶,之後呼叫 signtx 進行簽名處理:
signtx 的具體實現程式碼如下所示:
校驗過賬戶的有效性後我們可以透過 signtx 來使用 keystore 進行簽名處理,在這裡緊接著呼叫 latestsignerforchainid 進行簽名:
之後再 signtx 函式 中使用私鑰進行簽名:
在 sign 中使用 ecdsa (橢圓曲線加密演算法) 進行簽名,之後返回簽名的結果:
交易驗證
交易驗證時整個交易環節最重要的一環,對於使用者來說,交易驗證時保證使用者財產安全的重要手段,而對於整個以太坊來說,交易驗證時保證以太坊穩定執行和持續發展的重要方式,交易驗證主要出現在以下幾個場景中:
- 使用者完成一筆交易的簽名時,需要將交易提交到區塊鏈網路中,是交易能夠儘快確認,節點在提交交易之前需要先驗證交易,確認交易的合法性;
- 節點收到其他節點廣播的交易時,節點需要先驗證交易是否合法,合法的交易才會加入節點的交易池;
- 當一個挖礦節點成功計算出符合要求的雜湊值後,節點會將交易池中的交易打包到區塊中,接地那在打包交易的時候需要驗證交易的合法性;
- 節點收到其他節點同步到的區塊是,也需要驗證區塊中包含的交易。
交易驗證由 validatetx 函式來完成,其邏輯程式碼如下所示,在這裡會檢查 eip2718 是否開啟以及交易的型別,之後檢查交易的 size、交易轉賬的額度、交易的 gas、交易簽名的正確性、確保交易遵循 nonce 順序、交易人資產是否足夠、確保交易的 gas price 幣基本的交易費用要高:
交易升級
交易升級主要是指將交易放入 pending 列表中去,該方法與 add 方法的不同之處在於 add 函式 是將獲得到的新交易插入 pending,而 promoteexecutables 是將把給定的賬號地址列表中可以執行的交易從 queue 列表中插入 pending 中,並檢查失效的交易,然後傳送交易池更新事件,其實現程式碼如下所示:
在這裡透過一個 for 迴圈來迭代所有的賬戶並升級交易,在這裡首先將所有 queue 中 nonce 低於賬戶當前 nonce 的交易刪除:
之後將所有 queue 中消費大於賬戶所持餘額或者 gas 大於最大 gas 限制的交易移除:
之後將所有可執行的交易從 queue 裡面新增到 pending 裡面,在這裡會呼叫 promotetx 方法將佇列中的交易 ( txs ) 放入 pending:
promotetx 實現程式碼如下所示,該函式首先將交易插入到 pending 佇列中去,如果舊交易更好 (交易 gasprice 大於或等於原交易價值的 110% 為標準,具體跟 pricebump 設定有關係) 則刪除當前這個交易,如果當前交易相較於舊的交易更好則刪除舊的交易,之後更新列表:
之後回到 promoteexecutables 函式中,如果非本地賬戶 queue 小於限制 ( accountqueue ) 則進行移除操作:
最後記錄移除的條目並更新 queuedgauge,如果佇列中此賬戶的交易為空則刪除此賬戶:
交易降級
交易降級是指當出現新的區塊時,已被打包的交易將從 padding 中降級到 queue 中,或者當另外一筆交易的 gas price 更高時則會從 padding 中降級到 queue 中,降級操作的關鍵實現函式為 demoteunexecutables,交易降級主要出現在以下三種情況中:
- 分叉導致 account 的 nonce 值降低:假如原規範鏈 a 上交易序號 m 花費了 20,且已經上鍊,而分叉後新規範鏈上交易序號 m 未上鍊,從而導致在規範鏈上記錄的賬戶的 nonce 降低,這樣交易 m 就必須要回滾到交易池,放到 queue 中去;
- 分叉後出現間隙:這種問題出現通常是因為交易餘額問題導致的,假如原規範鏈上交易 m 花費 100,分叉後該賬戶又發出一個交易 m 花費 200,這就導致該賬戶餘額本來可以支付原來規範鏈上的某筆交易,但在新的規範鏈上可能就不夠了,這個餘額不足的交易如果是 m+3,那麼在 m+2,m+4 號交易之間就出現了空隙,這就導致從 m+3 開始往後所有的交易都要降級;
- 分叉導致 pending 最前一個交易的 nonce 值與狀態的 nonce 值不等。
demoteunexecutables 程式碼如下所示,在這裡首先透過遍歷 pending 列表來獲取每個 addr 的最新 nonce 值,之後刪除 nonce 小於之前查詢所得 nonce 值的交易,之後返回賬戶餘額已經不足以支付交易費用和一些暫時無效的交易,並將暫時無效的交易放到 queue 中,此時如果有間隙,則將後面的交易移動到 queue 列表中,如果經過上面的降級,如果 pending 裡某個 addr 一個交易都沒有,就把該賬戶給刪除:
池子重置
我們可以透過 reset 來重置交易池,該方法具體程式碼如下所示:
如果老區塊不為空且老區塊不是新區塊的父區塊,則檢查老區塊和新區塊之間的差值是否大於 64,如果超過 64 則不進行重組,否則獲取舊頭和新頭的最新區塊,如果舊頭為 null 則檢查新頭的高度是否小於舊頭的高度,則列印日誌並直接 return,如果不滿足則繼續向下執行;
如果舊頭不為 null 則開始進行重組,此時如果舊鏈的頭區塊大於新鏈的頭區塊高度時則舊鏈先後回退並回收所有回退的交易,如果新鏈的頭區塊大於舊鏈的頭區塊則新鏈後退並回收交易,當新鏈和舊鏈的到達同一高度時則同時回退直到找到共同的父節點,之後找出所有儲存在 discard 裡面但是不在 included 裡面的值,之後將這些交易重新插入到 pool 裡面:
之後設定最新的世界狀態、設定新鏈頭區塊的狀態,然後把舊鏈回退的交易放入交易池:
文末小結
區塊鏈由區塊以鏈式結構相互連結而成,每一個區塊有區塊頭和區塊主體兩部分組成,其中區塊主體儲存交易記錄,故而 " 交易 " 成為了鏈上資料的關鍵所在,也是鏈上價值轉移的主要途徑,在公鏈體系中交易的構建流程、交易的驗證、交易的簽名、gas 費用的設計等環節都存在值得考慮的安全風險,例如:當交易費用 ( gasprice ) 可為 0 時的零手續費惡意 dos 攻擊、交易簽名偽造、雙花攻擊、交易簽名資料長度未校驗導致簽名時節點 oom 等。
本篇文章透過從原始碼角度對以太坊交易池資料結構、交易手續費設定、交易構建、交易簽名、交易入池、交易驗證、交易升級、交易降級、交易池重置等功能模組的分析,探索了以太坊交易處理的流程以及安全設計,而公鏈安全體系的建設依舊是長路漫漫,有待進一步深入探索。
參考連結:
https://blog.csdn.net/lj900911/article/details/84825739
https://blog.csdn.net/pulong0748/article/details/109103562
知道創宇區塊鏈安全實驗室官網:www.knownseclab.com
知道創宇唯一指定存證平臺:www.attest.im
聯絡我們:[email protected]
知道創宇區塊鏈安全實驗室導航
微信公眾號
@ 創宇區塊鏈安全實驗室
微博
@ 知道創宇區塊鏈實驗室
https://weibo.com/blockchainlab
知乎
@ 知道創宇區塊鏈安全實驗室
https://www.zhihu.com/org/zhi-dao-chuang-yu-qu-kuai-lian-an-quan-shi-yan-shi
twitter
@ks_blockchain_
https://twitter.com/ks_blockchain