From 8bc63c40de0bb7dfde8d3a3741ee503d34611c9f Mon Sep 17 00:00:00 2001 From: David Date: Wed, 19 Jul 2023 13:50:01 +0800 Subject: [PATCH] feat(proposer): handle transaction replacement underpriced error --- proposer/proposer.go | 79 ++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/proposer/proposer.go b/proposer/proposer.go index f75bf9c42..831e255cf 100644 --- a/proposer/proposer.go +++ b/proposer/proposer.go @@ -8,6 +8,7 @@ import ( "math" "math/big" "math/rand" + "strings" "sync" "time" @@ -27,8 +28,10 @@ import ( ) var ( - errNoNewTxs = errors.New("no new transactions") - waitReceiptTimeout = 1 * time.Minute + errNoNewTxs = errors.New("no new transactions") + waitReceiptTimeout = 1 * time.Minute + maxSendProposeBlockTxRetry = 10 + txReplacementTipMultiplier = 2 ) // Proposer keep proposing new transactions from L2 execution engine's tx pool at a fixed interval. @@ -278,36 +281,70 @@ func (p *Proposer) ProposeTxList( txNum uint, nonce *uint64, ) error { - if p.minBlockGasLimit != nil && meta.GasLimit < uint32(*p.minBlockGasLimit) { - meta.GasLimit = uint32(*p.minBlockGasLimit) - } + sendProposeBlockTx := func(isReplacement bool) (*types.Transaction, error) { + if p.minBlockGasLimit != nil && meta.GasLimit < uint32(*p.minBlockGasLimit) { + meta.GasLimit = uint32(*p.minBlockGasLimit) + } - // Propose the transactions list - inputs, err := encoding.EncodeProposeBlockInput(meta) - if err != nil { - return err - } + // Propose the transactions list + inputs, err := encoding.EncodeProposeBlockInput(meta) + if err != nil { + return nil, err + } - opts, err := getTxOpts(ctx, p.rpc.L1, p.l1ProposerPrivKey, p.rpc.L1ChainID) - if err != nil { - return err - } - if nonce != nil { - opts.Nonce = new(big.Int).SetUint64(*nonce) + opts, err := getTxOpts(ctx, p.rpc.L1, p.l1ProposerPrivKey, p.rpc.L1ChainID) + if err != nil { + return nil, err + } + if nonce != nil { + opts.Nonce = new(big.Int).SetUint64(*nonce) + } + if p.proposeBlockTxGasLimit != nil { + opts.GasLimit = *p.proposeBlockTxGasLimit + } + + if isReplacement { + opts.GasTipCap = new(big.Int).Mul(opts.GasTipCap, new(big.Int).SetUint64(uint64(txReplacementTipMultiplier))) + } + + proposeTx, err := p.rpc.TaikoL1.ProposeBlock(opts, inputs, txListBytes) + if err != nil { + return nil, encoding.TryParsingCustomError(err) + } + + return proposeTx, nil } - if p.proposeBlockTxGasLimit != nil { - opts.GasLimit = *p.proposeBlockTxGasLimit + + var ( + try = 0 + tx *types.Transaction + isReplacement bool + err error + ) + + for try < maxSendProposeBlockTxRetry { + if tx, err = sendProposeBlockTx(isReplacement); err != nil { + try += 1 + log.Warn("Failed to send propose block transaction, retrying", "error", err) + if strings.Contains(err.Error(), "replacement transaction underpriced") { + isReplacement = true + } else { + isReplacement = false + } + continue + } + + break } - proposeTx, err := p.rpc.TaikoL1.ProposeBlock(opts, inputs, txListBytes) if err != nil { - return encoding.TryParsingCustomError(err) + return err } ctxWithTimeout, cancel := context.WithTimeout(ctx, waitReceiptTimeout) defer cancel() - if _, err := rpc.WaitReceipt(ctxWithTimeout, p.rpc.L1, proposeTx); err != nil { + if _, err := rpc.WaitReceipt(ctxWithTimeout, p.rpc.L1, tx); err != nil { return err }