以太坊交易池源码解析

交易池概念原理

交易池工作概况:

image-20201013202559291

  1. 交易池的数据来源主要来自:
    • 本地提交,也就是第三方应用通过调用本地以太坊节点的RPC服务所提交的交易;
    • 远程同步,是指通过广播同步的形式,将其他以太坊节点的交易数据同步至本地节点;
  2. 交易池中交易去向:被Miner模块获取并验证,用于挖矿;挖矿成功后写进区块并被广播
  3. Miner取走交易是复制,交易池中的交易并不减少。直到交易被写进规范链后才从交易池删除;
  4. 交易如果被写进分叉,交易池中的交易也不减少,等待重新打包。

关键数据结构

TxPoolConfig

type TxPoolConfig struct {
	Locals    []common.Address // 本地账户地址存放
	NoLocals  bool             // 是否开启本地交易机制
	Journal   string           // 本地交易存放路径
	Rejournal time.Duration    // 持久化本地交易的间隔

	PriceLimit uint64         // 价格超出比例,若想覆盖一笔交易的时候,若价格上涨比例达不到要求,那么不能覆盖
  
	PriceBump  uint64 // 替换现有交易的最低价格涨幅百分比(一次)

	AccountSlots uint64 // 每个账户的可执行交易限制
	GlobalSlots  uint64 // 全部账户最大可执行交易
	AccountQueue uint64 // 单个账户不可执行的交易限制
	GlobalQueue  uint64 // 全部账户最大非执行交易限制
  
	Lifetime time.Duration // 一个账户在queue中的交易可以存活的时间
}

默认配置:

Journal:   "transactions.rlp",Rejournal: time.Hour,PriceLimit: 1,PriceBump:  10,AccountSlots: 16,GlobalSlots:  4096,AccountQueue: 64,GlobalQueue:  1024,Lifetime: 3 * time.Hour

TxPool

type TxPool struct {
	config      TxPoolConfig // 交易池配置
	chainconfig *params.ChainConfig // 区块链配置
	chain       blockChain // 定义blockchain接口
	gasPrice    *big.Int
	txFeed      event.Feed //时间流
	scope       event.SubscriptionScope // 订阅范围
	signer      types.Signer //签名
	mu          sync.RWMutex

	istanbul bool // Fork indicator whether we are in the istanbul stage.

	currentState  *state.StateDB // 当前头区块对应的状态
	pendingNonces *txNoncer      // Pending state tracking virtual nonces
	currentMaxGas uint64         // Current gas limit for transaction caps

	locals  *accountSet // Set of local transaction to exempt from eviction 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     *txLookup                    // All transactions to allow lookups
	priced  *txPricedList                // All transactions sorted by price

	chainHeadCh     chan ChainHeadEvent
	chainHeadSub    event.Subscription
	reqResetCh      chan *txpoolResetRequest
	reqPromoteCh    chan *accountSet
	queueTxEventCh  chan *types.Transaction
	reorgDoneCh     chan chan struct{}
	reorgShutdownCh chan struct{}  // requests shutdown of scheduleReorgLoop
	wg              sync.WaitGroup // tracks loop,scheduleReorgLoop
}

txpool初始化

Txpool初始化主要做了以下几件事:

  1. 检查配置 配置有问题则用默认值填充

    config = (&config).sanitize()
    

    对于这部分的检查查看TxPoolConfig的字段。

  2. 初始化本地账户

pool.locals = newAccountSet(pool.signer)
  1. 将配置的本地账户地址加到交易池
pool.locals.add(addr)

我们在安装以太坊客户端往往可以指定一个数据存储目录,此目录便会存储着所有我们导入的或者通过本地客户端创建的帐户keystore文件。而这个加载过程便是从该目录加载帐户数据

  1. 更新交易池

    pool.reset(nil,chain.CurrentBlock().Header())
    
  2. 创建所有交易存储的列表,所有交易的价格用最小堆存放

    pool.priced = newTxPricedList(pool.all)
    

    通过排序,优先处理gasprice越高的交易。

  3. 如果本地交易开启 那么从本地磁盘加载本地交易

    if !config.NoLocals && config.Journal != "" {
    		pool.journal = newTxJournal(config.Journal)
    
    		if err := pool.journal.load(pool.AddLocals); 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)
    		}
    	}
    
  4. 订阅链上事件消息

    pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
    
  5. 开启主循环

    go pool.loop()
    

