diff --git a/driver/anchor_tx_constructor/anchor_tx_constructor.go b/driver/anchor_tx_constructor/anchor_tx_constructor.go index 31990f5c6..309ec4641 100644 --- a/driver/anchor_tx_constructor/anchor_tx_constructor.go +++ b/driver/anchor_tx_constructor/anchor_tx_constructor.go @@ -153,3 +153,8 @@ func (c *AnchorTxConstructor) signTxPayload(hash []byte) ([]byte, error) { func (c *AnchorTxConstructor) GasLimit() uint64 { return anchorGasLimit } + +// SignalServiceAddress returns protocol's L1 singalService constant address. +func (c *AnchorTxConstructor) SignalServiceAddress() common.Address { + return c.signalServiceAddress +} diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index 0d34e8142..151435813 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -168,6 +168,7 @@ func (s *Syncer) onBlockProposed( reorged, l1CurrentToReset, lastInsertedBlockIDToReset, err = s.rpc.CheckL1ReorgFromL2EE( ctx, new(big.Int).Sub(event.BlockId, common.Big1), + s.anchorConstructor.SignalServiceAddress(), ) if err != nil { return fmt.Errorf("failed to check whether L1 chain has been reorged: %w", err) diff --git a/driver/driver_test.go b/driver/driver_test.go index b61560a15..85cd69589 100644 --- a/driver/driver_test.go +++ b/driver/driver_test.go @@ -143,7 +143,11 @@ func (s *DriverTestSuite) TestCheckL1ReorgToHigherFork() { s.Greater(l2Head2.Number.Uint64(), l2Head1.Number.Uint64()) s.Greater(l1Head2.Number.Uint64(), l1Head1.Number.Uint64()) - reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE(context.Background(), l2Head2.Number) + reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE( + context.Background(), + l2Head2.Number, + common.HexToAddress(os.Getenv("L1_SIGNAL_SERVICE_CONTRACT_ADDRESS")), + ) s.Nil(err) s.False(reorged) @@ -202,7 +206,11 @@ func (s *DriverTestSuite) TestCheckL1ReorgToLowerFork() { s.Greater(l2Head2.Number.Uint64(), l2Head1.Number.Uint64()) s.Greater(l1Head2.Number.Uint64(), l1Head1.Number.Uint64()) - reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE(context.Background(), l2Head2.Number) + reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE( + context.Background(), + l2Head2.Number, + common.HexToAddress(os.Getenv("L1_SIGNAL_SERVICE_CONTRACT_ADDRESS")), + ) s.Nil(err) s.False(reorged) @@ -258,7 +266,11 @@ func (s *DriverTestSuite) TestCheckL1ReorgToSameHeightFork() { s.Greater(l2Head2.Number.Uint64(), l2Head1.Number.Uint64()) s.Greater(l1Head2.Number.Uint64(), l1Head1.Number.Uint64()) - reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE(context.Background(), l2Head2.Number) + reorged, _, _, err := s.RPCClient.CheckL1ReorgFromL2EE( + context.Background(), + l2Head2.Number, + common.HexToAddress(os.Getenv("L1_SIGNAL_SERVICE_CONTRACT_ADDRESS")), + ) s.Nil(err) s.False(reorged) diff --git a/pkg/rpc/methods.go b/pkg/rpc/methods.go index a8ec44f09..60f504eb9 100644 --- a/pkg/rpc/methods.go +++ b/pkg/rpc/methods.go @@ -19,6 +19,8 @@ import ( "golang.org/x/sync/errgroup" "github.com/taikoxyz/taiko-client/bindings" + "github.com/taikoxyz/taiko-client/bindings" + "github.com/taikoxyz/taiko-client/bindings/encoding" ) var ( @@ -386,7 +388,11 @@ func (c *Client) GetStorageRoot( // CheckL1ReorgFromL2EE checks whether the L1 chain has been reorged from the L1Origin records in L2 EE, // if so, returns the l1Current cursor and L2 blockID that need to reset to. -func (c *Client) CheckL1ReorgFromL2EE(ctx context.Context, blockID *big.Int) (bool, *types.Header, *big.Int, error) { +func (c *Client) CheckL1ReorgFromL2EE( + ctx context.Context, + blockID *big.Int, + l1SignalService common.Address, +) (bool, *types.Header, *big.Int, error) { var ( reorged bool l1CurrentToReset *types.Header @@ -467,6 +473,20 @@ func (c *Client) CheckL1ReorgFromL2EE(ctx context.Context, blockID *big.Int) (bo continue } + isSyncedL1SnippetValid, err := c.CheckL1ReorgFromAnchor( + ctx, blockID, l1Origin.L1BlockHeight.Uint64(), l1SignalService, + ) + if err != nil { + return false, nil, nil, fmt.Errorf("failed to check L1 reorg from anchor transaction: %w", err) + } + + if !isSyncedL1SnippetValid { + log.Info("Reorg detected due to invalid L1 snippet", "blockID", blockID) + reorged = true + blockID = new(big.Int).Sub(blockID, common.Big1) + continue + } + l1CurrentToReset = l1Header blockIDToReset = l1Origin.BlockID break @@ -478,11 +498,123 @@ func (c *Client) CheckL1ReorgFromL2EE(ctx context.Context, blockID *big.Int) (bo "l1CurrentToResetNumber", l1CurrentToReset.Number, "l1CurrentToResetHash", l1CurrentToReset.Hash(), "blockIDToReset", blockIDToReset, - ) return reorged, l1CurrentToReset, blockIDToReset, nil } +func (c *Client) CheckL1ReorgFromAnchor( + ctx context.Context, + blockID *big.Int, + l1Height uint64, + l1SignalService common.Address, +) (bool, error) { + log.Info("Check L1 reorg from anchor", "blockID", blockID) + block, err := c.L2.BlockByNumber(ctx, blockID) + if err != nil { + return false, err + } + parent, err := c.L2.BlockByHash(ctx, block.ParentHash()) + if err != nil { + return false, err + } + + l1BlockHash, l1SignalRoot, l1HeightInAnchor, parentGasUsed, err := c.getSyncedL1SnippetFromAnchcor( + ctx, + block.Transactions()[0], + ) + if err != nil { + return false, err + } + + if l1HeightInAnchor != l1Height { + log.Info("Reorg detected due to L1 height mismatch", "blockID", blockID) + return true, nil + } + + if parentGasUsed != uint32(parent.GasUsed()) { + log.Info("Reorg detected due to parent gas used mismatch", "blockID", blockID) + return true, nil + } + + l1Header, err := c.L1.HeaderByHash(ctx, l1BlockHash) + if err != nil { + return false, err + } + + currentRoot, err := c.GetStorageRoot(ctx, c.L1GethClient, l1SignalService, l1Header.Number) + if err != nil { + return false, err + } + + if currentRoot != l1SignalRoot { + log.Info("Reorg detected due to L1 signal root mismatch", "blockID", blockID) + return true, nil + } + + return false, nil +} + +func (c *Client) getSyncedL1SnippetFromAnchcor( + ctx context.Context, + tx *types.Transaction, +) ( + l1BlockHash common.Hash, + l1SignalRoot common.Hash, + l1Height uint64, + parentGasUsed uint32, + err error, +) { + method, err := encoding.TaikoL2ABI.MethodById(tx.Data()) + if err != nil { + return common.Hash{}, common.Hash{}, 0, 0, err + } + + if method.Name != "anchor" { + return common.Hash{}, common.Hash{}, 0, 0, fmt.Errorf("invalid method name for anchor transaction: %s", method.Name) + } + + args := map[string]interface{}{} + + if err := method.Inputs.UnpackIntoMap(args, tx.Data()[4:]); err != nil { + return common.Hash{}, common.Hash{}, 0, 0, err + } + + l1BlockHash, ok := args["l1BlockHash"].([32]byte) + if !ok { + return common.Hash{}, + common.Hash{}, + 0, + 0, + fmt.Errorf("failed to parse l1BlockHash from anchor transaction calldata") + } + l1SignalRoot, ok = args["l1SignalRoot"].([32]byte) + if !ok { + return common.Hash{}, + common.Hash{}, + 0, + 0, + fmt.Errorf("failed to parse l1SignalRoot from anchor transaction calldata") + } + l1Height, ok = args["l1Height"].(uint64) + if !ok { + return common.Hash{}, + common.Hash{}, + 0, + 0, + fmt.Errorf("failed to parse l1Height from anchor transaction calldata") + } + parentGasUsed, ok = args["parentGasUsed"].(uint32) + if !ok { + return common.Hash{}, + common.Hash{}, + 0, + 0, + fmt.Errorf("failed to parse parentGasUsed from anchor transaction calldata") + } + + return l1BlockHash, l1SignalRoot, l1Height, parentGasUsed, nil +} + // CheckL1ReorgFromL1Cursor checks whether the L1 chain has been reorged from the given l1Current cursor, // if so, returns the l1Current cursor that need to reset to. func (c *Client) CheckL1ReorgFromL1Cursor( diff --git a/prover/prover.go b/prover/prover.go index 07b2e98b0..b7a873d8b 100644 --- a/prover/prover.go +++ b/prover/prover.go @@ -64,6 +64,7 @@ type Prover struct { l1Current *types.Header reorgDetectedFlag bool tiers []*rpc.TierProviderTierWithID + l1SingalService common.Address // Proof submitters proofSubmitters []proofSubmitter.Submitter @@ -132,6 +133,15 @@ func InitFromConfig(ctx context.Context, p *Prover, cfg *Config) (err error) { log.Info("Protocol configs", "configs", p.protocolConfigs) + p.l1SingalService, err = p.rpc.TaikoL1.Resolve0( + &bind.CallOpts{Context: ctx}, + rpc.StringToBytes32("signal_service"), + false, + ) + if err != nil { + return fmt.Errorf("failed to resolve L1 signal service address: %w", err) + } + p.proverAddress = crypto.PubkeyToAddress(p.cfg.L1ProverPrivKey.PublicKey) chBufferSize := p.protocolConfigs.BlockMaxProposals @@ -551,6 +561,7 @@ func (p *Prover) onBlockProposed( reorged, l1CurrentToReset, lastHandledBlockIDToReset, err := p.rpc.CheckL1ReorgFromL2EE( ctx, new(big.Int).Sub(event.BlockId, common.Big1), + p.l1SingalService, ) if err != nil { return fmt.Errorf("failed to check whether L1 chain was reorged from L2EE (eventID %d): %w", event.BlockId, err)