Skip to content

Commit 132fcce

Browse files
SozinMclaude
andcommitted
arc-rbuilder: source chain spec from the node; E2E-verified block building
- rbuilder now uses the node's chain spec instead of the config 'chain' value (a mismatch made every order fail simulation with InvalidChainId) - log failed order simulations (trace) and non-critically discarded engine-payload blocks (debug) — both were previously silent - sim-path regression test executing a signed tx through the ctx EVM Verified end-to-end against arc-localdev driving the engine API like Malachite: FCU(attrs) -> rbuilder builds (mempool txs + payout) -> getPayload returns rbuilder's block -> newPayload validates it VALID (arc executor re-checks state root, ADR-004 extra_data base fee, SystemAccounting gas values) -> head advances to rbuilder's block. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d5b15a1 commit 132fcce

5 files changed

Lines changed: 136 additions & 7 deletions

File tree

crates/arc-rbuilder/config-arc-example.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ error_storage_path = "/tmp/rbuilder.error.sqlite"
1818
# env: COINBASE_SECRET_KEY
1919
coinbase_secret_key = "env:COINBASE_SECRET_KEY"
2020

21-
# chain is unused by arc-rbuilder itself (the node's --chain flag wins) but
22-
# must parse; use the same network here.
23-
chain = "arc-testnet"
21+
# arc-rbuilder always builds for the chain the node runs (--chain flag); this
22+
# value only needs to parse. Keep it matching the node anyway.
23+
chain = "arc-localdev"
2424

2525
full_telemetry_server_port = 6060
2626
redacted_telemetry_server_port = 6061
@@ -36,6 +36,10 @@ simulation_threads = 4
3636

3737
live_builders = ["mp-ordering", "mgp-ordering"]
3838

39+
# Required: sparse trie root hash (parallel impl currently disallowed).
40+
root_hash_use_sparse_trie = true
41+
root_hash_compare_sparse_trie = false
42+
3943
[[builders]]
4044
name = "mgp-ordering"
4145
algo = "ordering-builder"

