type TxsPool struct {
config TxsPoolConfig
chainconfig *params.ChainConfig
bc common.IBlockChain
currentState *state.IntraBlockState
pendingNonces *txNoncer
currentMaxGas uint64
ctx context.Context //
cancel context.CancelFunc
wg sync.WaitGroup
mu sync.RWMutex // lock
istanbul bool // Fork indicator whether we are in the istanbul stage.
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.
shanghai bool // Fork indicator whether we are in the Shanghai stage.
locals *accountSet
pending map[types.Address]*txsList
queue map[types.Address]*txsList
beats map[types.Address]time.Time
all *txLookup
priced *txPricedList
gasPrice *uint256.Int
// channel
reqResetCh chan *txspoolResetRequest
reqPromoteCh chan *accountSet
queueTxEventCh chan *transaction.Transaction
reorgDoneCh chan chan struct{}
reorgShutdownCh chan struct{}
changesSinceReorg int
isRun uint32
deposit *deposit.Deposit
}
Has(hash types.Hash) bool
根据输入的hash值判断交易池中是否有该交易,返回bool值
input:hash
output:bool
Pending(enforceTips bool) map[types.Address][]*transaction.Transaction
函数遍历所有当前可处理的交易,按原始帐户分组并按随机数排序。
input:enforceTips 参数可用于对挂起的交易进行额外的过滤,只返回那些在下一个挂起的执行环境中有效提示足够大的交易。
return pending;返回的交易集是一个副本,可以通过调用代码自由修改。
input:enforceTips
output:pending(账户+交易)
txs := list.Flatten()
使用txpool_list中的Flatten()函数可以实现按照nonce排序,并将排序结果缓存下来,形成副本,用于保存。
pending[addr] = txs
这一行代码实现了按照账户将交易进行分组。
GetTransaction() ([]*transaction.Transaction, error)
获取pending队列里面的交易,返回pending,且err:nil
GetTx(hash types.Hash) *transaction.Transaction
通过pool对象中的txLookup类型的all,通过其中的pool.all.Get(hash)方法Get(hash types.Hash) *transaction.Transaction
可以根据hash值获取对应的交易。
AddRemotes(txs []*transaction.Transaction) []error
将远程交易加入交易池中,调用pool对象中的addTxs()方法func (pool *TxsPool) addTxs(txs []*transaction.Transaction, local bool, sync bool) []error
,其中bool local为false,bool sync为false,sync是同步的意思
addTxs(txs []*transaction.Transaction, local bool, sync bool) []error
无需池锁
遍历整个交易列表:
addTxsLocked(txs []*transaction.Transaction, local bool) ([]error, *accountSet)
addTxsLocked 尝试对一批有效的交易进行排队。
必须持有交易池锁。
dirty的定义是调用tx_list中的**newAccountSet()方法实现的。
遍历交易列表,通过调用pool.add()方法进行处理。如果没有被替换的且没有错误信息,就可以调用addTx(tx)**将交易加入交易池中
func newAccountSet(addrs ...types.Address) *accountSet
newAccountSet 创建一个新地址集,其中包含用于发送者派生的关联签名者。
func (pool *TxsPool) add(tx *transaction.Transaction, local bool) (replaced bool, err error)
add 验证事务并将其插入到不可执行队列queue中以供稍后挂起的提升和执行。 如果该交易是已待处理或排队交易的替代交易,则如果其价格较高,它将覆盖前一交易。
如果新添加的交易被标记为本地local,则其发送帐户将被添加到白名单中,以防止任何关联交易因定价限制而被从池中删除。
isLocal := local || pool.locals.containsTx(tx)
如果它来自本地源或来自网络但发送者之前被标记为本地,则将其视为本地事务。
func (pool *TxsPool) validateTx(tx *transaction.Transaction, local bool) error
完成对交易的验证
func (pool *TxsPool) removeTx(hash types.Hash, outofbound bool)
removeTx 从队列中删除单个事务,将所有后续事务移回未来队列。
pool.all.Remove(hash)
从已知队列中删除pending.Remove(tx)
进行删除AddLocal(tx *transaction.Transaction) error
通过调用pool.AddLocals([]*transaction.Transaction{tx})
将本地交易加入交易池
func (pool *TxsPool) AddLocals(txs []*transaction.Transaction) []error
这里调用pool.addTxs(txs, !pool.config.NoLocals, true)
,其中的sync是异步。参考5.1
Stats() (int, int, int, int)
通过遍历pending和queue中的交易获得输出
主要有四个输出:
pendingAddresses:待处理的地址数量
pendingTxs:待处理的交易数量
queuedAddresses:排队等待处理的地址数量
queuedTxs:排队等待处理的交易数量
Nonce(addr types.Address) uint64
获取地址的pending随机数标识符列表
pool.mu.RLock()
是一个用于获取读锁的方法,通常用于在多线程环境中保护共享资源。当一个线程需要读取共享资源时,它会先获取读锁,其他线程在此期间无法修改共享资源。当线程完成读取操作后,它会释放读锁,以便其他线程可以访问共享资源。
defer
关键字用于延迟执行一个函数或方法,直到包含它的函数返回。因此,defer pool.mu.RUnlock()
表示在当前函数返回之前,pool.mu.RUnlock()
方法来释放读锁。这通常用于处理资源池、互斥锁等需要手动释放的资源。通过使用defer
关键字,可以确保即使出现异常情况,也能正确地释放资源。
pool.pendingNonces.get(addr)
是一个用于获取指定地址的待处理随机数的方法。在以太坊智能合约中,随机数生成器通常使用一个待处理的随机数集合来存储待处理的随机数。当合约需要生成一个新的随机数时,它会将该随机数添加到待处理集合中,并返回一个唯一的随机数标识符。当合约完成随机数的使用后,它会从待处理集合中删除该随机数标识符。
通过调用pool对象中的 pool.pendingNonces.get(addr)
方法,可以获取指定地址的待处理随机数标识符列表。这有助于确保每个地址只生成一次随机数,并且可以防止重复生成相同的随机数。
Content() (map[types.Address][]*transaction.Transaction, map[types.Address][]*transaction.Transaction)
获取pending和queue,其中调用了tx_List的对象list,对其中的数据进行了排序,Flatten 根据松散排序的内部表示创建随机数排序的事务切片。 排序结果将被缓存,以备在对内容进行任何修改之前再次请求时使用。其中也需要进行读取锁和释放锁。
SetDeposit(deposit *deposit.Deposit)
这是一个名为SetDeposit
的函数,它接受一个指向deposit.Deposit
类型的指针作为参数。这个函数的作用是将传入的存款对象设置到交易池中。
pool.deposit = deposit
func (pool *TxsPool) promoteTx(addr types.Address, hash types.Hash, tx *transaction.Transaction) bool
PromotionTx 将交易添加到待处理(可处理)的交易列表中,并返回该交易是否已插入或较旧的更好。
注意,此方法假设持有池锁!
pending[addr]==nil
,创建交易列表inserted, old := list.Add(tx, pool.config.PriceBump)
。Add 尝试将新交易插入列表中,返回该交易是否被接受,如果是,则返回它替换的任何先前交易。如果新交易被接受到列表中,列表的成本和气体阈值也可能会更新。if old.GasFeeCapCmp(tx) >= 1 || old.GasTipCapCmp(tx) >= 1 {return false, nil}
这种情况下,交易不被接受。
func (pool *TxsPool) promoteExecutables(accounts []types.Address) []*transaction.Transaction
PromotionExecutables 将已变得可处理的事务从未来队列移动到待处理事务集pending。 在此过程中,所有无效交易(低随机数、低余额)都将被删除。
func (pool *TxsPool) truncatePending()
如果池高于挂起限制,truncatePending 将从挂起队列中删除事务。 对于具有许多待处理交易的所有账户,该算法尝试将交易计数减少大约相同的数量。 该函数的主要目的是在交易池中对待处理的交易进行修剪,以确保交易池中的交易数量不超过全局插槽数。如果某个地址的待处理交易数量超过了阈值,那么将该地址的所有待处理交易减少到阈值以下。。
pool.config.GlobalSlots
,如果是,则直接返回,不执行任何操作。spammers
队列中。(每个地址是否包含在本地地址列表中(!pool.locals.contains(addr)
),以及该地址对应的待处理事务数量是否大于账户插槽数(uint64(list.Len()) > pool.config.AccountSlots
)for pending > pool.config.GlobalSlots && !spammers.Empty()
if len(offenders) > 1
(即待处理交易数量超过全局插槽数的地址)。如果有多个违规者,那么计算所有违规者的阈值,即他们的待处理交易数量。pool.priced.Removed(len(caps))
func (pool *TxsPool) truncateQueue()
如果池高于全局队列限制,则 truncate Queue 会删除队列中最旧的事务。
pool.config.GlobalQueue
,如果是则直接返回,不执行任何操作。adddressByHeart
的对象,其中有len、less、swap三种方法if !pool.locals.contains(addr)
pool.config.GlobalQueue
或仅保留本地for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0;
if size := uint64(list.Len()); size <= drop {
for _, tx := range list.Flatten() {
hash := tx.Hash()
pool.removeTx(hash, true)
}
drop -= size
continue
}
func (pool *TxsPool) demoteUnexecutables()
demoteUnexecutables 从池可执行/待处理队列中删除无效和已处理的事务,并且任何变得不可执行的后续事务都将移回到未来队列中。
注意:交易不会在价目表中标记为已删除,因为重新堆总是由 SetBaseFee 显式触发,并且此函数触发重新堆是不必要且浪费的
这段代码是以太坊中的一部分,它定义了一个名为demoteUnexecutables
的函数。这个函数的主要目的是处理待处理交易池中的不可执行交易。这个函数主要用于确保交易池中的交易都是可执行的,并且保持了交易的有效性和顺序。
pool.currentState.GetNonce(addr)
获取其当前的nonce值。func (pool *TxsPool) blockChangeLoop()
作用是监听区块高度的变化,并在新的区块被插入时重置交易池。
defer pool.wg.Done()
确保在函数退出时调用pool.wg.Done()
来标记工作完成。highestBlockCh
的通道,用于接收最新的区块高度信息。highestBlockCh := make(chan common.ChainHighestBlock)
defer close(highestBlockCh)
确保在函数退出时关闭通道。event.GlobalEvent
,并将highestBlockCh
作为回调函数传递给它。highestSub := event.GlobalEvent.Subscribe(highestBlockCh)
defer highestSub.Unsubscribe()
确保在函数退出时取消订阅。oldBlock
中。oldBlock := pool.bc.CurrentBlock()
select
语句监听以下事件:
highestSub.Err()
:如果发生错误,则返回并结束函数执行。pool.ctx.Done()
:如果上下文被取消,则返回并结束函数执行。highestBlockCh
:如果从通道中接收到新的区块高度信息,则进行以下操作:
pool.requestReset(oldBlock, pool.bc.CurrentBlock())
方法重置交易池,并更新oldBlock
为当前区块的高度。case highestBlock, ok := <-highestBlockCh:
if ok && highestBlock.Inserted {
pool.requestReset(oldBlock, pool.bc.CurrentBlock())
oldBlock = pool.bc.CurrentBlock()
}
func (pool *TxsPool) requestReset(oldBlock block.IBlock, newBlock block.IBlock) <-chan struct{}
该方法接收两个参数:oldBlock
和newBlock
,它们都是block.IBlock
类型的接口。这个方法的作用是向交易池发送一个重置请求,并等待重置操作完成或发生异常情况。
方法内部使用了select
语句来处理不同的事件。
txspoolResetRequest
类型的请求对象发送到pool.reqResetCh
通道中,该请求对象包含了旧区块和新区块的信息。然后,它等待从pool.reorgDoneCh
通道中接收一个信号,表示重置操作已经完成。pool.ctx
被取消,则返回上下文的完成信号。func (pool *TxsPool) scheduleLoop()
defer pool.wg.Done()
确保在函数退出时调用pool.wg.Done()
来标记工作完成。curDone:一个非空的通道struct chan,当runReorg处于活动状态时,该通道不为空。
nextDone:一个空的通道,用于等待下一个操作完成。
launchNextRun:一个布尔值,表示是否启动下一个运行。
reset:一个指向txspoolResetRequest类型的指针,用于存储重置请求的信息。
dirtyAccounts:一个指向accountSet类型的指针,用于存储脏账户信息。
queuedEvents:一个映射,将地址映射到指向txsSortedMap类型的指针,用于存储排队的事件。
// Launch next background reorg if needed
if curDone == nil && launchNextRun {
// Run the background reorg and announcements
go pool.runReorg(nextDone, reset, dirtyAccounts, queuedEvents)
curDone, nextDone = nextDone, make(chan struct{})
launchNextRun = false
reset, dirtyAccounts = nil, nil
queuedEvents = make(map[types.Address]*txsSortedMap)
}
这段代码是用于在需要时启动下一个后台重组(background reorg)的。如果当前没有正在进行的重组(curDone == nil),并且需要启动下一个运行(launchNextRun),则执行以下操作:
pool.runReorg
)。req := <-pool.reqResetCh
: 从pool.reqResetCh
通道中接收重置请求。如果当前没有正在进行的重置(reset == nil
),则将请求赋值给reset
变量;否则,将新块更新到reset
变量中。然后将nextDone
发送到pool.reorgDoneCh
通道,表示重置已完成。req := <-pool.reqPromoteCh
: 从pool.reqPromoteCh
通道中接收提升请求。如果当前没有正在进行的提升(dirtyAccounts == nil
),则将请求赋值给dirtyAccounts
变量;否则,将请求合并到dirtyAccounts
变量中。然后将nextDone
发送到pool.reorgDoneCh
通道,表示提升已完成。tx := <-pool.queueTxEventCh
: 从pool.queueTxEventCh
通道中接收交易事件。获取交易的发送地址,并将交易添加到与该地址关联的队列中。如果该地址尚未在队列中,则创建一个新的排序映射并将其分配给该地址。<-curDone
: 等待当前运行完成。如果curDone
不为空,则等待其完成。case <-pool.ctx.Done():
: 等待当前运行完成。如果curDone
不为空,则等待其完成。然后关闭nextDone
通道并返回。这段代码的主要目的是处理不同类型的请求,并根据请求类型执行相应的操作。