Skip to content

Commit 9397d29

Browse files
chore: split the main file into a few making it easier to read (#12)
* split the main file into a few making it easier to read * Update bin/lumen/src/attributes.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * address comments * pull out validator --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 44fe2dc commit 9397d29

File tree

5 files changed

+535
-483
lines changed

5 files changed

+535
-483
lines changed

bin/lumen/src/attributes.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use alloy_eips::{eip4895::Withdrawals, Decodable2718};
2+
use alloy_primitives::{Address, Bytes, B256};
3+
use alloy_rpc_types::{
4+
engine::{PayloadAttributes as EthPayloadAttributes, PayloadId},
5+
Withdrawal,
6+
};
7+
use reth_engine_local::payload::UnsupportedLocalAttributes;
8+
use reth_ethereum::{
9+
node::api::payload::{PayloadAttributes, PayloadBuilderAttributes},
10+
TransactionSigned,
11+
};
12+
use reth_payload_builder::EthPayloadBuilderAttributes;
13+
use serde::{Deserialize, Serialize};
14+
15+
use crate::error::RollkitEngineError;
16+
17+
/// Rollkit payload attributes that support passing transactions via Engine API
18+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
19+
pub struct RollkitEnginePayloadAttributes {
20+
/// Standard Ethereum payload attributes
21+
#[serde(flatten)]
22+
pub inner: EthPayloadAttributes,
23+
/// Transactions to be included in the payload (passed via Engine API)
24+
pub transactions: Option<Vec<Bytes>>,
25+
/// Optional gas limit for the payload
26+
#[serde(rename = "gasLimit")]
27+
pub gas_limit: Option<u64>,
28+
}
29+
30+
impl UnsupportedLocalAttributes for RollkitEnginePayloadAttributes {}
31+
32+
impl PayloadAttributes for RollkitEnginePayloadAttributes {
33+
fn timestamp(&self) -> u64 {
34+
self.inner.timestamp()
35+
}
36+
37+
fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
38+
self.inner.withdrawals()
39+
}
40+
41+
fn parent_beacon_block_root(&self) -> Option<B256> {
42+
self.inner.parent_beacon_block_root()
43+
}
44+
}
45+
46+
/// Rollkit payload builder attributes
47+
#[derive(Clone, Debug, PartialEq, Eq)]
48+
pub struct RollkitEnginePayloadBuilderAttributes {
49+
/// Ethereum payload builder attributes
50+
pub ethereum_attributes: EthPayloadBuilderAttributes,
51+
/// Decoded transactions from the Engine API
52+
pub transactions: Vec<TransactionSigned>,
53+
/// Gas limit for the payload
54+
pub gas_limit: Option<u64>,
55+
}
56+
57+
impl PayloadBuilderAttributes for RollkitEnginePayloadBuilderAttributes {
58+
type RpcPayloadAttributes = RollkitEnginePayloadAttributes;
59+
type Error = RollkitEngineError;
60+
61+
fn try_new(
62+
parent: B256,
63+
attributes: RollkitEnginePayloadAttributes,
64+
_version: u8,
65+
) -> Result<Self, Self::Error> {
66+
let ethereum_attributes = EthPayloadBuilderAttributes::new(parent, attributes.inner);
67+
68+
// Decode transactions from bytes if provided
69+
let transactions = attributes
70+
.transactions
71+
.unwrap_or_default()
72+
.into_iter()
73+
.map(|tx_bytes| {
74+
TransactionSigned::network_decode(&mut tx_bytes.as_ref())
75+
.map_err(|e| RollkitEngineError::InvalidTransactionData(e.to_string()))
76+
})
77+
.collect::<Result<Vec<_>, _>>()?;
78+
79+
Ok(Self {
80+
ethereum_attributes,
81+
transactions,
82+
gas_limit: attributes.gas_limit,
83+
})
84+
}
85+
86+
fn payload_id(&self) -> PayloadId {
87+
self.ethereum_attributes.id
88+
}
89+
90+
fn parent(&self) -> B256 {
91+
self.ethereum_attributes.parent
92+
}
93+
94+
fn timestamp(&self) -> u64 {
95+
self.ethereum_attributes.timestamp
96+
}
97+
98+
fn parent_beacon_block_root(&self) -> Option<B256> {
99+
self.ethereum_attributes.parent_beacon_block_root
100+
}
101+
102+
fn suggested_fee_recipient(&self) -> Address {
103+
self.ethereum_attributes.suggested_fee_recipient
104+
}
105+
106+
fn prev_randao(&self) -> B256 {
107+
self.ethereum_attributes.prev_randao
108+
}
109+
110+
fn withdrawals(&self) -> &Withdrawals {
111+
&self.ethereum_attributes.withdrawals
112+
}
113+
}