注意:local交易比remote交易具有更高的权限,一是不轻易被替换;二是持久化,即通过一个本地的journal文件保存尚未打包的local交易。所以在节点启动的时候,优先从本地加载local交易。

本地地址会被加入白名单,凡由此地址发送的交易均被认为是local交易,不论是从本地递交还是从远端发送来的。

到此为止交易池加载过程结束。

添加交易到txpool

之前我们说过交易池中交易的来源一方面是其他节点广播过来的,一方面是本地提交的,追根到源代码一个是AddLocal,一个是AddRemote,不管哪个都会调用addTxs。我们对添加交易的讨论就会从这个函数开始,它主要做了以下几件事:

  1. 过滤池中已经存在的交易

    if pool.all.Get(tx.Hash()) != nil {
      errs[i] = fmt.Errorf("known transaction: %x",tx.Hash())
    			knownTxMeter.Mark(1)
    			continue
    		}
    
  2. 将交易添加到队列中

    newErrs,dirtyAddrs := pool.addTxsLocked(news,local)
    
    进入到addTxsLocked函数中:
    replaced,err := pool.add(tx,local)
    

    进入到 pool.add函数中,这个add函数相当重要,它是将交易添加到queue中,等待后面的promote,到pending中去。如果在queue或者pending中已经存在,并且它的gas price更高时,将覆盖之前的交易。下面来拆开的分析一下add 这个函数。

    ①:看交易是否收到过,如果已经收到过就丢弃

    if pool.all.Get(hash) != nil {
    		log.Trace("Discarding already known transaction","hash",hash)
    		knownTxMeter.Mark(1)
    		return false,fmt.Errorf("known transaction: %x",hash)
    	}
    

    ②:如果交易没通过验证也要丢弃,这里的重点是验证函数:

    validateTx: 主要做了以下几件事
    - 交易大小不能超过32kb
    - 交易金额不能为负
    - 交易gas值不能超出当前交易池设定的gaslimit
    - 交易签名必须正确
    - 如果交易为远程交易,则需验证其gasprice是否小于交易池gasprice最小值,如果是本地,优先打包,不管gasprice
    - 判断当前交易nonce值是否过低
    - 交易所需花费的转帐手续费是否大于帐户余额  cost == V + GP * GL
    - 判断交易花费gas是否小于其预估花费gas
    

    ③:如果交易池已满,丢弃价格过低的交易

    if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
    		// If the new transaction is underpriced,don't accept it
    		if !local && pool.priced.Underpriced(tx,pool.locals) {
    			log.Trace("Discarding underpriced transaction",hash,"price",tx.GasPrice())
    			underpricedTxMeter.Mark(1)
    			return false,ErrUnderpriced
    		}
    		// New transaction is better than our worse ones,make room for it
    		drop := pool.priced.Discard(pool.all.Count()-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1),pool.locals)
    		for _,tx := range drop {
    			log.Trace("Discarding freshly underpriced transaction",tx.Hash(),tx.GasPrice())
    			underpricedTxMeter.Mark(1)
    			pool.removeTx(tx.Hash(),false)
    		}
    	}
    

    注意这边的GlobalSlots和GlobalQueue ,就是我们说的pending和queue的最大容量,如果交易池的交易数超过两者之和,就要丢弃价格过低的交易。

    ④:判断当前交易在pending队列中是否存在nonce值相同的交易。存在则判断当前交易所设置的gasprice是否超过设置的PriceBump百分比,超过则替换覆盖已存在的交易,否则报错返回替换交易gasprice过低,并且把它扔到queue队列中(enqueueTx)。

    if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
    		// Nonce already pending,check if required price bump is met
    		inserted,old := list.Add(tx,pool.config.PriceBump)
    		if !inserted {
    			pendingDiscardMeter.Mark(1)
    			return false,ErrReplaceUnderpriced
    		}
    		// New transaction is better,replace old one
    		if old != nil {
    			pool.all.Remove(old.Hash())
    			pool.priced.Removed(1)
    			pendingReplaceMeter.Mark(1)
    		}
    		pool.all.Add(tx)
    		pool.priced.Put(tx)
    		pool.journalTx(from,tx)
    		pool.queueTxEvent(tx)
    		log.Trace("Pooled new executable transaction","from",from,"to",tx.To())
    		return old != nil,nil
    	}
    	// New transaction isn't replacing a pending one,push into queue
    	replaced,err = pool.enqueueTx(hash,tx)
    

    添加交易的流程就到此为止了。接下来就是如何把queue(暂时不可执行)中添加的交易扔到pending(可执行交易)中,速成promote。

  3. 提升交易

    提升交易主要把交易从queue扔到pending中,我们在接下来的里面重点讲

    done := pool.requestPromoteExecutables(dirtyAddrs)
    

