-
Notifications
You must be signed in to change notification settings - Fork 14
Feat: pbft to rollup sequencer, include P2P & block produce #274
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
base: main
Are you sure you want to change the base?
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.