bin/lumen/src/builder.rs

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
use alloy_primitives::U256;
2+
use clap::Parser;
3+
use lumen_node::{RollkitPayloadBuilder, RollkitPayloadBuilderConfig};
4+
use lumen_rollkit::RollkitPayloadAttributes;
5+
use reth_basic_payload_builder::{
6+
BuildArguments, BuildOutcome, HeaderForPayload, PayloadBuilder, PayloadConfig,
7+
};
8+
use reth_ethereum::{
9+
chainspec::{ChainSpec, ChainSpecProvider},
10+
node::{
11+
api::{payload::PayloadBuilderAttributes, FullNodeTypes, NodeTypes},
12+
builder::{components::PayloadBuilderBuilder, BuilderContext},
13+
EthEvmConfig,
14+
},
15+
pool::{PoolTransaction, TransactionPool},
16+
primitives::Header,
17+
TransactionSigned,
18+
};
19+
use reth_payload_builder::{EthBuiltPayload, PayloadBuilderError};
20+
use reth_provider::HeaderProvider;
21+
use reth_revm::cached::CachedReads;
22+
use serde::{Deserialize, Serialize};
23+
use std::sync::Arc;
24+
use tracing::info;
25+
26+
use crate::{attributes::RollkitEnginePayloadBuilderAttributes, RollkitEngineTypes};
27+
28+
/// Rollkit-specific command line arguments
29+
#[derive(Debug, Clone, Parser, PartialEq, Eq, Serialize, Deserialize, Default)]
30+
pub struct RollkitArgs {
31+
/// Enable Rollkit mode for the node (enabled by default)
32+
#[arg(
33+
long = "rollkit.enable",
34+
default_value = "true",
35+
help = "Enable Rollkit integration for transaction processing via Engine API"
36+
)]
37+
pub enable_rollkit: bool,
38+
}
39+
40+
/// Rollkit payload service builder that integrates with the rollkit payload builder
41+
#[derive(Debug, Clone)]
42+
#[non_exhaustive]
43+
pub struct RollkitPayloadBuilderBuilder {
44+
config: RollkitPayloadBuilderConfig,
45+
}
46+
47+
impl RollkitPayloadBuilderBuilder {
48+
/// Create a new builder with rollkit args
49+
pub fn new(_args: &RollkitArgs) -> Self {
50+
let config = RollkitPayloadBuilderConfig {
51+
max_transactions: 1000,
52+
min_gas_price: 1_000_000_000, // 1 Gwei
53+
};
54+
info!("Created Rollkit payload builder with config: {:?}", config);
55+
Self { config }
56+
}
57+
}
58+
59+
impl Default for RollkitPayloadBuilderBuilder {
60+
fn default() -> Self {
61+
Self::new(&RollkitArgs::default())
62+
}
63+
}
64+
65+
/// The rollkit engine payload builder that integrates with the rollkit payload builder
66+
#[derive(Debug, Clone)]
67+
pub struct RollkitEnginePayloadBuilder<Pool, Client>
68+
where
69+
Pool: Clone,
70+
Client: Clone,
71+
{
72+
pub(crate) rollkit_builder: Arc<RollkitPayloadBuilder<Client>>,
73+
#[allow(dead_code)]
74+
pub(crate) pool: Pool,
75+
#[allow(dead_code)]
76+
pub(crate) config: RollkitPayloadBuilderConfig,
77+
}
78+
79+
impl<Node, Pool> PayloadBuilderBuilder<Node, Pool, EthEvmConfig> for RollkitPayloadBuilderBuilder
80+
where
81+
Node: FullNodeTypes<
82+
Types: NodeTypes<
83+
Payload = RollkitEngineTypes,
84+
ChainSpec = ChainSpec,
85+
Primitives = reth_ethereum::EthPrimitives,
86+
>,
87+
>,
88+
Pool: TransactionPool<Transaction: PoolTransaction<Consensus = TransactionSigned>>
89+
+ Unpin
90+
+ 'static,
91+
{
92+
type PayloadBuilder = RollkitEnginePayloadBuilder<Pool, Node::Provider>;
93+
94+
async fn build_payload_builder(
95+
self,
96+
ctx: &BuilderContext<Node>,
97+
pool: Pool,
98+
evm_config: EthEvmConfig,
99+
) -> eyre::Result<Self::PayloadBuilder> {
100+
let rollkit_builder = Arc::new(RollkitPayloadBuilder::new(
101+
Arc::new(ctx.provider().clone()),
102+
evm_config,
103+
));
104+
105+
Ok(RollkitEnginePayloadBuilder {
106+
rollkit_builder,
107+
pool,
108+
config: self.config,
109+
})
110+
}
111+
}
112+
113+
impl<Pool, Client> PayloadBuilder for RollkitEnginePayloadBuilder<Pool, Client>
114+
where
115+
Client: reth_ethereum::provider::StateProviderFactory
116+
+ ChainSpecProvider<ChainSpec = ChainSpec>
117+
+ HeaderProvider<Header = Header>
118+
+ Clone
119+
+ Send
120+
+ Sync
121+
+ 'static,
122+
Pool: TransactionPool<Transaction: PoolTransaction<Consensus = TransactionSigned>>,
123+
{
124+
type Attributes = RollkitEnginePayloadBuilderAttributes;
125+
type BuiltPayload = EthBuiltPayload;
126+
127+
fn try_build(
128+
&self,
129+
args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
130+
) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError> {
131+
let BuildArguments {
132+
cached_reads: _,
133+
config,
134+
cancel: _,
135+
best_payload,
136+
} = args;
137+
let PayloadConfig {
138+
parent_header,
139+
attributes,
140+
} = config;
141+
142+
info!(
143+
"Rollkit engine payload builder: building payload with {} transactions",
144+
attributes.transactions.len()
145+
);
146+
147+
// Convert Engine API attributes to Rollkit payload attributes
148+
let rollkit_attrs = RollkitPayloadAttributes::new(
149+
attributes.transactions.clone(),
150+
attributes.gas_limit,
151+
attributes.timestamp(),
152+
attributes.prev_randao(),
153+
attributes.suggested_fee_recipient(),
154+
attributes.parent(),
155+
parent_header.number + 1,
156+
);
157+
158+
// Build the payload using the rollkit payload builder - use spawn_blocking for async work
159+
let rollkit_builder = self.rollkit_builder.clone();
160+
let sealed_block = tokio::task::block_in_place(|| {
161+
tokio::runtime::Handle::current().block_on(rollkit_builder.build_payload(rollkit_attrs))
162+
})
163+
.map_err(PayloadBuilderError::other)?;
164+
165+
info!(
166+
"Rollkit engine payload builder: built block with {} transactions, gas used: {}",
167+
sealed_block.transaction_count(),
168+
sealed_block.gas_used
169+
);
170+
171+
// Convert to EthBuiltPayload
172+
let gas_used = sealed_block.gas_used;
173+
let built_payload = EthBuiltPayload::new(
174+
attributes.payload_id(), // Use the proper payload ID from attributes
175+
Arc::new(sealed_block),
176+
U256::from(gas_used), // Block gas used
177+
None, // No blob sidecar for rollkit
178+
);
179+
180+
if let Some(best) = best_payload {
181+
if built_payload.fees() <= best.fees() {
182+
return Ok(BuildOutcome::Aborted {
183+
fees: built_payload.fees(),
184+
cached_reads: CachedReads::default(),
185+
});
186+
}
187+
}
188+
189+
Ok(BuildOutcome::Better {
190+
payload: built_payload,
191+
cached_reads: CachedReads::default(),
192+
})
193+
}
194+
195+
fn build_empty_payload(
196+
&self,
197+
config: PayloadConfig<Self::Attributes, HeaderForPayload<Self::BuiltPayload>>,
198+
) -> Result<Self::BuiltPayload, PayloadBuilderError> {
199+
let PayloadConfig {
200+
parent_header,
201+
attributes,
202+
} = config;
203+
204+
info!("Rollkit engine payload builder: building empty payload");
205+
206+
// Create empty rollkit attributes (no transactions)
207+
let rollkit_attrs = RollkitPayloadAttributes::new(
208+
vec![],
209+
attributes.gas_limit,
210+
attributes.timestamp(),
211+
attributes.prev_randao(),
212+
attributes.suggested_fee_recipient(),
213+
attributes.parent(),
214+
parent_header.number + 1,
215+
);
216+
217+
// Build empty payload - use spawn_blocking for async work
218+
let rollkit_builder = self.rollkit_builder.clone();
219+
let sealed_block = tokio::task::block_in_place(|| {
220+
tokio::runtime::Handle::current().block_on(rollkit_builder.build_payload(rollkit_attrs))
221+
})
222+
.map_err(PayloadBuilderError::other)?;
223+
224+
let gas_used = sealed_block.gas_used;
225+
Ok(EthBuiltPayload::new(
226+
attributes.payload_id(),
227+
Arc::new(sealed_block),
228+
U256::from(gas_used),
229+
None,
230+
))
231+
}
232+
}

bin/lumen/src/error.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use lumen_rollkit::PayloadAttributesError;
2+
use thiserror::Error;
3+
4+
/// Custom error type used in payload attributes validation
5+
#[derive(Debug, Error)]
6+
pub enum RollkitEngineError {
7+
#[error("Invalid transaction data: {0}")]
8+
InvalidTransactionData(String),
9+
#[error("Gas limit exceeded")]
10+
GasLimitExceeded,
11+
#[error("Rollkit payload attributes error: {0}")]
12+
PayloadAttributes(#[from] PayloadAttributesError),
13+
}

0 commit comments

Comments
 (0)