Feat: pbft to rollup sequencer, include P2P & block produce#274
Feat: pbft to rollup sequencer, include P2P & block produce#274tomatoishealthy wants to merge 1 commit intomainfrom
Conversation
📝 WalkthroughWalkthroughThe changes introduce a new V2 API method for L2 block assembly ( Changes
Sequence DiagramsequenceDiagram
participant Client as Client App
participant EngineAPI as Engine API<br/>(ethclient)
participant ConsensusAPI as Consensus API<br/>(l2_api)
participant Miner as Miner
participant StateDB as State Manager
Client->>EngineAPI: AssembleL2BlockV2(parentHash, txs)
EngineAPI->>EngineAPI: Marshal transactions to binary
EngineAPI->>ConsensusAPI: engine_assembleL2BlockV2 RPC call
ConsensusAPI->>ConsensusAPI: Log operation start
ConsensusAPI->>StateDB: Fetch parent header by hash
alt Parent not found
ConsensusAPI-->>EngineAPI: Error: parent not found
else Parent found
ConsensusAPI->>ConsensusAPI: Decode transactions
alt Decode error
ConsensusAPI-->>EngineAPI: Error: decode failed
else Decode success
ConsensusAPI->>Miner: Build block with parent & txs
Miner->>StateDB: Execute transactions & compute state root
Miner-->>ConsensusAPI: Built block with metadata
ConsensusAPI->>ConsensusAPI: Populate block fields<br/>(receipts, logs, gas, fees, roots)
ConsensusAPI->>ConsensusAPI: Compute withdraw trie root
ConsensusAPI->>ConsensusAPI: Store execution results
ConsensusAPI->>ConsensusAPI: Log completion & metrics
ConsensusAPI-->>EngineAPI: ExecutableL2Data payload
end
end
EngineAPI-->>Client: ExecutableL2Data or error
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
ethclient/authclient/engine.go (1)
60-77: LGTM! Implementation is correct.The method correctly implements the parent-hash-based assembly flow, properly marshaling transactions and calling the corresponding RPC endpoint.
The transaction marshaling logic (lines 65-72) is duplicated from
AssembleL2Block(lines 16-23). Consider extracting this to a helper function to reduce duplication:🔎 Optional refactor to extract transaction marshaling
+// marshalTransactions converts a slice of transactions to binary format +func marshalTransactions(transactions types.Transactions) ([][]byte, 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) + } + return txs, nil +} + // AssembleL2Block assembles L2 Block used for L2 sequencer to propose a block in L2 consensus progress func (ec *Client) AssembleL2Block(ctx context.Context, number *big.Int, 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) - } + txs, err := marshalTransactions(transactions) + if err != nil { + return nil, err + } var result catalyst.ExecutableL2Data - err := ec.c.CallContext(ctx, &result, "engine_assembleL2Block", &catalyst.AssembleL2BlockParams{ + err = ec.c.CallContext(ctx, &result, "engine_assembleL2Block", &catalyst.AssembleL2BlockParams{ Number: number.Uint64(), Transactions: txs, }) return &result, err }Then apply the same helper to
AssembleL2BlockV2: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) - } + txs, err := marshalTransactions(transactions) + if err != nil { + return nil, err + } var result catalyst.ExecutableL2Data - err := ec.c.CallContext(ctx, &result, "engine_assembleL2BlockV2", parentHash, txs) + err = ec.c.CallContext(ctx, &result, "engine_assembleL2BlockV2", parentHash, txs) return &result, err }eth/catalyst/l2_api.go (1)
364-413: LGTM! Implementation correctly enables parent-hash-based block assembly.The method properly handles parent lookup, transaction decoding, block building, and result caching. The nil check for the parent header (lines 372-374) prevents potential panics.
The implementation duplicates significant logic from
AssembleL2Block(lines 67-114), including transaction decoding (lines 377-384 vs 75-82) andExecutableL2Dataconstruction (lines 395-412 vs 96-113). Consider extracting shared logic to reduce maintenance burden:🔎 Recommended refactor to extract common logic
Add helper methods to reduce duplication:
// decodeTransactionsList decodes a slice of binary-encoded transactions func decodeTransactionsList(encodedTxs [][]byte) (types.Transactions, error) { transactions := make(types.Transactions, 0, len(encodedTxs)) for i, otx := range encodedTxs { 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) } return transactions, nil } // buildExecutableL2Data constructs ExecutableL2Data from a block build result func (api *l2ConsensusAPI) buildExecutableL2Data(newBlockResult *miner.BlockResult, procTime time.Duration) *ExecutableL2Data { 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(), } }Then refactor both methods:
func (api *l2ConsensusAPI) AssembleL2Block(params AssembleL2BlockParams) (*ExecutableL2Data, error) { log.Info("Assembling block", "block number", params.Number) parent := api.eth.BlockChain().CurrentHeader() expectedBlockNumber := parent.Number.Uint64() + 1 if params.Number != expectedBlockNumber { log.Warn("Cannot assemble block with discontinuous block number", "expected number", expectedBlockNumber, "actual number", params.Number) return nil, fmt.Errorf("cannot assemble block with discontinuous block number %d, expected number is %d", params.Number, expectedBlockNumber) } - transactions := make(types.Transactions, 0, len(params.Transactions)) - for i, otx := range params.Transactions { - 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) - } + transactions, err := decodeTransactionsList(params.Transactions) + if err != nil { + return nil, err + } start := time.Now() newBlockResult, err := api.eth.Miner().BuildBlock(parent.Hash(), 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 + return api.buildExecutableL2Data(newBlockResult, procTime), nil }func (api *l2ConsensusAPI) AssembleL2BlockV2(parentHash common.Hash, txs [][]byte) (*ExecutableL2Data, error) { log.Debug("AssembleL2BlockV2", "parentHash", parentHash.Hex()) parent := api.eth.BlockChain().GetHeaderByHash(parentHash) if parent == nil { return nil, fmt.Errorf("parent block not found: %s", parentHash.Hex()) } - 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) - } + transactions, err := decodeTransactionsList(txs) + if err != nil { + return nil, err + } 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 + return api.buildExecutableL2Data(newBlockResult, procTime), nil }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
eth/catalyst/l2_api.goethclient/authclient/engine.go
🧰 Additional context used
🧬 Code graph analysis (1)
ethclient/authclient/engine.go (4)
ethclient/authclient/client.go (1)
Client(10-12)core/types/transaction.go (1)
Transactions(615-615)eth/catalyst/api_types.go (1)
ExecutableL2Data(89-111)eth/catalyst/gen_l2_ed.go (2)
ExecutableL2Data(18-33)ExecutableL2Data(59-74)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (go)
1. Purpose or design rationale of this PR
Migrate PBFT consensus to a single sequencer and add new block-handling APIs in geth.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.