交易升级

promoteExecutables将future queue中的交易移动到pending中,同时也会删除很多无效交易比如nonce低或者余额低等等,主要分以下步骤:

①:将所有queue中nonce低于账户当前nonce的交易从all里面删除

forwards := list.Forward(pool.currentState.GetNonce(addr))
		for _,tx := range forwards {
			hash := tx.Hash()
			pool.all.Remove(hash)
			log.Trace("Removed old queued transaction",hash)
		}

②:将所有queue中花费大于账户余额 或者gas大于限制的交易从all里面删除

drops,_ := list.Filter(pool.currentState.GetBalance(addr),pool.currentMaxGas)
		for _,tx := range drops {
			hash := tx.Hash()
			pool.all.Remove(hash)
			log.Trace("Removed unpayable queued transaction",hash)
		}

③:将所有可执行的交易从queue里面移到pending里面(proteTx)

注:可执行交易:将pending里面nonce值大于等于账户当前状态nonce的且nonce连续的几笔交易作为准备好的交易

readies := list.Ready(pool.pendingNonces.get(addr))
		for _,tx := range readies {
			hash := tx.Hash()
			if pool.promoteTx(addr,tx) {
				log.Trace("Promoting queued transaction",hash)
				promoted = append(promoted,tx)
			}
		}

重点就是 promoteTx的处理,这个方法与add的不同之处在于,add是获得到的新交易插入pending,而promoteTx是将queue列表中的Txs放入pending接下来我们先看看里面是如何来处理的:

inserted,pool.config.PriceBump)
	if !inserted {
		// An older transaction was better,discard this
		// 老的交易更好,删除这个交易
		pool.all.Remove(hash)
		pool.priced.Removed(1)

		pendingDiscardMeter.Mark(1)
		return false
	}
	// Otherwise discard any previous transaction and mark this
	// 现在这个交易更好,删除旧的交易
	if old != nil {
		pool.all.Remove(old.Hash())
		pool.priced.Removed(1)

		pendingReplaceMeter.Mark(1)
	} else {
		// Nothing was replaced,bump the pending counter
		pendingGauge.Inc(1)
	}

主要就做了这几件事:

  1. 将交易插入pending中,如果待插入的交易nonce在pending列表中存在,那么待插入的交易gas price大于或等于原交易价值的110%(跟pricebump设定有关)时,替换原交易
  2. 如果新交易替换了某个交易,从all列表中删除老交易
  3. 最后更新一下all列表

经过proteTx之后,要扔到pending的交易都放在了promoted []*types.Transaction中,再回到promoteExecutables中,继续下面步骤:

④:如果非本地账户queue大于限制(AccountQueue),从最后取出nonce较大的交易进行remove

