Repository: taikoxyz/taiko-mono
Pull request #19195: feat(taiko-client): introduce `payloadQueue` for P2P gossip messages
Author: login: davidtaikocha
Labels: name: category.enhancement
name: area.client
Created: 2025-04-02T02:36:40Z | Merged: 2025-04-13T06:04:00Z



Diff:
diff --git a/packages/taiko-client/bindings/encoding/struct.go b/packages/taiko-client/bindings/encoding/struct.go
index b7a4255ad6d..a4cd3dc16e7 100644
--- a/packages/taiko-client/bindings/encoding/struct.go
+++ b/packages/taiko-client/bindings/encoding/struct.go
@@ -150,7 +150,8 @@ func TransitionContestedEventToV2(
 	}
 }
 
-// BlockVerifiedEventToV2 converts a *ontakeBindings.OntakeClients.TaikoL1ClientBlockVerified to *ontakeBindings.OntakeClients.TaikoL1ClientBlockVerifiedV2.
+// BlockVerifiedEventToV2 converts a *ontakeBindings.OntakeClients.TaikoL1ClientBlockVerified
+// to *ontakeBindings.OntakeClients.TaikoL1ClientBlockVerifiedV2.
 func BlockVerifiedEventToV2(e *ontakeBindings.TaikoL1ClientBlockVerified) *ontakeBindings.TaikoL1ClientBlockVerifiedV2 {
 	return &ontakeBindings.TaikoL1ClientBlockVerifiedV2{
 		BlockId:   e.BlockId,
diff --git a/packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/common.go b/packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/common.go
index 691d3066c46..4f11c9a0608 100644
--- a/packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/common.go
+++ b/packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/common.go
@@ -449,7 +449,6 @@ func assembleCreateExecutionPayloadMetaPacaya(
 	}, anchorTx, nil
 }
 
-// updateL1OriginForBatch updates the each block's L1 origin in the given batch.
 func updateL1OriginForBatch(
 	ctx context.Context,
 	rpc *rpc.Client,
@@ -466,26 +465,32 @@ func updateL1OriginForBatch(
 
 	for i := 0; i < len(meta.GetBlocks()); i++ {
 		g.Go(func() error {
-			blockID := new(big.Int).SetUint64(meta.GetLastBlockID() - uint64(i))
+			blockID := new(big.Int).SetUint64(meta.GetLastBlockID() - uint64(len(meta.GetBlocks())-1-i))
+
 			header, err := rpc.L2.HeaderByNumber(ctx, blockID)
 			if err != nil {
 				return fmt.Errorf("failed to get block by number %d: %w", blockID, err)
 			}
+
 			l1Origin := &rawdb.L1Origin{
 				BlockID:       blockID,
 				L2BlockHash:   header.Hash(),
 				L1BlockHeight: meta.GetRawBlockHeight(),
 				L1BlockHash:   meta.GetRawBlockHash(),
 			}
-			// Update the l1Origin and headL1Origin cursor for that preconfirmed block.
+
 			if _, err := rpc.L2Engine.UpdateL1Origin(ctx, l1Origin); err != nil {
 				return fmt.Errorf("failed to update L1 origin: %w", err)
 			}
+
+			// If this is the most recent block, update the HeadL1Origin.
 			if i == len(meta.GetBlocks())-1 {
+				log.Info("Update head L1 origin", "blockID", blockID, "l1Origin", l1Origin)
 				if _, err := rpc.L2Engine.SetHeadL1Origin(ctx, l1Origin.BlockID); err != nil {
 					return fmt.Errorf("failed to write head L1 origin: %w", err)
 				}
 			}
+
 			return nil
 		})
 	}
diff --git a/packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go b/packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go
index 9a0adfce7b6..a1df9ec03ef 100644
--- a/packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go
+++ b/packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go
@@ -195,15 +195,24 @@ func (i *BlocksInserterPacaya) InsertBlocks(
 
 		log.Debug("Payload data", "hash", lastPayloadData.BlockHash, "txs", len(lastPayloadData.Transactions))
 
+		// Wait till the corresponding L2 header to be existed in the L2 EE.
+		if _, err := i.rpc.WaitL2Header(ctx, new(big.Int).SetUint64(lastPayloadData.Number)); err != nil {
+			return fmt.Errorf("failed to wait for L2 header (%d): %w", lastPayloadData.Number, err)
+		}
+
 		log.Info(
 			"🔗 New L2 block inserted",
 			"blockID", lastPayloadData.Number,
 			"hash", lastPayloadData.BlockHash,
+			"coinbase", lastPayloadData.FeeRecipient.Hex(),
 			"transactions", len(lastPayloadData.Transactions),
 			"timestamp", lastPayloadData.Timestamp,
 			"baseFee", utils.WeiToGWei(lastPayloadData.BaseFeePerGas),
 			"withdrawals", len(lastPayloadData.Withdrawals),
 			"batchID", meta.GetBatchID(),
+			"gasLimit", lastPayloadData.GasLimit,
+			"gasUsed", lastPayloadData.GasUsed,
+			"parentHash", lastPayloadData.ParentHash,
 			"indexInBatch", j,
 		)
 
@@ -213,14 +222,31 @@ func (i *BlocksInserterPacaya) InsertBlocks(
 	return nil
 }
 
-// InsertPreconfBlockFromExecutionPayload inserts a preconf block from the given execution payload.
-func (i *BlocksInserterPacaya) InsertPreconfBlockFromExecutionPayload(
+// InsertPreconfBlocksFromExecutionPayloads inserts preconf blocks from the given execution payloads.
+func (i *BlocksInserterPacaya) InsertPreconfBlocksFromExecutionPayloads(
 	ctx context.Context,
-	executableData *eth.ExecutionPayload,
-) (*types.Header, error) {
+	executionPayloads []*eth.ExecutionPayload,
+) ([]*types.Header, error) {
 	i.mutex.Lock()
 	defer i.mutex.Unlock()
 
+	headers := make([]*types.Header, len(executionPayloads))
+	for j, executableData := range executionPayloads {
+		header, err := i.insertPreconfBlockFromExecutionPayload(ctx, executableData)
+		if err != nil {
+			return nil, fmt.Errorf("failed to insert preconf block: %w", err)
+		}
+		headers[j] = header
+	}
+
+	return headers, nil
+}
+
+// insertPreconfBlockFromExecutionPayload the inner method to insert a preconf block from the given execution payload.
+func (i *BlocksInserterPacaya) insertPreconfBlockFromExecutionPayload(
+	ctx context.Context,
+	executableData *eth.ExecutionPayload,
+) (*types.Header, error) {
 	// Ensure the preconfirmation block number is greater than the current head L1 origin block ID.
 	headL1Origin, err := i.rpc.L2.HeadL1Origin(ctx)
 	if err != nil && err.Error() != ethereum.NotFound.Error() {
@@ -231,7 +257,7 @@ func (i *BlocksInserterPacaya) InsertPreconfBlockFromExecutionPayload(
 	if headL1Origin != nil {
 		if uint64(executableData.BlockNumber) <= headL1Origin.BlockID.Uint64() {
 			return nil, fmt.Errorf(
-				"preconfirmation block number (%d) is less than or equal to the current head L1 origin block ID (%d)",
+				"preconfirmation block ID (%d) is less than or equal to the current head L1 origin block ID (%d)",
 				executableData.BlockNumber,
 				headL1Origin.BlockID,
 			)
@@ -242,6 +268,11 @@ func (i *BlocksInserterPacaya) InsertPreconfBlockFromExecutionPayload(
 		return nil, fmt.Errorf("no transactions data in the payload")
 	}
 
+	// Decompress the transactions list.
+	if executableData.Transactions[0], err = utils.DecompressPacaya(executableData.Transactions[0]); err != nil {
+		return nil, fmt.Errorf("failed to decompress transactions list bytes: %w", err)
+	}
+
 	var u256BaseFee = uint256.Int(executableData.BaseFeePerGas)
 	payload, err := createExecutionPayloadsAndSetHead(
 		ctx,
diff --git a/packages/taiko-client/driver/chain_syncer/chain_syncer.go b/packages/taiko-client/driver/chain_syncer/chain_syncer.go
index 36107313378..5a54c701aa4 100644
--- a/packages/taiko-client/driver/chain_syncer/chain_syncer.go
+++ b/packages/taiko-client/driver/chain_syncer/chain_syncer.go
@@ -47,13 +47,7 @@ func New(
 	go tracker.Track(ctx)
 
 	beaconSyncer := beaconsync.NewSyncer(ctx, rpc, state, tracker)
-	blobSyncer, err := blob.NewSyncer(
-		ctx,
-		rpc,
-		state,
-		tracker,
-		blobServerEndpoint,
-	)
+	blobSyncer, err := blob.NewSyncer(ctx, rpc, state, tracker, blobServerEndpoint)
 	if err != nil {
 		return nil, err
 	}
diff --git a/packages/taiko-client/driver/driver.go b/packages/taiko-client/driver/driver.go
index 15a5043198c..4f59af331b9 100644
--- a/packages/taiko-client/driver/driver.go
+++ b/packages/taiko-client/driver/driver.go
@@ -117,6 +117,7 @@ func (d *Driver) InitFromConfig(ctx context.Context, cfg *Config) (err error) {
 		if d.preconfBlockServer, err = preconfBlocks.New(
 			d.PreconfBlockServerCORSOrigins,
 			d.PreconfBlockServerJWTSecret,
+			d.TaikoL2Address,
 			d.l2ChainSyncer.BlobSyncer().BlocksInserterPacaya(),
 			d.rpc,
 		); err != nil {
diff --git a/packages/taiko-client/driver/driver_test.go b/packages/taiko-client/driver/driver_test.go
index 988456e9e82..cee466d2f01 100644
--- a/packages/taiko-client/driver/driver_test.go
+++ b/packages/taiko-client/driver/driver_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"math/big"
+	"math/rand"
 	"net/url"
 	"os"
 	"testing"
@@ -500,7 +501,7 @@ func (s *DriverTestSuite) TestInsertPreconfBlocksNotReorg() {
 	s.Nil(err)
 	s.True(res.IsSuccess())
 
-	// Try to insert one preconfirmation block
+	// Try to insert two preconfirmation blocks
 	s.True(s.insertPreconfBlock(s.preconfServerURL, l1Head1, l2Head2.Number.Uint64()+1).IsSuccess())
 	l2Head3, err := s.d.rpc.L2.BlockByNumber(context.Background(), nil)
 	s.Nil(err)
@@ -514,25 +515,32 @@ func (s *DriverTestSuite) TestInsertPreconfBlocksNotReorg() {
 	s.Equal(common.Hash{}, l1Origin.L1BlockHash)
 	s.True(l1Origin.IsPreconfBlock())
 
-	// Propose a same L2 block batch
-	s.proposePreconfBatch([]*types.Block{l2Head3}, []*types.Header{l1Head1})
-
+	s.True(s.insertPreconfBlock(s.preconfServerURL, l1Head1, l2Head2.Number.Uint64()+2).IsSuccess())
 	l2Head4, err := s.d.rpc.L2.BlockByNumber(context.Background(), nil)
 	s.Nil(err)
-	s.Equal(l2Head3.Number().Uint64(), l2Head4.Number().Uint64())
+	s.Equal(l2Head3.Number().Uint64()+1, l2Head4.Number().Uint64())
 	s.Equal(2, len(l2Head4.Transactions()))
 
-	l1Origin2, err := s.RPCClient.L2.L1OriginByID(context.Background(), new(big.Int).Add(l2Head2.Number, common.Big1))
+	// Propose two same L2 blocks in a batch
+	s.proposePreconfBatch([]*types.Block{l2Head3, l2Head4}, []*types.Header{l1Head1, l1Head1})
+
+	l2Head5, err := s.d.rpc.L2.BlockByNumber(context.Background(), nil)
 	s.Nil(err)
-	s.Equal(l2Head4.Number().Uint64(), l1Origin2.BlockID.Uint64())
+	s.Equal(l2Head4.Number().Uint64(), l2Head5.Number().Uint64())
+	s.Equal(2, len(l2Head5.Transactions()))
+
+	l1Origin2, err := s.RPCClient.L2.L1OriginByID(context.Background(), new(big.Int).Add(l2Head2.Number, common.Big2))
+	s.Nil(err)
+	s.Equal(l2Head5.Number().Uint64(), l1Origin2.BlockID.Uint64())
+	s.Equal(l2Head5.Hash(), l1Origin2.L2BlockHash)
 	s.Equal(l2Head4.Hash(), l1Origin2.L2BlockHash)
-	s.Equal(l2Head3.Hash(), l1Origin2.L2BlockHash)
 	s.NotEqual(common.Hash{}, l1Origin2.L1BlockHash)
 	s.False(l1Origin2.IsPreconfBlock())
 
 	canonicalL1Origin, err := s.RPCClient.L2.HeadL1Origin(context.Background())
 	s.Nil(err)
 	s.Equal(l1Origin2, canonicalL1Origin)
+	s.Equal(l2Head5.Number().Uint64(), canonicalL1Origin.BlockID.Uint64())
 }
 
 func (s *DriverTestSuite) TestOnUnsafeL2Payload() {
@@ -567,7 +575,6 @@ func (s *DriverTestSuite) TestOnUnsafeL2Payload() {
 	b, err := utils.EncodeAndCompressTxList(types.Transactions{anchorTx})
 	s.Nil(err)
 
-	// failed to decode txList: rlp: expected input list for types.Transactions
 	payload := &eth.ExecutionPayload{
 		ParentHash:    l2Head1.Hash(),
 		FeeRecipient:  s.TestAddr,
@@ -600,6 +607,195 @@ func (s *DriverTestSuite) TestOnUnsafeL2Payload() {
 	s.Equal(anchorTx.Hash(), l2Head2.Transactions()[0].Hash())
 }
 
+func (s *DriverTestSuite) TestOnUnsafeL2PayloadWithInvalidPayload() {
+	s.ForkIntoPacaya(s.p, s.d.ChainSyncer().BlobSyncer())
+	// Propose some valid L2 blocks
+	s.ProposeAndInsertEmptyBlocks(s.p, s.d.ChainSyncer().BlobSyncer())
+
+	l2Head1, err := s.d.rpc.L2.HeaderByNumber(context.Background(), nil)
+	s.Nil(err)
+
+	b, err := utils.Compress(testutils.RandomBytes(32))
+	s.Nil(err)
+
+	baseFee, overflow := uint256.FromBig(common.Big256)
+	s.False(overflow)
+
+	payload := &eth.ExecutionPayload{
+		ParentHash:    l2Head1.Hash(),
+		FeeRecipient:  s.TestAddr,
+		PrevRandao:    eth.Bytes32(testutils.RandomHash()),
+		BlockNumber:   eth.Uint64Quantity(l2Head1.Number.Uint64() + 1),
+		GasLimit:      eth.Uint64Quantity(l2Head1.GasLimit),
+		Timestamp:     eth.Uint64Quantity(time.Now().Unix()),
+		ExtraData:     l2Head1.Extra,
+		BaseFeePerGas: eth.Uint256Quantity(*baseFee),
+		Transactions:  []eth.Data{b},
+		Withdrawals:   &types.Withdrawals{},
+	}
+
+	s.Nil(s.d.preconfBlockServer.OnUnsafeL2Payload(
+		context.Background(),
+		peer.ID(testutils.RandomBytes(32)),
+		&eth.ExecutionPayloadEnvelope{ExecutionPayload: payload},
+	))
+
+	l2Head2, err := s.d.rpc.L2.BlockByNumber(context.Background(), nil)
+	s.Nil(err)
+	s.Equal(l2Head1.Number.Uint64(), l2Head2.Number().Uint64())
+	s.Equal(l2Head1.Hash(), l2Head2.Hash())
+}
+
+func (s *DriverTestSuite) TestOnUnsafeL2PayloadWithMissingAncients() {
+	s.ForkIntoPacaya(s.p, s.d.ChainSyncer().BlobSyncer())
+	// Propose some valid L2 blocks
+	s.ProposeAndInsertEmptyBlocks(s.p, s.d.ChainSyncer().BlobSyncer())
+
+	l2Head1, err := s.d.rpc.L2.HeaderByNumber(context.Background(), nil)
+	s.Nil(err)
+
+	headL1Origin, err := s.RPCClient.L2.HeadL1Origin(context.Background())
+	s.Nil(err)
+	s.Equal(l2Head1.Number.Uint64(), headL1Origin.BlockID.Uint64())
+
+	snapshotID := s.SetL1Snapshot()
+
+	for i := 0; i < rand.Intn(6)+5; i++ {
+		s.ProposeAndInsertEmptyBlocks(s.p, s.d.ChainSyncer().BlobSyncer())
+	}
+
+	l2Head2, err := s.d.rpc.L2.HeaderByNumber(context.Background(), nil)
+	s.Nil(err)
+	s.Greater(l2Head2.Number.Uint64(), l2Head1.Number.Uint64())
+
+	blocks := []*types.Block{}
+	for i := l2Head1.Number.Uint64() + 1; i <= l2Head2.Number.Uint64(); i++ {
+		block, err := s.RPCClient.L2.BlockByNumber(context.Background(), new(big.Int).SetUint64(i))
+		s.Nil(err)
+		blocks = append(blocks, block)
+	}
+	s.Equal(l2Head2.Number.Uint64()-l2Head1.Number.Uint64(), uint64(len(blocks)))
+
+	s.RevertL1Snapshot(snapshotID)
+	s.Nil(rpc.SetHead(context.Background(), s.RPCClient.L2, l2Head1.Number))
+	_, err = s.RPCClient.L2Engine.SetHeadL1Origin(context.Background(), headL1Origin.BlockID)
+	s.Nil(err)
+
+	headL1Origin, err = s.RPCClient.L2.HeadL1Origin(context.Background())
+	s.Nil(err)
+	s.Equal(l2Head1.Number.Uint64(), headL1Origin.BlockID.Uint64())
+
+	l2Head3, err := s.d.rpc.L2.HeaderByNumber(context.Background(), nil)
+	s.Nil(err)
+	s.Equal(l2Head1.Number.Uint64(), l2Head3.Number.Uint64())
+
+	// Randomly gossip preconfirmation messages with missing ancients
+	blockNums := rand.Perm(len(blocks))
+	for i := range blockNums {
+		blockNums[i] += int(l2Head1.Number.Uint64() + 1)
+	}
+
+	getBlock := func(blockNum uint64) *types.Block {
+		for _, b := range blocks {
+			if b.Number().Uint64() == blockNum {
+				return b
+			}
+		}
+		return nil
+	}
+
+	insertPayloadFromBlock := func(block *types.Block, gossipRandom bool) {
+		baseFee, overflow := uint256.FromBig(block.BaseFee())
+		s.False(overflow)
+
+		b, err := utils.EncodeAndCompressTxList(block.Transactions())
+		s.Nil(err)
+		s.GreaterOrEqual(len(block.Transactions()), 1)
+
+		s.Nil(s.d.preconfBlockServer.OnUnsafeL2Payload(
+			context.Background(),
+			peer.ID(testutils.RandomBytes(32)),
+			&eth.ExecutionPayloadEnvelope{ExecutionPayload: &eth.ExecutionPayload{
+				BlockHash:     block.Hash(),
+				ParentHash:    block.ParentHash(),
+				FeeRecipient:  block.Coinbase(),
+				PrevRandao:    eth.Bytes32(block.MixDigest()),
+				BlockNumber:   eth.Uint64Quantity(block.Number().Uint64()),
+				GasLimit:      eth.Uint64Quantity(block.GasLimit()),
+				Timestamp:     eth.Uint64Quantity(block.Time()),
+				ExtraData:     block.Extra(),
+				BaseFeePerGas: eth.Uint256Quantity(*baseFee),
+				Transactions:  []eth.Data{b},
+				Withdrawals:   &types.Withdrawals{},
+			}},
+		))
+
+		if gossipRandom {
+			// Also gossip some random blocks
+			s.Nil(s.d.preconfBlockServer.OnUnsafeL2Payload(
+				context.Background(),
+				peer.ID(testutils.RandomBytes(32)),
+				&eth.ExecutionPayloadEnvelope{ExecutionPayload: &eth.ExecutionPayload{
+					BlockHash:     common.BytesToHash(testutils.RandomBytes(32)),
+					ParentHash:    common.BytesToHash(testutils.RandomBytes(32)),
+					FeeRecipient:  block.Coinbase(),
+					PrevRandao:    eth.Bytes32(common.BytesToHash(testutils.RandomBytes(32))),
+					BlockNumber:   eth.Uint64Quantity(block.Number().Uint64()),
+					GasLimit:      eth.Uint64Quantity(block.GasLimit()),
+					Timestamp:     eth.Uint64Quantity(block.Time()),
+					ExtraData:     block.Extra(),
+					BaseFeePerGas: eth.Uint256Quantity(*baseFee),
+					Transactions:  []eth.Data{b},
+					Withdrawals:   &types.Withdrawals{},
+				}},
+			))
+
+			s.Nil(s.d.preconfBlockServer.OnUnsafeL2Payload(
+				context.Background(),
+				peer.ID(testutils.RandomBytes(32)),
+				&eth.ExecutionPayloadEnvelope{ExecutionPayload: &eth.ExecutionPayload{
+					BlockHash:     common.BytesToHash(testutils.RandomBytes(32)),
+					ParentHash:    block.ParentHash(),
+					FeeRecipient:  block.Coinbase(),
+					PrevRandao:    eth.Bytes32(common.BytesToHash(testutils.RandomBytes(32))),
+					BlockNumber:   eth.Uint64Quantity(block.Number().Uint64()),
+					GasLimit:      eth.Uint64Quantity(block.GasLimit()),
+					Timestamp:     eth.Uint64Quantity(block.Time()),
+					ExtraData:     block.Extra(),
+					BaseFeePerGas: eth.Uint256Quantity(*baseFee),
+					Transactions:  []eth.Data{b},
+					Withdrawals:   &types.Withdrawals{},
+				}},
+			))
+		}
+	}
+
+	// Insert all blocks except the first one
+	for _, blockNum := range blockNums {
+		if blockNum == int(l2Head1.Number.Uint64()+1) {
+			continue
+		}
+
+		block := getBlock(uint64(blockNum))
+		s.NotNil(block)
+
+		insertPayloadFromBlock(block, true)
+	}
+
+	l2Head4, err := s.d.rpc.L2.BlockByNumber(context.Background(), nil)
+	s.Nil(err)
+	s.Equal(l2Head1.Number.Uint64(), l2Head4.Number().Uint64())
+
+	// Insert the only missing ancient block
+	block := getBlock(l2Head1.Number.Uint64() + 1)
+	s.NotNil(block)
+	insertPayloadFromBlock(block, false)
+
+	l2Head5, err := s.d.rpc.L2.BlockByNumber(context.Background(), nil)
+	s.Nil(err)
+	s.Equal(l2Head2.Number.Uint64(), l2Head5.Number().Uint64())
+}
+
 func (s *DriverTestSuite) proposePreconfBatch(blocks []*types.Block, anchoredL1Blocks []*types.Header) {
 	var (
 		to          = &s.p.TaikoL1Address
diff --git a/packages/taiko-client/driver/preconf_blocks/api.go b/packages/taiko-client/driver/preconf_blocks/api.go
index fde80498494..ffe1cab3111 100644
--- a/packages/taiko-client/driver/preconf_blocks/api.go
+++ b/packages/taiko-client/driver/preconf_blocks/api.go
@@ -2,7 +2,6 @@ package preconfblocks
 
 import (
 	"errors"
-	"fmt"
 	"math/big"
 	"net/http"
 
@@ -77,26 +76,32 @@ func (s *PreconfBlockAPIServer) BuildPreconfBlock(c echo.Context) error {
 		"gasLimit", reqBody.ExecutableData.GasLimit,
 		"baseFeePerGas", utils.WeiToEther(new(big.Int).SetUint64(reqBody.ExecutableData.BaseFeePerGas)),
 		"extraData", common.Bytes2Hex(reqBody.ExecutableData.ExtraData),
+		"parentHash", reqBody.ExecutableData.ParentHash.Hex(),
 	)
 
-	if reqBody.ExecutableData.Timestamp == 0 {
-		return s.returnError(c, http.StatusBadRequest, errors.New("non-zero timestamp is required"))
-	}
-	if reqBody.ExecutableData.FeeRecipient == (common.Address{}) {
-		return s.returnError(c, http.StatusBadRequest, errors.New("empty L2 fee recipient"))
-	}
-	if reqBody.ExecutableData.GasLimit == 0 {
-		return s.returnError(c, http.StatusBadRequest, errors.New("non-zero gas limit is required"))
-	}
-	if reqBody.ExecutableData.BaseFeePerGas == 0 {
-		return s.returnError(c, http.StatusBadRequest, errors.New("non-zero base fee per gas is required"))
+	difficulty, err := encoding.CalculatePacayaDifficulty(new(big.Int).SetUint64(reqBody.ExecutableData.Number))
+	if err != nil {
+		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
 	}
 	baseFee, overflow := uint256.FromBig(new(big.Int).SetUint64(reqBody.ExecutableData.BaseFeePerGas))
 	if overflow {
 		return s.returnError(c, http.StatusBadRequest, errors.New("base fee per gas is too large"))
 	}
-	if len(reqBody.ExecutableData.ExtraData) == 0 {
-		return s.returnError(c, http.StatusBadRequest, errors.New("empty extra data"))
+
+	executablePayload := &eth.ExecutionPayload{
+		ParentHash:    reqBody.ExecutableData.ParentHash,
+		FeeRecipient:  reqBody.ExecutableData.FeeRecipient,
+		PrevRandao:    eth.Bytes32(difficulty[:]),
+		BlockNumber:   eth.Uint64Quantity(reqBody.ExecutableData.Number),
+		GasLimit:      eth.Uint64Quantity(reqBody.ExecutableData.GasLimit),
+		Timestamp:     eth.Uint64Quantity(reqBody.ExecutableData.Timestamp),
+		ExtraData:     eth.BytesMax32(reqBody.ExecutableData.ExtraData),
+		BaseFeePerGas: eth.Uint256Quantity(*baseFee),
+		Transactions:  []eth.Data{reqBody.ExecutableData.Transactions},
+	}
+
+	if err := s.ValidateExecutionPayload(executablePayload); err != nil {
+		return s.returnError(c, http.StatusBadRequest, err)
 	}
 
 	// Check if the L2 execution engine is syncing from L1.
@@ -108,35 +113,19 @@ func (s *PreconfBlockAPIServer) BuildPreconfBlock(c echo.Context) error {
 		return s.returnError(c, http.StatusBadRequest, errors.New("L2 execution engine is syncing"))
 	}
 
-	difficulty, err := encoding.CalculatePacayaDifficulty(new(big.Int).SetUint64(reqBody.ExecutableData.Number))
-	if err != nil {
-		return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
-	}
-
-	// Decompress the transactions list.
-	decompressed, err := utils.DecompressPacaya(reqBody.ExecutableData.Transactions)
-	if err != nil {
-		return fmt.Errorf("failed to decompress transactions list bytes: %w", err)
-	}
-
 	// Insert the preconf block.
-	header, err := s.chainSyncer.InsertPreconfBlockFromExecutionPayload(
+	headers, err := s.chainSyncer.InsertPreconfBlocksFromExecutionPayloads(
 		c.Request().Context(),
-		&eth.ExecutionPayload{
-			ParentHash:    reqBody.ExecutableData.ParentHash,
-			FeeRecipient:  reqBody.ExecutableData.FeeRecipient,
-			PrevRandao:    eth.Bytes32(difficulty[:]),
-			BlockNumber:   eth.Uint64Quantity(reqBody.ExecutableData.Number),
-			GasLimit:      eth.Uint64Quantity(reqBody.ExecutableData.GasLimit),
-			Timestamp:     eth.Uint64Quantity(reqBody.ExecutableData.Timestamp),
-			ExtraData:     eth.BytesMax32(reqBody.ExecutableData.ExtraData),
-			BaseFeePerGas: eth.Uint256Quantity(*baseFee),
-			Transactions:  []eth.Data{decompressed},
-		},
+		[]*eth.ExecutionPayload{executablePayload},
 	)
 	if err != nil {
 		return s.returnError(c, http.StatusInternalServerError, err)
 	}
+	if len(headers) == 0 {
+		return s.returnError(c, http.StatusInternalServerError, errors.New("no inserted header returned"))
+	}
+
+	header := headers[0]
 
 	log.Info(
 		"⏰ New preconfirmation L2 block inserted",
@@ -148,7 +137,8 @@ func (s *PreconfBlockAPIServer) BuildPreconfBlock(c echo.Context) error {
 		"gasUsed", header.GasUsed,
 		"mixDigest", common.Bytes2Hex(header.MixDigest[:]),
 		"extraData", common.Bytes2Hex(header.Extra),
-		"baseFee", utils.WeiToEther(header.BaseFee),
+		"baseFee", utils.WeiToGWei(header.BaseFee),
+		"parentHash", header.ParentHash.Hex(),
 	)
 
 	// Propagate the preconfirmation block to the P2P network, if the current server
diff --git a/packages/taiko-client/driver/preconf_blocks/queue.go b/packages/taiko-client/driver/preconf_blocks/queue.go
new file mode 100644
index 00000000000..279214c7101
--- /dev/null
+++ b/packages/taiko-client/driver/preconf_blocks/queue.go
@@ -0,0 +1,117 @@
+package preconfblocks
+
+import (
+	"slices"
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768 // equal to `maxBlocksPerBatch`
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
+	lock     sync.RWMutex
+}
+
+// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
+// all containing empty items.
+func newPayloadQueue() *payloadQueue {
+	return &payloadQueue{
+		payloads: make([]*payloadQueueItem, maxTrackedPayloads),
+	}
+}
+
+// put inserts a new payload into the queue at the given id.
+func (q *payloadQueue) put(id uint64, payload *eth.ExecutionPayload) {
+	q.lock.Lock()
+	defer q.lock.Unlock()
+
+	copy(q.payloads[1:], q.payloads)
+	q.payloads[0] = &payloadQueueItem{
+		id:      id,
+		payload: payload,
+	}
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) get(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id && item.payload.BlockHash == hash {
+			return item.payload
+		}
+	}
+	return nil
+}
+
+// getChildren retrieves the longest previously stored payload items that are children of the
+// given parent payload.
+func (q *payloadQueue) getChildren(parentID uint64, parentHash common.Hash) []*eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	longestChildren := []*eth.ExecutionPayload{}
+
+	var searchLongestChildren func(currentPayload *eth.ExecutionPayload, chain []*eth.ExecutionPayload)
+	searchLongestChildren = func(currentpayload *eth.ExecutionPayload, chain []*eth.ExecutionPayload) {
+		children := []*eth.ExecutionPayload{}
+		for _, item := range q.payloads {
+			if item == nil {
+				break // no more items
+			}
+			if item.id == uint64(currentpayload.BlockNumber)+1 && item.payload.ParentHash == currentpayload.BlockHash {
+				children = append(children, item.payload)
+			}
+		}
+		if len(children) == 0 {
+			if len(chain) > len(longestChildren) {
+				longestChildren = slices.Clone(chain)
+			}
+			return
+		}
+
+		for _, child := range children {
+			searchLongestChildren(child, append(chain, child))
+		}
+	}
+
+	searchLongestChildren(&eth.ExecutionPayload{
+		BlockNumber: eth.Uint64Quantity(parentID),
+		BlockHash:   parentHash,
+	}, []*eth.ExecutionPayload{})
+
+	return longestChildren
+}
+
+// has checks if a particular payload is already tracked.
+func (q *payloadQueue) has(id uint64, hash common.Hash) bool {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return false
+		}
+		if item.id == id && item.payload.BlockHash == hash {
+			return true
+		}
+	}
+	return false
+}
diff --git a/packages/taiko-client/driver/preconf_blocks/queue_test.go b/packages/taiko-client/driver/preconf_blocks/queue_test.go
new file mode 100644
index 00000000000..487a90a8f18
--- /dev/null
+++ b/packages/taiko-client/driver/preconf_blocks/queue_test.go
@@ -0,0 +1,73 @@
+package preconfblocks
+
+import (
+	"math/rand"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+
+	"github.com/taikoxyz/taiko-mono/packages/taiko-client/internal/testutils"
+)
+
+func (s *PreconfBlockAPIServerTestSuite) TestCacheGet() {
+	cache := newPayloadQueue()
+	s.Nil(cache.get(uint64(testutils.RandomPort()), testutils.RandomHash()))
+	s.False(cache.has(uint64(testutils.RandomPort()), testutils.RandomHash()))
+
+	payload := &eth.ExecutionPayload{
+		BlockNumber: eth.Uint64Quantity(uint64(testutils.RandomPort())),
+		BlockHash:   testutils.RandomHash(),
+	}
+	cache.put(uint64(payload.BlockNumber), payload)
+	payloadCached := cache.get(uint64(payload.BlockNumber), payload.BlockHash)
+	s.Equal(payload, payloadCached)
+	s.True(cache.has(uint64(payload.BlockNumber), payload.BlockHash))
+}
+
+func (s *PreconfBlockAPIServerTestSuite) TestCacheGetLongestChildren() {
+	cache := newPayloadQueue()
+	currentPayload := &eth.ExecutionPayload{
+		BlockNumber: eth.Uint64Quantity(uint64(testutils.RandomPort())),
+		BlockHash:   testutils.RandomHash(),
+	}
+
+	createFork := func(currentPayload *eth.ExecutionPayload, len int) []*eth.ExecutionPayload {
+		payloads := make([]*eth.ExecutionPayload, len)
+		parent := currentPayload
+		for i := 0; i < len; i++ {
+			payload := &eth.ExecutionPayload{
+				BlockNumber: eth.Uint64Quantity(uint64(currentPayload.BlockNumber) + uint64(i+1)),
+				BlockHash:   testutils.RandomHash(),
+				ParentHash:  parent.BlockHash,
+			}
+			payloads[i] = payload
+			parent = payload
+			cache.put(uint64(payload.BlockNumber), payload)
+		}
+		return payloads
+	}
+
+	// Create forks of different lengths
+	randomLen := rand.Intn(6) + 5
+	fork1 := createFork(currentPayload, randomLen)
+	fork2 := createFork(currentPayload, randomLen+1)
+	fork3 := createFork(currentPayload, randomLen+3)
+
+	s.Equal(len(fork1), randomLen)
+	s.Equal(len(fork2), randomLen+1)
+	s.Equal(len(fork3), randomLen+3)
+	s.NotEqual(fork1[len(fork1)-1].BlockHash, fork2[len(fork2)-1].BlockHash)
+	s.NotEqual(fork1[len(fork1)-1].BlockHash, fork3[len(fork3)-1].BlockHash)
+	s.NotEqual(fork2[len(fork2)-1].BlockHash, fork3[len(fork3)-1].BlockHash)
+	for i := 0; i < len(fork1)-1; i++ {
+		s.Equal(fork1[i].BlockHash, fork1[i+1].ParentHash)
+		s.Equal(uint64(fork1[i].BlockNumber+1), uint64(fork1[i+1].BlockNumber))
+	}
+
+	// Search for the longest fork
+	longestFork := cache.getChildren(uint64(currentPayload.BlockNumber), currentPayload.BlockHash)
+	s.Equal(len(longestFork), len(fork3))
+	for i := 0; i < len(longestFork)-1; i++ {
+		s.Equal(longestFork[i].BlockNumber, fork3[i].BlockNumber)
+		s.Equal(longestFork[i].BlockHash, fork3[i].BlockHash)
+	}
+}
diff --git a/packages/taiko-client/driver/preconf_blocks/server.go b/packages/taiko-client/driver/preconf_blocks/server.go
index 15528a92c11..ced48461f6a 100644
--- a/packages/taiko-client/driver/preconf_blocks/server.go
+++ b/packages/taiko-client/driver/preconf_blocks/server.go
@@ -12,22 +12,25 @@ import (
 	"github.com/ethereum-optimism/optimism/op-service/eth"
 	"github.com/ethereum/go-ethereum"
 	"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/log"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/holiman/uint256"
 	echojwt "github.com/labstack/echo-jwt/v4"
 	"github.com/labstack/echo/v4"
 	"github.com/labstack/echo/v4/middleware"
 	"github.com/libp2p/go-libp2p/core/peer"
-	"golang.org/x/sync/errgroup"
 
 	"github.com/taikoxyz/taiko-mono/packages/taiko-client/internal/metrics"
 	"github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/rpc"
 	"github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/utils"
+	validator "github.com/taikoxyz/taiko-mono/packages/taiko-client/prover/anchor_tx_validator"
 )
 
 // preconfBlockChainSyncer is an interface for preconf block chain syncer.
 type preconfBlockChainSyncer interface {
-	InsertPreconfBlockFromExecutionPayload(context.Context, *eth.ExecutionPayload) (*types.Header, error)
+	InsertPreconfBlocksFromExecutionPayloads(context.Context, []*eth.ExecutionPayload) ([]*types.Header, error)
 	RemovePreconfBlocks(ctx context.Context, newLastBlockID uint64) error
 }
 
@@ -43,12 +46,14 @@ type preconfBlockChainSyncer interface {
 // @license.url https://github.com/taikoxyz/taiko-mono/blob/main/LICENSE.md
 // PreconfBlockAPIServer represents a preconfirmation block server instance.
 type PreconfBlockAPIServer struct {
-	echo        *echo.Echo
-	chainSyncer preconfBlockChainSyncer
-	rpc         *rpc.Client
+	echo            *echo.Echo
+	chainSyncer     preconfBlockChainSyncer
+	rpc             *rpc.Client
+	anchorValidator *validator.AnchorTxValidator
 	// P2P network for preconf block propagation
 	p2pNode        *p2p.NodeP2P
 	p2pSigner      p2p.Signer
+	payloadsCache  *payloadQueue
 	lookahead      *Lookahead
 	lookaheadMutex sync.Mutex
 }
@@ -57,14 +62,22 @@ type PreconfBlockAPIServer struct {
 func New(
 	cors string,
 	jwtSecret []byte,
+	taikoAnchorAddress common.Address,
 	chainSyncer preconfBlockChainSyncer,
 	cli *rpc.Client,
 ) (*PreconfBlockAPIServer, error) {
+	anchorValidator, err := validator.New(taikoAnchorAddress, cli.L2.ChainID, cli)
+	if err != nil {
+		return nil, err
+	}
+
 	server := &PreconfBlockAPIServer{
-		echo:        echo.New(),
-		chainSyncer: chainSyncer,
-		rpc:         cli,
-		lookahead:   &Lookahead{},
+		echo:            echo.New(),
+		anchorValidator: anchorValidator,
+		chainSyncer:     chainSyncer,
+		rpc:             cli,
+		payloadsCache:   newPayloadQueue(),
+		lookahead:       &Lookahead{},
 	}
 
 	server.echo.HideBanner = true
@@ -155,68 +168,240 @@ func (s *PreconfBlockAPIServer) OnUnsafeL2Payload(
 
 	metrics.DriverPreconfP2PEnvelopeCounter.Inc()
 
-	if len(msg.ExecutionPayload.Transactions) != 1 {
-		return fmt.Errorf("only one transaction list is allowed")
+	// Check if the payload is valid.
+	if err := s.ValidateExecutionPayload(msg.ExecutionPayload); err != nil {
+		log.Warn(
+			"Invalid preconfirmation block payload",
+			"peer", from,
+			"blockID", uint64(msg.ExecutionPayload.BlockNumber),
+			"hash", msg.ExecutionPayload.BlockHash.Hex(),
+			"error", err,
+		)
+		return nil
 	}
 
-	var (
-		parent *types.Header
-		header *types.Header
-		err    error
-		g      = new(errgroup.Group)
+	// Ensure the preconfirmation block number is greater than the current head L1 origin block ID.
+	headL1Origin, err := s.rpc.L2.HeadL1Origin(ctx)
+	if err != nil && err.Error() != ethereum.NotFound.Error() {
+		return fmt.Errorf("failed to fetch head L1 origin: %w", err)
+	}
+	if headL1Origin != nil && uint64(msg.ExecutionPayload.BlockNumber) <= headL1Origin.BlockID.Uint64() {
+		return fmt.Errorf(
+			"preconfirmation block ID (%d) is less than or equal to the current head L1 origin block ID (%d)",
+			msg.ExecutionPayload.BlockNumber,
+			headL1Origin.BlockID,
+		)
+	}
+
+	// Check if the parent block is in the canonical chain, if not, we try to
+	// find all the missing ancients from the cache and import them, if we can't, then we cache the message.
+	parentInCanonical, err := s.rpc.L2.HeaderByNumber(
+		ctx,
+		new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber-1)),
 	)
-	g.Go(func() error {
-		parent, err = s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber-1)))
-		if err != nil && !errors.Is(err, ethereum.NotFound) {
-			return fmt.Errorf("failed to fetch parent header: %w", err)
-		}
-		if parent == nil {
-			return fmt.Errorf("parent block not found: %d", msg.ExecutionPayload.BlockNumber-1)
-		}
-		if parent.Hash() != msg.ExecutionPayload.ParentHash {
-			return fmt.Errorf(
-				"parent block not in canonical chain: %s != %s",
-				parent.Hash().Hex(),
-				msg.ExecutionPayload.ParentHash.Hex(),
+	if err != nil && !errors.Is(err, ethereum.NotFound) {
+		return fmt.Errorf("failed to fetch parent header by number: %w", err)
+	}
+	parentInFork, err := s.rpc.L2.HeaderByHash(ctx, msg.ExecutionPayload.ParentHash)
+	if err != nil && !errors.Is(err, ethereum.NotFound) {
+		return fmt.Errorf("failed to fetch parent header by hash: %w", err)
+	}
+	if parentInFork == nil && parentInCanonical == nil {
+		log.Info(
+			"Parent block not in L2 canonical / fork chain",
+			"peer", from,
+			"blockID", uint64(msg.ExecutionPayload.BlockNumber),
+			"hash", msg.ExecutionPayload.BlockHash.Hex(),
+			"parentHash", msg.ExecutionPayload.ParentHash,
+		)
+		// Try to find all the missing ancients from the cache and import them.
+		if err := s.ImportMissingAncientsFromCache(ctx, msg.ExecutionPayload, headL1Origin); err != nil {
+			log.Info(
+				"Unable to find all the missing ancients from the cache, cache the current payload",
+				"peer", from,
+				"blockID", uint64(msg.ExecutionPayload.BlockNumber),
+				"hash", msg.ExecutionPayload.BlockHash.Hex(),
+				"reason", err,
 			)
+			if !s.payloadsCache.has(uint64(msg.ExecutionPayload.BlockNumber), msg.ExecutionPayload.BlockHash) {
+				log.Info(
+					"Payload is cached",
+					"peer", from,
+					"blockID", uint64(msg.ExecutionPayload.BlockNumber),
+					"blockHash", msg.ExecutionPayload.BlockHash.Hex(),
+				)
+
+				s.payloadsCache.put(uint64(msg.ExecutionPayload.BlockNumber), msg.ExecutionPayload)
+			}
+			return nil
 		}
-		return nil
-	})
-	g.Go(func() error {
-		header, err = s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber)))
-		if err != nil && !errors.Is(err, ethereum.NotFound) {
-			return fmt.Errorf("failed to fetch header by hash: %w", err)
-		}
-		if header != nil {
-			log.Debug(
+	}
+
+	// Check if the block already exists in the canonical chain, if it does, we ignore the message.
+	header, err := s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber)))
+	if err != nil && !errors.Is(err, ethereum.NotFound) {
+		return fmt.Errorf("failed to fetch header by hash: %w", err)
+	}
+	if header != nil {
+		if header.Hash() == msg.ExecutionPayload.BlockHash {
+			log.Info(
 				"Preconfirmation block already exists",
 				"peer", from,
 				"blockID", uint64(msg.ExecutionPayload.BlockNumber),
 				"hash", msg.ExecutionPayload.BlockHash.Hex(),
-				"txs", len(msg.ExecutionPayload.Transactions),
+				"parentHash", msg.ExecutionPayload.ParentHash.Hex(),
+			)
+			return nil
+		} else {
+			log.Info(
+				"Preconfirmation block already exists with different hash",
+				"peer", from,
+				"blockID", uint64(msg.ExecutionPayload.BlockNumber),
+				"hash", msg.ExecutionPayload.BlockHash.Hex(),
+				"parentHash", msg.ExecutionPayload.ParentHash.Hex(),
+				"headerHash", header.Hash().Hex(),
+				"headerParentHash", header.ParentHash.Hex(),
 			)
+		}
+	}
+
+	// Insert the preconfirmation block into the L2 EE chain.
+	if _, err := s.chainSyncer.InsertPreconfBlocksFromExecutionPayloads(
+		ctx,
+		[]*eth.ExecutionPayload{msg.ExecutionPayload},
+	); err != nil {
+		return fmt.Errorf("failed to insert preconfirmation block from P2P network: %w", err)
+	}
+
+	// Try to import the child blocks from the cache, if any.
+	if err := s.ImportChildBlocksFromCache(ctx, msg.ExecutionPayload); err != nil {
+		return fmt.Errorf("failed to try importing child blocks from cache: %w", err)
+	}
+
+	return nil
+}
+
+// ImportMissingAncientsFromCache tries to import cached payloads from the cached payload queue, if we can't
+// find all the missing ancients and import them, an error will be returned.
+func (s *PreconfBlockAPIServer) ImportMissingAncientsFromCache(
+	ctx context.Context,
+	currentPayload *eth.ExecutionPayload,
+	headL1Origin *rawdb.L1Origin,
+) error {
+	// Try searching the missing ancients in the cache.
+	payloadsToImport := make([]*eth.ExecutionPayload, 0)
+	for {
+		parentPayload := s.payloadsCache.get(uint64(currentPayload.BlockNumber)-1, currentPayload.ParentHash)
+		if parentPayload == nil {
 			return fmt.Errorf(
-				"preconfirmation block (%d) already exists, hash %s",
-				uint64(msg.ExecutionPayload.BlockNumber),
-				msg.ExecutionPayload.BlockHash.Hex(),
+				"failed to find parent payload in the cache, number %d, hash %s",
+				currentPayload.BlockNumber-1,
+				currentPayload.ParentHash.Hex(),
 			)
 		}
-		return nil
-	})
+		payloadsToImport = append([]*eth.ExecutionPayload{parentPayload}, payloadsToImport...)
+
+		// Check if the found parent payload is in the canonical chain,
+		// if it is not, continue to find the parent payload.
+		parentHeader, err := s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(parentPayload.BlockNumber)))
+		if err != nil && !errors.Is(err, ethereum.NotFound) {
+			return fmt.Errorf("failed to fetch parent header: %w", err)
+		}
+		if parentHeader == nil || parentHeader.Hash() != parentPayload.BlockHash {
+			log.Debug(
+				"Parent block not in L2 canonical chain, continue to search cached payloads",
+				"blockID", parentPayload.BlockNumber,
+				"hash", parentPayload.BlockHash.Hex(),
+			)
+			if headL1Origin != nil && uint64(parentPayload.BlockNumber) <= headL1Origin.BlockID.Uint64() {
+				return fmt.Errorf(
+					"missing parent block ID (%d) is less than or equal to the current head L1 origin block ID (%d)",
+					parentPayload.BlockNumber,
+					headL1Origin.BlockID,
+				)
+			}
+			currentPayload = parentPayload
+			continue
+		}
+
+		break
+	}
 
-	if err := g.Wait(); err != nil {
-		log.Warn("Preconfirmation message check error", "error", err)
+	log.Info(
+		"Found all missing ancient payloads in the cache, start importing",
+		"count", len(payloadsToImport),
+	)
+
+	// If all ancient payloads are found, try to import them.
+	if _, err := s.chainSyncer.InsertPreconfBlocksFromExecutionPayloads(ctx, payloadsToImport); err != nil {
+		return fmt.Errorf("failed to insert ancient preconfirmation blocks from cache: %w", err)
+	}
+
+	return nil
+}
+
+// ImportChildBlocksFromCache tries to import the longest cached child payloads from the cached payload queue.
+func (s *PreconfBlockAPIServer) ImportChildBlocksFromCache(
+	ctx context.Context,
+	currentPayload *eth.ExecutionPayload,
+) error {
+	// Try searching if there is any available child block in the cache.
+	childPayloads := s.payloadsCache.getChildren(uint64(currentPayload.BlockNumber), currentPayload.BlockHash)
+	if len(childPayloads) == 0 {
 		return nil
 	}
 
-	if msg.ExecutionPayload.Transactions[0], err = utils.DecompressPacaya(
-		msg.ExecutionPayload.Transactions[0],
-	); err != nil {
-		return fmt.Errorf("failed to decompress transactions list bytes: %w", err)
+	log.Info(
+		"Found available child payloads in the cache, start importing",
+		"count", len(childPayloads),
+	)
+
+	// Try to import all available child payloads.
+	if _, err := s.chainSyncer.InsertPreconfBlocksFromExecutionPayloads(ctx, childPayloads); err != nil {
+		return fmt.Errorf("failed to insert child preconfirmation blocks from cache: %w", err)
 	}
 
-	if _, err := s.chainSyncer.InsertPreconfBlockFromExecutionPayload(ctx, msg.ExecutionPayload); err != nil {
-		return fmt.Errorf("failed to insert preconfirmation block from P2P network: %w", err)
+	return nil
+}
+
+// ValidateExecutionPayload validates the execution payload.
+func (s *PreconfBlockAPIServer) ValidateExecutionPayload(payload *eth.ExecutionPayload) error {
+	if payload.Timestamp == 0 {
+		return errors.New("non-zero timestamp is required")
+	}
+	if payload.FeeRecipient == (common.Address{}) {
+		return errors.New("empty L2 fee recipient")
+	}
+	if payload.GasLimit == 0 {
+		return errors.New("non-zero gas limit is required")
+	}
+	var u256BaseFee = uint256.Int(payload.BaseFeePerGas)
+	if u256BaseFee.ToBig().Cmp(common.Big0) == 0 {
+		return errors.New("non-zero base fee per gas is required")
+	}
+	if len(payload.ExtraData) == 0 {
+		return errors.New("empty extra data")
+	}
+	if len(payload.Transactions) != 1 {
+		return fmt.Errorf("only one transaction list is allowed")
+	}
+	if len(payload.Transactions[0]) > (eth.MaxBlobDataSize * eth.MaxBlobsPerBlobTx) {
+		return errors.New("compressed transactions size exceeds max blob data size")
+	}
+
+	var txs types.Transactions
+	b, err := utils.DecompressPacaya(payload.Transactions[0])
+	if err != nil {
+		return fmt.Errorf("invalid zlib bytes for transactions: %w", err)
+	}
+	if err := rlp.DecodeBytes(b, &txs); err != nil {
+		return fmt.Errorf("invalid RLP bytes for transactions: %w", err)
+	}
+	if len(txs) == 0 {
+		return errors.New("empty transactions list, missing anchor transaction")
+	}
+	if err := s.anchorValidator.ValidateAnchorTx(txs[0]); err != nil {
+		return fmt.Errorf("invalid anchor transaction: %w", err)
 	}
 
 	return nil
diff --git a/packages/taiko-client/driver/preconf_blocks/server_test.go b/packages/taiko-client/driver/preconf_blocks/server_test.go
index 1acd25777f0..c5c5ed9826c 100644
--- a/packages/taiko-client/driver/preconf_blocks/server_test.go
+++ b/packages/taiko-client/driver/preconf_blocks/server_test.go
@@ -2,8 +2,10 @@ package preconfblocks
 
 import (
 	"context"
+	"os"
 	"testing"
 
+	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/stretchr/testify/suite"
 
@@ -17,7 +19,7 @@ type PreconfBlockAPIServerTestSuite struct {
 
 func (s *PreconfBlockAPIServerTestSuite) SetupTest() {
 	s.ClientTestSuite.SetupTest()
-	server, err := New("*", nil, nil, s.RPCClient)
+	server, err := New("*", nil, common.HexToAddress(os.Getenv("TAIKO_ANCHOR")), nil, s.RPCClient)
 	s.Nil(err)
 	s.s = server
 	go func() {
diff --git a/packages/taiko-client/pkg/rpc/methods.go b/packages/taiko-client/pkg/rpc/methods.go
index eac9a393cba..412015d6ad5 100644
--- a/packages/taiko-client/pkg/rpc/methods.go
+++ b/packages/taiko-client/pkg/rpc/methods.go
@@ -1074,8 +1074,17 @@ func (c *Client) calculateBaseFeePacaya(
 	currentTimestamp uint64,
 	baseFeeConfig *pacayaBindings.LibSharedDataBaseFeeConfig,
 ) (*big.Int, error) {
+	log.Info(
+		"Calculate base fee for the Pacaya block",
+		"parentNumber", l2Head.Number,
+		"parentHash", l2Head.Hash(),
+		"parentGasUsed", l2Head.GasUsed,
+		"currentTimestamp", currentTimestamp,
+		"baseFeeConfig", baseFeeConfig,
+	)
+
 	baseFeeInfo, err := c.PacayaClients.TaikoAnchor.GetBasefeeV2(
-		&bind.CallOpts{BlockNumber: l2Head.Number, Context: ctx},
+		&bind.CallOpts{BlockNumber: l2Head.Number, BlockHash: l2Head.Hash(), Context: ctx},
 		uint32(l2Head.GasUsed),
 		currentTimestamp,
 		*baseFeeConfig,


Reviews:
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T07:27:05Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a payload queue for managing P2P gossip messages and refactors several methods to handle batches of execution payloads.  
- Added a new payload queue to temporarily cache payloads for missing block imports.  
- Updated API and block insertion logic to support batch processing via new methods.  
- Introduced tests to validate scenarios with missing ancients.

### Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| packages/taiko-client/driver/preconf_blocks/server.go | Updated payload processing logic to support batch block insertion and caching of missing ancestors. |
| packages/taiko-client/driver/preconf_blocks/queue.go | Added a new payload queue implementation for managing P2P payloads. |
| packages/taiko-client/driver/preconf_blocks/api.go | Modified API to use the new batch insertion method and remove decompression logic. |
| packages/taiko-client/driver/driver_test.go | Added tests covering payloads with missing ancients. |
| packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go | Refactored block insertion to support batch processing and updated error messages. |
| .github/workflows/taiko-client--docker.yml | Updated branch trigger list for CI builds. |
</details>



<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**packages/taiko-client/driver/preconf_blocks/queue.go:52**
* In the 'get' function, the loop exits upon encountering the first nil item. If the underlying slice becomes non-contiguous due to evictions or other operations, this may bypass valid entries later in the slice. Consider iterating over the entire slice instead of stopping at the first nil.
```
if item == nil { return nil // no more items }
```
**packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go:276**
* [nitpick] The parameter name 'executableData' used in this function is inconsistent with the rest of the code which uses 'executionPayloads'. Renaming it to 'payload' (or 'executionPayload') would improve clarity and consistency.
```
func (i *BlocksInserterPacaya) insertPreconfBlockFromExecutionPayload(
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T07:45:48Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T07:47:18Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T07:50:21Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T07:52:48Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T07:56:52Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T07:57:38Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T07:59:07Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T08:05:46Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T08:31:09Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T08:31:24Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T08:31:31Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T08:32:16Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T08:33:17Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T08:37:37Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T12:10:15Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T12:10:19Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T12:11:36Z
user: login: YoGhurt111
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T12:15:23Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T12:29:29Z
user: login: davidtaikocha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T12:59:12Z
user: login: mask-pp
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T13:13:22Z
user: login: YoGhurt111
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-02T13:15:38Z
user: login: mask-pp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T03:05:44Z

Review comments:
diff_hunk: @@ -2,7 +2,7 @@ name: "Build and Push Multi-Arch Docker Image"
 
 on:
   push:
-    branches: [main]
+    branches: [main, p2p-payloadQueue]
path: .github/workflows/taiko-client--docker.yml
user: login: davidtaikocha
body: Will remove this before merging the PR
created_at: 2025-04-02T07:27:04Z
author_association: COLLABORATOR
diff_hunk: @@ -252,14 +252,31 @@ func (i *BlocksInserterPacaya) InsertBlocks(
 	return nil
 }
 
-// InsertPreconfBlockFromExecutionPayload inserts a preconf block from the given execution payload.
-func (i *BlocksInserterPacaya) InsertPreconfBlockFromExecutionPayload(
+// InsertPreconfBlocksFromExecutionPayloads inserts preconf blocks from the given execution payloads.
+func (i *BlocksInserterPacaya) InsertPreconfBlocksFromExecutionPayloads(
 	ctx context.Context,
-	executableData *eth.ExecutionPayload,
-) (*types.Header, error) {
+	executionPayloads []*eth.ExecutionPayload,
+) ([]*types.Header, error) {
 	i.mutex.Lock()
 	defer i.mutex.Unlock()
 
+	headers := make([]*types.Header, 0)
path: packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go
user: login: YoGhurt111
body: ```suggestion
	headers := make([]*types.Header, len(executionPayloads))
```
created_at: 2025-04-02T07:47:17Z
author_association: MEMBER
diff_hunk: @@ -281,6 +298,11 @@ func (i *BlocksInserterPacaya) InsertPreconfBlockFromExecutionPayload(
 		return nil, fmt.Errorf("no transactions data in the payload")
 	}
 
+	// Decompress the transactions list.
+	if executableData.Transactions[0], err = utils.DecompressPacaya(executableData.Transactions[0]); err != nil {
path: packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go
user: login: YoGhurt111
body: Shall we add a check for the length of `executableData.Transactions`?
created_at: 2025-04-02T07:50:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: YoGhurt111
body: Shall we add a notice for the value? It equals to the `maxBlocksPerBatch` in protocol.
created_at: 2025-04-02T07:52:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
+	lock     sync.RWMutex
+}
+
+// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
+// all containing empty items.
+func newPayloadQueue() *payloadQueue {
+	return &payloadQueue{
+		payloads: make([]*payloadQueueItem, maxTrackedPayloads),
+	}
+}
+
+// put inserts a new payload into the queue at the given id.
+func (q *payloadQueue) put(id uint64, payload *eth.ExecutionPayload) {
+	q.lock.Lock()
+	defer q.lock.Unlock()
+
+	copy(q.payloads[1:], q.payloads)
+	q.payloads[0] = &payloadQueueItem{
+		id:      id,
+		payload: payload,
+	}
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) get(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id && item.payload.BlockHash == hash {
+			return item.payload
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: YoGhurt111
body: Shall we give the copy of value rather than the pionter?
created_at: 2025-04-02T07:56:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
+	lock     sync.RWMutex
+}
+
+// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
+// all containing empty items.
+func newPayloadQueue() *payloadQueue {
+	return &payloadQueue{
+		payloads: make([]*payloadQueueItem, maxTrackedPayloads),
+	}
+}
+
+// put inserts a new payload into the queue at the given id.
+func (q *payloadQueue) put(id uint64, payload *eth.ExecutionPayload) {
+	q.lock.Lock()
+	defer q.lock.Unlock()
+
+	copy(q.payloads[1:], q.payloads)
+	q.payloads[0] = &payloadQueueItem{
+		id:      id,
+		payload: payload,
+	}
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) get(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id && item.payload.BlockHash == hash {
+			return item.payload
+		}
+	}
+	return nil
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) getChild(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id+1 && item.payload.ParentHash == hash {
+			return item.payload
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: YoGhurt111
body: ditto
created_at: 2025-04-02T07:57:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: YoGhurt111
body: We don't have the del function to remove item from the `payloadQueue`.
created_at: 2025-04-02T07:59:07Z
author_association: MEMBER
diff_hunk: @@ -171,64 +172,169 @@ func (s *PreconfBlockAPIServer) OnUnsafeL2Payload(
 		return fmt.Errorf("only one transaction list is allowed")
 	}
 
-	var (
-		parent *types.Header
-		header *types.Header
-		err    error
-		g      = new(errgroup.Group)
-	)
-	g.Go(func() error {
-		parent, err = s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber-1)))
-		if err != nil && !errors.Is(err, ethereum.NotFound) {
-			return fmt.Errorf("failed to fetch parent header: %w", err)
-		}
-		if parent == nil {
-			return fmt.Errorf("parent block not found: %d", msg.ExecutionPayload.BlockNumber-1)
+	// Ensure the preconfirmation block number is greater than the current head L1 origin block ID.
+	headL1Origin, err := s.rpc.L2.HeadL1Origin(ctx)
+	if err != nil && err.Error() != ethereum.NotFound.Error() {
+		return fmt.Errorf("failed to fetch head L1 origin: %w", err)
+	}
+	if headL1Origin != nil && uint64(msg.ExecutionPayload.BlockNumber) <= headL1Origin.BlockID.Uint64() {
path: packages/taiko-client/driver/preconf_blocks/server.go
user: login: YoGhurt111
body: In cases where the values are equal, should we additionally check the parentHash and stateRoot to prevent it from being an insertion after a reorg, rather than directly returning an error?
created_at: 2025-04-02T08:05:46Z
author_association: MEMBER
diff_hunk: @@ -281,6 +298,11 @@ func (i *BlocksInserterPacaya) InsertPreconfBlockFromExecutionPayload(
 		return nil, fmt.Errorf("no transactions data in the payload")
 	}
 
+	// Decompress the transactions list.
+	if executableData.Transactions[0], err = utils.DecompressPacaya(executableData.Transactions[0]); err != nil {
path: packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go
user: login: davidtaikocha
body: Not necessary but its a nice to have, let me add it.
created_at: 2025-04-02T08:31:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
+	lock     sync.RWMutex
+}
+
+// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
+// all containing empty items.
+func newPayloadQueue() *payloadQueue {
+	return &payloadQueue{
+		payloads: make([]*payloadQueueItem, maxTrackedPayloads),
+	}
+}
+
+// put inserts a new payload into the queue at the given id.
+func (q *payloadQueue) put(id uint64, payload *eth.ExecutionPayload) {
+	q.lock.Lock()
+	defer q.lock.Unlock()
+
+	copy(q.payloads[1:], q.payloads)
+	q.payloads[0] = &payloadQueueItem{
+		id:      id,
+		payload: payload,
+	}
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) get(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id && item.payload.BlockHash == hash {
+			return item.payload
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: davidtaikocha
body: Don't its necessary, no?
created_at: 2025-04-02T08:31:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
+	lock     sync.RWMutex
+}
+
+// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
+// all containing empty items.
+func newPayloadQueue() *payloadQueue {
+	return &payloadQueue{
+		payloads: make([]*payloadQueueItem, maxTrackedPayloads),
+	}
+}
+
+// put inserts a new payload into the queue at the given id.
+func (q *payloadQueue) put(id uint64, payload *eth.ExecutionPayload) {
+	q.lock.Lock()
+	defer q.lock.Unlock()
+
+	copy(q.payloads[1:], q.payloads)
+	q.payloads[0] = &payloadQueueItem{
+		id:      id,
+		payload: payload,
+	}
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) get(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id && item.payload.BlockHash == hash {
+			return item.payload
+		}
+	}
+	return nil
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) getChild(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id+1 && item.payload.ParentHash == hash {
+			return item.payload
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: davidtaikocha
body: Don't its necessary neither, no?
created_at: 2025-04-02T08:31:31Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: davidtaikocha
body: The first item will be deleted automatically, when inserting a new item
created_at: 2025-04-02T08:32:15Z
author_association: COLLABORATOR
diff_hunk: @@ -171,64 +172,169 @@ func (s *PreconfBlockAPIServer) OnUnsafeL2Payload(
 		return fmt.Errorf("only one transaction list is allowed")
 	}
 
-	var (
-		parent *types.Header
-		header *types.Header
-		err    error
-		g      = new(errgroup.Group)
-	)
-	g.Go(func() error {
-		parent, err = s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber-1)))
-		if err != nil && !errors.Is(err, ethereum.NotFound) {
-			return fmt.Errorf("failed to fetch parent header: %w", err)
-		}
-		if parent == nil {
-			return fmt.Errorf("parent block not found: %d", msg.ExecutionPayload.BlockNumber-1)
+	// Ensure the preconfirmation block number is greater than the current head L1 origin block ID.
+	headL1Origin, err := s.rpc.L2.HeadL1Origin(ctx)
+	if err != nil && err.Error() != ethereum.NotFound.Error() {
+		return fmt.Errorf("failed to fetch head L1 origin: %w", err)
+	}
+	if headL1Origin != nil && uint64(msg.ExecutionPayload.BlockNumber) <= headL1Origin.BlockID.Uint64() {
path: packages/taiko-client/driver/preconf_blocks/server.go
user: login: davidtaikocha
body: No we should not, a preconf block should not reorg any proposed block.
created_at: 2025-04-02T08:33:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: davidtaikocha
body: Added
created_at: 2025-04-02T08:37:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
+	lock     sync.RWMutex
+}
+
+// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
+// all containing empty items.
+func newPayloadQueue() *payloadQueue {
+	return &payloadQueue{
+		payloads: make([]*payloadQueueItem, maxTrackedPayloads),
+	}
+}
+
+// put inserts a new payload into the queue at the given id.
+func (q *payloadQueue) put(id uint64, payload *eth.ExecutionPayload) {
+	q.lock.Lock()
+	defer q.lock.Unlock()
+
+	copy(q.payloads[1:], q.payloads)
+	q.payloads[0] = &payloadQueueItem{
+		id:      id,
+		payload: payload,
+	}
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) get(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id && item.payload.BlockHash == hash {
+			return item.payload
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: YoGhurt111
body: It is not necessary, just for security reasons.
created_at: 2025-04-02T12:10:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
+	lock     sync.RWMutex
+}
+
+// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
+// all containing empty items.
+func newPayloadQueue() *payloadQueue {
+	return &payloadQueue{
+		payloads: make([]*payloadQueueItem, maxTrackedPayloads),
+	}
+}
+
+// put inserts a new payload into the queue at the given id.
+func (q *payloadQueue) put(id uint64, payload *eth.ExecutionPayload) {
+	q.lock.Lock()
+	defer q.lock.Unlock()
+
+	copy(q.payloads[1:], q.payloads)
+	q.payloads[0] = &payloadQueueItem{
+		id:      id,
+		payload: payload,
+	}
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) get(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id && item.payload.BlockHash == hash {
+			return item.payload
+		}
+	}
+	return nil
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) getChild(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id+1 && item.payload.ParentHash == hash {
+			return item.payload
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: YoGhurt111
body: It is not necessary, just for security reasons.
created_at: 2025-04-02T12:10:19Z
author_association: MEMBER
diff_hunk: @@ -171,64 +172,169 @@ func (s *PreconfBlockAPIServer) OnUnsafeL2Payload(
 		return fmt.Errorf("only one transaction list is allowed")
 	}
 
-	var (
-		parent *types.Header
-		header *types.Header
-		err    error
-		g      = new(errgroup.Group)
-	)
-	g.Go(func() error {
-		parent, err = s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber-1)))
-		if err != nil && !errors.Is(err, ethereum.NotFound) {
-			return fmt.Errorf("failed to fetch parent header: %w", err)
-		}
-		if parent == nil {
-			return fmt.Errorf("parent block not found: %d", msg.ExecutionPayload.BlockNumber-1)
+	// Ensure the preconfirmation block number is greater than the current head L1 origin block ID.
+	headL1Origin, err := s.rpc.L2.HeadL1Origin(ctx)
+	if err != nil && err.Error() != ethereum.NotFound.Error() {
+		return fmt.Errorf("failed to fetch head L1 origin: %w", err)
+	}
+	if headL1Origin != nil && uint64(msg.ExecutionPayload.BlockNumber) <= headL1Origin.BlockID.Uint64() {
path: packages/taiko-client/driver/preconf_blocks/server.go
user: login: YoGhurt111
body: OK
created_at: 2025-04-02T12:11:36Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: YoGhurt111
body: If it always delete the first item when `put` is called, how do the lenght of queue increase? 
created_at: 2025-04-02T12:15:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,93 @@
+package preconfblocks
+
+import (
+	"sync"
+
+	"github.com/ethereum-optimism/optimism/op-service/eth"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// maxTrackedPayloads is the maximum number of prepared payloads the execution
+// engine tracks before evicting old ones.
+const maxTrackedPayloads = 768
+
+// payloadQueueItem represents an id->payload tuple to store until it's retrieved
+// or evicted.
+type payloadQueueItem struct {
+	id      uint64
+	payload *eth.ExecutionPayload
+}
+
+// payloadQueue tracks the latest payloads from the P2P gossip messages.
+type payloadQueue struct {
+	payloads []*payloadQueueItem
+	lock     sync.RWMutex
+}
+
+// newPayloadQueue creates a pre-initialized queue with a fixed number of slots
+// all containing empty items.
+func newPayloadQueue() *payloadQueue {
+	return &payloadQueue{
+		payloads: make([]*payloadQueueItem, maxTrackedPayloads),
+	}
+}
+
+// put inserts a new payload into the queue at the given id.
+func (q *payloadQueue) put(id uint64, payload *eth.ExecutionPayload) {
+	q.lock.Lock()
+	defer q.lock.Unlock()
+
+	copy(q.payloads[1:], q.payloads)
+	q.payloads[0] = &payloadQueueItem{
+		id:      id,
+		payload: payload,
+	}
+}
+
+// get retrieves a previously stored payload item or nil if it does not exist.
+func (q *payloadQueue) get(id uint64, hash common.Hash) *eth.ExecutionPayload {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	for _, item := range q.payloads {
+		if item == nil {
+			return nil // no more items
+		}
+		if item.id == id && item.payload.BlockHash == hash {
+			return item.payload
path: packages/taiko-client/driver/preconf_blocks/queue.go
user: login: davidtaikocha
body: Don't think we need it, according to geth: https://github.com/ethereum/go-ethereum/blob/master/eth/catalyst/queue.go#L138
created_at: 2025-04-02T12:29:29Z
author_association: COLLABORATOR
diff_hunk: @@ -281,6 +298,11 @@ func (i *BlocksInserterPacaya) InsertPreconfBlockFromExecutionPayload(
 		return nil, fmt.Errorf("no transactions data in the payload")
 	}
 
+	// Decompress the transactions list.
+	if executableData.Transactions[0], err = utils.DecompressPacaya(executableData.Transactions[0]); err != nil {
path: packages/taiko-client/driver/chain_syncer/blob/blocks_inserter/pacaya.go
user: login: davidtaikocha
body: Checks added: https://github.com/taikoxyz/taiko-mono/pull/19195/commits/5c8ea0cccf65ec4d6f93a8418e7cd50b2c5934d9
created_at: 2025-04-02T12:59:12Z
author_association: COLLABORATOR
diff_hunk: @@ -155,68 +168,230 @@ func (s *PreconfBlockAPIServer) OnUnsafeL2Payload(
 
 	metrics.DriverPreconfP2PEnvelopeCounter.Inc()
 
-	if len(msg.ExecutionPayload.Transactions) != 1 {
-		return fmt.Errorf("only one transaction list is allowed")
+	// Check if the payload is valid.
+	if err := s.ValidateExecutionPayload(msg.ExecutionPayload); err != nil {
+		log.Warn(
+			"Invalid preconfirmation block payload",
+			"peer", from,
+			"blockID", uint64(msg.ExecutionPayload.BlockNumber),
+			"hash", msg.ExecutionPayload.BlockHash.Hex(),
+			"error", err,
+		)
+		return nil
 	}
 
-	var (
-		parent *types.Header
-		header *types.Header
-		err    error
-		g      = new(errgroup.Group)
-	)
-	g.Go(func() error {
-		parent, err = s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber-1)))
-		if err != nil && !errors.Is(err, ethereum.NotFound) {
-			return fmt.Errorf("failed to fetch parent header: %w", err)
-		}
-		if parent == nil {
-			return fmt.Errorf("parent block not found: %d", msg.ExecutionPayload.BlockNumber-1)
+	// Ensure the preconfirmation block number is greater than the current head L1 origin block ID.
+	headL1Origin, err := s.rpc.L2.HeadL1Origin(ctx)
+	if err != nil && err.Error() != ethereum.NotFound.Error() {
+		return fmt.Errorf("failed to fetch head L1 origin: %w", err)
+	}
+	if headL1Origin != nil && uint64(msg.ExecutionPayload.BlockNumber) <= headL1Origin.BlockID.Uint64() {
+		return fmt.Errorf(
+			"preconfirmation block ID (%d) is less than or equal to the current head L1 origin block ID (%d)",
+			msg.ExecutionPayload.BlockNumber,
+			headL1Origin.BlockID,
+		)
+	}
+
+	// Check if the parent block is in the canonical chain, if not, we try to
+	// find all the missing ancients from the cache and import them, if we can't, then we cache the message.
+	parent, err := s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber-1)))
+	if err != nil && !errors.Is(err, ethereum.NotFound) {
+		return fmt.Errorf("failed to fetch parent header: %w", err)
+	}
+	if parent == nil || parent.Hash() != msg.ExecutionPayload.ParentHash {
+		log.Info(
+			"Parent block not in L2 canonical chain",
+			"peer", from,
+			"blockID", uint64(msg.ExecutionPayload.BlockNumber),
+			"hash", msg.ExecutionPayload.BlockHash.Hex(),
+			"parentHash", msg.ExecutionPayload.ParentHash,
+		)
+		// Try to find all the missing ancients from the cache and import them.
+		if err := s.ImportMissingAncientsFromCache(ctx, msg.ExecutionPayload, headL1Origin); err != nil {
+			log.Info(
+				"Unable to find all the missing ancients from the cache, cache the current payload",
+				"peer", from,
+				"blockID", uint64(msg.ExecutionPayload.BlockNumber),
+				"hash", msg.ExecutionPayload.BlockHash.Hex(),
+				"reason", err,
+			)
+			if !s.payloadsCache.has(uint64(msg.ExecutionPayload.BlockNumber), msg.ExecutionPayload.BlockHash) {
+				log.Info("Payload is cached",
+					"peer", from,
+					"blockID", uint64(msg.ExecutionPayload.BlockNumber),
+					"blockHash", msg.ExecutionPayload.BlockHash.Hex(),
+				)
+
+				s.payloadsCache.put(uint64(msg.ExecutionPayload.BlockNumber), msg.ExecutionPayload)
+			}
+			return nil
 		}
-		if parent.Hash() != msg.ExecutionPayload.ParentHash {
+	}
+
+	// Check if the block already exists in the canonical chain, if it does, we ignore the message.
+	header, err := s.rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(uint64(msg.ExecutionPayload.BlockNumber)))
+	if err != nil && !errors.Is(err, ethereum.NotFound) {
+		return fmt.Errorf("failed to fetch header by hash: %w", err)
+	}
+	if header != nil {
path: packages/taiko-client/driver/preconf_blocks/server.go
user: login: mask-pp
body: ```suggestion
	if header != nil && header.Hash() == msg.ExecutionPayload.BlockHash {
```
created_at: 2025-04-05T03:05:44Z
author_association: CONTRIBUTOR

PR comments:
user: login: cyberhorsey
created_at: 2025-04-02T16:22:37Z
body: Deploying this on helder to test, will approve/merge if works.
author_association: CONTRIBUTOR
user: login: davidtaikocha
created_at: 2025-04-03T10:08:23Z
body: > Deploying this on helder to test, will approve/merge if works.

FYI also added more checks for P2P messages: https://github.com/taikoxyz/taiko-mono/pull/19195/commits/970588189cc9e80e926301731386dba4e78954f8 , not much changes but may need to build again 🙏
author_association: COLLABORATOR