以太坊原始碼分析(26)core-txpool交易池原始碼分析
txpool主要用來存放當前提交的等待寫入區塊的交易,有遠端和本地的。
txpool裡面的交易分為兩種,
1. 提交但是還不能執行的,放在queue裡面等待能夠執行(比如說nonce太高)。
2. 等待執行的,放在pending裡面等待執行。
從txpool的測試案例來看,txpool主要功能有下面幾點。
1. 交易驗證的功能,包括餘額不足,Gas不足,Nonce太低, value值是合法的,不能為負數。
2. 能夠快取Nonce比當前本地賬號狀態高的交易。 存放在queue欄位。 如果是能夠執行的交易存放在pending欄位
3. 相同使用者的相同Nonce的交易只會保留一個GasPrice最大的那個。 其他的插入不成功。
4. 如果賬號沒有錢了,那麼queue和pending中對應賬號的交易會被刪除。
5. 如果賬號的餘額小於一些交易的額度,那麼對應的交易會被刪除,同時有效的交易會從pending移動到queue裡面。防止被廣播。
6. txPool支援一些限制PriceLimit(remove的最低GasPrice限制),PriceBump(替換相同Nonce的交易的價格的百分比) AccountSlots(每個賬戶的pending的槽位的最小值) GlobalSlots(全域性pending佇列的最大值)AccountQueue(每個賬戶的queueing的槽位的最小值) GlobalQueue(全域性queueing的最大值) Lifetime(在queue佇列的最長等待時間)
7. 有限的資源情況下按照GasPrice的優先順序進行替換。
8. 本地的交易會使用journal的功能存放在磁碟上,重啟之後會重新匯入。 遠端的交易不會。
資料結構
// TxPool contains all currently known transactions. Transactions
// enter the pool when they are received from the network or submitted
// locally. They exit the pool when they are included in the blockchain.
// TxPool 包含了當前知的交易, 當前網路接收到交易,或者本地提交的交易會加入到TxPool。
// 當他們已經被新增到區塊鏈的時候被移除。
// The pool separates processable transactions (which can be applied to the
// current state) and future transactions. Transactions move between those
// two states over time as they are received and processed.
// TxPool分為可執行的交易(可以應用到當前的狀態)和未來的交易。 交易在這兩種狀態之間轉換,
type TxPool struct {
config TxPoolConfig
chainconfig *params.ChainConfig
chain blockChain
gasPrice *big.Int //最低的GasPrice限制
txFeed event.Feed //通過txFeed來訂閱TxPool的訊息
scope event.SubscriptionScope
chainHeadCh chan ChainHeadEvent // 訂閱了區塊頭的訊息,當有了新的區塊頭生成的時候會在這裡收到通知
chainHeadSub event.Subscription // 區塊頭訊息的訂閱器。
signer types.Signer // 封裝了事務簽名處理。
mu sync.RWMutex
currentState *state.StateDB // Current state in the blockchain head
pendingState *state.ManagedState // Pending state tracking virtual nonces
currentMaxGas *big.Int // Current gas limit for transaction caps 目前交易上限的GasLimit
locals *accountSet // Set of local transaction to exepmt from evicion rules 本地交易免除驅逐規則
journal *txJournal // Journal of local transaction to back up to disk 本地交易會寫入磁碟
pending map[common.Address]*txList // All currently processable transactions 所有當前可以處理的交易
queue map[common.Address]*txList // Queued but non-processable transactions 當前還不能處理的交易
beats map[common.Address]time.Time // Last heartbeat from each known account 每一個已知賬號的最後一次心跳資訊的時間
all map[common.Hash]*types.Transaction // All transactions to allow lookups 可以查詢到所有交易
priced *txPricedList // All transactions sorted by price 按照價格排序的交易
wg sync.WaitGroup // for shutdown sync
homestead bool // 家園版本
}
構建
// NewTxPool creates a new transaction pool to gather, sort and filter inbound
// trnsactions from the network.
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
// Sanitize the input to ensure no vulnerable gas prices are set
config = (&config).sanitize()
// Create the transaction pool with its initial settings
pool := &TxPool{
config: config,
chainconfig: chainconfig,
chain: chain,
signer: types.NewEIP155Signer(chainconfig.ChainId),
pending: make(map[common.Address]*txList),
queue: make(map[common.Address]*txList),
beats: make(map[common.Address]time.Time),
all: make(map[common.Hash]*types.Transaction),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
}
pool.locals = newAccountSet(pool.signer)
pool.priced = newTxPricedList(&pool.all)
pool.reset(nil, chain.CurrentBlock().Header())
// If local transactions and journaling is enabled, load from disk
// 如果本地交易被允許,而且配置的Journal目錄不為空,那麼從指定的目錄載入日誌.
// 然後rotate交易日誌. 因為老的交易可能已經失效了, 所以呼叫add方法之後再把被接收的交易寫入日誌.
//
if !config.NoLocals && config.Journal != "" {
pool.journal = newTxJournal(config.Journal)
if err := pool.journal.load(pool.AddLocal); err != nil {
log.Warn("Failed to load transaction journal", "err", err)
}
if err := pool.journal.rotate(pool.local()); err != nil {
log.Warn("Failed to rotate transaction journal", "err", err)
}
}
// Subscribe events from blockchain 從區塊鏈訂閱事件。
pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
// Start the event loop and return
pool.wg.Add(1)
go pool.loop()
return pool
}
reset方法檢索區塊鏈的當前狀態並且確保事務池的內容關於當前的區塊鏈狀態是有效的。主要功能包括:
1. 因為更換了區塊頭,所以原有的區塊中有一些交易因為區塊頭的更換而作廢,這部分交易需要重新加入到txPool裡面等待插入新的區塊
2. 生成新的currentState和pendingState
3. 因為狀態的改變。將pending中的部分交易移到queue裡面
4. 因為狀態的改變,將queue裡面的交易移入到pending裡面。
reset程式碼
// reset retrieves the current state of the blockchain and ensures the content
// of the transaction pool is valid with regard to the chain state.
func (pool *TxPool) reset(oldHead, newHead *types.Header) {
// If we're reorging an old state, reinject all dropped transactions
var reinject types.Transactions
if oldHead != nil && oldHead.Hash() != newHead.ParentHash {
// If the reorg is too deep, avoid doing it (will happen during fast sync)
oldNum := oldHead.Number.Uint64()
newNum := newHead.Number.Uint64()
if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 { //如果老的頭和新的頭差距太遠, 那麼取消重建
log.Warn("Skipping deep transaction reorg", "depth", depth)
} else {
// Reorg seems shallow enough to pull in all transactions into memory
var discarded, included types.Transactions
var (
rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64())
add = pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64())
)
// 如果老的高度大於新的.那麼需要把多的全部刪除.
for rem.NumberU64() > add.NumberU64() {
discarded = append(discarded, rem.Transactions()...)
if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
return
}
}
// 如果新的高度大於老的, 那麼需要增加.
for add.NumberU64() > rem.NumberU64() {
included = append(included, add.Transactions()...)
if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
return
}
}
// 高度相同了.如果hash不同,那麼需要往後找,一直找到他們相同hash根的節點.
for rem.Hash() != add.Hash() {
discarded = append(discarded, rem.Transactions()...)
if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
return
}
included = append(included, add.Transactions()...)
if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
return
}
}
// 找出所有存在discard裡面,但是不在included裡面的值.
// 需要等下把這些交易重新插入到pool裡面。
reinject = types.TxDifference(discarded, included)
}
}
// Initialize the internal state to the current head
if newHead == nil {
newHead = pool.chain.CurrentBlock().Header() // Special case during testing
}
statedb, err := pool.chain.StateAt(newHead.Root)
if err != nil {
log.Error("Failed to reset txpool state", "err", err)
return
}
pool.currentState = statedb
pool.pendingState = state.ManageState(statedb)
pool.currentMaxGas = newHead.GasLimit
// Inject any transactions discarded due to reorgs
log.Debug("Reinjecting stale transactions", "count", len(reinject))
pool.addTxsLocked(reinject, false)
// validate the pool of pending transactions, this will remove
// any transactions that have been included in the block or
// have been invalidated because of another transaction (e.g.
// higher gas price)
// 驗證pending transaction池裡面的交易, 會移除所有已經存在區塊鏈裡面的交易,或者是因為其他交易導致不可用的交易(比如有一個更高的gasPrice)
// demote 降級 將pending中的一些交易降級到queue裡面。
pool.demoteUnexecutables()
// Update all accounts to the latest known pending nonce
// 根據pending佇列的nonce更新所有賬號的nonce
for addr, list := range pool.pending {
txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway
pool.pendingState.SetNonce(addr, txs[len(txs)-1].Nonce()+1)
}
// Check the queue and move transactions over to the pending if possible
// or remove those that have become invalid
// 檢查佇列並儘可能地將事務移到pending,或刪除那些已經失效的事務
// promote 升級
pool.promoteExecutables(nil)
}
addTx
// addTx enqueues a single transaction into the pool if it is valid.
func (pool *TxPool) addTx(tx *types.Transaction, local bool) error {
pool.mu.Lock()
defer pool.mu.Unlock()
// Try to inject the transaction and update any state
replace, err := pool.add(tx, local)
if err != nil {
return err
}
// If we added a new transaction, run promotion checks and return
if !replace {
from, _ := types.Sender(pool.signer, tx) // already validated
pool.promoteExecutables([]common.Address{from})
}
return nil
}
addTxsLocked
// addTxsLocked attempts to queue a batch of transactions if they are valid,
// whilst assuming the transaction pool lock is already held.
// addTxsLocked嘗試把有效的交易放入queue佇列,呼叫這個函式的時候假設已經獲取到鎖
func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) error {
// Add the batch of transaction, tracking the accepted ones
dirty := make(map[common.Address]struct{})
for _, tx := range txs {
if replace, err := pool.add(tx, local); err == nil {
if !replace { // replace 是替換的意思, 如果不是替換,那麼就說明狀態有更新,有可以下一步處理的可能。
from, _ := types.Sender(pool.signer, tx) // already validated
dirty[from] = struct{}{}
}
}
}
// Only reprocess the internal state if something was actually added
if len(dirty) > 0 {
addrs := make([]common.Address, 0, len(dirty))
for addr, _ := range dirty {
addrs = append(addrs, addr)
}
// 傳入了被修改的地址,
pool.promoteExecutables(addrs)
}
return nil
}
demoteUnexecutables 從pending刪除無效的或者是已經處理過的交易,其他的不可執行的交易會被移動到future queue中。
// demoteUnexecutables removes invalid and processed transactions from the pools
// executable/pending queue and any subsequent transactions that become unexecutable
// are moved back into the future queue.
func (pool *TxPool) demoteUnexecutables() {
// Iterate over all accounts and demote any non-executable transactions
for addr, list := range pool.pending {
nonce := pool.currentState.GetNonce(addr)
// Drop all transactions that are deemed too old (low nonce)
// 刪除所有小於當前地址的nonce的交易,並從pool.all刪除。
for _, tx := range list.Forward(nonce) {
hash := tx.Hash()
log.Trace("Removed old pending transaction", "hash", hash)
delete(pool.all, hash)
pool.priced.Removed()
}
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
// 刪除所有的太昂貴的交易。 使用者的balance可能不夠用。或者是out of gas
drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
for _, tx := range drops {
hash := tx.Hash()
log.Trace("Removed unpayable pending transaction", "hash", hash)
delete(pool.all, hash)
pool.priced.Removed()
pendingNofundsCounter.Inc(1)
}
for _, tx := range invalids {
hash := tx.Hash()
log.Trace("Demoting pending transaction", "hash", hash)
pool.enqueueTx(hash, tx)
}
// If there's a gap in front, warn (should never happen) and postpone all transactions
// 如果存在一個空洞(nonce空洞), 那麼需要把所有的交易都放入future queue。
// 這一步確實應該不可能發生,因為Filter已經把 invalids的都處理了。 應該不存在invalids的交易,也就是不存在空洞的。
if list.Len() > 0 && list.txs.Get(nonce) == nil {
for _, tx := range list.Cap(0) {
hash := tx.Hash()
log.Error("Demoting invalidated transaction", "hash", hash)
pool.enqueueTx(hash, tx)
}
}
// Delete the entire queue entry if it became empty.
if list.Empty() {
delete(pool.pending, addr)
delete(pool.beats, addr)
}
}
}
enqueueTx 把一個新的交易插入到future queue。 這個方法假設已經獲取了池的鎖。
// enqueueTx inserts a new transaction into the non-executable transaction queue.
//
// Note, this method assumes the pool lock is held!
func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, error) {
// Try to insert the transaction into the future queue
from, _ := types.Sender(pool.signer, tx) // already validated
if pool.queue[from] == nil {
pool.queue[from] = newTxList(false)
}
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
if !inserted {
// An older transaction was better, discard this
queuedDiscardCounter.Inc(1)
return false, ErrReplaceUnderpriced
}
// Discard any previous transaction and mark this
if old != nil {
delete(pool.all, old.Hash())
pool.priced.Removed()
queuedReplaceCounter.Inc(1)
}
pool.all[hash] = tx
pool.priced.Put(tx)
return old != nil, nil
}
promoteExecutables方法把 已經變得可以執行的交易從future queue 插入到pending queue。通過這個處理過程,所有的無效的交易(nonce太低,餘額不足)會被刪除。
// promoteExecutables moves transactions that have become processable from the
// future queue to the set of pending transactions. During this process, all
// invalidated transactions (low nonce, low balance) are deleted.
func (pool *TxPool) promoteExecutables(accounts []common.Address) {
// Gather all the accounts potentially needing updates
// accounts儲存了所有潛在需要更新的賬戶。 如果賬戶傳入為nil,代表所有已知的賬戶。
if accounts == nil {
accounts = make([]common.Address, 0, len(pool.queue))
for addr, _ := range pool.queue {
accounts = append(accounts, addr)
}
}
// Iterate over all accounts and promote any executable transactions
for _, addr := range accounts {
list := pool.queue[addr]
if list == nil {
continue // Just in case someone calls with a non existing account
}
// Drop all transactions that are deemed too old (low nonce)
// 刪除所有的nonce太低的交易
for _, tx := range list.Forward(pool.currentState.GetNonce(addr)) {
hash := tx.Hash()
log.Trace("Removed old queued transaction", "hash", hash)
delete(pool.all, hash)
pool.priced.Removed()
}
// Drop all transactions that are too costly (low balance or out of gas)
// 刪除所有餘額不足的交易。
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
for _, tx := range drops {
hash := tx.Hash()
log.Trace("Removed unpayable queued transaction", "hash", hash)
delete(pool.all, hash)
pool.priced.Removed()
queuedNofundsCounter.Inc(1)
}
// Gather all executable transactions and promote them
// 得到所有的可以執行的交易,並promoteTx加入pending
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
hash := tx.Hash()
log.Trace("Promoting queued transaction", "hash", hash)
pool.promoteTx(addr, hash, tx)
}
// Drop all transactions over the allowed limit
// 刪除所有超過限制的交易。
if !pool.locals.contains(addr) {
for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()
queuedRateLimitCounter.Inc(1)
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
}
}
// Delete the entire queue entry if it became empty.
if list.Empty() {
delete(pool.queue, addr)
}
}
// If the pending limit is overflown, start equalizing allowances
pending := uint64(0)
for _, list := range pool.pending {
pending += uint64(list.Len())
}
// 如果pending的總數超過系統的配置。
if pending > pool.config.GlobalSlots {
pendingBeforeCap := pending
// Assemble a spam order to penalize large transactors first
spammers := prque.New()
for addr, list := range pool.pending {
// Only evict transactions from high rollers
// 首先把所有大於AccountSlots最小值的賬戶記錄下來, 會從這些賬戶裡面剔除一些交易。
// 注意spammers是一個優先順序佇列,也就是說是按照交易的多少從大到小排序的。
if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots {
spammers.Push(addr, float32(list.Len()))
}
}
// Gradually drop transactions from offenders
offenders := []common.Address{}
for pending > pool.config.GlobalSlots && !spammers.Empty() {
/*
模擬一下offenders佇列的賬戶交易數量的變化情況。
第一次迴圈 [10] 迴圈結束 [10]
第二次迴圈 [10, 9] 迴圈結束 [9,9]
第三次迴圈 [9, 9, 7] 迴圈結束 [7, 7, 7]
第四次迴圈 [7, 7 , 7 ,2] 迴圈結束 [2, 2 ,2, 2]
*/
// Retrieve the next offender if not local address
offender, _ := spammers.Pop()
offenders = append(offenders, offender.(common.Address))
// Equalize balances until all the same or below threshold
if len(offenders) > 1 { // 第一次進入這個迴圈的時候, offenders佇列裡面有交易數量最大的兩個賬戶
// Calculate the equalization threshold for all current offenders
// 把最後加入的賬戶的交易數量當成本次的閾值
threshold := pool.pending[offender.(common.Address)].Len()
// Iteratively reduce all offenders until below limit or threshold reached
// 遍歷直到pending有效,或者是倒數第二個的交易數量等於最後一個的交易數量
for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold {
// 遍歷除了最後一個賬戶以外的所有賬戶, 把他們的交易數量減去1.
for i := 0; i < len(offenders)-1; i++ {
list := pool.pending[offenders[i]]
for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()
// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(offenders[i]) > nonce {
pool.pendingState.SetNonce(offenders[i], nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}
pending--
}
}
}
}
// If still above threshold, reduce to limit or min allowance
// 經過上面的迴圈,所有的超過AccountSlots的賬戶的交易數量都變成了之前的最小值。
// 如果還是超過閾值,那麼在繼續從offenders裡面每次刪除一個。
if pending > pool.config.GlobalSlots && len(offenders) > 0 {
for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots {
for _, addr := range offenders {
list := pool.pending[addr]
for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()
// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
pool.pendingState.SetNonce(addr, nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}
pending--
}
}
}
pendingRateLimitCounter.Inc(int64(pendingBeforeCap - pending))
} //end if pending > pool.config.GlobalSlots {
// If we've queued more transactions than the hard limit, drop oldest ones
// 我們處理了pending的限制, 下面需要處理future queue的限制了。
queued := uint64(0)
for _, list := range pool.queue {
queued += uint64(list.Len())
}
if queued > pool.config.GlobalQueue {
// Sort all accounts with queued transactions by heartbeat
addresses := make(addresssByHeartbeat, 0, len(pool.queue))
for addr := range pool.queue {
if !pool.locals.contains(addr) { // don't drop locals
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
}
}
sort.Sort(addresses)
// Drop transactions until the total is below the limit or only locals remain
// 從後往前,也就是心跳越新的就越會被刪除。
for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
addr := addresses[len(addresses)-1]
list := pool.queue[addr.address]
addresses = addresses[:len(addresses)-1]
// Drop all transactions if they are less than the overflow
if size := uint64(list.Len()); size <= drop {
for _, tx := range list.Flatten() {
pool.removeTx(tx.Hash())
}
drop -= size
queuedRateLimitCounter.Inc(int64(size))
continue
}
// Otherwise drop only last few transactions
txs := list.Flatten()
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
pool.removeTx(txs[i].Hash())
drop--
queuedRateLimitCounter.Inc(1)
}
}
}
}
promoteTx把某個交易加入到pending 佇列. 這個方法假設已經獲取到了鎖.
// promoteTx adds a transaction to the pending (processable) list of transactions.
//
// Note, this method assumes the pool lock is held!
func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) {
// Try to insert the transaction into the pending queue
if pool.pending[addr] == nil {
pool.pending[addr] = newTxList(true)
}
list := pool.pending[addr]
inserted, old := list.Add(tx, pool.config.PriceBump)
if !inserted { // 如果不能替換, 已經存在一個老的交易了. 刪除.
// An older transaction was better, discard this
delete(pool.all, hash)
pool.priced.Removed()
pendingDiscardCounter.Inc(1)
return
}
// Otherwise discard any previous transaction and mark this
if old != nil {
delete(pool.all, old.Hash())
pool.priced.Removed()
pendingReplaceCounter.Inc(1)
}
// Failsafe to work around direct pending inserts (tests)
if pool.all[hash] == nil {
pool.all[hash] = tx
pool.priced.Put(tx)
}
// Set the potentially new pending nonce and notify any subsystems of the new tx
// 把交易加入到佇列,併傳送訊息告訴所有的訂閱者, 這個訂閱者在eth協議內部. 會接收這個訊息並把這個訊息通過網路廣播出去.
pool.beats[addr] = time.Now()
pool.pendingState.SetNonce(addr, tx.Nonce()+1)
go pool.txFeed.Send(TxPreEvent{tx})
}
removeTx,刪除某個交易, 並把所有後續的交易移動到future queue
// removeTx removes a single transaction from the queue, moving all subsequent
// transactions back to the future queue.
func (pool *TxPool) removeTx(hash common.Hash) {
// Fetch the transaction we wish to delete
tx, ok := pool.all[hash]
if !ok {
return
}
addr, _ := types.Sender(pool.signer, tx) // already validated during insertion
// Remove it from the list of known transactions
delete(pool.all, hash)
pool.priced.Removed()
// Remove the transaction from the pending lists and reset the account nonce
// 把交易從pending刪除, 並把因為這個交易的刪除而變得無效的交易放到future queue
// 然後更新pendingState的狀態
if pending := pool.pending[addr]; pending != nil {
if removed, invalids := pending.Remove(tx); removed {
// If no more transactions are left, remove the list
if pending.Empty() {
delete(pool.pending, addr)
delete(pool.beats, addr)
} else {
// Otherwise postpone any invalidated transactions
for _, tx := range invalids {
pool.enqueueTx(tx.Hash(), tx)
}
}
// Update the account nonce if needed
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
pool.pendingState.SetNonce(addr, nonce)
}
return
}
}
// Transaction is in the future queue
// 把交易從future queue刪除.
if future := pool.queue[addr]; future != nil {
future.Remove(tx)
if future.Empty() {
delete(pool.queue, addr)
}
}
}
loop是txPool的一個goroutine.也是主要的事件迴圈.等待和響應外部區塊鏈事件以及各種報告和交易驅逐事件。
// loop is the transaction pool's main event loop, waiting for and reacting to
// outside blockchain events as well as for various reporting and transaction
// eviction events.
func (pool *TxPool) loop() {
defer pool.wg.Done()
// Start the stats reporting and transaction eviction tickers
var prevPending, prevQueued, prevStales int
report := time.NewTicker(statsReportInterval)
defer report.Stop()
evict := time.NewTicker(evictionInterval)
defer evict.Stop()
journal := time.NewTicker(pool.config.Rejournal)
defer journal.Stop()
// Track the previous head headers for transaction reorgs
head := pool.chain.CurrentBlock()
// Keep waiting for and reacting to the various events
for {
select {
// Handle ChainHeadEvent
// 監聽到區塊頭的事件, 獲取到新的區塊頭.
// 呼叫reset方法
case ev := <-pool.chainHeadCh:
if ev.Block != nil {
pool.mu.Lock()
if pool.chainconfig.IsHomestead(ev.Block.Number()) {
pool.homestead = true
}
pool.reset(head.Header(), ev.Block.Header())
head = ev.Block
pool.mu.Unlock()
}
// Be unsubscribed due to system stopped
case <-pool.chainHeadSub.Err():
return
// Handle stats reporting ticks 報告就是列印了一些日誌
case <-report.C:
pool.mu.RLock()
pending, queued := pool.stats()
stales := pool.priced.stales
pool.mu.RUnlock()
if pending != prevPending || queued != prevQueued || stales != prevStales {
log.Debug("Transaction pool status report", "executable", pending, "queued", queued, "stales", stales)
prevPending, prevQueued, prevStales = pending, queued, stales
}
// Handle inactive account transaction eviction
// 處理超時的交易資訊,
case <-evict.C:
pool.mu.Lock()
for addr := range pool.queue {
// Skip local transactions from the eviction mechanism
if pool.locals.contains(addr) {
continue
}
// Any non-locals old enough should be removed
if time.Since(pool.beats[addr]) > pool.config.Lifetime {
for _, tx := range pool.queue[addr].Flatten() {
pool.removeTx(tx.Hash())
}
}
}
pool.mu.Unlock()
// Handle local transaction journal rotation 處理定時寫交易日誌的資訊.
case <-journal.C:
if pool.journal != nil {
pool.mu.Lock()
if err := pool.journal.rotate(pool.local()); err != nil {
log.Warn("Failed to rotate local tx journal", "err", err)
}
pool.mu.Unlock()
}
}
}
}
add 方法, 驗證交易並將其插入到future queue. 如果這個交易是替換了當前存在的某個交易,那麼會返回之前的那個交易,這樣外部就不用呼叫promote方法. 如果某個新增加的交易被標記為local, 那麼它的傳送賬戶會進入白名單,這個賬戶的關聯的交易將不會因為價格的限制或者其他的一些限制被刪除.
// add validates a transaction and inserts it into the non-executable queue for
// later pending promotion and execution. If the transaction is a replacement for
// an already pending or queued one, it overwrites the previous and returns this
// so outer code doesn't uselessly call promote.
//
// If a newly added transaction is marked as local, its sending account will be
// whitelisted, preventing any associated transaction from being dropped out of
// the pool due to pricing constraints.
func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
// If the transaction is already known, discard it
hash := tx.Hash()
if pool.all[hash] != nil {
log.Trace("Discarding already known transaction", "hash", hash)
return false, fmt.Errorf("known transaction: %x", hash)
}
// If the transaction fails basic validation, discard it
// 如果交易不能通過基本的驗證,那麼丟棄它
if err := pool.validateTx(tx, local); err != nil {
log.Trace("Discarding invalid transaction", "hash", hash, "err", err)
invalidTxCounter.Inc(1)
return false, err
}
// If the transaction pool is full, discard underpriced transactions
// 如果交易池滿了. 那麼刪除一些低價的交易.
if uint64(len(pool.all)) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
// If the new transaction is underpriced, don't accept it
// 如果新交易本身就是低價的.那麼不接收它
if pool.priced.Underpriced(tx, pool.locals) {
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
underpricedTxCounter.Inc(1)
return false, ErrUnderpriced
}
// New transaction is better than our worse ones, make room for it
// 否則刪除低價值的給他騰空間.
drop := pool.priced.Discard(len(pool.all)-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals)
for _, tx := range drop {
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice())
underpricedTxCounter.Inc(1)
pool.removeTx(tx.Hash())
}
}
// If the transaction is replacing an already pending one, do directly
from, _ := types.Sender(pool.signer, tx) // already validated
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
// Nonce already pending, check if required price bump is met
// 如果交易對應的Nonce已經在pending佇列了,那麼產看是否能夠替換.
inserted, old := list.Add(tx, pool.config.PriceBump)
if !inserted {
pendingDiscardCounter.Inc(1)
return false, ErrReplaceUnderpriced
}
// New transaction is better, replace old one
if old != nil {
delete(pool.all, old.Hash())
pool.priced.Removed()
pendingReplaceCounter.Inc(1)
}
pool.all[tx.Hash()] = tx
pool.priced.Put(tx)
pool.journalTx(from, tx)
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
return old != nil, nil
}
// New transaction isn't replacing a pending one, push into queue
// 新交易不能替換pending裡面的任意一個交易,那麼把他push到futuren 佇列裡面.
replace, err := pool.enqueueTx(hash, tx)
if err != nil {
return false, err
}
// Mark local addresses and journal local transactions
if local {
pool.locals.add(from)
}
// 如果是本地的交易,會被記錄進入journalTx
pool.journalTx(from, tx)
log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
return replace, nil
}
validateTx 使用一致性規則來檢查一個交易是否有效,並採用本地節點的一些啟發式的限制.
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
if tx.Size() > 32*1024 {
return ErrOversizedData
}
// Transactions can't be negative. This may never happen using RLP decoded
// transactions but may occur if you create a transaction using the RPC.
if tx.Value().Sign() < 0 {
return ErrNegativeValue
}
// Ensure the transaction doesn't exceed the current block limit gas.
if pool.currentMaxGas.Cmp(tx.Gas()) < 0 {
return ErrGasLimit
}
// Make sure the transaction is signed properly
// 確保交易被正確簽名.
from, err := types.Sender(pool.signer, tx)
if err != nil {
return ErrInvalidSender
}
// Drop non-local transactions under our own minimal accepted gas price
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
// 如果不是本地的交易,並且GasPrice低於我們的設定,那麼也不會接收.
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
return ErrUnderpriced
}
// Ensure the transaction adheres to nonce ordering
// 確保交易遵守了Nonce的順序
if pool.currentState.GetNonce(from) > tx.Nonce() {
return ErrNonceTooLow
}
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL
// 確保使用者有足夠的餘額來支付.
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
intrGas := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
// 如果交易是一個合約建立或者呼叫. 那麼看看是否有足夠的 初始Gas.
if tx.Gas().Cmp(intrGas) < 0 {
return ErrIntrinsicGas
}
return nil
}
網址:http://www.qukuailianxueyuan.io/
欲領取造幣技術與全套虛擬機器資料
區塊鏈技術交流QQ群:756146052 備註:CSDN
尹成學院微信:備註:CSDN
相關文章
- 以太坊交易池原始碼分析原始碼
- 以太坊原始碼分析(18)以太坊交易執行分析原始碼
- 以太坊原始碼分析(12)交易資料分析原始碼
- 以太坊交易池原始碼解析原始碼
- 以太坊原始碼分析(25)core-txlist交易池的一些資料結構原始碼分析原始碼資料結構
- 以太坊原始碼分析(36)ethdb原始碼分析原始碼
- 以太坊原始碼分析(38)event原始碼分析原始碼
- 以太坊原始碼分析(41)hashimoto原始碼分析原始碼
- 以太坊原始碼分析(43)node原始碼分析原始碼
- 以太坊原始碼分析(52)trie原始碼分析原始碼
- 以太坊原始碼分析(51)rpc原始碼分析原始碼RPC
- 以太坊原始碼分析(20)core-bloombits原始碼分析原始碼OOM
- 以太坊原始碼分析(24)core-state原始碼分析原始碼
- 以太坊原始碼分析(29)core-vm原始碼分析原始碼
- 以太坊原始碼分析(23)core-state-process原始碼分析原始碼
- 以太坊原始碼分析(34)eth-downloader原始碼分析原始碼
- 以太坊原始碼分析(35)eth-fetcher原始碼分析原始碼
- 以太坊原始碼分析(5)accounts程式碼分析原始碼
- 以太坊原始碼分析(3)以太坊交易手續費明細原始碼
- open-ethereum-pool以太坊礦池原始碼分析(2)API分析原始碼API
- 以太坊原始碼分析(28)core-vm-stack-memory原始碼分析原始碼
- 以太坊原始碼分析(30)eth-bloombits和filter原始碼分析原始碼OOMFilter
- 以太坊原始碼分析(31)eth-downloader-peer原始碼分析原始碼
- 以太坊原始碼分析(32)eth-downloader-peer原始碼分析原始碼
- 以太坊原始碼分析(33)eth-downloader-statesync原始碼分析原始碼
- 以太坊原始碼分析(8)區塊分析原始碼
- 以太坊原始碼分析(9)cmd包分析原始碼
- 以太坊原始碼分析(13)RPC分析原始碼RPC
- 以太坊原始碼分析(16)挖礦分析原始碼
- open-ethereum-pool以太坊礦池原始碼分析(1)-main入口分析原始碼AI
- 以太坊原始碼分析(37)eth以太坊協議分析原始碼協議
- 以太坊原始碼分析(27)core-vm-jumptable-instruction原始碼分析原始碼Struct
- 以太坊原始碼分析(44)p2p-database.go原始碼分析原始碼DatabaseGo
- 以太坊原始碼分析(45)p2p-dial.go原始碼分析原始碼Go
- 以太坊原始碼分析(46)p2p-peer.go原始碼分析原始碼Go
- 以太坊原始碼分析(48)p2p-server.go原始碼分析原始碼ServerGo
- 以太坊原始碼分析(49)p2p-table.go原始碼分析原始碼Go
- 以太坊原始碼分析(50)p2p-udp.go原始碼分析原始碼UDPGo