Skip to content

Commit 87aa769

Browse files
committed
Add state root calculation on payload resolution
1 parent 38204c5 commit 87aa769

1 file changed

Lines changed: 181 additions & 31 deletions

File tree

  • crates/op-rbuilder/src/builders/flashblocks

crates/op-rbuilder/src/builders/flashblocks/payload.rs

Lines changed: 181 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,17 @@ use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes};
3434
use reth_optimism_forks::OpHardforks;
3535
use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes};
3636
use reth_optimism_primitives::{OpReceipt, OpTransactionSigned};
37+
use reth_payload_primitives::BuiltPayload;
3738
use reth_payload_util::BestPayloadTransactions;
3839
use reth_primitives_traits::RecoveredBlock;
3940
use reth_provider::{
4041
ExecutionOutcome, HashedPostStateProvider, ProviderError, StateRootProvider,
4142
StorageRootProvider,
4243
};
4344
use reth_revm::{
44-
State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention,
45+
State,
46+
database::StateProviderDatabase,
47+
db::{BundleState, states::bundle_state::BundleRetention},
4548
};
4649
use reth_transaction_pool::TransactionPool;
4750
use reth_trie::{HashedPostState, updates::TrieUpdates};
@@ -331,7 +334,7 @@ where
331334
async fn build_payload(
332335
&self,
333336
args: BuildArguments<OpPayloadBuilderAttributes<OpTransactionSigned>, OpBuiltPayload>,
334-
best_payload: BlockCell<OpBuiltPayload>,
337+
resolve_payload: BlockCell<OpBuiltPayload>,
335338
) -> Result<(), PayloadBuilderError> {
336339
let block_build_start_time = Instant::now();
337340
let BuildArguments {
@@ -400,25 +403,14 @@ where
400403
);
401404
};
402405

403-
let (payload, fb_payload) = build_block(
404-
&mut state,
405-
&ctx,
406-
&mut info,
407-
!disable_state_root || ctx.attributes().no_tx_pool, // need to calculate state root for CL sync
408-
)?;
409-
406+
// We should always calculate state root for fallback payload
407+
let (fallback_payload, fb_payload, bundle_state) =
408+
build_block(&mut state, &ctx, &mut info, true)?;
410409
self.built_fb_payload_tx
411-
.send(payload.clone())
410+
.send(fallback_payload.clone())
412411
.await
413412
.map_err(PayloadBuilderError::other)?;
414-
if let Err(e) = self.built_payload_tx.send(payload.clone()).await {
415-
warn!(
416-
target: "payload_builder",
417-
error = %e,
418-
"Failed to send updated payload"
419-
);
420-
}
421-
best_payload.set(payload);
413+
let mut best_payload = (fallback_payload.clone(), bundle_state);
422414

