From 4cc065b03ebe898ed353eef3dd401d1a9e70ee2a Mon Sep 17 00:00:00 2001 From: gavin Date: Tue, 19 Mar 2024 15:32:21 +0800 Subject: [PATCH 01/13] improve reorg check logic --- cmd/flags/driver.go | 8 ++ driver/chain_syncer/calldata/syncer.go | 115 +++++++++++++----- driver/chain_syncer/calldata/syncer_test.go | 2 + driver/chain_syncer/chain_syncer.go | 3 +- driver/chain_syncer/chain_syncer_test.go | 1 + driver/config.go | 2 + driver/driver.go | 1 + pkg/rpc/methods.go | 13 +- .../proof_submitter/proof_submitter_test.go | 1 + 9 files changed, 107 insertions(+), 39 deletions(-) diff --git a/cmd/flags/driver.go b/cmd/flags/driver.go index dbf24bd4b..19216c6b9 100644 --- a/cmd/flags/driver.go +++ b/cmd/flags/driver.go @@ -43,6 +43,14 @@ var ( Usage: "HTTP RPC endpoint of another synced L2 execution engine node", Category: driverCategory, } + // syncer specific flag + MaxExponent = &cli.Uint64Flag{ + Name: "syncer.maxExponent", + Usage: "Maximum exponent of retrieving L1 blocks when there is a mismatch between protocol and L2 EE," + + "0 means that it is reset to the genesis height", + Value: 0, + Category: driverCategory, + } ) // DriverFlags All driver flags. diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index 50706ae97..a350f9b37 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "math/big" "time" @@ -15,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + ethereum "github.com/ethereum/go-ethereum" "github.com/taikoxyz/taiko-client/bindings" "github.com/taikoxyz/taiko-client/bindings/encoding" anchorTxConstructor "github.com/taikoxyz/taiko-client/driver/anchor_tx_constructor" @@ -39,6 +41,7 @@ type Syncer struct { // Used by BlockInserter lastInsertedBlockID *big.Int reorgDetectedFlag bool + maxRetrieveExponent uint64 } // NewSyncer creates a new syncer instance. @@ -47,6 +50,7 @@ func NewSyncer( client *rpc.Client, state *state.State, progressTracker *beaconsync.SyncProgressTracker, + maxRetrieveExponent uint64, ) (*Syncer, error) { configs, err := client.TaikoL1.GetConfig(&bind.CallOpts{Context: ctx}) if err != nil { @@ -69,6 +73,7 @@ func NewSyncer( rpc.BlockMaxTxListBytes, client.L2.ChainID, ), + maxRetrieveExponent: maxRetrieveExponent, }, nil } @@ -517,27 +522,98 @@ func (s *Syncer) createExecutionPayloads( // checkLastVerifiedBlockMismatch checks if there is a mismatch between protocol's last verified block hash and // the corresponding L2 EE block hash. -func (s *Syncer) checkLastVerifiedBlockMismatch(ctx context.Context) (bool, error) { +func (s *Syncer) checkLastVerifiedBlockMismatch(ctx context.Context) (*rpc.ReorgCheckResult, error) { + var ( + reorgCheckResult = new(rpc.ReorgCheckResult) + err error + ) + stateVars, err := s.rpc.GetProtocolStateVariables(&bind.CallOpts{Context: ctx}) if err != nil { - return false, err + return reorgCheckResult, err } if s.state.GetL2Head().Number.Uint64() < stateVars.B.LastVerifiedBlockId { - return false, nil + return reorgCheckResult, nil } - blockInfo, err := s.rpc.GetL2BlockInfo(ctx, new(big.Int).SetUint64(stateVars.B.LastVerifiedBlockId)) + reorgCheckResult, err = s.retrievePastBlock(ctx, stateVars.B.LastVerifiedBlockId, 0) if err != nil { - return false, err + return reorgCheckResult, err + } + + return reorgCheckResult, nil +} + +// retrievePastBlock find proper L1 header and L2 block id to reset when there is a mismatch +func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries uint64) (*rpc.ReorgCheckResult, error) { + if retries > s.maxRetrieveExponent { + genesisL1Header, err := s.rpc.GetGenesisL1Header(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch genesis L1 header: %w", err) + } + return &rpc.ReorgCheckResult{ + IsReorged: true, + L1CurrentToReset: genesisL1Header, + LastHandledBlockIDToReset: new(big.Int).SetUint64(blockID), + }, nil + } + + var ( + reorgCheckResult = new(rpc.ReorgCheckResult) + err error + currentBlockID uint64 + l1HeaderToSet *types.Header + ) + + if float64(blockID) > math.Pow(2, float64(retries)) { + currentBlockID = uint64(float64(blockID)-math.Pow(2, float64(retries))) + 1 + } else { + currentBlockID = 0 } - l2Header, err := s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(stateVars.B.LastVerifiedBlockId)) + blockInfo, err := s.rpc.GetL2BlockInfo(ctx, new(big.Int).SetUint64(currentBlockID)) if err != nil { - return false, err + return reorgCheckResult, err } - return blockInfo.Ts.BlockHash != l2Header.Hash(), nil + l2Header, err := s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(currentBlockID)) + if err != nil { + return reorgCheckResult, err + } + if blockInfo.Ts.BlockHash == l2Header.Hash() { + l1Origin, err := s.rpc.L2.L1OriginByID(ctx, new(big.Int).SetUint64(currentBlockID)) + if err != nil { + if err.Error() == ethereum.NotFound.Error() { + log.Info("L1Origin not found in retrievePastBlock because the L2 EE is just synced through P2P", "blockID", blockID) + // cant find l1Origin in L2 EE, so we call contract to get block info + blockInfo, err := s.rpc.TaikoL1.GetBlock(&bind.CallOpts{Context: ctx}, currentBlockID) + if err != nil { + return reorgCheckResult, err + } + l1HeaderToSet, err = s.rpc.L1.HeaderByNumber(ctx, new(big.Int).SetUint64(blockInfo.Blk.ProposedIn)) + if err != nil { + return reorgCheckResult, err + } + } else { + return reorgCheckResult, err + } + } else { + l1HeaderToSet, err = s.rpc.L1.HeaderByNumber(ctx, l1Origin.L1BlockHeight) + if err != nil { + return reorgCheckResult, err + } + } + reorgCheckResult.IsReorged = retries > 0 + reorgCheckResult.L1CurrentToReset = l1HeaderToSet + reorgCheckResult.LastHandledBlockIDToReset = new(big.Int).SetUint64(currentBlockID) + } else { + reorgCheckResult, err = s.retrievePastBlock(ctx, blockID, retries+1) + if err != nil { + return reorgCheckResult, err + } + } + return reorgCheckResult, nil } // checkReorg checks whether the L1 chain has been reorged, and resets the L1Current cursor if necessary. @@ -545,37 +621,20 @@ func (s *Syncer) checkReorg( ctx context.Context, event *bindings.TaikoL1ClientBlockProposed, ) (*rpc.ReorgCheckResult, error) { - var ( - reorgCheckResult = new(rpc.ReorgCheckResult) - err error - ) - // If the L2 chain is at genesis, we don't need to check L1 reorg. if s.state.GetL1Current().Number == s.state.GenesisL1Height { - return reorgCheckResult, nil + return new(rpc.ReorgCheckResult), nil } // 1. The latest verified block - mismatch, err := s.checkLastVerifiedBlockMismatch(ctx) + reorgCheckResult, err := s.checkLastVerifiedBlockMismatch(ctx) if err != nil { return nil, fmt.Errorf("failed to check if last verified block in L2 EE has been reorged: %w", err) } // If the latest verified block in chain is mismatched, we reset the L2 chain to genesis, and restart // the calldata sync process. - // TODO(Gavin): improve this approach. - if mismatch { - log.Warn("The latest verified block mismatch detected, reset L2 chain to genesis") - - genesisL1Header, err := s.rpc.GetGenesisL1Header(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch genesis L1 header: %w", err) - } - - reorgCheckResult.IsReorged = true - reorgCheckResult.L1CurrentToReset = genesisL1Header - reorgCheckResult.LastHandledBlockIDToReset = common.Big0 - } else { + if !reorgCheckResult.IsReorged { // 2. Parent block reorgCheckResult, err = s.rpc.CheckL1Reorg( ctx, diff --git a/driver/chain_syncer/calldata/syncer_test.go b/driver/chain_syncer/calldata/syncer_test.go index c44421f90..90e9f239d 100644 --- a/driver/chain_syncer/calldata/syncer_test.go +++ b/driver/chain_syncer/calldata/syncer_test.go @@ -38,6 +38,7 @@ func (s *CalldataSyncerTestSuite) SetupTest() { s.RPCClient, state, beaconsync.NewSyncProgressTracker(s.RPCClient.L2, 1*time.Hour), + 0, ) s.Nil(err) s.s = syncer @@ -78,6 +79,7 @@ func (s *CalldataSyncerTestSuite) TestCancelNewSyncer() { s.RPCClient, s.s.state, s.s.progressTracker, + 0, ) s.Nil(syncer) s.NotNil(err) diff --git a/driver/chain_syncer/chain_syncer.go b/driver/chain_syncer/chain_syncer.go index f9d4f1ac8..903175f6b 100644 --- a/driver/chain_syncer/chain_syncer.go +++ b/driver/chain_syncer/chain_syncer.go @@ -41,12 +41,13 @@ func New( state *state.State, p2pSyncVerifiedBlocks bool, p2pSyncTimeout time.Duration, + maxRetrieveExponent uint64, ) (*L2ChainSyncer, error) { tracker := beaconsync.NewSyncProgressTracker(rpc.L2, p2pSyncTimeout) go tracker.Track(ctx) beaconSyncer := beaconsync.NewSyncer(ctx, rpc, state, tracker) - calldataSyncer, err := calldata.NewSyncer(ctx, rpc, state, tracker) + calldataSyncer, err := calldata.NewSyncer(ctx, rpc, state, tracker, maxRetrieveExponent) if err != nil { return nil, err } diff --git a/driver/chain_syncer/chain_syncer_test.go b/driver/chain_syncer/chain_syncer_test.go index 96e8a4050..544bbfb49 100644 --- a/driver/chain_syncer/chain_syncer_test.go +++ b/driver/chain_syncer/chain_syncer_test.go @@ -39,6 +39,7 @@ func (s *ChainSyncerTestSuite) SetupTest() { state, false, 1*time.Hour, + 0, ) s.Nil(err) s.s = syncer diff --git a/driver/config.go b/driver/config.go index f61557bc1..a1d906365 100644 --- a/driver/config.go +++ b/driver/config.go @@ -20,6 +20,7 @@ type Config struct { P2PSyncTimeout time.Duration RPCTimeout time.Duration RetryInterval time.Duration + MaxExponent uint64 } // NewConfigFromCliContext creates a new config instance from @@ -60,5 +61,6 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { P2PSyncVerifiedBlocks: p2pSyncVerifiedBlocks, P2PSyncTimeout: c.Duration(flags.P2PSyncTimeout.Name), RPCTimeout: timeout, + MaxExponent: c.Uint64(flags.MaxExponent.Name), }, nil } diff --git a/driver/driver.go b/driver/driver.go index 36b1d1ca8..3abedc0be 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -81,6 +81,7 @@ func (d *Driver) InitFromConfig(ctx context.Context, cfg *Config) (err error) { d.state, cfg.P2PSyncVerifiedBlocks, cfg.P2PSyncTimeout, + cfg.MaxExponent, ); err != nil { return err } diff --git a/pkg/rpc/methods.go b/pkg/rpc/methods.go index 947b7bfa9..9cbee877e 100644 --- a/pkg/rpc/methods.go +++ b/pkg/rpc/methods.go @@ -406,11 +406,11 @@ type ReorgCheckResult struct { // CheckL1Reorg checks whether the L2 block's corresponding L1 block has been reorged or not. // We will skip the reorg check if: // 1. When the L2 chain has just finished a P2P sync, so there is no L1Origin information recorded in -// its local database, and we assume the last verified L2 block is old enough, so its coreesponding -// L1 block should has also been finalized. +// its local database, and we assume the last verified L2 block is old enough, so its corresponding +// L1 block should have also been finalized. // // Then we will check: -// 1. If the L2 block's coreesponding L1 block which in L1Origin has been reorged +// 1. If the L2 block's corresponding L1 block which in L1Origin has been reorged // 2. If the L1 information which in the given L2 block's anchor transaction has been reorged // // And if a reorg is detected, we return a new L1 block cursor which need to reset to. @@ -447,13 +447,6 @@ func (c *Client) CheckL1Reorg(ctx context.Context, blockID *big.Int) (*ReorgChec // its local database, we skip this check. if err.Error() == ethereum.NotFound.Error() { log.Info("L1Origin not found, the L2 execution engine has just synced from P2P network", "blockID", blockID) - l1Header, err := c.L1.HeaderByNumber(ctxWithTimeout, l1Origin.L1BlockHeight) - if err != nil { - return nil, err - } - // If we rollback to that just P2P synced block, we reset the L1 cursor to the L1 block which in that L1Origin. - result.L1CurrentToReset = l1Header - result.LastHandledBlockIDToReset = l1Origin.BlockID return result, nil } diff --git a/prover/proof_submitter/proof_submitter_test.go b/prover/proof_submitter/proof_submitter_test.go index cc168efed..5a0da5163 100644 --- a/prover/proof_submitter/proof_submitter_test.go +++ b/prover/proof_submitter/proof_submitter_test.go @@ -76,6 +76,7 @@ func (s *ProofSubmitterTestSuite) SetupTest() { s.RPCClient, testState, tracker, + 0, ) s.Nil(err) From cc30c1d7eb71d68b3f43bc23679f57756ecdc500 Mon Sep 17 00:00:00 2001 From: Gavin Yu Date: Tue, 19 Mar 2024 17:02:38 +0800 Subject: [PATCH 02/13] Update driver/chain_syncer/calldata/syncer.go Co-authored-by: maskpp --- driver/chain_syncer/calldata/syncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index a350f9b37..5fb37ac75 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -566,7 +566,7 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries l1HeaderToSet *types.Header ) - if float64(blockID) > math.Pow(2, float64(retries)) { + if val := uint64(1 << retries); blockID > val { currentBlockID = uint64(float64(blockID)-math.Pow(2, float64(retries))) + 1 } else { currentBlockID = 0 From bf9dbc4663a2e31aeade2dcbd9d9af4ad6d96dd0 Mon Sep 17 00:00:00 2001 From: Gavin Yu Date: Tue, 19 Mar 2024 17:02:58 +0800 Subject: [PATCH 03/13] Update driver/chain_syncer/calldata/syncer.go Co-authored-by: maskpp --- driver/chain_syncer/calldata/syncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index 5fb37ac75..73fafec1b 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -567,7 +567,7 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries ) if val := uint64(1 << retries); blockID > val { - currentBlockID = uint64(float64(blockID)-math.Pow(2, float64(retries))) + 1 + currentBlockID = blockID - val + 1 } else { currentBlockID = 0 } From a1902238d001ceb3a5b0551a7aec3bc7a5c3c116 Mon Sep 17 00:00:00 2001 From: gavin Date: Tue, 19 Mar 2024 17:35:16 +0800 Subject: [PATCH 04/13] remove import and refactor judgement --- driver/chain_syncer/calldata/syncer.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index 73fafec1b..7d48c134a 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "math" "math/big" "time" @@ -582,6 +581,10 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries return reorgCheckResult, err } if blockInfo.Ts.BlockHash == l2Header.Hash() { + // To reduce the number of call contracts by bringing forward the termination condition judgement + if retries == 0 { + return reorgCheckResult, nil + } l1Origin, err := s.rpc.L2.L1OriginByID(ctx, new(big.Int).SetUint64(currentBlockID)) if err != nil { if err.Error() == ethereum.NotFound.Error() { From 64ee33c279b860a81d705aaf708ce369e627849f Mon Sep 17 00:00:00 2001 From: Gavin Yu Date: Wed, 20 Mar 2024 14:55:58 +0800 Subject: [PATCH 05/13] Update driver/chain_syncer/calldata/syncer.go Co-authored-by: David --- driver/chain_syncer/calldata/syncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index 7d48c134a..f59f62119 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -589,7 +589,7 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries if err != nil { if err.Error() == ethereum.NotFound.Error() { log.Info("L1Origin not found in retrievePastBlock because the L2 EE is just synced through P2P", "blockID", blockID) - // cant find l1Origin in L2 EE, so we call contract to get block info + // Can't find l1Origin in L2 EE, so we call the contract to get block info blockInfo, err := s.rpc.TaikoL1.GetBlock(&bind.CallOpts{Context: ctx}, currentBlockID) if err != nil { return reorgCheckResult, err From 3ae1d24587ed8d1c12b39d14f7b87a0c5ff90e70 Mon Sep 17 00:00:00 2001 From: Gavin Yu Date: Wed, 20 Mar 2024 15:02:13 +0800 Subject: [PATCH 06/13] Update driver/chain_syncer/calldata/syncer.go Co-authored-by: David --- driver/chain_syncer/calldata/syncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index f59f62119..e945d7d82 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -529,7 +529,7 @@ func (s *Syncer) checkLastVerifiedBlockMismatch(ctx context.Context) (*rpc.Reorg stateVars, err := s.rpc.GetProtocolStateVariables(&bind.CallOpts{Context: ctx}) if err != nil { - return reorgCheckResult, err + return nil, err } if s.state.GetL2Head().Number.Uint64() < stateVars.B.LastVerifiedBlockId { From 419e1d8d8319ffd20af69c7f40766e7e37e53b04 Mon Sep 17 00:00:00 2001 From: Gavin Yu Date: Wed, 20 Mar 2024 15:02:23 +0800 Subject: [PATCH 07/13] Update driver/chain_syncer/calldata/syncer.go Co-authored-by: David --- driver/chain_syncer/calldata/syncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index e945d7d82..bd105da8a 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -538,7 +538,7 @@ func (s *Syncer) checkLastVerifiedBlockMismatch(ctx context.Context) (*rpc.Reorg reorgCheckResult, err = s.retrievePastBlock(ctx, stateVars.B.LastVerifiedBlockId, 0) if err != nil { - return reorgCheckResult, err + return nil, err } return reorgCheckResult, nil From 27122b2432d15f39283d0cc418bc446197bf10a3 Mon Sep 17 00:00:00 2001 From: Gavin Yu Date: Wed, 20 Mar 2024 15:02:38 +0800 Subject: [PATCH 08/13] Update driver/chain_syncer/calldata/syncer.go Co-authored-by: David --- driver/chain_syncer/calldata/syncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index bd105da8a..ce135cbf9 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -578,7 +578,7 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries l2Header, err := s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(currentBlockID)) if err != nil { - return reorgCheckResult, err + return nil, err } if blockInfo.Ts.BlockHash == l2Header.Hash() { // To reduce the number of call contracts by bringing forward the termination condition judgement From 1e5e6c9216eafd293a5abfcdfab214495655a773 Mon Sep 17 00:00:00 2001 From: Gavin Yu Date: Wed, 20 Mar 2024 15:03:50 +0800 Subject: [PATCH 09/13] Update driver/chain_syncer/calldata/syncer.go Co-authored-by: David --- driver/chain_syncer/calldata/syncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index ce135cbf9..0d32eb3ca 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -573,7 +573,7 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries blockInfo, err := s.rpc.GetL2BlockInfo(ctx, new(big.Int).SetUint64(currentBlockID)) if err != nil { - return reorgCheckResult, err + return nil, err } l2Header, err := s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(currentBlockID)) From 4ede2c9dfdda6ad03011314d979f533897d9e9b9 Mon Sep 17 00:00:00 2001 From: gavin Date: Wed, 20 Mar 2024 15:36:56 +0800 Subject: [PATCH 10/13] fix it as suggested --- cmd/flags/driver.go | 1 + driver/chain_syncer/calldata/syncer.go | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/flags/driver.go b/cmd/flags/driver.go index 19216c6b9..bfbfdb6a3 100644 --- a/cmd/flags/driver.go +++ b/cmd/flags/driver.go @@ -62,4 +62,5 @@ var DriverFlags = MergeFlags(CommonFlags, []cli.Flag{ P2PSyncVerifiedBlocks, P2PSyncTimeout, CheckPointSyncURL, + MaxExponent, }) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index bbf3116a3..9eaed8a8d 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -640,9 +640,7 @@ func (s *Syncer) checkReorg( return nil, fmt.Errorf("failed to check if last verified block in L2 EE has been reorged: %w", err) } - // If the latest verified block in chain is mismatched, we reset the L2 chain to genesis, and restart - // the calldata sync process. - if !reorgCheckResult.IsReorged { + if reorgCheckResult == nil { // 2. Parent block reorgCheckResult, err = s.rpc.CheckL1Reorg( ctx, From 02e971fb9b84a1a67cd07388c631385b76e84c21 Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 21 Mar 2024 15:38:46 +0800 Subject: [PATCH 11/13] return nil when no reorg --- driver/chain_syncer/calldata/syncer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index 9eaed8a8d..aeca534e0 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -588,7 +588,7 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries if blockInfo.Ts.BlockHash == l2Header.Hash() { // To reduce the number of call contracts by bringing forward the termination condition judgement if retries == 0 { - return reorgCheckResult, nil + return nil, nil } l1Origin, err := s.rpc.L2.L1OriginByID(ctx, new(big.Int).SetUint64(currentBlockID)) if err != nil { From 8b3a7c4e87f465eb1b4720a97127d91fa89a3f9b Mon Sep 17 00:00:00 2001 From: gavin Date: Fri, 22 Mar 2024 19:11:30 +0800 Subject: [PATCH 12/13] test: add test case --- driver/chain_syncer/calldata/syncer.go | 34 +++++++------ driver/chain_syncer/calldata/syncer_test.go | 39 +++++++++++++- internal/testutils/helper.go | 56 +++++++++++++++++++++ 3 files changed, 111 insertions(+), 18 deletions(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index dccbde10a..2bd67342d 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -541,7 +541,11 @@ func (s *Syncer) checkLastVerifiedBlockMismatch(ctx context.Context) (*rpc.Reorg return reorgCheckResult, nil } - reorgCheckResult, err = s.retrievePastBlock(ctx, stateVars.B.LastVerifiedBlockId, 0) + genesisL1Header, err := s.rpc.GetGenesisL1Header(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch genesis L1 header: %w", err) + } + reorgCheckResult, err = s.retrievePastBlock(ctx, stateVars.B.LastVerifiedBlockId, 0, genesisL1Header) if err != nil { return nil, err } @@ -550,12 +554,8 @@ func (s *Syncer) checkLastVerifiedBlockMismatch(ctx context.Context) (*rpc.Reorg } // retrievePastBlock find proper L1 header and L2 block id to reset when there is a mismatch -func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries uint64) (*rpc.ReorgCheckResult, error) { +func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries uint64, genesisL1Header *types.Header) (*rpc.ReorgCheckResult, error) { if retries > s.maxRetrieveExponent { - genesisL1Header, err := s.rpc.GetGenesisL1Header(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch genesis L1 header: %w", err) - } return &rpc.ReorgCheckResult{ IsReorged: true, L1CurrentToReset: genesisL1Header, @@ -567,7 +567,7 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries reorgCheckResult = new(rpc.ReorgCheckResult) err error currentBlockID uint64 - l1HeaderToSet *types.Header + l1HeaderToSet = genesisL1Header ) if val := uint64(1 << retries); blockID > val { @@ -593,32 +593,34 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries l1Origin, err := s.rpc.L2.L1OriginByID(ctx, new(big.Int).SetUint64(currentBlockID)) if err != nil { if err.Error() == ethereum.NotFound.Error() { - log.Info("L1Origin not found in retrievePastBlock because the L2 EE is just synced through P2P", "blockID", blockID) + log.Info("L1Origin not found in retrievePastBlock because the L2 EE is just synced through P2P", "blockID", currentBlockID) // Can't find l1Origin in L2 EE, so we call the contract to get block info blockInfo, err := s.rpc.TaikoL1.GetBlock(&bind.CallOpts{Context: ctx}, currentBlockID) if err != nil { - return reorgCheckResult, err + return nil, err } - l1HeaderToSet, err = s.rpc.L1.HeaderByNumber(ctx, new(big.Int).SetUint64(blockInfo.Blk.ProposedIn)) - if err != nil { - return reorgCheckResult, err + if blockInfo.Blk.ProposedIn != 0 { + l1HeaderToSet, err = s.rpc.L1.HeaderByNumber(ctx, new(big.Int).SetUint64(blockInfo.Blk.ProposedIn)) + if err != nil { + return nil, err + } } } else { - return reorgCheckResult, err + return nil, err } } else { l1HeaderToSet, err = s.rpc.L1.HeaderByNumber(ctx, l1Origin.L1BlockHeight) if err != nil { - return reorgCheckResult, err + return nil, err } } reorgCheckResult.IsReorged = retries > 0 reorgCheckResult.L1CurrentToReset = l1HeaderToSet reorgCheckResult.LastHandledBlockIDToReset = new(big.Int).SetUint64(currentBlockID) } else { - reorgCheckResult, err = s.retrievePastBlock(ctx, blockID, retries+1) + reorgCheckResult, err = s.retrievePastBlock(ctx, blockID, retries+1, genesisL1Header) if err != nil { - return reorgCheckResult, err + return nil, err } } return reorgCheckResult, nil diff --git a/driver/chain_syncer/calldata/syncer_test.go b/driver/chain_syncer/calldata/syncer_test.go index 90e9f239d..30de31d9c 100644 --- a/driver/chain_syncer/calldata/syncer_test.go +++ b/driver/chain_syncer/calldata/syncer_test.go @@ -30,13 +30,13 @@ type CalldataSyncerTestSuite struct { func (s *CalldataSyncerTestSuite) SetupTest() { s.ClientTestSuite.SetupTest() - state, err := state.New(context.Background(), s.RPCClient) + state2, err := state.New(context.Background(), s.RPCClient) s.Nil(err) syncer, err := NewSyncer( context.Background(), s.RPCClient, - state, + state2, beaconsync.NewSyncProgressTracker(s.RPCClient.L2, 1*time.Hour), 0, ) @@ -213,6 +213,41 @@ func (s *CalldataSyncerTestSuite) TestTreasuryIncome() { s.Zero(balanceAfter.Cmp(balance)) } +func (s *CalldataSyncerTestSuite) TestRetrievePastBlock() { + syncer, err := NewSyncer( + context.Background(), + s.RPCClient, + s.s.state, + s.s.progressTracker, + 5, + ) + s.Nil(err) + sender := s.p.GetSender() + + s.s = syncer + for i := 0; i < 10; i++ { + s.ProposeAndInsertValidBlock(s.p, s.s) + } + genesisL1Header, err := s.RPCClient.GetGenesisL1Header(context.Background()) + s.Nil(err) + l1Snapshot := s.SetL1Snapshot() + for i := 0; i < 5; i++ { + s.ProposeAndInsertValidBlock(s.p, s.s) + } + s.RevertL1Snapshot(l1Snapshot) + // Because of evm_revert operation, the nonce of the proposer need to be adjusted. + s.Nil(sender.SetNonce(nil, true)) + // Propose 5 blocks on another fork + for i := 0; i < 5; i++ { + s.ProposeInvalidTxListBytes(s.p) + } + reorgResult, err := s.s.retrievePastBlock(context.Background(), 12, 0, genesisL1Header) + s.Nil(err) + s.NotNil(reorgResult) + s.Equal(reorgResult.IsReorged, true) + s.GreaterOrEqual(reorgResult.L1CurrentToReset.Number.Uint64(), genesisL1Header.Number.Uint64()) +} + func TestCalldataSyncerTestSuite(t *testing.T) { suite.Run(t, new(CalldataSyncerTestSuite)) } diff --git a/internal/testutils/helper.go b/internal/testutils/helper.go index 535ae38e3..e8e11a49d 100644 --- a/internal/testutils/helper.go +++ b/internal/testutils/helper.go @@ -161,6 +161,62 @@ func (s *ClientTestSuite) ProposeAndInsertValidBlock( return event } +func (s *ClientTestSuite) ProposeValidBlock( + proposer Proposer, +) *bindings.TaikoL1ClientBlockProposed { + l1Head, err := s.RPCClient.L1.HeaderByNumber(context.Background(), nil) + s.Nil(err) + + l2Head, err := s.RPCClient.L2.HeaderByNumber(context.Background(), nil) + s.Nil(err) + + // Propose txs in L2 execution engine's mempool + sink := make(chan *bindings.TaikoL1ClientBlockProposed) + + sub, err := s.RPCClient.TaikoL1.WatchBlockProposed(nil, sink, nil, nil) + s.Nil(err) + defer func() { + sub.Unsubscribe() + close(sink) + }() + + baseFeeInfo, err := s.RPCClient.TaikoL2.GetBasefee(nil, l1Head.Number.Uint64()+1, uint32(l2Head.GasUsed)) + s.Nil(err) + + nonce, err := s.RPCClient.L2.PendingNonceAt(context.Background(), s.TestAddr) + s.Nil(err) + + tx := types.NewTransaction( + nonce, + common.BytesToAddress(RandomBytes(32)), + common.Big1, + 100000, + baseFeeInfo.Basefee, + []byte{}, + ) + signedTx, err := types.SignTx(tx, types.LatestSignerForChainID(s.RPCClient.L2.ChainID), s.TestAddrPrivKey) + s.Nil(err) + s.Nil(s.RPCClient.L2.SendTransaction(context.Background(), signedTx)) + + s.Nil(proposer.ProposeOp(context.Background())) + + event := <-sink + + _, isPending, err := s.RPCClient.L1.TransactionByHash(context.Background(), event.Raw.TxHash) + s.Nil(err) + s.False(isPending) + + receipt, err := s.RPCClient.L1.TransactionReceipt(context.Background(), event.Raw.TxHash) + s.Nil(err) + s.Equal(types.ReceiptStatusSuccessful, receipt.Status) + + newL1Head, err := s.RPCClient.L1.HeaderByNumber(context.Background(), nil) + s.Nil(err) + s.Greater(newL1Head.Number.Uint64(), l1Head.Number.Uint64()) + + return event +} + // NewTestProverServer starts a new prover server that has channel listeners to respond and react // to requests for capacity, which provers can call. func (s *ClientTestSuite) NewTestProverServer( From 392b5709d24a4a034299782b6183d6ed22270146 Mon Sep 17 00:00:00 2001 From: gavin Date: Fri, 22 Mar 2024 19:19:32 +0800 Subject: [PATCH 13/13] style: fix lint --- driver/chain_syncer/calldata/syncer.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index 2bd67342d..1ef4a888d 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -554,7 +554,11 @@ func (s *Syncer) checkLastVerifiedBlockMismatch(ctx context.Context) (*rpc.Reorg } // retrievePastBlock find proper L1 header and L2 block id to reset when there is a mismatch -func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries uint64, genesisL1Header *types.Header) (*rpc.ReorgCheckResult, error) { +func (s *Syncer) retrievePastBlock( + ctx context.Context, + blockID uint64, + retries uint64, + genesisL1Header *types.Header) (*rpc.ReorgCheckResult, error) { if retries > s.maxRetrieveExponent { return &rpc.ReorgCheckResult{ IsReorged: true, @@ -593,7 +597,9 @@ func (s *Syncer) retrievePastBlock(ctx context.Context, blockID uint64, retries l1Origin, err := s.rpc.L2.L1OriginByID(ctx, new(big.Int).SetUint64(currentBlockID)) if err != nil { if err.Error() == ethereum.NotFound.Error() { - log.Info("L1Origin not found in retrievePastBlock because the L2 EE is just synced through P2P", "blockID", currentBlockID) + log.Info("L1Origin not found in retrievePastBlock because the L2 EE is just synced through P2P", + "blockID", + currentBlockID) // Can't find l1Origin in L2 EE, so we call the contract to get block info blockInfo, err := s.rpc.TaikoL1.GetBlock(&bind.CallOpts{Context: ctx}, currentBlockID) if err != nil {