从源代码分析以太坊replacement transaction underpriced异常

本文深入探讨以太坊转账中出现的replacementtransactionunderpriced异常,解析nonce和gas价格机制,提出两种解决方案:手动管理nonce或增加gas费用。

项目中有使用到以太坊转账功能,在有一天以态坊网络堵塞,转帐报了replacement transaction underpriced异常,根据这个异常关键词搜索以态坊源码,发现是这里报错的。

func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err error) {
//---------省去前面的代码------------------------
   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
      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)
      }
//------------省略后面的代码-------------------

   }
}
func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) {
   // If there's an older better transaction, abort
   old := l.txs.Get(tx.Nonce())
   if old != nil {
      threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100))
      // Have to ensure that the new gas price is higher than the old gas
      // price as well as checking the percentage threshold to ensure that
      // this is accurate for low (Wei-level) gas price replacements
//pending队列已经有相同nonce值的交易,且新旧两条交易的nonce相同或者新交易的值小于threshold值
      if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 {
         return false, nil
      }
   }
   // Otherwise overwrite the old transaction with the current one
   l.txs.Put(tx)
   if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 {
      l.costcap = cost
   }
   if gas := tx.Gas(); l.gascap < gas {
      l.gascap = gas
   }
   return true, old
}

pending队列已经有相同nonce值的交易,且新旧两条交易的nonce相同或者新交易的值小于threshold值,这是报错的原因,但由于gas的费用是通过自动计算的,所以原因还是在nonce值上,再看下发送交易的代码,是没有传nonce值进去,结点收到交易消息后,判断如果传入参数nonce值为空就从已有pending队列里获取相同的nonce,nonce值只有等到区块打包时,把交易消息持久化到stateDB时才有+1变化。
//以下是获取默认nonce的代码
 

// setDefaults is a helper function that fills in default values for unspecified tx fields.
func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
   //省去前面代码
   if args.Nonce == nil {
      nonce, err := b.GetPoolNonce(ctx, args.From)
      if err != nil {
         return err
      }
      args.Nonce = (*hexutil.Uint64)(&nonce)
   }
   //省去后面代码
}

//以下是执行交易时打包入块的代码

// TransitionDb will transition the state by applying the current message and
// returning the result including the used gas. It returns an error if failed.
// An error indicates a consensus issue.
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {

   //省略前面的代码	
   if contractCreation {
      ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
   } else {
      // Increment the nonce for the next transaction
      st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)  //这里才+1
      ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
   }
   //省略后面的代码
}

要解决这个问题有两种方式:
1、    调用方维护nonce值,自己每次创建交易的nonce值都变化并且传入交易参数
2、    在上条交易费用的基础上再加多点gas

引用\[1\]:大多数项目只需要一个事务管理器。然而,有些项目为了提高效率、或者有多个完全不同又不相干的数据源,最好用多个事务管理器。机智的Spring的Transactional管理已经考虑到了这一点,首先分别定义多个transactional manager,并为qualifier属性指定不同的值;然后在需要使用@Transactional注解的时候指定TransactionManager的qualifier属性值或者直接使用bean名称。\[1\]引用\[2\]:或者,直接使用transactin manager 的bean名字: @Transactional("transactionManager1") 如果是使用@Transactional(),相当于使用缺省的transaction mananger名字,即:@Transactional("transactionManager")\[2\]引用\[3\]://以下是执行交易时打包入块的代码 // TransitionDb will transition the state by applying the current message and // returning the result including the used gas. It returns an error if failed. // An error indicates a consensus issue. func (st *StateTransition) TransitionDb() (ret \[\]byte, usedGas uint64, failed bool, err error) { //省略前面的代码 if contractCreation { ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) } else { // Increment the nonce for the next transaction st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) //这里才+1 ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value) } //省略后面的代码 }\[3\] 问题:代码中多个TransactionManager是如何使用的? 回答: 在Spring框架中,如果需要使用多个事务管理器,可以首先分别定义多个transactional manager,并为qualifier属性指定不同的值。然后在需要使用@Transactional注解的时候,可以通过指定TransactionManager的qualifier属性值或者直接使用bean名称来选择使用哪个事务管理器。例如,可以使用注解@Transactional("transactionManager1")来指定使用名为"transactionManager1"的事务管理器,如果使用注解@Transactional(),则相当于使用缺省的transaction manager名字,即"transactionManager"。\[1\]\[2\] #### 引用[.reference_title] - *1* *2* [Spring的多事务配置(多个Transaction Manager)和使用方法](https://blog.youkuaiyun.com/ClementAD/article/details/47275227)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [从源代码分析以太坊replacement transaction underpriced异常](https://blog.youkuaiyun.com/phil_code/article/details/100077226)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值