if !pool.locals.contains(addr) {
			caps = list.Cap(int(pool.config.AccountQueue))
			for _,tx := range caps {
				hash := tx.Hash()
				pool.all.Remove(hash)
			...
		}

⑤:最后如果队列中此账户的交易为空则删除此账户

if list.Empty() {
			delete(pool.queue,addr)
		}

到此我们的升级交易要做的事情就完毕了。

交易降级

交易降级的几个场景:

  1. 出现了新的区块,将会从pending中移除出现在区块中的交易到queue中
  2. 或者是另外一笔交易(gas price 更高),则会从pending中移除到queue中

关键函数:demoteUnexecutables,主要做的事情如下:

①:遍历pending中所有地址对应的交易列表

for addr,list := range pool.pending {
  ...}

②:删除所有认为过旧的交易(low nonce)

olds := list.Forward(nonce)
		for _,tx := range olds {
			hash := tx.Hash()
			pool.all.Remove(hash)
			log.Trace("Removed old pending transaction",hash)
		}

③:删除所有费用过高的交易(余额低或用尽),并将所有无效者送到queue中以备后用

drops,invalids := list.Filter(pool.currentState.GetBalance(addr),tx := range drops {
			hash := tx.Hash()
			log.Trace("Removed unpayable pending transaction",hash)
			pool.all.Remove(hash)
		}
		pool.priced.Removed(len(olds) + len(drops))
		pendingNofundsMeter.Mark(int64(len(drops)))

		for _,tx := range invalids {
			hash := tx.Hash()
			log.Trace("Demoting pending transaction",hash)
			pool.enqueueTx(hash,tx)
		}

④:如果交易前面有间隙,将后面的交易移到queue中

if list.Len() > 0 && list.txs.Get(nonce) == nil {
			gapped := list.Cap(0)
			for _,tx := range gapped {
				hash := tx.Hash()
				log.Error("Demoting invalidated transaction",hash)
				pool.enqueueTx(hash,tx)
			}
			pendingGauge.Dec(int64(len(gapped)))
		}

注:间隙的出现通常是因为交易余额问题导致的。假如原规范链A 上交易m花费10,分叉后该账户又在分叉链B发出一个交易m花费20,这就导致该账户余额本来可以支付A链上的某笔交易,但在B链上可能就不够了。这个余额不足的交易在B如果是n+3,那么在A链上n+2,n+4号交易之间就出现了空隙,这就导致从n+3开始往后所有的交易都要降级;

到底交易降级结束。

重置交易池

重置交易池将检索区块链的当前状态(主要由于更新导致链状态变化),并确保交易池的内容对于链状态而言是有效的。

流程图如下:

image-20201015185551752

根据上面流程图,主要功能是由于规范链的更新,重新整理交易池:

  1. 找到由于规范链更新而作废的交易
  2. 给交易池设置最新的世界状态
  3. 把旧链退回的交易重新放入交易池

参考:

https://github.com/mindcarver/blockchain_guide (很优秀的区块链开源学习营地)

https://learnblockchain.cn/2019/06/03/eth-txpool/#清理交易池

https://blog.csdn.net/lj900911/article/details/84825739

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读903次。文章主要介绍了收益聚合器Beefy协议在币安智能链测试网网上的编译测试部署流程,以Pancake上的USDC-BUSD最新Curve版流动池的农场质押为例,详细介绍了完整的操作流程。_怎么在bsc网络上部署应用
文章浏览阅读952次。比特币的主要思路是,构建一个无中心、去信任的分布式记账系统。交易签名只能保证交易不是他人伪造的,却不能阻止交易的发起者自己进行多重交易,即交易的发起者将一个比特币同时转账给两个人,也就是所谓的双花。比特币应用的区块链场景也叫做公链,因为这个区块链对所有人都是公开的。除此之外,还有一种区块链应用场景,被称作联盟链。区块链的出现,使得低成本,去信任的跨组织合作成为可能,将重构组织间的关系,这个关系既包括企业间的关系,也包括政府和企业间的关系,还有政府部门间的关系。
文章浏览阅读2.5k次。虚拟人从最初的不温不火,到现在步入“出生高峰期”,元宇宙可以说是功不可没。此前,量子位发布了《虚拟数字人深度产业报告》,报告显示,到2030年我国虚拟数字人整体市场规模将达到2700亿元。其中,“身份型虚拟人”市场规模预计达到1750亿元,占主导地位,而“服务型虚拟人”总规模也将超过950亿元。得益于AI、VR/AR 等技术的发展,虚拟人的应用场景正在从传统的虚拟偶像等娱乐行业迈向更多元化的领域。_最喜欢的虚拟角色
文章浏览阅读1.3k次,点赞25次,收藏13次。通过调查和分析用户需求、兴趣和行为,你可以更好地定位你的目标受众,并在市场中找到你的定位。在设计你的Web3.0项目时,注重用户界面的友好性、交互流畅性和功能的创新性,以提供独特的用户体验。通过与有影响力的人或组织进行合作,推广你的Web3.0项目。通过与他们分享你的项目并抓住他们的推荐,可以迅速获得更多的关注度。通过优化你的网站和内容,将有助于提高你的排名,并增加有机流量。通过提供奖励激励计划,如空投、奖励机制等,激励用户参与你的Web3.0项目。的人或组织合作,可以增加你的项目的曝光度。
文章浏览阅读1.7k次。这个智能合约安全系列提供了一个广泛的列表,列出了在 Solidity 智能合约中容易反复出现的问题和漏洞。Solidity 中的安全问题可以归结为智能合约的行为方式不符合它们的意图。我们不可能对所有可能出错的事情做一个全面的列表。然而,正如传统的软件工程有常见的漏洞主题,如 SQL 注入、缓冲区超限和跨网站脚本,智能合约中也有反复出现的。_solidity安全漏洞
文章浏览阅读1.3k次。本文描述了比特币核心的编译与交互方法_编译比特币
文章浏览阅读884次。四水归堂,是中国建筑艺术中的一种独特形式。这种形式下,由四面房屋围出一个天井,房屋内侧坡向天井内倾斜,下雨时雨水会从东西南北四方流入天井,从而起到收集水源,防涝护屋的作用,寓意水聚天心,天人合一。在科技产业当中,很多时候我们需要学习古人的智慧与意蕴,尝试打通各个生态,聚四方之力为我所用,这样才能为最终用户带来最大化价值。随着数字化、智能化的发展,算力成为生产力的根基。在这一大背景下,算力需要贯通软..._超聚变csdn
文章浏览阅读1k次,点赞24次,收藏19次。云计算和区块链是当代科技领域两个备受关注的核心技术。本文将深入探讨云计算和区块链的发展历程,详细剖析其起初阶段的奠基、面临的问题、业务内容、当前研究方向、用到的技术、实际应用场景、未来发展趋势,并提供相关链接供读者深入了解。
文章浏览阅读1.5k次。融入对等网络的奥妙,了解集中式、全分布式和混合式对等网络的差异,以及区块链网络的结构与协议,让你跃入区块链的连结网络。揭开密码学的神秘面纱,探寻对称密码学、非对称密码学、哈希函数、数字签名等关键技术,让你了解信息安全的核心。解码共识算法的精髓,从理论到实践,从PoW、PoS到PBFT,让你深入了解区块链如何达成共识。探索智能合约的世界,从定义到生命周期,从执行引擎到开发与部署,带你进入无限可能的合约领域。了解令人惊叹的区块链世界,从概念到价值,从发展历程到政策法规,一篇章串联出区块链的精髓。
文章浏览阅读777次。8 月份,加密货币市场经历了明显的波动,比特币价格波动幅度较大。与此同时,NFT 市场出现大幅下跌,引发了人们对这一新兴行业未来发展趋势的担忧
文章浏览阅读8.8k次,点赞53次,收藏37次。近二十年来,我国信息科技发展日益成熟,出现的网络完全问题也是“百花齐放”。而元宇宙作为5G技术、AR/VR技术、云计算以及区块链等技术的组合体,其安全性指定会被人们所广泛关注。根据前面所讲,元宇宙融合了虚拟世界和现实世界,通过数据将现实世界的各种元素映射到数字化的虚拟世界中。所以没有数据,就等于没有元宇宙的一切;没有信息安全,元宇宙的社会生产、生活就不能正常有序地进行。所以足以可见数据安全、信息安全对元宇宙发展起到的重要作用!!_元宇宙 安全计算
文章浏览阅读1.4k次。最早使用历史 1991年采用 时间戳 追溯 数字文档,之后 2009年后创始人**中本聪** (satoshi nakamoto )日裔美国人,在设计比特币数字货币中将此理念写入应用程序中_web3.0学习
文章浏览阅读1.7k次。DeFi收益来源全面概述_drfi收益
文章浏览阅读941次,点赞17次,收藏21次。号外:教链内参1.28《从BTC现货ETF的近期数据看到的》隔夜BTC经历现货ETF通过后的情绪冷静,一度破位40k后又逐渐修复至42k上方。请珍惜42k的BTC吧。也许到下个周期,我们将不再有机会见到这个高度的BTC了。下面,让我们重温,42k的BTC,在过去四年穿越牛熊的过程中,带给我们的启迪吧。需要提醒的是,历史文字,自有历史局限性,回顾,也须带着批判性的目光阅读和审视。2021年2月8日,...
文章浏览阅读1.2k次,点赞23次,收藏21次。其实一开始我也是这么想的,但根据PoW算法机制,如果你的计算量不够大,是无法控制区块链的走向的,也就是说,即使你投入了大量的成本用于完成任务,也不能保证自己成功。例如,你持有100个币,总共持有了30天,那么,此时你的币龄就为3000,这个时候,如果你发现了一个PoS区块,那么你的币龄就会被减去一定的值,每减少365个币龄,将会从区块中获得0.05个币的利息(可理解为年利率5%),那么在这个案例中,利息=3000×5%/365=0.41个币。前面说过,谁的算力强,谁最先解决问题的概率就越大。
文章浏览阅读1.9k次。这里主要实现的部分继续下去,对 Blockchain 这个对象有一些修改,如果使用 TS 的话可能要修改对应的 interface,但是如果是 JS 的话就无所谓了。需要安装的依赖有:express现在的 express 已经不内置 body-parser,需要作为单独的依赖下载request不下载会报错,是使用 request-promise 所需要的依赖和已经 deprecated 了,具体 reference 可以参考。_js区块链
文章浏览阅读1k次,点赞19次,收藏19次。作者:Zach Pandl Grayscale编译:象牙山首席村民 碳链价值以太坊在2023年取得了丰厚的回报。但表现不如比特币以及其他一些智能合约公链代币。我们认为,这反映了今年比特币特有的积极因素以及以太坊链上活动的缓慢复苏。尽管以太坊的涨幅低于比特币,但从绝对值和风险调整值来看,今年以太坊的表现优于传统资产类别。以太坊不断增长的L2生态系统的发展可能会吸引新用户,并在2024年支撑以太币的...
文章浏览阅读908次,点赞20次,收藏20次。通证是以数字形式存在,代表的是一种权利、一种固有和内在的价值。徐教授告诉我:多年的职业经历,多年的为易货贸易的思考,认识到在处理贸易和经济领域的关系时,应以提高人民生活水平、保证社会成员充分就业、保证就业成员实际收入和有效需求的大幅稳定增长、实现世界资源的充分利用以及扩大货物的生产和交换为目的,期望通过达成互惠互利安排,实行公开、公平、公正的“三公原则”,开展国家与国家、企业与企业之间的易货贸易,规避因信用问题引起的各类风险,消除国际贸易中的歧视待遇,促进全球国家的经济发展,从而为实现上述目标做出贡献。
文章浏览阅读2.5k次。由于webase文档原因,查找起来比较局限,有时候想找一个api却又忘了在哪个模块的目录下,需要一步一步单独点,而利用文档自带的检索功能又因为查找文档全部信息,显得十分缓慢,所以整理了有关WeBASE的api列表但不可否认,现在只有列表,没有对应的页面跳转,文章目的也只是为了多了解webase的接口_webase私钥管理里获取
文章浏览阅读1.4k次,点赞28次,收藏21次。基于​openzeppelin来构建我们的NFT,并用一个例子来手把手的说明如何在opensea快速发布自己的NFT智能合约(ERC721)。