主页 > imtoken市场打不开 > 交易排队

交易排队

imtoken市场打不开 2023-10-04 05:10:15

交易排队

这是关于以太坊交易池的第三篇文章。 第一篇是对以太坊交易池的整体概述,第二篇是对以太坊本地交易存储的讲解。 第三篇详细讲解了交易时如何进入交易池及其影响。 内容很多,请坐板凳。

交易进入交易池分三个步骤:验证、入队和容量检查。 以 AddLocalTx 为例。 核心代码集中在交易池的func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) 方法中。

验证交易合法性

任何交易在进入交易池之前,都需要验证交易数据的合法性。 如果交易验证失败,则交易被拒绝。

//core/tx_pool.go:662
if err := pool.validateTx(tx, local); err != nil {
   log.Trace("Discarding invalid transaction", "hash", hash, "err", err)
   invalidTxCounter.Inc(1)
   return false, err
}

那么如何验证呢? 代码逻辑集中在 func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error 方法中。

首先是防止DOS攻击,交易数据不允许超过32KB。

if tx.Size() > 32*1024 {
   return ErrOversizedData
}

那么交易的转账金额不允许为负数。 其实这个判断很难命中,因为从外面收到的交易数据是经过RLP编码的,不能处理负数。 当然,还是在这里做个验证比较稳妥。

if tx.Value().Sign() < 0 {
   return ErrNegativeValue
}

在虚拟机中执行交易时会消耗 GAS。 为了防止程序出错,允许用户在交易中携带GAS上限,以防发生意外。 同样,为了避免异常的块消耗和控制块数据大小。 还有一个块气体限制。 一个区块的GAS量是每笔交易执行消耗的GAS总和,因此交易的GAS上限不可能超过区块GAS上限。 一旦超过,交易就不能打包进区块,超过限制的交易可以直接在交易池中拒绝。

if pool.currentMaxGas < tx.Gas() {
   return ErrGasLimit
}

sitebitett.com 以太坊怎么交易_以太坊交易皆选AAX_以太坊币交易官方网站

每笔交易都需要携带交易签名信息,并从签名中解析出签名者的地址。 只有有效的签名才能成功解析签名者。 一旦解析失败,则拒绝交易。

from, err := types.Sender(pool.signer, tx)
if err != nil {
   return ErrInvalidSender
}

由于已知是交易发送者(签名者),发送者也可能来自交易池标记的本地账户。 因此,当交易不是本地交易时,进一步检查是否属于本地账户。

local = local || pool.locals.contains(from)

如果不是本地交易,交易的GasPrice也必须不低于交易池设定的最低GasPrice。 这种限制检查允许矿工自己确定 GasPrice。 一些矿工可能只愿意处理愿意支付高额费用的交易。 当然,忽略本地事务以避免拦截本地生成的事务。

if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
   return ErrUnderpriced
}

以太坊中的每个账户都有一个数字类型的 Nonce 字段。 它是一个有序的数字,每次都更大。 虚拟机每执行一次这个账户的交易,新的Nonce就会给这个交易的Nonce加1。 如果使用得当,Nonce可以间接表示该账户的Nonce交易已经被打包。 由于不会变小,因此Nonce小于本账户当前Nonce的交易不允许进入交易池。

if pool.currentState.GetNonce(from) > tx.Nonce() {
   return ErrNonceTooLow
}

如果交易被包含在区块中,交易应该收取多少费用? 虽然无法知道最终会花多少钱,但至少会花多少手续费是可以预知的。 手续费加上本次交易转出的以太币数量将从账户中扣除。 那么这个账户至少需要转多少以太币就很清楚了。

因此,在交易池中,会检查账户余额。 只有当账户资产足够时,才允许交易继续进行。 否则,如果事务在虚拟机中执行,事务将失败。

if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
   return ErrInsufficientFunds
}

我们不仅知道最低成本,还可以知道最低成本是多少 gas。 因此,还要检查交易所设置的gas limit是否正确。 一次交易至少需要20,000 Gas,而交易中设置的Gas上限确实是10,000 GAS。 那么交易必然失败,剩下10,000 GAS。

intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)

sitebitett.com 以太坊怎么交易_以太坊交易皆选AAX_以太坊币交易官方网站

if err != nil { return err } if tx.Gas() < intrGas { return ErrIntrinsicGas }

所以最后。 如果交易 gas limit 低于已知的最小 gas 成本,则拒绝必须失败的交易。

交易排队

在事务池中,不是一个队列管理数据,而是多个数据集共同管理事务。

ethereum-tx-pool-txManager

如上图所示,交易池首先使用一个txLookup(内部映射)来跟踪所有交易。 同时按照本地优先和价格优先的原则将交易分为队列和待处理两部分。 这两笔交易按账户分别跟踪。

在进入交易队列之前,会判断所有的交易队列是否都达到了上限。 如果达到上限,则需要将优先级最低的交易从交易池或当前交易中移除。

