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

项目中有使用到以太坊转账功能,在有一天以态坊网络堵塞,转帐报了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

你可能感兴趣的:(以太坊)