Skip to content
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
51 changes: 51 additions & 0 deletions eth/catalyst/l2_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,54 @@ func (api *l2ConsensusAPI) SetBlockTags(safeBlockHash common.Hash, finalizedBloc

return nil
}

// AssembleL2BlockV2 assembles a new L2 block based on parent hash.
// This differs from AssembleL2Block which uses block number.
// Using parent hash allows building on any parent block, enabling future reorg support.
func (api *l2ConsensusAPI) AssembleL2BlockV2(parentHash common.Hash, txs [][]byte) (*ExecutableL2Data, error) {
log.Debug("AssembleL2BlockV2", "parentHash", parentHash.Hex())

// Get parent block by hash
parent := api.eth.BlockChain().GetHeaderByHash(parentHash)
if parent == nil {
return nil, fmt.Errorf("parent block not found: %s", parentHash.Hex())
}

// Decode transactions
transactions := make(types.Transactions, 0, len(txs))
for i, otx := range txs {
var tx types.Transaction
if err := tx.UnmarshalBinary(otx); err != nil {
return nil, fmt.Errorf("transaction %d is not valid: %v", i, err)
}
transactions = append(transactions, &tx)
}

start := time.Now()
newBlockResult, err := api.eth.Miner().BuildBlock(parentHash, time.Now(), transactions)
if err != nil {
return nil, err
}

procTime := time.Since(start)
withdrawTrieRoot := api.writeVerified(newBlockResult.State, newBlockResult.Block, newBlockResult.Receipts, procTime)

return &ExecutableL2Data{
ParentHash: newBlockResult.Block.ParentHash(),
Number: newBlockResult.Block.NumberU64(),
Miner: newBlockResult.Block.Coinbase(),
Timestamp: newBlockResult.Block.Time(),
GasLimit: newBlockResult.Block.GasLimit(),
BaseFee: newBlockResult.Block.BaseFee(),
Transactions: encodeTransactions(newBlockResult.Block.Transactions()),

StateRoot: newBlockResult.Block.Root(),
GasUsed: newBlockResult.Block.GasUsed(),
ReceiptRoot: newBlockResult.Block.ReceiptHash(),
LogsBloom: newBlockResult.Block.Bloom().Bytes(),
NextL1MessageIndex: newBlockResult.Block.Header().NextL1MsgIndex,
WithdrawTrieRoot: withdrawTrieRoot,

Hash: newBlockResult.Block.Hash(),
}, nil
}
18 changes: 18 additions & 0 deletions ethclient/authclient/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,21 @@ func (ec *Client) AppendBlsSignature(ctx context.Context, batchHash common.Hash,
func (ec *Client) SetBlockTags(ctx context.Context, safeBlockHash common.Hash, finalizedBlockHash common.Hash) error {
return ec.c.CallContext(ctx, nil, "engine_setBlockTags", safeBlockHash, finalizedBlockHash)
}

// AssembleL2BlockV2 assembles a L2 Block based on parent hash.
// This differs from AssembleL2Block which uses block number.
// Using parent hash allows building on any parent block, enabling future reorg support.
func (ec *Client) AssembleL2BlockV2(ctx context.Context, parentHash common.Hash, transactions types.Transactions) (*catalyst.ExecutableL2Data, error) {
txs := make([][]byte, 0, len(transactions))
for i, tx := range transactions {
bz, err := tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal tx, index: %d, error: %v", i, err)
}
txs = append(txs, bz)
}
var result catalyst.ExecutableL2Data
err := ec.c.CallContext(ctx, &result, "engine_assembleL2BlockV2", parentHash, txs)
return &result, err
}