From b6c6f821fe600229c84a0276029541deec522098 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 May 2023 01:12:28 +0800 Subject: [PATCH 1/4] feat: update some logic and add more tests based on #216 --- driver/chain_syncer/calldata/syncer.go | 108 +++++--------------- driver/chain_syncer/calldata/syncer_test.go | 64 ++++++++++++ integration_test/nodes/docker-compose.yml | 2 +- 3 files changed, 90 insertions(+), 84 deletions(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index 799a8a025..21fda009e 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -254,10 +254,12 @@ func (s *Syncer) handleReorg(ctx context.Context, event *bindings.TaikoL1ClientB ) // rewind chain by 1 until we find a block that is still in the chain - var lastKnownGoodBlockId *big.Int - var blockId *big.Int = s.lastInsertedBlockID - var block *types.Block - var err error + var ( + lastKnownGoodBlockId *big.Int + blockId *big.Int = s.lastInsertedBlockID + block *types.Block + err error + ) stateVars, err := s.rpc.GetProtocolStateVariables(nil) if err != nil { @@ -265,13 +267,15 @@ func (s *Syncer) handleReorg(ctx context.Context, event *bindings.TaikoL1ClientB } for { - if blockId.Cmp(big.NewInt(0)) == 0 { - lastKnownGoodBlockId = new(big.Int).SetUint64(0) + if blockId == nil && blockId.Cmp(common.Big0) == 0 { + if block, err = s.rpc.L2.BlockByNumber(ctx, common.Big0); err != nil { + return err + } + lastKnownGoodBlockId = common.Big0 break } - block, err = s.rpc.L2.BlockByNumber(ctx, blockId) - if err != nil && !errors.Is(err, ethereum.NotFound) { + if block, err = s.rpc.L2.BlockByNumber(ctx, blockId); err != nil && !errors.Is(err, ethereum.NotFound) { return err } @@ -281,7 +285,7 @@ func (s *Syncer) handleReorg(ctx context.Context, event *bindings.TaikoL1ClientB break } else { // otherwise, sub 1 from blockId and try again - blockId = new(big.Int).Sub(s.lastInsertedBlockID, big.NewInt(1)) + blockId = new(big.Int).Sub(blockId, common.Big1) } } @@ -296,91 +300,29 @@ func (s *Syncer) handleReorg(ctx context.Context, event *bindings.TaikoL1ClientB "blockID", lastKnownGoodBlockId, ) - var parentBlockInfo *ParentBlockInfo - - if lastKnownGoodBlockId.Cmp(common.Big0) == 0 { - parentBlockInfo = &ParentBlockInfo{ - Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), - Number: big.NewInt(0), - GasUsed: 0, - } - } else { - parent, err := s.rpc.L2ParentByBlockId(ctx, lastKnownGoodBlockId) - if err != nil { - return fmt.Errorf("error getting l2 parent by block id: %w", err) - } - - parentBlockInfo = &ParentBlockInfo{ - Hash: parent.Hash(), - Number: parent.Number, - GasUsed: parent.GasUsed, - } - } - - // reset l1 current to when the last known good block was inserted, and return the event. - blockProposedEvent, _, err := s.state.ResetL1Current(ctx, &state.HeightOrID{Height: block.Number()}) + fcRes, err := s.rpc.L2Engine.ForkchoiceUpdate(ctx, &engine.ForkchoiceStateV1{HeadBlockHash: block.Hash()}, nil) if err != nil { - return fmt.Errorf("failed to reset l1 current: %w", err) - } - - tx, err := s.rpc.L1.TransactionInBlock( - ctx, - block.Hash(), - blockProposedEvent.Raw.TxIndex, - ) - if err != nil { - return fmt.Errorf("failed to fetch original TaikoL1.proposeBlock transaction: %w", err) - } - - txListBytes, hint, _, err := s.txListValidator.ValidateTxList(block.Number(), tx.Data()) - if err != nil { - return fmt.Errorf("failed to validate transactions list: %w", err) - } - - if hint != txListValidator.HintOK { - log.Info("Invalid transactions list, insert an empty L2 block instead", "blockID", block.NumberU64()) - txListBytes = []byte{} - } - - l1Origin := &rawdb.L1Origin{ - BlockID: block.Number(), - L2BlockHash: common.Hash{}, // Will be set by taiko-geth. - L1BlockHeight: new(big.Int).SetUint64(blockProposedEvent.Raw.BlockNumber), - L1BlockHash: blockProposedEvent.Raw.BlockHash, + return err } - - payloadData, rpcError, payloadError := s.insertNewHead( - ctx, - blockProposedEvent, - parentBlockInfo, - s.state.GetHeadBlockID(), - txListBytes, - l1Origin, - ) - - if rpcError != nil { - return fmt.Errorf("failed to insert new head to L2 execution engine: %w", rpcError) + if fcRes.PayloadStatus.Status != engine.VALID { + return fmt.Errorf("unexpected ForkchoiceUpdate response status: %s", fcRes.PayloadStatus.Status) } - if payloadError != nil { - log.Warn( - "Ignore invalid block context", "blockID", event.Id, "payloadError", payloadError, "payloadData", payloadData, - ) - return nil + // reset l1 current to when the last known good block was inserted, and return the event. + if _, _, err := s.state.ResetL1Current(ctx, &state.HeightOrID{ID: lastKnownGoodBlockId}); err != nil { + return fmt.Errorf("failed to reset L1 current: %w", err) } - log.Debug("Payload data", "hash", payloadData.BlockHash, "txs", len(payloadData.Transactions)) - log.Info( "🔗 Rewound chain and inserted last known good block as new head", "blockID", event.Id, - "height", payloadData.Number, - "hash", payloadData.BlockHash, + "height", block.Number(), + "hash", block.Hash(), "latestVerifiedBlockHeight", s.state.GetLatestVerifiedBlock().Height, "latestVerifiedBlockHash", s.state.GetLatestVerifiedBlock().Hash, - "transactions", len(payloadData.Transactions), - "baseFee", payloadData.BaseFeePerGas, - "withdrawals", len(payloadData.Withdrawals), + "transactions", len(block.Transactions()), + "baseFee", block.BaseFee(), + "withdrawals", len(block.Withdrawals()), ) metrics.DriverL1CurrentHeightGauge.Update(int64(event.Raw.BlockNumber)) diff --git a/driver/chain_syncer/calldata/syncer_test.go b/driver/chain_syncer/calldata/syncer_test.go index daf007bfd..530889f78 100644 --- a/driver/chain_syncer/calldata/syncer_test.go +++ b/driver/chain_syncer/calldata/syncer_test.go @@ -9,16 +9,20 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/suite" "github.com/taikoxyz/taiko-client/bindings" "github.com/taikoxyz/taiko-client/driver/chain_syncer/beaconsync" "github.com/taikoxyz/taiko-client/driver/state" + "github.com/taikoxyz/taiko-client/proposer" "github.com/taikoxyz/taiko-client/testutils" ) type CalldataSyncerTestSuite struct { testutils.ClientTestSuite s *Syncer + p testutils.Proposer } func (s *CalldataSyncerTestSuite) SetupTest() { @@ -36,6 +40,22 @@ func (s *CalldataSyncerTestSuite) SetupTest() { ) s.Nil(err) s.s = syncer + + prop := new(proposer.Proposer) + l1ProposerPrivKey, err := crypto.ToECDSA(common.Hex2Bytes(os.Getenv("L1_PROPOSER_PRIVATE_KEY"))) + s.Nil(err) + proposeInterval := 1024 * time.Hour // No need to periodically propose transactions list in unit tests + s.Nil(proposer.InitFromConfig(context.Background(), prop, (&proposer.Config{ + L1Endpoint: os.Getenv("L1_NODE_WS_ENDPOINT"), + L2Endpoint: os.Getenv("L2_EXECUTION_ENGINE_WS_ENDPOINT"), + TaikoL1Address: common.HexToAddress(os.Getenv("TAIKO_L1_ADDRESS")), + TaikoL2Address: common.HexToAddress(os.Getenv("TAIKO_L2_ADDRESS")), + L1ProposerPrivKey: l1ProposerPrivKey, + L2SuggestedFeeRecipient: common.HexToAddress(os.Getenv("L2_SUGGESTED_FEE_RECIPIENT")), + ProposeInterval: &proposeInterval, + }))) + + s.p = prop } func (s *CalldataSyncerTestSuite) TestProcessL1Blocks() { @@ -86,6 +106,50 @@ func (s *CalldataSyncerTestSuite) TestInsertNewHead() { s.Nil(payloadErr) } +func (s *CalldataSyncerTestSuite) TestHandleReorgToGenesis() { + testutils.ProposeAndInsertEmptyBlocks(&s.ClientTestSuite, s.p, s.s) + + l2Head1, err := s.s.rpc.L2.BlockByNumber(context.Background(), nil) + s.Nil(err) + s.Greater(l2Head1.NumberU64(), uint64(0)) + s.NotZero(s.s.lastInsertedBlockID.Uint64()) + s.s.lastInsertedBlockID = common.Big0 // let the chain reorg to genesis + + s.Nil(s.s.handleReorg(context.Background(), &bindings.TaikoL1ClientBlockProposed{ + Id: l2Head1.Number(), + Raw: types.Log{Removed: true}, + })) + + l2Head2, err := s.s.rpc.L2.BlockByNumber(context.Background(), nil) + s.Nil(err) + s.Equal(uint64(0), l2Head2.NumberU64()) +} + +func (s *CalldataSyncerTestSuite) TestHandleReorgToNoneGenesis() { + testutils.ProposeAndInsertEmptyBlocks(&s.ClientTestSuite, s.p, s.s) + + l2Head1, err := s.s.rpc.L2.BlockByNumber(context.Background(), nil) + s.Nil(err) + s.Greater(l2Head1.NumberU64(), uint64(0)) + s.NotZero(s.s.lastInsertedBlockID.Uint64()) + s.s.lastInsertedBlockID = common.Big1 // let the chain reorg to height 1 + + s.Nil(s.s.handleReorg(context.Background(), &bindings.TaikoL1ClientBlockProposed{ + Id: l2Head1.Number(), + Raw: types.Log{Removed: true}, + })) + + l2Head2, err := s.s.rpc.L2.BlockByNumber(context.Background(), nil) + s.Nil(err) + s.Equal(uint64(1), l2Head2.NumberU64()) + + testutils.ProposeAndInsertEmptyBlocks(&s.ClientTestSuite, s.p, s.s) + l2Head3, err := s.s.rpc.L2.BlockByNumber(context.Background(), nil) + s.Nil(err) + s.Greater(l2Head3.NumberU64(), l2Head2.NumberU64()) + s.Greater(s.s.lastInsertedBlockID.Uint64(), uint64(1)) +} + func TestCalldataSyncerTestSuite(t *testing.T) { suite.Run(t, new(CalldataSyncerTestSuite)) } diff --git a/integration_test/nodes/docker-compose.yml b/integration_test/nodes/docker-compose.yml index 64a816b99..04f852f4b 100644 --- a/integration_test/nodes/docker-compose.yml +++ b/integration_test/nodes/docker-compose.yml @@ -14,7 +14,7 @@ services: - "0.0.0.0" l2_execution_engine: - image: gcr.io/evmchain/taiko-geth:taiko + image: gcr.io/evmchain/taiko-geth:sha-f8be24a # TODO: change back to taiko tag restart: unless-stopped pull_policy: always volumes: From e006f617896368d77da60db28ed264a9ff64ba09 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 May 2023 01:31:21 +0800 Subject: [PATCH 2/4] change && to || --- 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 21fda009e..d8b4d8423 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -267,7 +267,7 @@ func (s *Syncer) handleReorg(ctx context.Context, event *bindings.TaikoL1ClientB } for { - if blockId == nil && blockId.Cmp(common.Big0) == 0 { + if blockId == nil || blockId.Cmp(common.Big0) == 0 { if block, err = s.rpc.L2.BlockByNumber(ctx, common.Big0); err != nil { return err } From ff7580ab4c461232938cad99ac5dc6b01d5c3514 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 May 2023 01:33:26 +0800 Subject: [PATCH 3/4] feat: init `blockId` with current L2 head --- driver/chain_syncer/calldata/syncer.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index d8b4d8423..fdcc8563c 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -256,11 +256,17 @@ func (s *Syncer) handleReorg(ctx context.Context, event *bindings.TaikoL1ClientB // rewind chain by 1 until we find a block that is still in the chain var ( lastKnownGoodBlockId *big.Int - blockId *big.Int = s.lastInsertedBlockID + blockId *big.Int block *types.Block err error ) + l2Head, err := s.rpc.L2.BlockByNumber(ctx, nil) + if err != nil { + return err + } + blockId = l2Head.Number() + stateVars, err := s.rpc.GetProtocolStateVariables(nil) if err != nil { return fmt.Errorf("failed to get state variables: %w", err) From a6d56132912a8e6aeeba08907db85ce915cb9a58 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 17 May 2023 01:34:47 +0800 Subject: [PATCH 4/4] feat: remove nil check --- 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 fdcc8563c..7b91e57ec 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -273,7 +273,7 @@ func (s *Syncer) handleReorg(ctx context.Context, event *bindings.TaikoL1ClientB } for { - if blockId == nil || blockId.Cmp(common.Big0) == 0 { + if blockId.Cmp(common.Big0) == 0 { if block, err = s.rpc.L2.BlockByNumber(ctx, common.Big0); err != nil { return err }