Skip to content

feat(taiko-client): introduce BuildPayloadArgsID for L1Origin #19444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect
)

replace github.com/ethereum/go-ethereum v1.15.5 => github.com/taikoxyz/taiko-geth v0.0.0-20250422211912-3a4f3775cc30
replace github.com/ethereum/go-ethereum v1.15.5 => github.com/taikoxyz/taiko-geth v1.15.1-0.20250513061338-4dedd6e4a405

replace github.com/ethereum-optimism/optimism v1.7.4 => github.com/taikoxyz/optimism v0.0.0-20250407113505-a4338a4857e6

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -878,8 +878,8 @@ github.com/taikoxyz/hive v0.0.0-20240827015317-405b241dd082 h1:ymZR+Y88LOnA8i3Ke
github.com/taikoxyz/hive v0.0.0-20240827015317-405b241dd082/go.mod h1:RHnIu3EFehrWX3JhFAMQSXD5uz7l0xaNroTzXrap7EQ=
github.com/taikoxyz/optimism v0.0.0-20250407113505-a4338a4857e6 h1:TZqB0xhP6eqTbuPSnMYB6md6EtGoYoYCMAK/v9HV1YM=
github.com/taikoxyz/optimism v0.0.0-20250407113505-a4338a4857e6/go.mod h1:V0VCkKtCzuaJH6qcL75SRcbdlakM9LhurMEJUhO6VXA=
github.com/taikoxyz/taiko-geth v0.0.0-20250422211912-3a4f3775cc30 h1:zduJWfvVFUMoSiBQ2SjlmhckQY3X+XkdtHitooaHpKg=
github.com/taikoxyz/taiko-geth v0.0.0-20250422211912-3a4f3775cc30/go.mod h1:1LG2LnMOx2yPRHR/S+xuipXH29vPr6BIH6GElD8N/fo=
github.com/taikoxyz/taiko-geth v1.15.1-0.20250513061338-4dedd6e4a405 h1:aOHNO/3XeBpwRv9N+CmHRfv9y5nuTikdWdn7AWg421k=
github.com/taikoxyz/taiko-geth v1.15.1-0.20250513061338-4dedd6e4a405/go.mod h1:1LG2LnMOx2yPRHR/S+xuipXH29vPr6BIH6GElD8N/fo=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
consensus "github.com/ethereum/go-ethereum/consensus/taiko"
Expand Down Expand Up @@ -54,6 +55,21 @@ func createPayloadAndSetHead(
meta.GasLimit += consensus.AnchorGasLimit
}

// Update execution payload id for the L1 origin.
var (
txListHash = crypto.Keccak256Hash(txListBytes)
args = &miner.BuildPayloadArgs{
Parent: meta.ParentHash,
Timestamp: meta.Timestamp,
FeeRecipient: meta.SuggestedFeeRecipient,
Random: meta.Difficulty,
Withdrawals: make([]*types.Withdrawal, 0),
Version: engine.PayloadV2,
TxListHash: &txListHash,
}
)
meta.L1Origin.BuildPayloadArgsID = args.Id()

// Create a new execution payload and set the chain head.
return createExecutionPayloadsAndSetHead(ctx, rpc, meta.createExecutionPayloadsMetaData, txListBytes)
}
Expand Down Expand Up @@ -193,9 +209,9 @@ func createExecutionPayloads(
return payload, nil
}