423415
info!(
424416
target: "payload_builder",
@@ -458,6 +450,14 @@ where
458450
.set(info.executed_transactions.len() as f64);
459451

460452
// return early since we don't need to build a block with transactions from the pool
453+
self.resolve_best_payload(
454+
&mut state,
455+
&ctx,
456+
best_payload,
457+
fallback_payload,
458+
&resolve_payload,
459+
)
460+
.await;
461461
return Ok(());
462462
}
463463
// We adjust our flashblocks timings based on time the fcu block building signal arrived
@@ -614,6 +614,14 @@ where
614614
ctx = ctx.with_cancel(new_fb_cancel);
615615
},
616616
_ = block_cancel.cancelled() => {
617+
self.resolve_best_payload(
618+
&mut state,
619+
&ctx,
620+
best_payload,
621+
fallback_payload,
622+
&resolve_payload,
623+
)
624+
.await;
617625
self.record_flashblocks_metrics(
618626
&ctx,
619627
&info,
@@ -637,6 +645,14 @@ where
637645
let _entered = fb_span.enter();
638646

639647
if ctx.flashblock_index() > ctx.target_flashblock_count() {
648+
self.resolve_best_payload(
649+
&mut state,
650+
&ctx,
651+
best_payload,
652+
fallback_payload,
653+
&resolve_payload,
654+
)
655+
.await;
640656
self.record_flashblocks_metrics(
641657
&ctx,
642658
&info,
@@ -656,13 +672,21 @@ where
656672
&state_provider,
657673
&mut best_txs,
658674
&block_cancel,
659-
&best_payload,
675+
&mut best_payload,
660676
&fb_span,
661677
)
662678
.await
663679
{
664680
Ok(Some(next_flashblocks_ctx)) => next_flashblocks_ctx,
665681
Ok(None) => {
682+
self.resolve_best_payload(
683+
&mut state,
684+
&ctx,
685+
best_payload,
686+
fallback_payload,
687+
&resolve_payload,
688+
)
689+
.await;
666690
self.record_flashblocks_metrics(
667691
&ctx,
668692
&info,
@@ -680,6 +704,14 @@ where
680704
ctx.block_number(),
681705
err
682706
);
707+
self.resolve_best_payload(
708+
&mut state,
709+
&ctx,
710+
best_payload,
711+
fallback_payload,
712+
&resolve_payload,
713+
)
714+
.await;
683715
return Err(PayloadBuilderError::Other(err.into()));
684716
}
685717
};
@@ -700,7 +732,7 @@ where
700732
state_provider: impl reth::providers::StateProvider + Clone,
701733
best_txs: &mut NextBestFlashblocksTxs<Pool>,
702734
block_cancel: &CancellationToken,
703-
best_payload: &BlockCell<OpBuiltPayload>,
735+
best_payload: &mut (OpBuiltPayload, BundleState),
704736
span: &tracing::Span,
705737
) -> eyre::Result<Option<FlashblocksExtraCtx>> {
706738
let flashblock_index = ctx.flashblock_index();
@@ -840,7 +872,7 @@ where
840872
ctx.metrics.invalid_built_blocks_count.increment(1);
841873
Err(err).wrap_err("failed to build payload")
842874
}
843-
Ok((new_payload, mut fb_payload)) => {
875+
Ok((new_payload, mut fb_payload, bundle_state)) => {
844876
fb_payload.index = flashblock_index;
845877
fb_payload.base = None;
846878

@@ -864,14 +896,7 @@ where
864896
.send(new_payload.clone())
865897
.await
866898
.wrap_err("failed to send built payload to handler")?;
867-
if let Err(e) = self.built_payload_tx.send(new_payload.clone()).await {
868-
warn!(
869-
target: "payload_builder",
870-
error = %e,
871-
"Failed to send updated payload"
872-
);
873-
}
874-
best_payload.set(new_payload);
899+
*best_payload = (new_payload, bundle_state);
875900

876901
// Record flashblock build duration
877902
ctx.metrics
@@ -925,6 +950,93 @@ where
925950
}
926951
}
927952

953+
async fn resolve_best_payload<
954+
DB: Database<Error = ProviderError> + std::fmt::Debug + AsRef<P>,
955+
P: StateRootProvider + HashedPostStateProvider + StorageRootProvider,
956+
>(
957+
&self,
958+
state: &mut State<DB>,
959+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
960+
best_payload: (OpBuiltPayload, BundleState),
961+
fallback_payload: OpBuiltPayload,
962+
resolve_payload: &BlockCell<OpBuiltPayload>,
963+
) {
964+
if resolve_payload.get().is_some() {
965+
return;
966+
}
967+
968+
let payload = match best_payload.0.block().header().state_root {
969+
B256::ZERO => {
970+
info!(target: "payload_builder", "Resolving payload with zero state root");
971+
self.resolve_zero_state_root(state, ctx, best_payload)
972+
.await
973+
.unwrap_or_else(|err| {
974+
warn!(
975+
target: "payload_builder",
976+
error = %err,
977+
"Failed to calculate state root, falling back to fallback payload"
978+
);
979+
fallback_payload
980+
})
981+
}
982+
_ => best_payload.0,
983+
};
984+
resolve_payload.set(payload);
985+
}
986+
987+
async fn resolve_zero_state_root<
988+
DB: Database<Error = ProviderError> + std::fmt::Debug + AsRef<P>,
989+
P: StateRootProvider + HashedPostStateProvider + StorageRootProvider,
990+
>(
991+
&self,
992+
state: &mut State<DB>,
993+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
994+
best_payload: (OpBuiltPayload, BundleState),
995+
) -> Result<OpBuiltPayload, PayloadBuilderError> {
996+
let (state_root, trie_updates, hashed_state) =
997+
calculate_state_root_on_resolve(state, ctx, best_payload.1)?;
998+
999+
let payload_id = best_payload.0.id();
1000+
let fees = best_payload.0.fees();
1001+
let executed_block = best_payload.0.executed_block().ok_or_else(|| {
1002+
PayloadBuilderError::Other(
1003+
eyre::eyre!("No executed block available in best payload for payload resolution")
1004+
.into(),
1005+
)
1006+
})?;
1007+
let block = best_payload.0.into_sealed_block().into_block();
1008+
let (mut header, body) = block.split();
1009+
header.state_root = state_root;
1010+
let updated_block = alloy_consensus::Block::<OpTransactionSigned>::new(header, body);
1011+
let recovered_block = RecoveredBlock::new_unhashed(
1012+
updated_block.clone(),
1013+
executed_block.recovered_block().senders().to_vec(),
1014+
);
1015+
let sealed_block = Arc::new(updated_block.seal_slow());
1016+
1017+
let executed = ExecutedBlock {
1018+
recovered_block: Arc::new(recovered_block),
1019+
execution_output: executed_block.execution_output.clone(),
1020+
hashed_state: Arc::new(hashed_state),
1021+
trie_updates: Arc::new(trie_updates),
1022+
};
1023+
let updated_payload = OpBuiltPayload::new(payload_id, sealed_block, fees, Some(executed));
1024+
if let Err(e) = self.built_payload_tx.send(updated_payload.clone()).await {
1025+
warn!(
1026+
target: "payload_builder",
1027+
error = %e,
1028+
"Failed to send updated payload"
1029+
);
1030+
}
1031+
debug!(
1032+
target: "payload_builder",
1033+
state_root = %state_root,
1034+
"Updated payload with calculated state root"
1035+
);
1036+
1037+
Ok(updated_payload)
1038+
}
1039+
9281040
/// Do some logging and metric recording when we stop build flashblocks
9291041
fn record_flashblocks_metrics(
9301042
&self,
@@ -1220,7 +1332,7 @@ pub(super) fn build_block<DB, P, ExtraCtx>(
12201332
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
12211333
info: &mut ExecutionInfo<FlashblocksExecutionInfo>,
12221334
calculate_state_root: bool,
1223-
) -> Result<(OpBuiltPayload, OpFlashblockPayload), PayloadBuilderError>
1335+
) -> Result<(OpBuiltPayload, OpFlashblockPayload, BundleState), PayloadBuilderError>
12241336
where
12251337
DB: Database<Error = ProviderError> + AsRef<P>,
12261338
P: StateRootProvider + HashedPostStateProvider + StorageRootProvider,
@@ -1438,7 +1550,7 @@ where
14381550
};
14391551

14401552
// We clean bundle and place initial state transaction back
1441-
state.take_bundle();
1553+
let bundle_state = state.take_bundle();
14421554
state.transition_state = untouched_transition_state;
14431555

14441556
Ok((
@@ -1449,5 +1561,43 @@ where
14491561
Some(executed),
14501562
),
14511563
fb_payload,
1564+
bundle_state,
14521565
))
14531566
}
1567+
1568+
/// Calculates only the state root for an existing payload
1569+
fn calculate_state_root_on_resolve<DB, P, ExtraCtx>(
1570+
state: &mut State<DB>,
1571+
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
1572+
bundle_state: BundleState,
1573+
) -> Result<(B256, TrieUpdates, HashedPostState), PayloadBuilderError>
1574+
where
1575+
DB: Database<Error = ProviderError> + AsRef<P>,
1576+
P: StateRootProvider + HashedPostStateProvider + StorageRootProvider,
1577+
ExtraCtx: std::fmt::Debug + Default,
1578+
{
1579+
let state_root_start_time = Instant::now();
1580+
let state_provider = state.database.as_ref();
1581+
let hashed_state = state_provider.hashed_post_state(&bundle_state);
1582+
let state_root_updates = state
1583+
.database
1584+
.as_ref()
1585+
.state_root_with_updates(hashed_state.clone())
1586+
.inspect_err(|err| {
1587+
warn!(target: "payload_builder",
1588+
parent_header=%ctx.parent().hash(),
1589+
%err,
1590+
"failed to calculate state root for payload"
1591+
);
1592+
})?;
1593+
1594+
let state_root_calculation_time = state_root_start_time.elapsed();
1595+
ctx.metrics
1596+
.state_root_calculation_duration
1597+
.record(state_root_calculation_time);
1598+
ctx.metrics
1599+
.state_root_calculation_gauge
1600+
.set(state_root_calculation_time);
1601+
1602+
Ok((state_root_updates.0, state_root_updates.1, hashed_state))
1603+
}

0 commit comments

Comments
 (0)