//core/tx_pool.go:668
if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue { //❶
   if !local && pool.priced.Underpriced(tx, pool.locals) {//❷
      log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
      underpricedTxCounter.Inc(1)
      return false, ErrUnderpriced
   }
   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", "hash", tx.Hash(), "price", tx.GasPrice())
      underpricedTxCounter.Inc(1)

以太坊交易皆选AAX_以太坊币交易官方网站_sitebitett.com 以太坊怎么交易

pool.removeTx(tx.Hash(), false) } }

那么哪些交易的优先级最低呢? 首先,本地交易受到保护,所以如果交易来自远程,它会检查交易的价格是否是整个交易池中的最低价格。 如果是,拒绝交易❷。 否则,在加入这笔交易之前,价格最低的交易将被从交易队列中全部删除。 为了高效的获取不同价格的交易,交易池已经将交易按照价格从低到高的顺序排列,存储在pool.priced中。

解决交易容量问题后,交易将通过测试以太坊交易皆选AAX,并立即进入交易内存池。 上图中,事务按照from组进行管理,一个from又分为不可执行事务队列(queue)和可执行事务队列(pending)。 默认情况下,新交易在非可执行队列中等待指令,但在一种情况下,如果from的可执行队列中存在具有相同nonce的交易,则需要进一步判断是否可以替换❹。

什么样的交易可以替代已经等待执行的交易? 以太坊早期的默认设计是只要价格(gasPrice)高于原交易就允许置换。 但它在 2017 年 7 月末的 #15401 中得到了改进。 人们愿意支付更多费用的原因有两个。 一种是紧急处理交易,但是如果是紧急交易,发送交易的地方会使用高于推荐的gasprice来处理交易。 另一种情况是,当以太坊的价格下跌时,人们愿意支付更多的费用。 增加多少手续费才合理? 如果以太币下跌10%,那么手续费可以增加10%。 毕竟对于用户来说,手续费的面值是一样的。 交易池 (pool.config.PriceBump) 的默认配置是 10%。 只有将交易费用增加 10% 的交易才允许替换已经等待执行的交易❺。 一旦可以替换,替换旧事务❺,移除旧事务❻,并将该事务同步存入所有事务的内存池中。

//core/tx_pool.go:685
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {//❹
   inserted, old := list.Add(tx, pool.config.PriceBump)//❺
   if !inserted {
      pendingDiscardCounter.Inc(1)
      return false, ErrReplaceUnderpriced
   }
   if old != nil { //❻
      pool.all.Remove(old.Hash())
      pool.priced.Removed()
      pendingReplaceCounter.Inc(1)
   }
   pool.all.Add(tx)
   pool.priced.Put(tx)
   pool.journalTx(from, tx)
   //...
   return old != nil, nil

sitebitett.com 以太坊怎么交易_以太坊交易皆选AAX_以太坊币交易官方网站

} replace, err := pool.enqueueTx(hash, tx)//❼ if err != nil { return false, err }

检查是否需要替换挂起的交易后,将该交易存入不可执行队列❼。 同样,在进入不可执行队列之前,也会检查是否需要替换具有相同nonce的交易❽。

func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, error) {
   //...
   inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) //❽
   if !inserted {
      queuedDiscardCounter.Inc(1)
      return false, ErrReplaceUnderpriced
   }
   if old != nil {
      pool.all.Remove(old.Hash())
      pool.priced.Removed()
      queuedReplaceCounter.Inc(1)
   }
   if pool.all.Get(hash) == nil {
      pool.all.Add(tx)
      pool.priced.Put(tx)
   }
   return old != nil, nil

sitebitett.com 以太坊怎么交易_以太坊币交易官方网站_以太坊交易皆选AAX

}

最后以太坊交易皆选AAX,如果交易是本地的,则需要格外小心。 如果交易属于本地交易,但是本地账户集合中不存在这个from,则更新本地账户集合❾,防止交易被存储⑩。 另外,如果开启本地交易存储,则本地交易会实时存储⑪。

// core/tx_pool.go:715
if local {
   if !pool.locals.contains(from) {
      log.Info("Setting new local account", "address", from)
      pool.locals.add(from)//❾
   }
}
pool.journalTx(from, tx)
//....
//core/tx_pool.go:757
func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) {
	// Only journal if it's enabled and the transaction is local
	if pool.journal == nil || !pool.locals.contains(from) {//⑩
		return
	}
	if err := pool.journal.insert(tx); err != nil {//⑪
		log.Warn("Failed to journal local transaction", "err", err)
	}
}

至此,一笔交易已经千里迢迢进入交易内存池,等待执行。

另外不难看出,在交易进入队列内存池时,定价队列被排列到定价队列中,所以定价队列是所有交易内存池的同步排序。 并且事务在进入pending queue或者queue队列后同步更新到all tr​​ansaction memory pool。

这里不打算解释pending和queue队列的内部实现,请自行研究。 因为忽略技术细节并不会影响你对以太坊各个技术点和模块的理解。 下一讲将讲解交易池的内存容量处理。