crates/arc-rbuilder/src/main.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ fn spawn_rbuilder<P, V, T, S>(
154154
+ StorageSettingsCache,
155155
> + reth_provider::StateProviderFactory
156156
+ HeaderProvider<Header = Header>
157-
+ reth_provider::ChainSpecProvider
157+
+ reth_provider::ChainSpecProvider<ChainSpec = rbuilder::chain::ChainSpec>
158158
+ Clone
159159
+ 'static,
160160
<P as ChainSpecProvider>::ChainSpec: EthereumHardforks,
@@ -196,7 +196,7 @@ where
196196
+ StorageSettingsCache,
197197
> + reth_provider::StateProviderFactory
198198
+ HeaderProvider<Header = Header>
199-
+ reth_provider::ChainSpecProvider
199+
+ reth_provider::ChainSpecProvider<ChainSpec = rbuilder::chain::ChainSpec>
200200
+ Clone
201201
+ 'static,
202202
<P as ChainSpecProvider>::ChainSpec: EthereumHardforks,
@@ -228,6 +228,10 @@ where
228228

229229
let blocklist_provider = base_config.blocklist_provider(cancel.clone()).await?;
230230

231+
// The chain spec must be the node's: rbuilder simulates and builds for the
232+
// chain the node runs, regardless of what the rbuilder config says.
233+
let node_chain_spec = reth_provider::ChainSpecProvider::chain_spec(&provider);
234+
231235
let state_provider_factory = StateProviderFactoryFromRethProvider::new(
232236
provider,
233237
base_config.live_root_hash_config()?,
@@ -250,7 +254,8 @@ where
250254
config.live_builders()?,
251255
base_config.max_order_execution_duration_warning(),
252256
);
253-
let live_builder = live_builder.with_builders(builders);
257+
let mut live_builder = live_builder.with_builders(builders);
258+
live_builder.chain_chain_spec = node_chain_spec;
254259

255260
live_builder.connect_to_transaction_pool(pool).await?;
256261
live_builder

crates/rbuilder/src/building/arc_support.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,71 @@ fn base_fee(ctx: &BlockBuildingContext) -> u64 {
274274
ctx.evm_env.block_env.basefee
275275
}
276276

277+
#[cfg(test)]
278+
pub(crate) mod tests_helpers {
279+
use super::*;
280+
use crate::{
281+
building::{builders::mock_block_building_helper::MockRootHasher, BlockBuildingContext},
282+
live_builder::order_input::mempool_txs_detector::MempoolTxsDetector,
283+
utils::Signer,
284+
};
285+
use alloy_primitives::{Address, U256};
286+
use alloy_rpc_types_beacon::events::{PayloadAttributesData, PayloadAttributesEvent};
287+
use alloy_rpc_types_engine::PayloadAttributes;
288+
use std::sync::Arc;
289+
290+
/// BlockBuildingContext for block 1 on the arc localdev chain with a
291+
/// parent extra_data encoded base fee of 12_345.
292+
pub(crate) fn make_arc_ctx() -> (BlockBuildingContext, Signer) {
293+
let chain_spec = chain::chain_spec_for_testing();
294+
let mut parent = chain::inner_chain_spec(&chain_spec)
295+
.sealed_genesis_header()
296+
.header()
297+
.clone();
298+
parent.extra_data = encode_base_fee_to_bytes(12_345);
299+
let parent_hash = parent.hash_slow();
300+
301+
let attributes = PayloadAttributesEvent {
302+
version: "arc".to_string(),
303+
data: PayloadAttributesData {
304+
proposal_slot: 1,
305+
parent_block_root: Default::default(),
306+
parent_block_number: parent.number,
307+
parent_block_hash: parent_hash,
308+
proposer_index: 0,
309+
payload_attributes: PayloadAttributes {
310+
timestamp: parent.timestamp + 1,
311+
prev_randao: Default::default(),
312+
suggested_fee_recipient: Address::random(),
313+
withdrawals: Some(vec![]),
314+
parent_beacon_block_root: Some(parent_hash),
315+
},
316+
},
317+
};
318+
319+
let signer = Signer::random();
320+
let ctx = BlockBuildingContext::from_attributes(
321+
attributes,
322+
&parent,
323+
signer.clone(),
324+
chain_spec,
325+
Default::default(),
326+
Some(55_000_000),
327+
vec![],
328+
None,
329+
Arc::new(MockRootHasher {}),
330+
0,
331+
false,
332+
true,
333+
U256::ZERO,
334+
Default::default(),
335+
Arc::new(MempoolTxsDetector::new()),
336+
)
337+
.unwrap();
338+
(ctx, signer)
339+
}
340+
}
341+
277342
#[cfg(test)]
278343
mod tests {
279344
use super::*;
@@ -358,6 +423,7 @@ mod tests {
358423

359424
assert_eq!(ctx.evm_env.block_env.basefee, 12_345);
360425
assert_eq!(ctx.evm_env.block_env.gas_limit, 55_000_000);
426+
assert_eq!(ctx.evm_env.cfg_env.chain_id, 1337);
361427
}
362428

363429
/// A plain value transfer through the rbuilder EVM factory must emit the
@@ -414,3 +480,52 @@ mod tests {
414480
);
415481
}
416482
}
483+
484+
#[cfg(test)]
485+
mod sim_repro_tests {
486+
use super::tests_helpers::make_arc_ctx;
487+
use crate::building::evm::EvmFactory as _;
488+
use alloy_consensus::TxEip1559;
489+
use alloy_primitives::{Address, TxKind, U256};
490+
use revm::{
491+
database::{CacheDB, EmptyDB},
492+
state::AccountInfo,
493+
};
494+
495+
/// Replicates the live simulation path: a signed EIP-1559 tx executed
496+
/// through ctx.evm_factory with the ctx evm_env.
497+
#[test]
498+
fn signed_tx_executes_through_ctx_evm() {
499+
let (ctx, signer) = make_arc_ctx();
500+
let recipient = Address::random();
501+
502+
let tx = signer
503+
.sign_tx(reth_primitives::Transaction::Eip1559(TxEip1559 {
504+
chain_id: 1337,
505+
nonce: 0,
506+
gas_limit: 21_000,
507+
max_fee_per_gas: 3_000_000_000,
508+
max_priority_fee_per_gas: 1_000_000_000,
509+
to: TxKind::Call(recipient),
510+
value: U256::from(1),
511+
..Default::default()
512+
}))
513+
.unwrap();
514+
515+
let mut db = CacheDB::new(EmptyDB::default());
516+
db.insert_account_info(
517+
signer.address,
518+
AccountInfo {
519+
balance: U256::from(10).pow(U256::from(18)),
520+
..Default::default()
521+
},
522+
);
523+
524+
let mut evm = ctx.evm_factory.create_evm(&mut db, ctx.evm_env.clone());
525+
let res = reth_evm::Evm::transact(&mut evm, &tx);
526+
match res {
527+
Ok(r) => assert!(r.result.is_success(), "tx failed: {:?}", r.result),
528+
Err(err) => panic!("transact error: {err:?}"),
529+
}
530+
}
531+
}

crates/rbuilder/src/live_builder/block_output/engine_payload_sink.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ impl UnfinishedBlockBuildingSink for EnginePayloadSink {
153153
Err(err) => {
154154
if err.is_critical() {
155155
error!(?err, "Failed to finalize block for engine payload");
156+
} else {
157+
tracing::debug!(?err, "Discarded block for engine payload");
156158
}
157159
return;
158160
}

crates/rbuilder/src/live_builder/simulation/sim_worker.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ pub fn run_sim_worker<P>(
107107

108108
true
109109
}
110-
OrderSimResult::Failed(_) => false,
110+
OrderSimResult::Failed(err) => {
111+
tracing::trace!(?order_id, ?err, "Order simulation failed");
112+
false
113+
}
111114
};
112115
telemetry::inc_simulated_orders(sim_ok);
113116
telemetry::inc_simulation_gas_used(sim_result.gas_used);

0 commit comments

Comments
 (0)