// isBatchPreconfirmed checks if all blocks in the given batch are preconfirmed,
// and returns the header of the last block in the batch if it is preconfirmed.
func isBatchPreconfirmed(
// isKnownCanonicalBatch checks if all blocks in the given batch are in the canonical chain already.,
// and returns the header of the last block in the batch if it is.
func isKnownCanonicalBatch(
ctx context.Context,
rpc *rpc.Client,
anchorConstructor *anchorTxConstructor.AnchorTxConstructor,
Expand Down Expand Up @@ -235,7 +251,7 @@ func isBatchPreconfirmed(
return fmt.Errorf("failed to RLP encode tx list: %w", err)
}

if headers[i], err = isBlockPreconfirmed(
if headers[i], err = isKnownCanonicalBlock(
ctx,
rpc,
&createPayloadAndSetHeadMetaData{
Expand All @@ -258,8 +274,8 @@ func isBatchPreconfirmed(
return headers[len(headers)-1], g.Wait()
}

// isBlockPreconfirmed checks if the block is preconfirmed.
func isBlockPreconfirmed(
// isKnownCanonicalBlock checks if the block is in canonical chain already.
func isKnownCanonicalBlock(
ctx context.Context,
rpc *rpc.Client,
meta *createPayloadAndSetHeadMetaData,
Expand All @@ -280,81 +296,31 @@ func isBlockPreconfirmed(
txListHash = crypto.Keccak256Hash(txListBytes[:])
args = &miner.BuildPayloadArgs{
Parent: meta.Parent.Hash(),
Timestamp: block.Time(),
FeeRecipient: block.Coinbase(),
Random: block.MixDigest(),
Timestamp: meta.Timestamp,
FeeRecipient: meta.SuggestedFeeRecipient,
Random: meta.Difficulty,
Withdrawals: make([]*types.Withdrawal, 0),
Version: engine.PayloadV2,
TxListHash: &txListHash,
}
id = args.Id()
)
executableData, err := rpc.L2Engine.GetPayload(ctx, &id)
if err != nil {
return nil, fmt.Errorf("failed to get payload: %w", err)
}

defer func() {
if err != nil {
log.Warn("Invalid preconfirmed block", "blockID", blockID, "coinbase", executableData.FeeRecipient, "reason", err)
}
}()

if executableData.BlockHash != block.Hash() {
err = fmt.Errorf("block hash mismatch: %s != %s", executableData.BlockHash, block.Hash())
return nil, err
}
if block.ParentHash() != meta.ParentHash {
err = fmt.Errorf("parent hash mismatch: %s != %s", block.ParentHash(), meta.ParentHash)
return nil, err
}
if block.Transactions().Len() == 0 {
err = errors.New("transactions list is empty")
return nil, err
}
if block.Transactions()[0].Hash() != anchorTx.Hash() {
err = fmt.Errorf("anchor transaction mismatch: %s != %s", block.Transactions()[0].Hash(), anchorTx.Hash())
return nil, err
}
if block.UncleHash() != types.EmptyUncleHash {
err = fmt.Errorf("uncle hash mismatch: %s != %s", block.UncleHash(), types.EmptyUncleHash)
return nil, err
l1Origin, err := rpc.L2.L1OriginByID(ctx, blockID)
if err != nil && !errors.Is(err, ethereum.NotFound) {
return nil, fmt.Errorf("failed to get L1Origin by ID %d: %w", blockID, err)
}
if block.Coinbase() != meta.SuggestedFeeRecipient {
err = fmt.Errorf("coinbase mismatch: %s != %s", block.Coinbase(), meta.SuggestedFeeRecipient)
return nil, err
}
if block.Difficulty().Cmp(common.Big0) != 0 {
err = fmt.Errorf("difficulty mismatch: %s != 0", block.Difficulty())
return nil, err
// If L1Origin is not found, it means this block is synced from beacon sync.
if l1Origin == nil {
return nil, fmt.Errorf("L1Origin not found by ID %d", blockID)
}
if block.MixDigest() != meta.Difficulty {
err = fmt.Errorf("mixDigest mismatch: %s != %s", block.MixDigest(), meta.Difficulty)
return nil, err
}
if block.Number().Uint64() != meta.BlockID.Uint64() {
err = fmt.Errorf("block number mismatch: %d != %d", block.Number(), meta.BlockID)
return nil, err
}
if block.GasLimit() != meta.GasLimit+consensus.AnchorV3GasLimit {
err = fmt.Errorf("gas limit mismatch: %d != %d", block.GasLimit(), meta.GasLimit+consensus.AnchorV3GasLimit)
return nil, err
}
if block.Time() != meta.Timestamp {
err = fmt.Errorf("timestamp mismatch: %d != %d", block.Time(), meta.Timestamp)
return nil, err
}
if !bytes.Equal(block.Extra(), meta.ExtraData) {
err = fmt.Errorf("extra data mismatch: %s != %s", block.Extra(), meta.ExtraData)
return nil, err
}
if block.BaseFee().Cmp(meta.BaseFee) != 0 {
err = fmt.Errorf("base fee mismatch: %s != %s", block.BaseFee(), meta.BaseFee)
return nil, err
}
if block.Withdrawals().Len() != 0 {
err = fmt.Errorf("withdrawals mismatch: %d != 0", block.Withdrawals().Len())
return nil, err
// If the payload ID matches, it means this block is already in the canonical chain.
if l1Origin.BuildPayloadArgsID != [8]byte{} && !bytes.Equal(l1Origin.BuildPayloadArgsID[:], id[:]) {
return nil, fmt.Errorf(
"payload ID for block %d mismatch, l1Origin payload id: %s, current payload id %s",
blockID,
engine.PayloadID(l1Origin.BuildPayloadArgsID),
id,
)
}

return block.Header(), nil
Expand Down Expand Up @@ -460,6 +426,7 @@ func assembleCreateExecutionPayloadMetaPacaya(
}, anchorTx, nil
}

// updateL1OriginForBatch updates the L1 origin for the given batch of blocks.
func updateL1OriginForBatch(
ctx context.Context,
rpc *rpc.Client,
Expand Down Expand Up @@ -489,9 +456,19 @@ func updateL1OriginForBatch(
L1BlockHeight: meta.GetRawBlockHeight(),
L1BlockHash: meta.GetRawBlockHash(),
}
// Fetch the original L1Origin to get the BuildPayloadArgsID.
originalL1Origin, err := rpc.L2.L1OriginByID(ctx, blockID)
if err != nil && !errors.Is(err, ethereum.NotFound) {
return fmt.Errorf("failed to get L1Origin by ID %d: %w", blockID, err)
}
// If L1Origin is not found, it means this block is synced from beacon sync,
// and we also won't set the `BuildPayloadArgsID` value.
if originalL1Origin != nil {
l1Origin.BuildPayloadArgsID = originalL1Origin.BuildPayloadArgsID
}

if _, err := rpc.L2Engine.UpdateL1Origin(ctx, l1Origin); err != nil {
return fmt.Errorf("failed to update L1 origin: %w", err)
return fmt.Errorf("failed to update L1Origin: %w", err)
}

// If this is the most recent block, update the HeadL1Origin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ 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/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
"github.com/holiman/uint256"

"github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/metadata"
Expand Down Expand Up @@ -138,11 +140,11 @@ func (i *BlocksInserterPacaya) InsertBlocks(
"beaconSyncTriggered", i.progressTracker.Triggered(),
)

// If this is the first block in the batch, we check if the whole batch has been preconfirmed by
// trying to fetch the last block header from L2 EE. If it is preconfirmed, we can skip the rest of the blocks,
// and only update the L1Origin in L2 EE for each block.
// If this is the first block in the batch, we check if the whole batch has been inserted by
// trying to fetch the last block header from L2 EE. If it is known in canonical,
// we can skip the rest of the blocks, and only update the L1Origin in L2 EE for each block.
if j == 0 {
lastBlockHeader, err := isBatchPreconfirmed(
lastBlockHeader, err := isKnownCanonicalBatch(
ctx,
i.rpc,
i.anchorConstructor,
Expand All @@ -152,10 +154,10 @@ func (i *BlocksInserterPacaya) InsertBlocks(
parent,
)
if err != nil {
log.Debug("Failed to check if batch is preconfirmed", "batchID", meta.GetBatchID(), "err", err)
log.Debug("Failed to check if batch is in canonical chain already", "batchID", meta.GetBatchID(), "err", err)
} else if lastBlockHeader != nil {
log.Info(
"🧬 The batch is preconfirmed",
"🧬 Known batch in canonical chain",
"batchID", meta.GetBatchID(),
"lastBlockID", meta.GetLastBlockID(),
"lastBlockHash", lastBlockHeader.Hash(),
Expand Down Expand Up @@ -315,7 +317,18 @@ func (i *BlocksInserterPacaya) insertPreconfBlockFromExecutionPayload(
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 (
txListHash = crypto.Keccak256Hash(executableData.Transactions[0])
args = &miner.BuildPayloadArgs{
Parent: executableData.ParentHash,
Timestamp: uint64(executableData.Timestamp),
FeeRecipient: executableData.FeeRecipient,
Random: common.Hash(executableData.PrevRandao),
Withdrawals: make([]*types.Withdrawal, 0),
Version: engine.PayloadV2,
TxListHash: &txListHash,
}
)
var u256BaseFee = uint256.Int(executableData.BaseFeePerGas)
payload, err := createExecutionPayloadsAndSetHead(
ctx,
Expand All @@ -329,10 +342,11 @@ func (i *BlocksInserterPacaya) insertPreconfBlockFromExecutionPayload(
Timestamp: uint64(executableData.Timestamp),
ParentHash: executableData.ParentHash,
L1Origin: &rawdb.L1Origin{
BlockID: new(big.Int).SetUint64(uint64(executableData.BlockNumber)),
L2BlockHash: common.Hash{}, // Will be set by taiko-geth.
L1BlockHeight: nil,
L1BlockHash: common.Hash{},
BlockID: new(big.Int).SetUint64(uint64(executableData.BlockNumber)),
L2BlockHash: common.Hash{}, // Will be set by taiko-geth.
L1BlockHeight: nil,
L1BlockHash: common.Hash{},
BuildPayloadArgsID: args.Id(),
},
BaseFee: u256BaseFee.ToBig(),
Withdrawals: make([]*types.Withdrawal, 0),
Expand Down