從源代碼分析以太坊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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章