Skip to content
This repository has been archived by the owner on May 11, 2024. It is now read-only.

feat(pkg): improve/simplify reorg check logic #647

Merged
merged 17 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cmd/flags/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ var (
Usage: "HTTP RPC endpoint of another synced L2 execution engine node",
Category: driverCategory,
}
// syncer specific flag
MaxExponent = &cli.Uint64Flag{
davidtaikocha marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down
118 changes: 90 additions & 28 deletions driver/chain_syncer/calldata/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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"
Expand All @@ -39,6 +40,7 @@ type Syncer struct {
// Used by BlockInserter
lastInsertedBlockID *big.Int
reorgDetectedFlag bool
maxRetrieveExponent uint64
}

// NewSyncer creates a new syncer instance.
Expand All @@ -47,6 +49,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 {
Expand All @@ -69,6 +72,7 @@ func NewSyncer(
rpc.BlockMaxTxListBytes,
client.L2.ChainID,
),
maxRetrieveExponent: maxRetrieveExponent,
}, nil
}

Expand Down Expand Up @@ -517,65 +521,123 @@ 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
YoGhurt111 marked this conversation as resolved.
Show resolved Hide resolved
}

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
YoGhurt111 marked this conversation as resolved.
Show resolved Hide resolved
}

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) {
YoGhurt111 marked this conversation as resolved.
Show resolved Hide resolved
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 val := uint64(1 << retries); blockID > val {
currentBlockID = blockID - val + 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
YoGhurt111 marked this conversation as resolved.
Show resolved Hide resolved
}

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
YoGhurt111 marked this conversation as resolved.
Show resolved Hide resolved
}
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() {
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
YoGhurt111 marked this conversation as resolved.
Show resolved Hide resolved
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.
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,
Expand Down
2 changes: 2 additions & 0 deletions driver/chain_syncer/calldata/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -78,6 +79,7 @@ func (s *CalldataSyncerTestSuite) TestCancelNewSyncer() {
s.RPCClient,
s.s.state,
s.s.progressTracker,
0,
)
s.Nil(syncer)
s.NotNil(err)
Expand Down
3 changes: 2 additions & 1 deletion driver/chain_syncer/chain_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions driver/chain_syncer/chain_syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func (s *ChainSyncerTestSuite) SetupTest() {
state,
false,
1*time.Hour,
0,
)
s.Nil(err)
s.s = syncer
Expand Down
2 changes: 2 additions & 0 deletions driver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
13 changes: 3 additions & 10 deletions pkg/rpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions prover/proof_submitter/proof_submitter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func (s *ProofSubmitterTestSuite) SetupTest() {
s.RPCClient,
testState,
tracker,
0,
)
s.Nil(err)

Expand Down
Loading