diff --git a/driver/chain_syncer/calldata/syncer.go b/driver/chain_syncer/calldata/syncer.go index d772c7b20..4ee80ec28 100644 --- a/driver/chain_syncer/calldata/syncer.go +++ b/driver/chain_syncer/calldata/syncer.go @@ -377,10 +377,12 @@ func (s *Syncer) insertNewHead( return nil, fmt.Errorf("failed to create execution payloads: %w", err) } - fc := &engine.ForkchoiceStateV1{HeadBlockHash: parent.Hash()} + fc := &engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash} + if err = s.fillForkchoiceState(ctx, event, fc); err != nil { + return nil, err + } // Update the fork choice - fc.HeadBlockHash = payload.BlockHash fcRes, err := s.rpc.L2Engine.ForkchoiceUpdate(ctx, fc, nil) if err != nil { return nil, err @@ -392,6 +394,37 @@ func (s *Syncer) insertNewHead( return payload, nil } +// fillForkchoiceState fills the forkchoice state with the finalized block hash and the safe block hash. +func (s *Syncer) fillForkchoiceState( + ctx context.Context, + event *bindings.TaikoL1ClientBlockProposed, + fc *engine.ForkchoiceStateV1, +) error { + // If the event is emitted from the genesis block, we don't need to fill the forkchoice state, + // should only happen when testing. + if event.Raw.BlockNumber == 0 { + return nil + } + + // Fetch the latest verified block's header from protocol. + variables, err := s.rpc.GetProtocolStateVariables( + &bind.CallOpts{Context: ctx, BlockNumber: new(big.Int).SetUint64(event.Raw.BlockNumber - 1)}, + ) + if err != nil { + return err + } + finalizeHeader, err := s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(variables.B.LastVerifiedBlockId)) + if err != nil { + return err + } + + // Fill the forkchoice state. + fc.FinalizedBlockHash = finalizeHeader.Hash() + fc.SafeBlockHash = finalizeHeader.ParentHash + + return nil +} + // createExecutionPayloads creates a new execution payloads through // Engine APIs. func (s *Syncer) createExecutionPayloads(