交易部分總結(jié),好記性不如爛筆頭
配合代碼食用(Geth v1.9.0 stable)
整體流程
發(fā)起交易:設(shè)定from,to,value,gas等參數(shù)生成交易
交易簽名:使用賬戶私鑰對交易進(jìn)行簽名
提交交易:把交易加入到交易池txpool中
廣播交易:把交易信息廣播給其他結(jié)點(diǎn)
處理交易:礦工取交易,EVM執(zhí)行,改變狀態(tài)
代碼流程(全節(jié)點(diǎn))
1.前端發(fā)起交易
2.internal/ethapi/api.go
PublicTransactionPoolAPI.SendTransaction,根據(jù)參數(shù)創(chuàng)建交易,簽名,submitTransaction提交交易
PublicTransactionPoolAPI.SendRawTransaction,解碼已簽名交易,submitTransaction提交交易
PrivateAccountAPI.SendTransaction,根據(jù)參數(shù)創(chuàng)建簽名交易,submitTransaction提交交易
submitTransaction,調(diào)用后端發(fā)送交易方法Backend.SendTx
3.eth/api_backend.go
- Backend.SendTx,調(diào)用交易池添加交易到本地EthAPIBackend.eth.txPool.AddLocal(signedTx)
4.core/tx_pool.go
tip:TxPool包含pending(當(dāng)前可執(zhí)行交易隊(duì)列)和queue(當(dāng)前不可執(zhí)行交易隊(duì)列)兩個交易隊(duì)列,類型為map[common.Address]*txList
TxPool.AddLocal,調(diào)用TxPool.addTx將交易加入交易池(除AddLocal還有AddRemote,內(nèi)部都調(diào)用TxPool.addTx,如果是local交易會寫入本地磁盤日志中,在節(jié)點(diǎn)重啟時加載恢復(fù),在一些過濾操作中保留的優(yōu)先級高)
-
TxPool.addTx內(nèi)部調(diào)用TxPool.add,add方法進(jìn)行一系列驗(yàn)證后將交易加入queue,用于后續(xù)提升至pending中去執(zhí)行,具體邏輯包括:
- 通過交易哈希驗(yàn)證是未知交易,如果交易池已包含該交易,return
- TxPool.validateTx交易有效驗(yàn)證,驗(yàn)證不通過,return
- 判斷交易池已滿時,判斷如果交易手續(xù)費(fèi)比當(dāng)前交易池中手續(xù)費(fèi)最低的交易還低,return,否則,移除一些交易以騰出空間
- 判斷交易池pending中包含相同from和nonce的交易時,比較gasPrice大于舊交易gasPrice且大于threshold*(門檻),則替換并協(xié)程發(fā)送事件進(jìn)行廣播 go TxPool.txFeed.Send(NewTxsEvent{types.Transactions{tx}}),否則return
- 不是以上兩種替換交易池中已有交易的情況,TxPool.enqueueTx將交易插入queue
- 判斷如果是local交易,記錄并將交易寫入本地磁盤日志中
add方法返回是否有替換交易(replace bool),如果為false,不是替換,僅僅是添加一個新的交易,執(zhí)行參數(shù)為from的TxPool.promoteExecutables
-
TxPool.promoteExecutables方法把queue中變得可執(zhí)行的交易轉(zhuǎn)移到pending中,并刪除所有無效交易,具體邏輯包括:
- 創(chuàng)建臨時變量交易列表promoted
- 判斷參數(shù)accounts(類型[]common.Address)是否為空,如果為空則賦值為queue中所有地址
- 遍歷accounts,取出key為addr的list,即from為addr的交易列表,在循環(huán)中:
- 丟棄nonce比當(dāng)前賬戶nonce低的交易
- 丟棄交易花費(fèi)超過賬戶余額和gas使用超出當(dāng)前交易池可用gas的交易
- 根據(jù)TxPool.pendingState的nonce值取出所有可執(zhí)行的交易,執(zhí)行TxPool.promoteTx將交易插入pending,如果插入成功則把交易加入到promoted
- 如果addr不是本地賬戶,如果列表大小超過交易池中每個賬戶最多不可執(zhí)行交易數(shù)量(默認(rèn)64),丟棄
- 如果列表已經(jīng)為空,刪除queue中addr這個key
- 判斷如果promoted大小大于0,協(xié)程發(fā)送事件進(jìn)行廣播 go pool.txFeed.Send(NewTxsEvent{promoted})
- 判斷如果pending中交易數(shù)量超出限制(4096),做平衡操作:創(chuàng)建集合記錄交易數(shù)量超出限制的非本地賬戶,丟棄pending中這些賬戶下的部分交易直到數(shù)量不超過限制,更新TxPool.pendingState
- 判斷如果queue中交易數(shù)量超出限制(1024),取出非本地賬戶交易,根據(jù)心跳排序,丟棄交易直到數(shù)量不超過限制
-
擴(kuò)展,有TxPool.promoteExecutables還有TxPool.demoteUnexecutables,具體邏輯:
- 遍歷pending,得到addr下交易列表:
- 丟棄nonce比當(dāng)前賬戶nonce低的交易
- 丟棄交易花費(fèi)超過賬戶余額和gas使用超出當(dāng)前交易池可用gas的交易,這些移除的交易最小的nonce為lowest,列表中nonce大于lowest的交易移除并加入臨時列表invalids返回
- 遍歷invalids,調(diào)用TxPool.enqueueTx插入queue
- 判斷如果當(dāng)前列表中找不到addr當(dāng)前nonce對應(yīng)的交易,從pending中移除列表中所有交易,調(diào)用TxPool.enqueueTx插入queue
- pending和beats的key該刪除的刪除
- 調(diào)用時機(jī):在reset中調(diào)用,reset方法在NewTxPool和TxPool.loop中收到ChainHeadEvent時調(diào)用,在礦工worker.resultLoop收到resultCh和BlockChain.InsertChain后會PostChainEvents
- 遍歷pending,得到addr下交易列表:
5.在有新的交易插入pending時,廣播NewTxsEvent,SubscribeNewTxsEvent方法用于監(jiān)聽到NewTxsEvent
調(diào)用時機(jī):
- ethstats/ethstats.go Start方法中開啟loop協(xié)程,在該協(xié)程中訂閱并處理,主要是通知交易事件,并在時間上過濾避免過于頻繁的事件處理
- eth/filters/filter_system.go
- eth/handler.go ProtocolManager.Start方法中訂閱(該方法會在節(jié)點(diǎn)啟動時調(diào)用),go pm.txBroadcastLoop()開啟協(xié)程處理,當(dāng)收到NewTxsEvent,調(diào)用ProtocolManager.BroadcastTxs方法廣播交易到其他所有沒有該交易的節(jié)點(diǎn)
- miner/worker.go newWorker方法中訂閱,并開啟了協(xié)程來處理,當(dāng)收到NewTxsEvent
- 判斷如果節(jié)點(diǎn)不在挖礦,這里會立即調(diào)用worker.commitTransactions方法處理并更新快照,其中worker.commitTransactions方法核心邏輯是調(diào)用core.ApplyTransaction處理交易
- 判斷如果節(jié)點(diǎn)在挖礦,不處理
6.core/state_processor.go
- ApplyTransaction方法,將Transaction轉(zhuǎn)化為Message,創(chuàng)建evm,調(diào)用core/state_transition.go ApplyMessage執(zhí)行Message,執(zhí)行成功后更新狀態(tài),創(chuàng)建交易回執(zhí)并返回
7.core/state_transition.go
- ApplyMessage會創(chuàng)建一個StateTransition對象并調(diào)用其TransitionDb方法,該方法讓虛擬機(jī)evm處理交易數(shù)據(jù)(創(chuàng)建合約或普通交易),并更新nonce,balance等狀態(tài)
計(jì)算threshold(門檻),core/tx_list.go Add方法
old.gasprice(100+priceBump)/100=threshold闕值