Skip to content

Commit 50b7853

Browse files
tac0turtleclaude
andauthored
feat: implement custom consensus for Rollkit to allow same timestamps (#19)
* feat: implement custom consensus for Rollkit to allow same timestamps This implements a custom consensus wrapper that allows multiple blocks to have the same timestamp, which is required for Rollkit's operation. The key changes: - Add RollkitConsensus that wraps EthBeaconConsensus - Override validate_header_against_parent to allow header.timestamp >= parent.timestamp - Integrate with node builder using RollkitConsensusBuilder - Add comprehensive unit tests for timestamp validation This addresses issue #15 where Rollkit needs to submit multiple blocks with the same timestamp. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * minimize * take into account proper validation and double check if the time is actaully in the past * address clippy * fix cargo check * fix test case * add commnet --------- Co-authored-by: Claude <[email protected]>
1 parent 561077e commit 50b7853

File tree

8 files changed

+327
-7
lines changed

8 files changed

+327
-7
lines changed

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,12 @@ reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth.git", version
6767
reth_ethereum_primitives = { git = "https://github.com/paradigmxyz/reth.git", version = "1.5.0" }
6868

6969

70-
# Test dependencies
70+
# Consensus dependencies
7171
reth-consensus = { git = "https://github.com/paradigmxyz/reth.git", version = "1.5.0", default-features = false }
72+
reth-consensus-common = { git = "https://github.com/paradigmxyz/reth.git", version = "1.5.0", default-features = false }
73+
reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth.git", version = "1.5.0", default-features = false }
74+
75+
# Test dependencies
7276
reth-testing-utils = { git = "https://github.com/paradigmxyz/reth.git", version = "1.5.0", default-features = false }
7377
reth-db = { git = "https://github.com/paradigmxyz/reth.git", version = "1.5.0", default-features = false }
7478
reth-tasks = { git = "https://github.com/paradigmxyz/reth.git", version = "1.5.0", default-features = false }

bin/lumen/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ reth-cli-util.workspace = true
2323
reth-ethereum-cli.workspace = true
2424
reth-ethereum = { workspace = true, features = ["node", "cli", "pool"] }
2525
reth-node-builder.workspace = true
26+
reth-node-api.workspace = true
2627
reth-chainspec.workspace = true
2728
reth-primitives-traits.workspace = true
2829
reth-engine-local.workspace = true
@@ -32,6 +33,8 @@ reth-payload-builder.workspace = true
3233
reth-revm.workspace = true
3334
reth-provider.workspace = true
3435
reth-trie-db.workspace = true
36+
reth-consensus.workspace = true
37+
reth-ethereum-primitives.workspace = true
3538

3639
# Alloy dependencies
3740
alloy-network.workspace = true

bin/lumen/src/main.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use alloy_rpc_types::engine::{
1717
use clap::Parser;
1818
use lumen_rollkit::{
1919
config::RollkitConfig,
20+
consensus::RollkitConsensusBuilder,
2021
rpc::txpool::{RollkitTxpoolApiImpl, RollkitTxpoolApiServer},
2122
};
2223
use reth_ethereum::{
@@ -28,10 +29,7 @@ use reth_ethereum::{
2829
rpc::RpcAddOns,
2930
Node, NodeAdapter, NodeComponentsBuilder,
3031
},
31-
node::{
32-
EthereumConsensusBuilder, EthereumExecutorBuilder, EthereumNetworkBuilder,
33-
EthereumPoolBuilder,
34-
},
32+
node::{EthereumExecutorBuilder, EthereumNetworkBuilder, EthereumPoolBuilder},
3533
EthereumEthApiBuilder,
3634
},
3735
primitives::SealedBlock,
@@ -127,7 +125,7 @@ where
127125
BasicPayloadServiceBuilder<RollkitPayloadBuilderBuilder>,
128126
EthereumNetworkBuilder,
129127
EthereumExecutorBuilder,
130-
EthereumConsensusBuilder,
128+
RollkitConsensusBuilder,
131129
>;
132130
type AddOns = RollkitNodeAddOns<
133131
NodeAdapter<N, <Self::ComponentsBuilder as NodeComponentsBuilder<N>>::Components>,
@@ -142,7 +140,7 @@ where
142140
RollkitPayloadBuilderBuilder::new(&self.args),
143141
))
144142
.network(EthereumNetworkBuilder::default())
145-
.consensus(EthereumConsensusBuilder::default())
143+
.consensus(RollkitConsensusBuilder::default())
146144
}
147145

