diff --git a/cmd/flags/prover.go b/cmd/flags/prover.go index 7b7465a07..6e1b19665 100644 --- a/cmd/flags/prover.go +++ b/cmd/flags/prover.go @@ -106,6 +106,17 @@ var ( Usage: "Gas limit will be used for TaikoL1.proveBlock transactions", Category: proverCategory, } + ProveBlockTxReplacementMultiplier = &cli.Uint64Flag{ + Name: "proveBlockTxReplacementMultiplier", + Value: 2, + Usage: "Gas tip multiplier when replacing a TaikoL1.proveBlock transaction with same nonce", + Category: proverCategory, + } + ProveBlockMaxTxGasTipCap = &cli.Uint64Flag{ + Name: "proveBlockMaxTxGasTipCap", + Usage: "Gas tip cap (in wei) for a TaikoL1.proveBlock transaction when doing the transaction replacement", + Category: proverCategory, + } ProverHTTPServerPort = &cli.Uint64Flag{ Name: "prover.httpServerPort", Usage: "Port to expose for http server", @@ -142,6 +153,8 @@ var ProverFlags = MergeFlags(CommonFlags, []cli.Flag{ OracleProverPrivateKey, OracleProofSubmissionDelay, ProofSubmissionMaxRetry, + ProveBlockTxReplacementMultiplier, + ProveBlockMaxTxGasTipCap, Graffiti, CheckProofWindowExpiredInterval, ProveUnassignedBlocks, diff --git a/pkg/rpc/utils.go b/pkg/rpc/utils.go index d889d884e..fe33a240d 100644 --- a/pkg/rpc/utils.go +++ b/pkg/rpc/utils.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/ethereum/go-ethereum/log" @@ -200,6 +201,55 @@ func ContentFrom( ) } +// IncreaseGasTipCap tries to increase the given transaction's gasTipCap. +func IncreaseGasTipCap( + ctx context.Context, + cli *Client, + opts *bind.TransactOpts, + address common.Address, + txReplacementTipMultiplier *big.Int, + maxGasTipCap *big.Int, +) (*bind.TransactOpts, error) { + ctxWithTimeout, cancel := ctxWithTimeoutOrDefault(ctx, defaultTimeout) + defer cancel() + + log.Info("Try replacing a transaction with same nonce", "sender", address, "nonce", opts.Nonce) + + originalTx, err := GetPendingTxByNonce(ctxWithTimeout, cli, address, opts.Nonce.Uint64()) + if err != nil || originalTx == nil { + log.Warn( + "Original transaction not found", + "sender", address, + "nonce", opts.Nonce, + "error", err, + ) + + opts.GasTipCap = new(big.Int).Mul(opts.GasTipCap, txReplacementTipMultiplier) + } else { + log.Info( + "Original transaction to replace", + "sender", address, + "nonce", opts.Nonce, + "gasTipCap", originalTx.GasTipCap(), + "gasFeeCap", originalTx.GasFeeCap(), + ) + + opts.GasTipCap = new(big.Int).Mul(originalTx.GasTipCap(), txReplacementTipMultiplier) + } + + if maxGasTipCap != nil && opts.GasTipCap.Cmp(maxGasTipCap) > 0 { + log.Info( + "New gasTipCap exceeds limit, keep waiting", + "multiplier", txReplacementTipMultiplier, + "newGasTipCap", opts.GasTipCap, + "maxTipCap", maxGasTipCap, + ) + return nil, txpool.ErrReplaceUnderpriced + } + + return opts, nil +} + // GetPendingTxByNonce tries to retrieve a pending transaction with a given nonce in a node's mempool. func GetPendingTxByNonce( ctx context.Context, diff --git a/proposer/proposer.go b/proposer/proposer.go index 817d5a3a0..f34bae587 100644 --- a/proposer/proposer.go +++ b/proposer/proposer.go @@ -14,6 +14,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -358,40 +359,15 @@ func (p *Proposer) sendProposeBlockTx( opts.GasLimit = *p.proposeBlockTxGasLimit } if isReplacement { - log.Info("Try replacing a transaction with same nonce", "sender", p.l1ProposerAddress, "nonce", nonce) - originalTx, err := rpc.GetPendingTxByNonce(ctx, p.rpc, p.l1ProposerAddress, *nonce) - if err != nil || originalTx == nil { - log.Warn( - "Original transaction not found", - "sender", p.l1ProposerAddress, - "nonce", nonce, - "error", err, - ) - - opts.GasTipCap = new(big.Int).Mul(opts.GasTipCap, new(big.Int).SetUint64(p.txReplacementTipMultiplier)) - } else { - log.Info( - "Original transaction to replace", - "sender", p.l1ProposerAddress, - "nonce", nonce, - "gasTipCap", originalTx.GasTipCap(), - "gasFeeCap", originalTx.GasFeeCap(), - ) - - opts.GasTipCap = new(big.Int).Mul( - originalTx.GasTipCap(), - new(big.Int).SetUint64(p.txReplacementTipMultiplier), - ) - } - - if p.proposeBlockTxGasTipCap != nil && opts.GasTipCap.Cmp(p.proposeBlockTxGasTipCap) > 0 { - log.Info( - "New gasTipCap exceeds limit, keep waiting", - "multiplier", p.txReplacementTipMultiplier, - "newGasTipCap", opts.GasTipCap, - "maxTipCap", p.proposeBlockTxGasTipCap, - ) - return nil, txpool.ErrReplaceUnderpriced + if opts, err = rpc.IncreaseGasTipCap( + ctx, + p.rpc, + opts, + p.l1ProposerAddress, + new(big.Int).SetUint64(p.txReplacementTipMultiplier), + p.proposeBlockTxGasTipCap, + ); err != nil { + return nil, err } } @@ -426,7 +402,10 @@ func (p *Proposer) ProposeTxList( return nil } if tx, err = p.sendProposeBlockTx(ctx, meta, txListBytes, nonce, assignment, fee, isReplacement); err != nil { - log.Warn("Failed to send propose block transaction, retrying", "error", encoding.TryParsingCustomError(err)) + log.Warn("Failed to send propose block transaction", "error", encoding.TryParsingCustomError(err)) + if strings.Contains(err.Error(), core.ErrNonceTooLow.Error()) { + return nil + } if strings.Contains(err.Error(), txpool.ErrReplaceUnderpriced.Error()) { isReplacement = true } else { diff --git a/prover/config.go b/prover/config.go index 80ee04173..b326c208b 100644 --- a/prover/config.go +++ b/prover/config.go @@ -15,37 +15,39 @@ import ( // Config contains the configurations to initialize a Taiko prover. type Config struct { - L1WsEndpoint string - L1HttpEndpoint string - L2WsEndpoint string - L2HttpEndpoint string - TaikoL1Address common.Address - TaikoL2Address common.Address - TaikoTokenAddress common.Address - L1ProverPrivKey *ecdsa.PrivateKey - ZKEvmRpcdEndpoint string - ZkEvmRpcdParamsPath string - StartingBlockID *big.Int - MaxConcurrentProvingJobs uint - Dummy bool - OracleProver bool - OracleProverPrivateKey *ecdsa.PrivateKey - OracleProofSubmissionDelay time.Duration - ProofSubmissionMaxRetry uint64 - Graffiti string - RandomDummyProofDelayLowerBound *time.Duration - RandomDummyProofDelayUpperBound *time.Duration - BackOffMaxRetrys uint64 - BackOffRetryInterval time.Duration - CheckProofWindowExpiredInterval time.Duration - ProveUnassignedBlocks bool - RPCTimeout *time.Duration - WaitReceiptTimeout time.Duration - ProveBlockGasLimit *uint64 - HTTPServerPort uint64 - Capacity uint64 - MinProofFee *big.Int - MaxExpiry time.Duration + L1WsEndpoint string + L1HttpEndpoint string + L2WsEndpoint string + L2HttpEndpoint string + TaikoL1Address common.Address + TaikoL2Address common.Address + TaikoTokenAddress common.Address + L1ProverPrivKey *ecdsa.PrivateKey + ZKEvmRpcdEndpoint string + ZkEvmRpcdParamsPath string + StartingBlockID *big.Int + MaxConcurrentProvingJobs uint + Dummy bool + OracleProver bool + OracleProverPrivateKey *ecdsa.PrivateKey + OracleProofSubmissionDelay time.Duration + ProofSubmissionMaxRetry uint64 + Graffiti string + RandomDummyProofDelayLowerBound *time.Duration + RandomDummyProofDelayUpperBound *time.Duration + BackOffMaxRetrys uint64 + BackOffRetryInterval time.Duration + CheckProofWindowExpiredInterval time.Duration + ProveUnassignedBlocks bool + RPCTimeout *time.Duration + WaitReceiptTimeout time.Duration + ProveBlockGasLimit *uint64 + ProveBlockTxReplacementMultiplier uint64 + ProveBlockMaxTxGasTipCap *big.Int + HTTPServerPort uint64 + Capacity uint64 + MinProofFee *big.Int + MaxExpiry time.Duration } // NewConfigFromCliContext creates a new config instance from command line flags. @@ -65,9 +67,7 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { return nil, fmt.Errorf("oracleProver flag set without oracleProverPrivateKey set") } - oracleProverPrivKeyStr := c.String(flags.OracleProverPrivateKey.Name) - - oracleProverPrivKey, err = crypto.ToECDSA(common.Hex2Bytes(oracleProverPrivKeyStr)) + oracleProverPrivKey, err = crypto.ToECDSA(common.Hex2Bytes(c.String(flags.OracleProverPrivateKey.Name))) if err != nil { return nil, fmt.Errorf("invalid oracle private key: %w", err) } @@ -128,6 +128,19 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { return nil, fmt.Errorf("invalid minProofFee: %v", minProofFee) } + proveBlockTxReplacementMultiplier := c.Uint64(flags.ProveBlockTxReplacementMultiplier.Name) + if proveBlockTxReplacementMultiplier == 0 { + return nil, fmt.Errorf( + "invalid --proveBlockTxReplacementMultiplier value: %d", + proveBlockTxReplacementMultiplier, + ) + } + + var proveBlockMaxTxGasTipCap *big.Int + if c.IsSet(flags.ProveBlockMaxTxGasTipCap.Name) { + proveBlockMaxTxGasTipCap = new(big.Int).SetUint64(c.Uint64(flags.ProveBlockMaxTxGasTipCap.Name)) + } + return &Config{ L1WsEndpoint: c.String(flags.L1WSEndpoint.Name), L1HttpEndpoint: c.String(flags.L1HTTPEndpoint.Name), @@ -154,13 +167,15 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { CheckProofWindowExpiredInterval: time.Duration( c.Uint64(flags.CheckProofWindowExpiredInterval.Name), ) * time.Second, - ProveUnassignedBlocks: c.Bool(flags.ProveUnassignedBlocks.Name), - RPCTimeout: timeout, - WaitReceiptTimeout: time.Duration(c.Uint64(flags.WaitReceiptTimeout.Name)) * time.Second, - ProveBlockGasLimit: proveBlockTxGasLimit, - Capacity: c.Uint64(flags.ProverCapacity.Name), - HTTPServerPort: c.Uint64(flags.ProverHTTPServerPort.Name), - MinProofFee: minProofFee, - MaxExpiry: time.Duration(c.Uint64(flags.MaxExpiry.Name)) * time.Second, + ProveUnassignedBlocks: c.Bool(flags.ProveUnassignedBlocks.Name), + RPCTimeout: timeout, + WaitReceiptTimeout: time.Duration(c.Uint64(flags.WaitReceiptTimeout.Name)) * time.Second, + ProveBlockGasLimit: proveBlockTxGasLimit, + Capacity: c.Uint64(flags.ProverCapacity.Name), + ProveBlockTxReplacementMultiplier: proveBlockTxReplacementMultiplier, + ProveBlockMaxTxGasTipCap: proveBlockMaxTxGasTipCap, + HTTPServerPort: c.Uint64(flags.ProverHTTPServerPort.Name), + MinProofFee: minProofFee, + MaxExpiry: time.Duration(c.Uint64(flags.MaxExpiry.Name)) * time.Second, }, nil } diff --git a/prover/config_test.go b/prover/config_test.go index a857e942a..dbd72b12a 100644 --- a/prover/config_test.go +++ b/prover/config_test.go @@ -50,6 +50,8 @@ func (s *ProverTestSuite) TestNewConfigFromCliContext_OracleProver() { s.Equal(rpcTimeout, *c.RPCTimeout) s.Equal(uint64(8), c.Capacity) s.Equal(minProofFee, c.MinProofFee.String()) + s.Equal(uint64(3), c.ProveBlockTxReplacementMultiplier) + s.Equal(uint64(256), c.ProveBlockMaxTxGasTipCap.Uint64()) s.Nil(new(Prover).InitFromCli(context.Background(), ctx)) return err @@ -57,25 +59,27 @@ func (s *ProverTestSuite) TestNewConfigFromCliContext_OracleProver() { s.Nil(app.Run([]string{ "TestNewConfigFromCliContext_OracleProver", - "-" + flags.L1WSEndpoint.Name, l1WsEndpoint, - "-" + flags.L1HTTPEndpoint.Name, l1HttpEndpoint, - "-" + flags.L2WSEndpoint.Name, l2WsEndpoint, - "-" + flags.L2HTTPEndpoint.Name, l2HttpEndpoint, - "-" + flags.TaikoL1Address.Name, taikoL1, - "-" + flags.TaikoL2Address.Name, taikoL2, - "-" + flags.L1ProverPrivKey.Name, os.Getenv("L1_PROVER_PRIVATE_KEY"), - "-" + flags.StartingBlockID.Name, "0", - "-" + flags.RPCTimeout.Name, "5", - "-" + flags.ProveBlockTxGasLimit.Name, "100000", - "-" + flags.Dummy.Name, - "-" + flags.RandomDummyProofDelay.Name, "30m-1h", - "-" + flags.MinProofFee.Name, minProofFee, - "-" + flags.ProverCapacity.Name, "8", - "-" + flags.OracleProver.Name, - "-" + flags.OracleProverPrivateKey.Name, os.Getenv("L1_PROVER_PRIVATE_KEY"), - "-" + flags.Graffiti.Name, "", - "-" + flags.CheckProofWindowExpiredInterval.Name, "30", - "-" + flags.ProveUnassignedBlocks.Name, "true", + "--" + flags.L1WSEndpoint.Name, l1WsEndpoint, + "--" + flags.L1HTTPEndpoint.Name, l1HttpEndpoint, + "--" + flags.L2WSEndpoint.Name, l2WsEndpoint, + "--" + flags.L2HTTPEndpoint.Name, l2HttpEndpoint, + "--" + flags.TaikoL1Address.Name, taikoL1, + "--" + flags.TaikoL2Address.Name, taikoL2, + "--" + flags.L1ProverPrivKey.Name, os.Getenv("L1_PROVER_PRIVATE_KEY"), + "--" + flags.StartingBlockID.Name, "0", + "--" + flags.RPCTimeout.Name, "5", + "--" + flags.ProveBlockTxGasLimit.Name, "100000", + "--" + flags.Dummy.Name, + "--" + flags.RandomDummyProofDelay.Name, "30m-1h", + "--" + flags.MinProofFee.Name, minProofFee, + "--" + flags.ProverCapacity.Name, "8", + "--" + flags.OracleProver.Name, + "--" + flags.ProveBlockTxReplacementMultiplier.Name, "3", + "--" + flags.ProveBlockMaxTxGasTipCap.Name, "256", + "--" + flags.OracleProverPrivateKey.Name, os.Getenv("L1_PROVER_PRIVATE_KEY"), + "--" + flags.Graffiti.Name, "", + "--" + flags.CheckProofWindowExpiredInterval.Name, "30", + "--" + flags.ProveUnassignedBlocks.Name, })) } @@ -190,6 +194,8 @@ func (s *ProverTestSuite) SetupApp() *cli.App { &cli.StringFlag{Name: flags.Graffiti.Name}, &cli.Uint64Flag{Name: flags.CheckProofWindowExpiredInterval.Name}, &cli.BoolFlag{Name: flags.ProveUnassignedBlocks.Name}, + &cli.Uint64Flag{Name: flags.ProveBlockTxReplacementMultiplier.Name}, + &cli.Uint64Flag{Name: flags.ProveBlockMaxTxGasTipCap.Name}, &cli.Uint64Flag{Name: flags.RPCTimeout.Name}, &cli.Uint64Flag{Name: flags.ProverCapacity.Name}, &cli.Uint64Flag{Name: flags.MinProofFee.Name}, diff --git a/prover/proof_submitter/util.go b/prover/proof_submitter/util.go index a8e02cc56..8f08c58b5 100644 --- a/prover/proof_submitter/util.go +++ b/prover/proof_submitter/util.go @@ -12,6 +12,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/taikoxyz/taiko-client/bindings" @@ -68,13 +69,14 @@ func sendTxWithBackoff( eventL1Hash common.Hash, proposedAt uint64, meta *bindings.TaikoDataBlockMetadata, - sendTxFunc func() (*types.Transaction, error), + sendTxFunc func(*big.Int) (*types.Transaction, error), retryInterval time.Duration, maxRetry *uint64, waitReceiptTimeout time.Duration, ) error { var ( isUnretryableError bool + nonce *big.Int backOffPolicy backoff.BackOff = backoff.NewConstantBackOff(retryInterval) ) @@ -112,7 +114,8 @@ func sendTxWithBackoff( // check if latest verified head is ahead of this block proof stateVars, err := cli.GetProtocolStateVariables(&bind.CallOpts{Context: ctx}) if err != nil { - log.Warn("failed to fetch state vars", + log.Warn( + "Failed to fetch state variables", "blockID", blockID, "error", err, ) @@ -120,20 +123,24 @@ func sendTxWithBackoff( } latestVerifiedId := stateVars.LastVerifiedBlockId - if new(big.Int).SetUint64(latestVerifiedId).Cmp(blockID) >= 0 { - log.Warn("Block is already verified, skip current proof submission", + log.Warn( + "Block is already verified, skip current proof submission", "blockID", blockID.Uint64(), "latestVerifiedId", latestVerifiedId, ) return nil } - tx, err := sendTxFunc() + tx, err := sendTxFunc(nonce) if err != nil { err = encoding.TryParsingCustomError(err) if isSubmitProofTxErrorRetryable(err, blockID) { log.Info("Retry sending TaikoL1.proveBlock transaction", "blockID", blockID, "reason", err) + if strings.Contains(err.Error(), core.ErrNonceTooLow.Error()) { + nonce = nil + } + return err } @@ -141,17 +148,26 @@ func sendTxWithBackoff( return nil } + nonce = new(big.Int).SetUint64(tx.Nonce()) ctxWithTimeout, cancel := context.WithTimeout(ctx, waitReceiptTimeout) defer cancel() if _, err := rpc.WaitReceipt(ctxWithTimeout, cli.L1, tx); err != nil { - log.Warn("Failed to wait till transaction executed", "blockID", blockID, "txHash", tx.Hash(), "error", err) + log.Warn( + "Failed to wait till transaction executed", + "blockID", blockID, + "txHash", tx.Hash(), + "nonce", nonce, + "error", err, + ) return err } log.Info( "💰 Your block proof was accepted", "blockID", blockID, + "txHash", tx.Hash(), + "nonce", nonce, "proposedAt", proposedAt, ) diff --git a/prover/proof_submitter/util_test.go b/prover/proof_submitter/util_test.go index a0f851144..116a039a1 100644 --- a/prover/proof_submitter/util_test.go +++ b/prover/proof_submitter/util_test.go @@ -48,7 +48,7 @@ func (s *ProofSubmitterTestSuite) TestSendTxWithBackoff() { l1Head.Hash(), 0, meta, - func() (*types.Transaction, error) { return nil, errors.New("L1_TEST") }, + func(nonce *big.Int) (*types.Transaction, error) { return nil, errors.New("L1_TEST") }, 12*time.Second, &testMaxRetry, 5*time.Second, @@ -61,7 +61,7 @@ func (s *ProofSubmitterTestSuite) TestSendTxWithBackoff() { l1Head.Hash(), 0, meta, - func() (*types.Transaction, error) { + func(nonce *big.Int) (*types.Transaction, error) { height, err := s.RpcClient.L1.BlockNumber(context.Background()) s.Nil(err) diff --git a/prover/proof_submitter/valid_proof_submitter.go b/prover/proof_submitter/valid_proof_submitter.go index 9416dd7bc..de6291e52 100644 --- a/prover/proof_submitter/valid_proof_submitter.go +++ b/prover/proof_submitter/valid_proof_submitter.go @@ -28,22 +28,24 @@ var _ ProofSubmitter = (*ValidProofSubmitter)(nil) // ValidProofSubmitter is responsible requesting zk proofs for the given valid L2 // blocks, and submitting the generated proofs to the TaikoL1 smart contract. type ValidProofSubmitter struct { - rpc *rpc.Client - proofProducer proofProducer.ProofProducer - resultCh chan *proofProducer.ProofWithHeader - anchorTxValidator *anchorTxValidator.AnchorTxValidator - proverPrivKey *ecdsa.PrivateKey - proverAddress common.Address - taikoL2Address common.Address - l1SignalService common.Address - l2SignalService common.Address - mutex *sync.Mutex - isOracleProver bool - graffiti [32]byte - submissionMaxRetry uint64 - retryInterval time.Duration - waitReceiptTimeout time.Duration - proveBlockTxGasLimit *uint64 + rpc *rpc.Client + proofProducer proofProducer.ProofProducer + resultCh chan *proofProducer.ProofWithHeader + anchorTxValidator *anchorTxValidator.AnchorTxValidator + proverPrivKey *ecdsa.PrivateKey + proverAddress common.Address + taikoL2Address common.Address + l1SignalService common.Address + l2SignalService common.Address + mutex *sync.Mutex + isOracleProver bool + graffiti [32]byte + submissionMaxRetry uint64 + retryInterval time.Duration + waitReceiptTimeout time.Duration + proveBlockTxGasLimit *uint64 + txReplacementTipMultiplier uint64 + proveBlockMaxTxGasTipCap *big.Int } // NewValidProofSubmitter creates a new ValidProofSubmitter instance. @@ -60,6 +62,8 @@ func NewValidProofSubmitter( retryInterval time.Duration, waitReceiptTimeout time.Duration, proveBlockTxGasLimit *uint64, + txReplacementTipMultiplier uint64, + proveBlockMaxTxGasTipCap *big.Int, ) (*ValidProofSubmitter, error) { anchorValidator, err := anchorTxValidator.New(taikoL2Address, rpcClient.L2ChainID, rpcClient) if err != nil { @@ -77,22 +81,24 @@ func NewValidProofSubmitter( } return &ValidProofSubmitter{ - rpc: rpcClient, - proofProducer: proofProducer, - resultCh: resultCh, - anchorTxValidator: anchorValidator, - proverPrivKey: proverPrivKey, - proverAddress: crypto.PubkeyToAddress(proverPrivKey.PublicKey), - l1SignalService: l1SignalService, - l2SignalService: l2SignalService, - taikoL2Address: taikoL2Address, - mutex: mutex, - isOracleProver: isOracleProver, - graffiti: rpc.StringToBytes32(graffiti), - submissionMaxRetry: submissionMaxRetry, - retryInterval: retryInterval, - waitReceiptTimeout: waitReceiptTimeout, - proveBlockTxGasLimit: proveBlockTxGasLimit, + rpc: rpcClient, + proofProducer: proofProducer, + resultCh: resultCh, + anchorTxValidator: anchorValidator, + proverPrivKey: proverPrivKey, + proverAddress: crypto.PubkeyToAddress(proverPrivKey.PublicKey), + l1SignalService: l1SignalService, + l2SignalService: l2SignalService, + taikoL2Address: taikoL2Address, + mutex: mutex, + isOracleProver: isOracleProver, + graffiti: rpc.StringToBytes32(graffiti), + submissionMaxRetry: submissionMaxRetry, + retryInterval: retryInterval, + waitReceiptTimeout: waitReceiptTimeout, + proveBlockTxGasLimit: proveBlockTxGasLimit, + txReplacementTipMultiplier: txReplacementTipMultiplier, + proveBlockMaxTxGasTipCap: proveBlockMaxTxGasTipCap, }, nil } @@ -248,19 +254,34 @@ func (s *ValidProofSubmitter) SubmitProof( } // Send the TaikoL1.proveBlock transaction. - txOpts, err := getProveBlocksTxOpts(ctx, s.rpc.L1, s.rpc.L1ChainID, s.proverPrivKey) - if err != nil { - return err - } - - if s.proveBlockTxGasLimit != nil { - txOpts.GasLimit = *s.proveBlockTxGasLimit - } - - sendTx := func() (*types.Transaction, error) { + sendTx := func(nonce *big.Int) (*types.Transaction, error) { s.mutex.Lock() defer s.mutex.Unlock() + txOpts, err := getProveBlocksTxOpts(ctx, s.rpc.L1, s.rpc.L1ChainID, s.proverPrivKey) + if err != nil { + return nil, err + } + + if s.proveBlockTxGasLimit != nil { + txOpts.GasLimit = *s.proveBlockTxGasLimit + } + + if nonce != nil { + txOpts.Nonce = nonce + + if txOpts, err = rpc.IncreaseGasTipCap( + ctx, + s.rpc, + txOpts, + s.proverAddress, + new(big.Int).SetUint64(s.txReplacementTipMultiplier), + s.proveBlockMaxTxGasTipCap, + ); err != nil { + return nil, err + } + } + return s.rpc.TaikoL1.ProveBlock(txOpts, blockID.Uint64(), input) } diff --git a/prover/proof_submitter/valid_proof_submitter_test.go b/prover/proof_submitter/valid_proof_submitter_test.go index 9d749c67f..cf5dca26d 100644 --- a/prover/proof_submitter/valid_proof_submitter_test.go +++ b/prover/proof_submitter/valid_proof_submitter_test.go @@ -53,6 +53,8 @@ func (s *ProofSubmitterTestSuite) SetupTest() { 12*time.Second, 10*time.Second, nil, + 2, + nil, ) s.Nil(err) diff --git a/prover/prover.go b/prover/prover.go index 09b1955c3..9b731f17e 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -201,6 +201,8 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { p.cfg.BackOffRetryInterval, p.cfg.WaitReceiptTimeout, p.cfg.ProveBlockGasLimit, + p.cfg.ProveBlockTxReplacementMultiplier, + p.cfg.ProveBlockMaxTxGasTipCap, ); err != nil { return err } diff --git a/prover/prover_test.go b/prover/prover_test.go index ad7954674..ba5528bd4 100644 --- a/prover/prover_test.go +++ b/prover/prover_test.go @@ -124,18 +124,19 @@ func (s *ProverTestSuite) TestInitError() { p := new(Prover) // Error should be "context canceled", instead is "Dial ethclient error:" s.ErrorContains(InitFromConfig(ctx, p, (&Config{ - L1WsEndpoint: os.Getenv("L1_NODE_WS_ENDPOINT"), - L1HttpEndpoint: os.Getenv("L1_NODE_HTTP_ENDPOINT"), - L2WsEndpoint: os.Getenv("L2_EXECUTION_ENGINE_WS_ENDPOINT"), - L2HttpEndpoint: os.Getenv("L2_EXECUTION_ENGINE_HTTP_ENDPOINT"), - TaikoL1Address: common.HexToAddress(os.Getenv("TAIKO_L1_ADDRESS")), - TaikoL2Address: common.HexToAddress(os.Getenv("TAIKO_L2_ADDRESS")), - L1ProverPrivKey: l1ProverPrivKey, - OracleProverPrivateKey: l1ProverPrivKey, - Dummy: true, - MaxConcurrentProvingJobs: 1, - CheckProofWindowExpiredInterval: 5 * time.Second, - ProveUnassignedBlocks: true, + L1WsEndpoint: os.Getenv("L1_NODE_WS_ENDPOINT"), + L1HttpEndpoint: os.Getenv("L1_NODE_HTTP_ENDPOINT"), + L2WsEndpoint: os.Getenv("L2_EXECUTION_ENGINE_WS_ENDPOINT"), + L2HttpEndpoint: os.Getenv("L2_EXECUTION_ENGINE_HTTP_ENDPOINT"), + TaikoL1Address: common.HexToAddress(os.Getenv("TAIKO_L1_ADDRESS")), + TaikoL2Address: common.HexToAddress(os.Getenv("TAIKO_L2_ADDRESS")), + L1ProverPrivKey: l1ProverPrivKey, + OracleProverPrivateKey: l1ProverPrivKey, + Dummy: true, + MaxConcurrentProvingJobs: 1, + CheckProofWindowExpiredInterval: 5 * time.Second, + ProveUnassignedBlocks: true, + ProveBlockTxReplacementMultiplier: 2, })), "dial tcp:") }