Skip to content

Commit 3e3ead7

Browse files
SozinMavalonche
andauthored
Fix bundle state and produce executed block (#223)
* WIP right now it has consensus error 2025-08-01T16:10:44.956046Z ERROR engine::persistence: Persistence service failed err=ProviderError(Database(Write(DatabaseWriteError { info: DatabaseErrorInfo { message: "the given key value is mismatched to the current cursor position", code: -30418 }, operation: CursorAppendDup, table_name: "AccountChangeSets", key: [0, 0, 0, 0, 0, 0, 0, 9] }))) * Hacky solution to mergin state * fmt * fmt * remove config.toml * Update crates/op-rbuilder/src/builders/flashblocks/payload.rs --------- Co-authored-by: shana <[email protected]>
1 parent daab741 commit 3e3ead7

File tree

2 files changed

+48
-45
lines changed

2 files changed

+48
-45
lines changed

.cargo/config.toml

Lines changed: 0 additions & 16 deletions
This file was deleted.

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

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use alloy_primitives::{map::foldhash::HashMap, Address, B256, U256};
1818
use core::time::Duration;
1919
use reth::payload::PayloadBuilderAttributes;
2020
use reth_basic_payload_builder::BuildOutcome;
21+
use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates};
2122
use reth_evm::{execute::BlockBuilder, ConfigureEvm};
2223
use reth_node_api::{Block, NodePrimitives, PayloadBuilderError};
2324
use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus};
@@ -26,14 +27,13 @@ use reth_optimism_forks::OpHardforks;
2627
use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes};
2728
use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned};
2829
use reth_payload_util::BestPayloadTransactions;
30+
use reth_primitives_traits::RecoveredBlock;
2931
use reth_provider::{
3032
ExecutionOutcome, HashedPostStateProvider, ProviderError, StateRootProvider,
3133
StorageRootProvider,
3234
};
3335
use reth_revm::{
34-
database::StateProviderDatabase,
35-
db::{states::bundle_state::BundleRetention, BundleState},
36-
State,
36+
database::StateProviderDatabase, db::states::bundle_state::BundleRetention, State,
3737
};
3838
use revm::Database;
3939
use rollup_boost::{
@@ -287,7 +287,7 @@ where
287287
ctx.add_builder_tx(&mut info, &mut state, builder_tx_gas, message.clone());
288288
}
289289

290-
let (payload, fb_payload, mut bundle_state) = build_block(state, &ctx, &mut info)?;
290+
let (payload, fb_payload) = build_block(&mut state, &ctx, &mut info)?;
291291

292292
best_payload.set(payload.clone());
293293
self.ws_pub
@@ -419,7 +419,6 @@ where
419419
"Building flashblock",
420420
);
421421
let flashblock_build_start_time = Instant::now();
422-
let db = StateProviderDatabase::new(&state_provider);
423422
// If it is the last flashblock, we need to account for the builder tx
424423
if ctx.is_last_flashblock() {
425424
total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas);
@@ -428,11 +427,6 @@ where
428427
*da_limit = da_limit.saturating_sub(builder_tx_da_size);
429428
}
430429
}
431-
let mut state = State::builder()
432-
.with_database(db)
433-
.with_bundle_update()
434-
.with_bundle_prestate(bundle_state)
435-
.build();
436430

437431
let best_txs_start_time = Instant::now();
438432
let best_txs = BestFlashblocksTxs::new(
@@ -488,7 +482,7 @@ where
488482
};
489483

490484
let total_block_built_duration = Instant::now();
491-
let build_result = build_block(state, &ctx, &mut info);
485+
let build_result = build_block(&mut state, &ctx, &mut info);
492486
let total_block_built_duration = total_block_built_duration.elapsed();
493487
ctx.metrics
494488
.total_block_built_duration
@@ -506,7 +500,7 @@ where
506500
// Return the error
507501
return Err(err);
508502
}
509-
Ok((new_payload, mut fb_payload, new_bundle_state)) => {
503+
Ok((new_payload, mut fb_payload)) => {
510504
fb_payload.index = ctx.increment_flashblock_index(); // fallback block is index 0, so we need to increment here
511505
fb_payload.base = None;
512506

@@ -516,6 +510,21 @@ where
516510
tokio::runtime::Handle::current()
517511
.block_on(async { ctx.cancel.cancelled().await });
518512
});
513+
514+
// If main token got canceled in here that means we received get_payload and we should drop everything and now update best_payload
515+
// To ensure that we will return same blocks as rollup-boost (to leverage caches)
516+
if block_cancel.is_cancelled() {
517+
ctx.metrics.block_built_success.increment(1);
518+
ctx.metrics
519+
.flashblock_count
520+
.record(ctx.flashblock_index() as f64);
521+
debug!(
522+
target: "payload_builder",
523+
message = "Payload building complete, job cancelled during execution"
524+
);
525+
span.record("flashblock_count", ctx.flashblock_index());
526+
return Ok(());
527+
}
519528
self.ws_pub
520529
.publish(&fb_payload)
521530
.map_err(PayloadBuilderError::other)?;
@@ -533,7 +542,6 @@ where
533542