148146
fn add_ons(&self) -> Self::AddOns {

crates/rollkit/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ reth-primitives.workspace = true
1515
reth-primitives-traits.workspace = true
1616
reth-engine-primitives.workspace = true
1717
reth-transaction-pool.workspace = true
18+
reth-consensus.workspace = true
19+
reth-consensus-common.workspace = true
20+
reth-ethereum-consensus.workspace = true
21+
reth-chainspec.workspace = true
22+
reth-node-api.workspace = true
23+
reth-ethereum = { workspace = true, features = ["node-api", "node"] }
24+
reth-ethereum-primitives.workspace = true
25+
reth-execution-types.workspace = true
1826

1927
# Alloy dependencies
2028
alloy-rpc-types-engine.workspace = true
@@ -31,6 +39,7 @@ async-trait.workspace = true
3139
jsonrpsee = { workspace = true, features = ["server", "macros"] }
3240
jsonrpsee-core.workspace = true
3341
jsonrpsee-proc-macros.workspace = true
42+
eyre.workspace = true
3443

3544
[dev-dependencies]
3645
serde_json.workspace = true

crates/rollkit/src/consensus.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//! Rollkit custom consensus implementation that allows same timestamps across blocks.
2+
3+
use reth_chainspec::ChainSpec;
4+
use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator};
5+
use reth_consensus_common::validation::validate_body_against_header;
6+
use reth_ethereum::node::builder::{components::ConsensusBuilder, BuilderContext};
7+
use reth_ethereum_consensus::EthBeaconConsensus;
8+
use reth_ethereum_primitives::{Block, BlockBody, EthPrimitives, Receipt};
9+
use reth_execution_types::BlockExecutionResult;
10+
use reth_node_api::{FullNodeTypes, NodeTypes};
11+
use reth_primitives::{RecoveredBlock, SealedBlock, SealedHeader};
12+
use std::sync::Arc;
13+
14+
/// Builder for `RollkitConsensus`
15+
#[derive(Debug, Default, Clone)]
16+
#[non_exhaustive]
17+
pub struct RollkitConsensusBuilder;
18+
19+
impl RollkitConsensusBuilder {
20+
/// Create a new `RollkitConsensusBuilder`
21+
pub const fn new() -> Self {
22+
Self
23+
}
24+
25+
/// Build the consensus implementation
26+
pub fn build(chain_spec: Arc<ChainSpec>) -> Arc<RollkitConsensus> {
27+
Arc::new(RollkitConsensus::new(chain_spec))
28+
}
29+
}
30+
31+
impl<Node> ConsensusBuilder<Node> for RollkitConsensusBuilder
32+
where
33+
Node: FullNodeTypes,
34+
Node::Types: NodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>,
35+
{
36+
type Consensus = Arc<dyn FullConsensus<EthPrimitives, Error = ConsensusError>>;
37+
38+
async fn build_consensus(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
39+
Ok(Arc::new(RollkitConsensus::new(ctx.chain_spec())) as Self::Consensus)
40+
}
41+
}
42+
43+
/// Rollkit consensus implementation that allows blocks with the same timestamp.
44+
///
45+
/// This consensus implementation wraps the standard Ethereum beacon consensus
46+
/// but modifies the timestamp validation to allow multiple blocks to have the
47+
/// same timestamp, which is required for Rollkit's operation.
48+
#[derive(Debug, Clone)]
49+
pub struct RollkitConsensus {
50+
/// Inner Ethereum beacon consensus for standard validation
51+
inner: EthBeaconConsensus<ChainSpec>,
52+
}
53+
54+
impl RollkitConsensus {
55+
/// Create a new Rollkit consensus instance
56+
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
57+
let inner = EthBeaconConsensus::new(chain_spec);
58+
Self { inner }
59+
}
60+
}
61+
62+
impl HeaderValidator for RollkitConsensus {
63+
fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> {
64+
// Use inner consensus for basic header validation
65+
self.inner.validate_header(header)
66+
}
67+
68+
fn validate_header_against_parent(
69+
&self,
70+
header: &SealedHeader,
71+
parent: &SealedHeader,
72+
) -> Result<(), ConsensusError> {
73+
match self.inner.validate_header_against_parent(header, parent) {
74+
Ok(()) => Ok(()),
75+
// upstream the check is that its greater than the parent's timestamp, if not we get
76+
// TimestampIsInPast we check if the timestamp is equal to the parent's timestamp, if so we
77+
// allow it
78+
Err(ConsensusError::TimestampIsInPast { .. }) => {
79+
if header.timestamp == parent.timestamp {
80+
Ok(())
81+
} else {
82+
Err(ConsensusError::TimestampIsInPast {
83+
parent_timestamp: parent.timestamp,
84+
timestamp: header.timestamp,
85+
})
86+
}
87+
}
88+
Err(e) => Err(e),
89+
}
90+
}
91+
}
92+
93+
impl Consensus<Block> for RollkitConsensus {
94+
type Error = ConsensusError;
95+
96+
fn validate_body_against_header(
97+
&self,
98+
body: &BlockBody,
99+
header: &SealedHeader,
100+
) -> Result<(), Self::Error> {
101+
validate_body_against_header(body, header.header())
102+
}
103+
104+
fn validate_block_pre_execution(&self, block: &SealedBlock) -> Result<(), Self::Error> {
105+
// Use inner consensus for pre-execution validation
106+
self.inner.validate_block_pre_execution(block)
107+
}
108+
}
109+
110+
impl FullConsensus<EthPrimitives> for RollkitConsensus {
111+
fn validate_block_post_execution(
112+
&self,
113+
block: &RecoveredBlock<Block>,
114+
result: &BlockExecutionResult<Receipt>,
115+
) -> Result<(), ConsensusError> {
116+
<EthBeaconConsensus<ChainSpec> as FullConsensus<EthPrimitives>>::validate_block_post_execution(&self.inner, block, result)
117+
}
118+
}

crates/rollkit/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! This crate provides Rollkit-specific functionality including:
44
//! - Custom payload attributes for Rollkit
55
//! - Rollkit-specific types and traits
6+
//! - Custom consensus implementation
67
78
/// Rollkit-specific types and related definitions.
89
pub mod types;
@@ -13,9 +14,13 @@ pub mod config;
1314
/// RPC modules for Rollkit functionality.
1415
pub mod rpc;
1516

17+
/// Custom consensus implementation for Rollkit.
18+
pub mod consensus;
19+
1620
#[cfg(test)]
1721
mod tests;
1822

1923
// Re-export public types
2024
pub use config::{RollkitConfig, DEFAULT_MAX_TXPOOL_BYTES};
25+
pub use consensus::{RollkitConsensus, RollkitConsensusBuilder};
2126
pub use types::{PayloadAttributesError, RollkitPayloadAttributes};

0 commit comments

Comments
 (0)