From 9b3870a4c1f627f8c9f3ab9b9d04ff4641991c21 Mon Sep 17 00:00:00 2001 From: Gavin Yu Date: Tue, 14 May 2024 08:45:40 +0800 Subject: [PATCH] feat(taiko-client): change guardian provers to directly submit proofs instead of contests (#17069) Co-authored-by: maskpp Co-authored-by: David --- .../event_handler/assignment_expired.go | 7 +- .../prover/event_handler/block_proposed.go | 82 ++++++------------- .../event_handler/block_proposed_test.go | 33 -------- .../event_handler/transition_contested.go | 2 +- .../prover/event_handler/transition_proved.go | 55 ++++++++++--- .../event_handler/transition_proved_test.go | 2 + .../taiko-client/prover/event_handler/util.go | 22 ++--- .../prover/event_handler/util_test.go | 10 +-- packages/taiko-client/prover/init.go | 9 +- .../prover/proof_submitter/proof_submitter.go | 75 ++++++++++++++++- .../proof_submitter/proof_submitter_test.go | 74 +++++++++++++++++ packages/taiko-client/prover/prover.go | 2 +- packages/taiko-client/prover/prover_test.go | 5 +- 13 files changed, 250 insertions(+), 128 deletions(-) diff --git a/packages/taiko-client/prover/event_handler/assignment_expired.go b/packages/taiko-client/prover/event_handler/assignment_expired.go index fbd8c5e04a..eb01a39f81 100644 --- a/packages/taiko-client/prover/event_handler/assignment_expired.go +++ b/packages/taiko-client/prover/event_handler/assignment_expired.go @@ -19,6 +19,8 @@ type AssignmentExpiredEventHandler struct { proofSubmissionCh chan<- *proofProducer.ProofRequestBody proofContestCh chan<- *proofProducer.ContestRequestBody contesterMode bool + // Guardian prover related. + isGuardian bool } // NewAssignmentExpiredEventHandler creates a new AssignmentExpiredEventHandler instance. @@ -28,8 +30,9 @@ func NewAssignmentExpiredEventHandler( proofSubmissionCh chan *proofProducer.ProofRequestBody, proofContestCh chan *proofProducer.ContestRequestBody, contesterMode bool, + isGuardian bool, ) *AssignmentExpiredEventHandler { - return &AssignmentExpiredEventHandler{rpc, proverAddress, proofSubmissionCh, proofContestCh, contesterMode} + return &AssignmentExpiredEventHandler{rpc, proverAddress, proofSubmissionCh, proofContestCh, contesterMode, isGuardian} } // Handle implements the AssignmentExpiredHandler interface. @@ -63,7 +66,7 @@ func (h *AssignmentExpiredEventHandler) Handle( // If there is no contester, we submit a contest to protocol. go func() { - if proofStatus.CurrentTransitionState.Contester == rpc.ZeroAddress { + if proofStatus.CurrentTransitionState.Contester == rpc.ZeroAddress && !h.isGuardian { h.proofContestCh <- &proofProducer.ContestRequestBody{ BlockID: e.BlockId, ProposedIn: new(big.Int).SetUint64(e.Raw.BlockNumber), diff --git a/packages/taiko-client/prover/event_handler/block_proposed.go b/packages/taiko-client/prover/event_handler/block_proposed.go index 3b7d616024..bda6fce31f 100644 --- a/packages/taiko-client/prover/event_handler/block_proposed.go +++ b/packages/taiko-client/prover/event_handler/block_proposed.go @@ -2,7 +2,6 @@ package handler import ( "context" - "crypto/rand" "errors" "fmt" "math/big" @@ -24,9 +23,8 @@ import ( ) var ( - errL1Reorged = errors.New("L1 reorged") - proofExpirationDelay = 6 * 12 * time.Second // 6 ethereum blocks - submissionDelayRandomBumpRange float64 = 20 + errL1Reorged = errors.New("L1 reorged") + proofExpirationDelay = 6 * 12 * time.Second // 6 ethereum blocks ) // BlockProposedEventHandler is responsible for handling the BlockProposed event as a prover. @@ -44,8 +42,7 @@ type BlockProposedEventHandler struct { contesterMode bool proveUnassignedBlocks bool // Guardian prover related. - isGuardian bool - submissionDelay time.Duration + isGuardian bool } // NewBlockProposedEventHandlerOps is the options for creating a new BlockProposedEventHandler. @@ -62,7 +59,6 @@ type NewBlockProposedEventHandlerOps struct { BackOffMaxRetrys uint64 ContesterMode bool ProveUnassignedBlocks bool - SubmissionDelay time.Duration } // NewBlockProposedEventHandler creates a new BlockProposedEventHandler instance. @@ -81,7 +77,6 @@ func NewBlockProposedEventHandler(opts *NewBlockProposedEventHandlerOps) *BlockP opts.ContesterMode, opts.ProveUnassignedBlocks, false, - opts.SubmissionDelay, } } @@ -223,29 +218,6 @@ func (h *BlockProposedEventHandler) checkL1Reorg( return nil } -// getRandomBumpedSubmissionDelay returns a random bumped submission delay. -func (h *BlockProposedEventHandler) getRandomBumpedSubmissionDelay(expiredAt time.Time) (time.Duration, error) { - if h.submissionDelay == 0 { - return h.submissionDelay, nil - } - - randomBump, err := rand.Int( - rand.Reader, - new(big.Int).SetUint64(uint64(h.submissionDelay.Seconds()*submissionDelayRandomBumpRange/100)), - ) - if err != nil { - return 0, err - } - - delay := time.Duration(h.submissionDelay.Seconds()+float64(randomBump.Uint64())) * time.Second - - if time.Since(expiredAt) >= delay { - return 0, nil - } - - return delay - time.Since(expiredAt), nil -} - // checkExpirationAndSubmitProof checks whether the proposed block's proving window is expired, // and submits a new proof if necessary. func (h *BlockProposedEventHandler) checkExpirationAndSubmitProof( @@ -295,28 +267,33 @@ func (h *BlockProposedEventHandler) checkExpirationAndSubmitProof( return nil } - // If the current proof has not been contested, we should contest it at first. - if proofStatus.CurrentTransitionState.Contester == rpc.ZeroAddress { - h.proofContestCh <- &proofProducer.ContestRequestBody{ - BlockID: e.BlockId, - ProposedIn: new(big.Int).SetUint64(e.Raw.BlockNumber), - ParentHash: proofStatus.ParentHeader.Hash(), - Meta: &e.Meta, - Tier: e.Meta.MinTier, - } + if h.isGuardian { + // In guardian prover, we submit a proof directly. + h.proofSubmissionCh <- &proofProducer.ProofRequestBody{Tier: encoding.TierGuardianMinorityID, Event: e} } else { - // The invalid proof submitted to protocol is contested by another prover, - // we need to submit a proof with a higher tier. - h.proofSubmissionCh <- &proofProducer.ProofRequestBody{ - Tier: proofStatus.CurrentTransitionState.Tier + 1, - Event: e, + // If the current proof has not been contested, we should contest it at first. + if proofStatus.CurrentTransitionState.Contester == rpc.ZeroAddress { + h.proofContestCh <- &proofProducer.ContestRequestBody{ + BlockID: e.BlockId, + ProposedIn: new(big.Int).SetUint64(e.Raw.BlockNumber), + ParentHash: proofStatus.ParentHeader.Hash(), + Meta: &e.Meta, + Tier: e.Meta.MinTier, + } + } else { + // The invalid proof submitted to protocol is contested by another prover, + // we need to submit a proof with a higher tier. + h.proofSubmissionCh <- &proofProducer.ProofRequestBody{ + Tier: proofStatus.CurrentTransitionState.Tier + 1, + Event: e, + } } } return nil } - windowExpired, expiredAt, timeToExpire, err := isProvingWindowExpired(e, h.sharedState.GetTiers()) + windowExpired, _, timeToExpire, err := IsProvingWindowExpired(&e.Meta, h.sharedState.GetTiers()) if err != nil { return fmt.Errorf("failed to check if the proving window is expired: %w", err) } @@ -339,7 +316,7 @@ func (h *BlockProposedEventHandler) checkExpirationAndSubmitProof( "timeToExpire", timeToExpire, ) time.AfterFunc( - // Add another 60 seconds, to ensure one more L1 block will be mined before the proof submission + // Add another 72 seconds, to ensure one more L1 block will be mined before the proof submission timeToExpire+proofExpirationDelay, func() { h.assignmentExpiredCh <- e }, ) @@ -352,12 +329,6 @@ func (h *BlockProposedEventHandler) checkExpirationAndSubmitProof( // try to submit a proof for this proposed block. tier := e.Meta.MinTier - // Get a random bumped submission delay, if necessary. - submissionDelay, err := h.getRandomBumpedSubmissionDelay(expiredAt) - if err != nil { - return err - } - if h.isGuardian { tier = encoding.TierGuardianMinorityID } @@ -367,15 +338,12 @@ func (h *BlockProposedEventHandler) checkExpirationAndSubmitProof( "blockID", e.BlockId, "assignProver", e.AssignedProver, "minTier", e.Meta.MinTier, - "submissionDelay", submissionDelay, "tier", tier, ) metrics.ProverProofsAssigned.Add(1) - time.AfterFunc(submissionDelay, func() { - h.proofSubmissionCh <- &proofProducer.ProofRequestBody{Tier: tier, Event: e} - }) + h.proofSubmissionCh <- &proofProducer.ProofRequestBody{Tier: tier, Event: e} return nil } diff --git a/packages/taiko-client/prover/event_handler/block_proposed_test.go b/packages/taiko-client/prover/event_handler/block_proposed_test.go index 2d3b980652..7b843d97d4 100644 --- a/packages/taiko-client/prover/event_handler/block_proposed_test.go +++ b/packages/taiko-client/prover/event_handler/block_proposed_test.go @@ -31,36 +31,3 @@ func (s *EventHandlerTestSuite) TestBlockProposedHandle() { err := handler.Handle(context.Background(), e, func() {}) s.Nil(err) } - -func (s *EventHandlerTestSuite) TestGetRandomBumpedSubmissionDelay() { - opts := &NewBlockProposedEventHandlerOps{ - SharedState: &state.SharedState{}, - ProverAddress: common.Address{}, - GenesisHeightL1: 0, - RPC: s.RPCClient, - ProofGenerationCh: make(chan *proofProducer.ProofWithHeader), - AssignmentExpiredCh: make(chan *bindings.TaikoL1ClientBlockProposed), - ProofSubmissionCh: make(chan *proofProducer.ProofRequestBody), - ProofContestCh: make(chan *proofProducer.ContestRequestBody), - BackOffRetryInterval: 1 * time.Minute, - BackOffMaxRetrys: 5, - ContesterMode: true, - ProveUnassignedBlocks: true, - } - handler1 := NewBlockProposedEventHandler(opts) - - delay, err := handler1.getRandomBumpedSubmissionDelay(time.Now()) - s.Nil(err) - s.Zero(delay) - - opts.SubmissionDelay = 1 * time.Hour - handler2 := NewBlockProposedEventHandler(opts) - delay, err = handler2.getRandomBumpedSubmissionDelay(time.Now()) - s.Nil(err) - s.NotZero(delay) - s.Greater(delay.Seconds(), opts.SubmissionDelay.Seconds()) - s.Less( - delay.Seconds(), - opts.SubmissionDelay.Seconds()*(1+(submissionDelayRandomBumpRange/100)), - ) -} diff --git a/packages/taiko-client/prover/event_handler/transition_contested.go b/packages/taiko-client/prover/event_handler/transition_contested.go index 53ef97f047..a34d350693 100644 --- a/packages/taiko-client/prover/event_handler/transition_contested.go +++ b/packages/taiko-client/prover/event_handler/transition_contested.go @@ -90,7 +90,7 @@ func (h *TransitionContestedEventHandler) Handle( return err } - blockProposedEvent, err := getBlockProposedEventFromBlockID( + blockProposedEvent, err := GetBlockProposedEventFromBlockID( ctx, h.rpc, e.BlockId, diff --git a/packages/taiko-client/prover/event_handler/transition_proved.go b/packages/taiko-client/prover/event_handler/transition_proved.go index c94610a19d..21416b5c51 100644 --- a/packages/taiko-client/prover/event_handler/transition_proved.go +++ b/packages/taiko-client/prover/event_handler/transition_proved.go @@ -4,6 +4,8 @@ import ( "context" "math/big" + "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/encoding" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -15,18 +17,28 @@ import ( // TransitionProvedEventHandler is responsible for handling the TransitionProved event. type TransitionProvedEventHandler struct { - rpc *rpc.Client - proofContestCh chan<- *proofProducer.ContestRequestBody - contesterMode bool + rpc *rpc.Client + proofContestCh chan<- *proofProducer.ContestRequestBody + proofSubmissionCh chan<- *proofProducer.ProofRequestBody + contesterMode bool + isGuardian bool } // NewTransitionProvedEventHandler creates a new TransitionProvedEventHandler instance. func NewTransitionProvedEventHandler( rpc *rpc.Client, proofContestCh chan *proofProducer.ContestRequestBody, + proofSubmissionCh chan *proofProducer.ProofRequestBody, contesterMode bool, + isGuardian bool, ) *TransitionProvedEventHandler { - return &TransitionProvedEventHandler{rpc, proofContestCh, contesterMode} + return &TransitionProvedEventHandler{ + rpc, + proofContestCh, + proofSubmissionCh, + contesterMode, + isGuardian, + } } // Handle implements the TransitionProvedHandler interface. @@ -78,15 +90,32 @@ func (h *TransitionProvedEventHandler) Handle( "blockHash", common.Bytes2Hex(e.Tran.BlockHash[:]), "stateRoot", common.Bytes2Hex(e.Tran.StateRoot[:]), ) - - go func() { - h.proofContestCh <- &proofProducer.ContestRequestBody{ - BlockID: e.BlockId, - ProposedIn: new(big.Int).SetUint64(blockInfo.ProposedIn), - ParentHash: e.Tran.ParentHash, - Meta: meta, - Tier: e.Tier, + if h.isGuardian { + blockProposedEvent, err := GetBlockProposedEventFromBlockID( + ctx, + h.rpc, + e.BlockId, + new(big.Int).SetUint64(blockInfo.ProposedIn), + ) + if err != nil { + return err } - }() + go func() { + h.proofSubmissionCh <- &proofProducer.ProofRequestBody{ + Tier: encoding.TierGuardianMinorityID, + Event: blockProposedEvent, + } + }() + } else { + go func() { + h.proofContestCh <- &proofProducer.ContestRequestBody{ + BlockID: e.BlockId, + ProposedIn: new(big.Int).SetUint64(blockInfo.ProposedIn), + ParentHash: e.Tran.ParentHash, + Meta: meta, + Tier: e.Tier, + } + }() + } return nil } diff --git a/packages/taiko-client/prover/event_handler/transition_proved_test.go b/packages/taiko-client/prover/event_handler/transition_proved_test.go index b6afa75ec2..992aa582b7 100644 --- a/packages/taiko-client/prover/event_handler/transition_proved_test.go +++ b/packages/taiko-client/prover/event_handler/transition_proved_test.go @@ -118,7 +118,9 @@ func (s *EventHandlerTestSuite) TestTransitionProvedHandle() { handler := NewTransitionProvedEventHandler( s.RPCClient, make(chan *proofProducer.ContestRequestBody), + make(chan *proofProducer.ProofRequestBody), true, + false, ) e := s.ProposeAndInsertValidBlock(s.proposer, s.blobSyncer) err := handler.Handle(context.Background(), &bindings.TaikoL1ClientTransitionProved{ diff --git a/packages/taiko-client/prover/event_handler/util.go b/packages/taiko-client/prover/event_handler/util.go index c21ad7f1e6..0a350d577a 100644 --- a/packages/taiko-client/prover/event_handler/util.go +++ b/packages/taiko-client/prover/event_handler/util.go @@ -54,13 +54,13 @@ func isValidProof( l2Header.Root == stateRoot, nil } -// getProvingWindow returns the provingWindow of the given proposed block. +// getProvingWindow returns the provingWindow of the given tier. func getProvingWindow( - e *bindings.TaikoL1ClientBlockProposed, + tier uint16, tiers []*rpc.TierProviderTierWithID, ) (time.Duration, error) { for _, t := range tiers { - if e.Meta.MinTier == t.ID { + if tier == t.ID { return time.Duration(t.ProvingWindow) * time.Minute, nil } } @@ -68,8 +68,8 @@ func getProvingWindow( return 0, errTierNotFound } -// getBlockProposedEventFromBlockID fetches the BlockProposed event by the given block id. -func getBlockProposedEventFromBlockID( +// GetBlockProposedEventFromBlockID fetches the BlockProposed event by the given block id. +func GetBlockProposedEventFromBlockID( ctx context.Context, rpc *rpc.Client, id *big.Int, @@ -120,28 +120,28 @@ func getMetadataFromBlockID( id *big.Int, proposedIn *big.Int, ) (*bindings.TaikoDataBlockMetadata, error) { - e, err := getBlockProposedEventFromBlockID(ctx, rpc, id, proposedIn) + e, err := GetBlockProposedEventFromBlockID(ctx, rpc, id, proposedIn) if err != nil { return nil, err } return &e.Meta, nil } -// isProvingWindowExpired returns true as the first return parameter if the assigned prover +// IsProvingWindowExpired returns true as the first return parameter if the assigned prover // proving window of the given proposed block is expired, and the second return parameter is the time // remaining til proving window is expired. -func isProvingWindowExpired( - e *bindings.TaikoL1ClientBlockProposed, +func IsProvingWindowExpired( + metadata *bindings.TaikoDataBlockMetadata, tiers []*rpc.TierProviderTierWithID, ) (bool, time.Time, time.Duration, error) { - provingWindow, err := getProvingWindow(e, tiers) + provingWindow, err := getProvingWindow(metadata.MinTier, tiers) if err != nil { return false, time.Time{}, 0, fmt.Errorf("failed to get proving window: %w", err) } var ( now = uint64(time.Now().Unix()) - expiredAt = e.Meta.Timestamp + uint64(provingWindow.Seconds()) + expiredAt = metadata.Timestamp + uint64(provingWindow.Seconds()) ) return now > expiredAt, time.Unix(int64(expiredAt), 0), time.Duration(expiredAt-now) * time.Second, nil diff --git a/packages/taiko-client/prover/event_handler/util_test.go b/packages/taiko-client/prover/event_handler/util_test.go index 95c103ec24..c6d1b1344d 100644 --- a/packages/taiko-client/prover/event_handler/util_test.go +++ b/packages/taiko-client/prover/event_handler/util_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/suite" - "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings" "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/encoding" "github.com/taikoxyz/taiko-mono/packages/taiko-client/internal/testutils" "github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/rpc" @@ -18,11 +17,10 @@ type ProverEventHandlerTestSuite struct { } func (s *ProverEventHandlerTestSuite) TestGetProvingWindowNotFound() { - _, err := getProvingWindow(&bindings.TaikoL1ClientBlockProposed{ - Meta: bindings.TaikoDataBlockMetadata{ - MinTier: encoding.TierGuardianMajorityID + 1, - }, - }, []*rpc.TierProviderTierWithID{}) + _, err := getProvingWindow( + encoding.TierGuardianMajorityID+1, + []*rpc.TierProviderTierWithID{}, + ) s.ErrorIs(err, errTierNotFound) } diff --git a/packages/taiko-client/prover/init.go b/packages/taiko-client/prover/init.go index 9f046fbbcd..dcc40ee119 100644 --- a/packages/taiko-client/prover/init.go +++ b/packages/taiko-client/prover/init.go @@ -14,6 +14,7 @@ import ( "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/encoding" "github.com/taikoxyz/taiko-mono/packages/taiko-client/internal/utils" + "github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/rpc" handler "github.com/taikoxyz/taiko-mono/packages/taiko-client/prover/event_handler" proofProducer "github.com/taikoxyz/taiko-mono/packages/taiko-client/prover/proof_producer" proofSubmitter "github.com/taikoxyz/taiko-mono/packages/taiko-client/prover/proof_submitter" @@ -94,6 +95,7 @@ func (p *Prover) setApprovalAmount(ctx context.Context, contract common.Address) func (p *Prover) initProofSubmitters( txmgr *txmgr.SimpleTxManager, txBuilder *transaction.ProveBlockTxBuilder, + tiers []*rpc.TierProviderTierWithID, ) error { for _, tier := range p.sharedState.GetTiers() { var ( @@ -144,6 +146,9 @@ func (p *Prover) initProofSubmitters( p.cfg.ProveBlockGasLimit, txmgr, txBuilder, + tiers, + p.IsGuardianProver(), + p.cfg.GuardianProofSubmissionDelay, ); err != nil { return err } @@ -227,7 +232,6 @@ func (p *Prover) initEventHandlers() error { ProveUnassignedBlocks: p.cfg.ProveUnassignedBlocks, } if p.IsGuardianProver() { - opts.SubmissionDelay = p.cfg.GuardianProofSubmissionDelay p.blockProposedHandler = handler.NewBlockProposedEventGuardianHandler( &handler.NewBlockProposedGuardianEventHandlerOps{ NewBlockProposedEventHandlerOps: opts, @@ -241,7 +245,9 @@ func (p *Prover) initEventHandlers() error { p.transitionProvedHandler = handler.NewTransitionProvedEventHandler( p.rpc, p.proofContestCh, + p.proofSubmissionCh, p.cfg.ContesterMode, + p.IsGuardianProver(), ) // ------- TransitionContested ------- p.transitionContestedHandler = handler.NewTransitionContestedEventHandler( @@ -256,6 +262,7 @@ func (p *Prover) initEventHandlers() error { p.proofSubmissionCh, p.proofContestCh, p.cfg.ContesterMode, + p.IsGuardianProver(), ) // ------- BlockVerified ------- diff --git a/packages/taiko-client/prover/proof_submitter/proof_submitter.go b/packages/taiko-client/prover/proof_submitter/proof_submitter.go index f9dc69220c..c453a589bf 100644 --- a/packages/taiko-client/prover/proof_submitter/proof_submitter.go +++ b/packages/taiko-client/prover/proof_submitter/proof_submitter.go @@ -2,8 +2,11 @@ package submitter import ( "context" + "crypto/rand" "errors" "fmt" + "math/big" + "time" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/common" @@ -14,11 +17,15 @@ import ( "github.com/taikoxyz/taiko-mono/packages/taiko-client/internal/metrics" "github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/rpc" validator "github.com/taikoxyz/taiko-mono/packages/taiko-client/prover/anchor_tx_validator" + handler "github.com/taikoxyz/taiko-mono/packages/taiko-client/prover/event_handler" proofProducer "github.com/taikoxyz/taiko-mono/packages/taiko-client/prover/proof_producer" "github.com/taikoxyz/taiko-mono/packages/taiko-client/prover/proof_submitter/transaction" ) -var _ Submitter = (*ProofSubmitter)(nil) +var ( + _ Submitter = (*ProofSubmitter)(nil) + submissionDelayRandomBumpRange float64 = 20 +) // ProofSubmitter is responsible requesting proofs for the given L2 // blocks, and submitting the generated proofs to the TaikoL1 smart contract. @@ -32,6 +39,10 @@ type ProofSubmitter struct { proverAddress common.Address taikoL2Address common.Address graffiti [32]byte + tiers []*rpc.TierProviderTierWithID + // Guardian prover related. + isGuardian bool + submissionDelay time.Duration } // NewProofSubmitter creates a new ProofSubmitter instance. @@ -44,6 +55,9 @@ func NewProofSubmitter( gasLimit uint64, txmgr *txmgr.SimpleTxManager, builder *transaction.ProveBlockTxBuilder, + tiers []*rpc.TierProviderTierWithID, + isGuardian bool, + submissionDelay time.Duration, ) (*ProofSubmitter, error) { anchorValidator, err := validator.New(taikoL2Address, rpcClient.L2.ChainID, rpcClient) if err != nil { @@ -60,6 +74,9 @@ func NewProofSubmitter( proverAddress: txmgr.From(), taikoL2Address: taikoL2Address, graffiti: rpc.StringToBytes32(graffiti), + tiers: tiers, + isGuardian: isGuardian, + submissionDelay: submissionDelay, }, nil } @@ -124,7 +141,7 @@ func (s *ProofSubmitter) SubmitProof( proofWithHeader *proofProducer.ProofWithHeader, ) (err error) { log.Info( - "NewProofSubmitter block proof", + "Submit block proof", "blockID", proofWithHeader.BlockID, "coinbase", proofWithHeader.Meta.Coinbase, "parentHash", proofWithHeader.Header.ParentHash, @@ -134,6 +151,37 @@ func (s *ProofSubmitter) SubmitProof( "tier", proofWithHeader.Tier, ) + // Check if we still need to generate a new proof for that block. + proofStatus, err := rpc.GetBlockProofStatus(ctx, s.rpc, proofWithHeader.BlockID, s.proverAddress) + if err != nil { + return err + } + if proofStatus.IsSubmitted && !proofStatus.Invalid { + return nil + } + + if s.isGuardian { + _, expiredAt, _, err := handler.IsProvingWindowExpired(proofWithHeader.Meta, s.tiers) + if err != nil { + return fmt.Errorf("failed to check if the proving window is expired: %w", err) + } + // Get a random bumped submission delay, if necessary. + submissionDelay, err := s.getRandomBumpedSubmissionDelay(expiredAt) + if err != nil { + return err + } + delayTimer := time.After(submissionDelay) + <-delayTimer + // Check again. + proofStatus, err := rpc.GetBlockProofStatus(ctx, s.rpc, proofWithHeader.BlockID, s.proverAddress) + if err != nil { + return err + } + if proofStatus.IsSubmitted && !proofStatus.Invalid { + return nil + } + } + metrics.ProverReceivedProofCounter.Add(1) // Get the corresponding L2 block. @@ -185,6 +233,29 @@ func (s *ProofSubmitter) SubmitProof( return nil } +// getRandomBumpedSubmissionDelay returns a random bumped submission delay. +func (s *ProofSubmitter) getRandomBumpedSubmissionDelay(expiredAt time.Time) (time.Duration, error) { + if s.submissionDelay == 0 { + return s.submissionDelay, nil + } + + randomBump, err := rand.Int( + rand.Reader, + new(big.Int).SetUint64(uint64(s.submissionDelay.Seconds()*submissionDelayRandomBumpRange/100)), + ) + if err != nil { + return 0, err + } + + delay := time.Duration(s.submissionDelay.Seconds()+float64(randomBump.Uint64())) * time.Second + + if time.Since(expiredAt) >= delay { + return 0, nil + } + + return delay - time.Since(expiredAt), nil +} + // Producer returns the inner proof producer. func (s *ProofSubmitter) Producer() proofProducer.ProofProducer { return s.proofProducer diff --git a/packages/taiko-client/prover/proof_submitter/proof_submitter_test.go b/packages/taiko-client/prover/proof_submitter/proof_submitter_test.go index 13c8c84101..5ca5876823 100644 --- a/packages/taiko-client/prover/proof_submitter/proof_submitter_test.go +++ b/packages/taiko-client/prover/proof_submitter/proof_submitter_test.go @@ -73,6 +73,9 @@ func (s *ProofSubmitterTestSuite) SetupTest() { ) s.Nil(err) + // Protocol proof tiers + tiers, err := s.RPCClient.GetTiers(context.Background()) + s.Nil(err) s.submitter, err = NewProofSubmitter( s.RPCClient, &producer.OptimisticProofProducer{}, @@ -82,6 +85,9 @@ func (s *ProofSubmitterTestSuite) SetupTest() { 0, txMgr, builder, + tiers, + false, + 0*time.Second, ) s.Nil(err) s.contester = NewProofContester( @@ -153,6 +159,74 @@ func (s *ProofSubmitterTestSuite) SetupTest() { s.proposer = prop } +func (s *ProofSubmitterTestSuite) TestGetRandomBumpedSubmissionDelay() { + l1ProverPrivKey, err := crypto.ToECDSA(common.FromHex(os.Getenv("L1_PROVER_PRIVATE_KEY"))) + s.Nil(err) + txMgr, err := txmgr.NewSimpleTxManager( + "proofSubmitterTestSuite", + log.Root(), + new(metrics.NoopTxMetrics), + txmgr.CLIConfig{ + L1RPCURL: os.Getenv("L1_NODE_WS_ENDPOINT"), + NumConfirmations: 0, + SafeAbortNonceTooLowCount: txmgr.DefaultBatcherFlagValues.SafeAbortNonceTooLowCount, + PrivateKey: common.Bytes2Hex(crypto.FromECDSA(l1ProverPrivKey)), + FeeLimitMultiplier: txmgr.DefaultBatcherFlagValues.FeeLimitMultiplier, + FeeLimitThresholdGwei: txmgr.DefaultBatcherFlagValues.FeeLimitThresholdGwei, + MinBaseFeeGwei: txmgr.DefaultBatcherFlagValues.MinBaseFeeGwei, + MinTipCapGwei: txmgr.DefaultBatcherFlagValues.MinTipCapGwei, + ResubmissionTimeout: txmgr.DefaultBatcherFlagValues.ResubmissionTimeout, + ReceiptQueryInterval: 1 * time.Second, + NetworkTimeout: txmgr.DefaultBatcherFlagValues.NetworkTimeout, + TxSendTimeout: txmgr.DefaultBatcherFlagValues.TxSendTimeout, + TxNotInMempoolTimeout: txmgr.DefaultBatcherFlagValues.TxNotInMempoolTimeout, + }, + ) + s.Nil(err) + + submitter1, err := NewProofSubmitter( + s.RPCClient, + &producer.OptimisticProofProducer{}, + s.proofCh, + common.HexToAddress(os.Getenv("TAIKO_L2_ADDRESS")), + "test", + 0, + txMgr, + s.submitter.txBuilder, + s.submitter.tiers, + false, + time.Duration(0), + ) + s.Nil(err) + + delay, err := submitter1.getRandomBumpedSubmissionDelay(time.Now()) + s.Nil(err) + s.Zero(delay) + + submitter2, err := NewProofSubmitter( + s.RPCClient, + &producer.OptimisticProofProducer{}, + s.proofCh, + common.HexToAddress(os.Getenv("TAIKO_L2_ADDRESS")), + "test", + 0, + txMgr, + s.submitter.txBuilder, + s.submitter.tiers, + false, + 1*time.Hour, + ) + s.Nil(err) + delay, err = submitter2.getRandomBumpedSubmissionDelay(time.Now()) + s.Nil(err) + s.NotZero(delay) + s.Greater(delay.Seconds(), 1*time.Hour.Seconds()) + s.Less( + delay.Seconds(), + time.Hour.Seconds()*(1+(submissionDelayRandomBumpRange/100)), + ) +} + func (s *ProofSubmitterTestSuite) TestProofSubmitterRequestProofDeadlineExceeded() { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() diff --git a/packages/taiko-client/prover/prover.go b/packages/taiko-client/prover/prover.go index 10e0bd5f3f..b20391cbad 100644 --- a/packages/taiko-client/prover/prover.go +++ b/packages/taiko-client/prover/prover.go @@ -160,7 +160,7 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { } // Proof submitters - if err := p.initProofSubmitters(p.txmgr, txBuilder); err != nil { + if err := p.initProofSubmitters(p.txmgr, txBuilder, tiers); err != nil { return err } diff --git a/packages/taiko-client/prover/prover_test.go b/packages/taiko-client/prover/prover_test.go index 101c3607c5..3e175f0dcd 100644 --- a/packages/taiko-client/prover/prover_test.go +++ b/packages/taiko-client/prover/prover_test.go @@ -308,7 +308,10 @@ func (s *ProverTestSuite) TestContestWrongBlocks() { s.p.cfg.GuardianProverMinorityAddress, ) s.p.proofSubmitters = nil - s.Nil(s.p.initProofSubmitters(s.p.txmgr, txBuilder)) + // Protocol proof tiers + tiers, err := s.RPCClient.GetTiers(context.Background()) + s.Nil(err) + s.Nil(s.p.initProofSubmitters(s.p.txmgr, txBuilder, tiers)) s.p.rpc.GuardianProverMajority, err = bindings.NewGuardianProver(s.p.cfg.GuardianProverMajorityAddress, s.p.rpc.L1) s.Nil(err)