534543
best_payload.set(new_payload.clone());
535544
// Update bundle_state for next iteration
536-
bundle_state = new_bundle_state;
537545
total_gas_per_batch += gas_per_batch;
538546
if let Some(da_limit) = da_per_batch {
539547
if let Some(da) = total_da_per_batch.as_mut() {
@@ -732,18 +740,17 @@ where
732740
}
733741

734742
fn build_block<DB, P, ExtraCtx>(
735-
mut state: State<DB>,
743+
state: &mut State<DB>,
736744
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
737745
info: &mut ExecutionInfo<ExtraExecutionInfo>,
738-
) -> Result<(OpBuiltPayload, FlashblocksPayloadV1, BundleState), PayloadBuilderError>
746+
) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError>
739747
where
740748
DB: Database<Error = ProviderError> + AsRef<P>,
741749
P: StateRootProvider + HashedPostStateProvider + StorageRootProvider,
742750
ExtraCtx: std::fmt::Debug + Default,
743751
{
744-
// TODO: We must run this only once per block, but we are running it on every flashblock
745-
// merge all transitions into bundle state, this would apply the withdrawal balance changes
746-
// and 4788 contract call
752+
// We use it to preserve state, so we run merge_transitions on transition state at most once
753+
let untouched_transition_state = state.transition_state.clone();
747754
let state_merge_start_time = Instant::now();
748755
state.merge_transitions(BundleRetention::Reverts);
749756
let state_transition_merge_time = state_merge_start_time.elapsed();
@@ -754,13 +761,11 @@ where
754761
.state_transition_merge_gauge
755762
.set(state_transition_merge_time);
756763

757-
let new_bundle = state.take_bundle();
758-
759764
let block_number = ctx.block_number();
760765
assert_eq!(block_number, ctx.parent().number + 1);
761766

762767
let execution_outcome = ExecutionOutcome::new(
763-
new_bundle.clone(),
768+
state.bundle_state.clone(),
764769
vec![info.receipts.clone()],
765770
block_number,
766771
vec![],
@@ -779,11 +784,12 @@ where
779784
.block_logs_bloom(block_number)
780785
.expect("Number is in range");
781786

787+
// TODO: maybe recreate state with bundle in here
782788
// // calculate the state root
783789
let state_root_start_time = Instant::now();
784790
let state_provider = state.database.as_ref();
785791
let hashed_state = state_provider.hashed_post_state(execution_outcome.state());
786-
let (state_root, _trie_output) = {
792+
let (state_root, trie_output) = {
787793
state
788794
.database
789795
.as_ref()
@@ -870,6 +876,19 @@ where
870876
},
871877
);
872878

879+
let recovered_block =
880+
RecoveredBlock::new_unhashed(block.clone(), info.executed_senders.clone());
881+
// create the executed block data
882+
let executed: ExecutedBlockWithTrieUpdates<OpPrimitives> = ExecutedBlockWithTrieUpdates {
883+
block: ExecutedBlock {
884+
recovered_block: Arc::new(recovered_block),
885+
execution_output: Arc::new(execution_outcome),
886+
hashed_state: Arc::new(hashed_state),
887+
},
888+
trie: ExecutedTrieUpdates::Present(Arc::new(trie_output)),
889+
};
890+
info!(target: "payload_builder", message = "Executed block created");
891+
873892
let sealed_block = Arc::new(block.seal_slow());
874893
debug!(target: "payload_builder", ?sealed_block, "sealed built block");
875894

@@ -891,7 +910,8 @@ where
891910
.zip(new_receipts.iter())
892911
.map(|(tx, receipt)| (tx.tx_hash(), receipt.clone()))
893912
.collect::<HashMap<B256, OpReceipt>>();
894-
let new_account_balances = new_bundle
913+
let new_account_balances = state
914+
.bundle_state
895915
.state
896916
.iter()
897917
.filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance)))
@@ -935,18 +955,17 @@ where
935955
metadata: serde_json::to_value(&metadata).unwrap_or_default(),
936956
};
937957

958+
// We clean bundle and place initial state transaction back
959+
state.take_bundle();
960+
state.transition_state = untouched_transition_state;
961+
938962
Ok((
939963
OpBuiltPayload::new(
940964
ctx.payload_id(),
941965
sealed_block,
942966
info.total_fees,
943-
// This must be set to NONE for now because we are doing merge transitions on every flashblock
944-
// when it should only happen once per block, thus, it returns a confusing state back to op-reth.
945-
// We can live without this for now because Op syncs up the executed block using new_payload
946-
// calls, but eventually we would want to return the executed block here.
947-
None,
967+
Some(executed),
948968
),
949969
fb_payload,
950-
new_bundle,
951970
))
952971
}

0 commit comments

Comments
 (0)