Skip to content

Commit 20b881b

Browse files
committed
Fix database layering and accumulate flashblocks state for metering
Corrects the database architecture to properly layer State -> CacheDB -> StateProviderDatabase, and ensures both cache and bundle state accumulate across flashblocks for accurate state root calculations in bundle metering. Key changes: - Fix database layering: State now wraps CacheDB instead of CacheDB wrapping State - Track accumulated bundle state in PendingBlocks alongside cache - Apply bundle prestate when building state to include previous flashblocks changes - Use merge_transitions before take_bundle to properly extract accumulated state
1 parent 16fb3a5 commit 20b881b

File tree

10 files changed

+93
-37
lines changed

10 files changed

+93
-37
lines changed

Cargo.lock

Lines changed: 2 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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ reth-db-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.8.2" }
7373
# revm
7474
revm = { version = "29.0.0", default-features = false }
7575
revm-bytecode = { version = "6.2.2", default-features = false }
76+
revm-database = { version = "7.0.5", default-features = false }
7677

7778
# alloy
7879
alloy-primitives = { version = "1.3.1", default-features = false, features = [

crates/flashblocks-rpc/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ reth-primitives.workspace = true
2929
reth-primitives-traits.workspace = true
3030
reth-exex.workspace = true
3131

32+
# revm
33+
revm-database.workspace = true
34+
3235
# alloy
3336
alloy-primitives.workspace = true
3437
alloy-eips.workspace = true

crates/flashblocks-rpc/src/pending_blocks.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use alloy_rpc_types_eth::{Filter, Header as RPCHeader, Log};
1010
use eyre::eyre;
1111
use op_alloy_network::Optimism;
1212
use op_alloy_rpc_types::{OpTransactionReceipt, Transaction};
13-
use reth::revm::{db::Cache, state::EvmState};
13+
use reth::revm::{db::{BundleState, Cache}, state::EvmState};
1414
use reth_rpc_eth_api::RpcBlock;
1515

1616
use crate::subscription::Flashblock;
@@ -28,6 +28,7 @@ pub struct PendingBlocksBuilder {
2828
state_overrides: Option<StateOverride>,
2929

3030
db_cache: Cache,
31+
bundle_state: BundleState,
3132
}
3233

3334
impl PendingBlocksBuilder {
@@ -43,6 +44,7 @@ impl PendingBlocksBuilder {
4344
transaction_state: HashMap::new(),
4445
state_overrides: None,
4546
db_cache: Cache::default(),
47+
bundle_state: BundleState::default(),
4648
}
4749
}
4850

@@ -107,6 +109,12 @@ impl PendingBlocksBuilder {
107109
self
108110
}
109111

112+
#[inline]
113+
pub(crate) fn with_bundle_state(&mut self, bundle_state: BundleState) -> &Self {
114+
self.bundle_state = bundle_state;
115+
self
116+
}
117+
110118
pub(crate) fn build(self) -> eyre::Result<PendingBlocks> {
111119
if self.headers.is_empty() {
112120
return Err(eyre!("missing headers"));
@@ -127,6 +135,7 @@ impl PendingBlocksBuilder {
127135
transaction_state: self.transaction_state,
128136
state_overrides: self.state_overrides,
129137
db_cache: self.db_cache,
138+
bundle_state: self.bundle_state,
130139
})
131140
}
132141
}
@@ -145,6 +154,7 @@ pub struct PendingBlocks {
145154
state_overrides: Option<StateOverride>,
146155

147156
db_cache: Cache,
157+
bundle_state: BundleState,
148158
}
149159

150160
impl PendingBlocks {
@@ -176,6 +186,10 @@ impl PendingBlocks {
176186
self.db_cache.clone()
177187
}
178188

189+
pub fn get_bundle_state(&self) -> BundleState {
190+
self.bundle_state.clone()
191+
}
192+
179193
pub fn get_transactions_for_block(&self, block_number: BlockNumber) -> Vec<Transaction> {
180194
self.transactions
181195
.iter()

crates/flashblocks-rpc/src/state.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use reth::providers::{BlockReaderIdExt, StateProviderFactory};
2222
use reth::revm::context::result::ResultAndState;
2323
use reth::revm::database::StateProviderDatabase;
2424
use reth::revm::db::CacheDB;
25+
use revm_database::states::bundle_state::BundleRetention;
2526
use reth::revm::{DatabaseCommit, State};
2627
use reth_evm::{ConfigureEvm, Evm};
2728
use reth_optimism_chainspec::OpHardforks;
@@ -406,18 +407,30 @@ where
406407
.client
407408
.state_by_block_number_or_tag(BlockNumberOrTag::Number(canonical_block))?;
408409
let state_provider_db = StateProviderDatabase::new(state_provider);
409-
let state = State::builder()
410-
.with_database(state_provider_db)
411-
.with_bundle_update()
412-
.build();
413410
let mut pending_blocks_builder = PendingBlocksBuilder::new();
414411

415-
let mut db = match &prev_pending_blocks {
412+
// Cache reads across flashblocks, accumulating caches from previous
413+
// pending blocks if available
414+
let cache_db = match &prev_pending_blocks {
416415
Some(pending_blocks) => CacheDB {
417416
cache: pending_blocks.get_db_cache(),
418-
db: state,
417+
db: state_provider_db,
419418
},
420-
None => CacheDB::new(state),
419+
None => CacheDB::new(state_provider_db),
420+
};
421+
422+
// Track state changes across flashblocks, accumulating bundle state
423+
// from previous pending blocks if available
424+
let mut db = match &prev_pending_blocks {
425+
Some(pending_blocks) => State::builder()
426+
.with_database(cache_db)
427+
.with_bundle_update()
428+
.with_bundle_prestate(pending_blocks.get_bundle_state())
429+
.build(),
430+
None => State::builder()
431+
.with_database(cache_db)
432+
.with_bundle_update()
433+
.build(),
421434
};
422435
let mut state_cache_builder = match &prev_pending_blocks {
423436
Some(pending_blocks) => {
@@ -658,7 +671,9 @@ where
658671
last_block_header = block.header.clone();
659672
}
660673

661-
pending_blocks_builder.with_db_cache(db.cache);
674+
db.merge_transitions(BundleRetention::Reverts);
675+
pending_blocks_builder.with_bundle_state(db.take_bundle());
676+
pending_blocks_builder.with_db_cache(db.database.cache);
662677
pending_blocks_builder.with_state_overrides(state_cache_builder.build());
663678
Ok(Some(Arc::new(pending_blocks_builder.build()?)))
664679
}

crates/metering/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ base-reth-flashblocks-rpc = { path = "../flashblocks-rpc" }
4040

4141
# revm
4242
revm.workspace = true
43+
revm-database.workspace = true
4344

4445
# rpc
4546
jsonrpsee.workspace = true

crates/metering/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ mod rpc;
33
#[cfg(test)]
44
mod tests;
55

6-
pub use meter::{meter_bundle, MeterBundleOutput};
6+
pub use meter::{meter_bundle, FlashblocksState, MeterBundleOutput};
77
pub use rpc::{MeteringApiImpl, MeteringApiServer};
88
pub use tips_core::types::{Bundle, MeterBundleResponse, TransactionResult};

crates/metering/src/meter.rs

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use alloy_consensus::{transaction::SignerRecoverable, BlockHeader, Transaction as _};
22
use alloy_primitives::{B256, U256};
33
use eyre::{eyre, Result as EyreResult};
4-
use reth::revm::db::{Cache, CacheDB, State};
4+
use reth::revm::db::{BundleState, Cache, CacheDB, State};
5+
use revm_database::states::bundle_state::BundleRetention;
56
use reth_evm::execute::BlockBuilder;
67
use reth_evm::ConfigureEvm;
78
use reth_optimism_chainspec::OpChainSpec;
@@ -12,6 +13,15 @@ use std::time::Instant;
1213

1314
use crate::TransactionResult;
1415

16+
/// State from pending flashblocks that is used as a base for metering
17+
#[derive(Debug, Clone)]
18+
pub struct FlashblocksState {
19+
/// The cache of account and storage data
20+
pub cache: Cache,
21+
/// The accumulated bundle of state changes
22+
pub bundle_state: BundleState,
23+
}
24+
1525
const BLOCK_TIME: u64 = 2; // 2 seconds per block
1626

1727
/// Output from metering a bundle of transactions
@@ -43,36 +53,40 @@ pub fn meter_bundle<SP>(
4353
decoded_txs: Vec<op_alloy_consensus::OpTxEnvelope>,
4454
header: &SealedHeader,
4555
bundle_with_metadata: &tips_core::types::BundleWithMetadata,
46-
db_cache: Option<Cache>,
56+
flashblocks_state: Option<FlashblocksState>,
4757
) -> EyreResult<MeterBundleOutput>
4858
where
4959
SP: reth_provider::StateProvider,
5060
{
5161
// Get bundle hash from BundleWithMetadata
5262
let bundle_hash = bundle_with_metadata.bundle_hash();
5363

54-
// Create state database with optional flashblocks cache
64+
// Create state database
5565
let state_db = reth::revm::database::StateProviderDatabase::new(state_provider);
56-
let base_state = State::builder()
57-
.with_database(state_db)
58-
.with_bundle_update()
59-
.build();
6066

61-
// If we have flashblocks cache, wrap with CacheDB to apply pending changes
62-
let cache_db = if let Some(cache) = db_cache {
67+
// If we have flashblocks state, apply both cache and bundle prestate
68+
let cache_db = if let Some(ref flashblocks) = flashblocks_state {
6369
CacheDB {
64-
cache,
65-
db: base_state,
70+
cache: flashblocks.cache.clone(),
71+
db: state_db,
6672
}
6773
} else {
68-
CacheDB::new(base_state)
74+
CacheDB::new(state_db)
6975
};
7076

71-
// Wrap the CacheDB in a State for the EVM builder
72-
let mut db = State::builder()
73-
.with_database(cache_db)
74-
.with_bundle_update()
75-
.build();
77+
// Wrap the CacheDB in a State to track bundle changes for state root calculation
78+
let mut db = if let Some(flashblocks) = flashblocks_state.as_ref() {
79+
State::builder()
80+
.with_database(cache_db)
81+
.with_bundle_update()
82+
.with_bundle_prestate(flashblocks.bundle_state.clone())
83+
.build()
84+
} else {
85+
State::builder()
86+
.with_database(cache_db)
87+
.with_bundle_update()
88+
.build()
89+
};
7690

7791
// Set up next block attributes
7892
// Use bundle.min_timestamp if provided, otherwise use header timestamp + BLOCK_TIME
@@ -138,11 +152,14 @@ where
138152
}
139153

140154
// Calculate state root and measure its calculation time
155+
// The bundle already includes flashblocks state if it was provided via with_bundle_prestate
156+
db.merge_transitions(BundleRetention::Reverts);
141157
let bundle = db.take_bundle();
142-
let state_provider = db.database.as_ref();
158+
let state_provider = db.database.db.as_ref();
143159
let state_root_start = Instant::now();
144-
let hashed_state = state_provider.hashed_post_state(&bundle);
145-
let _ = state_provider.state_root_with_updates(hashed_state);
160+
161+
let hashed_post_state = state_provider.hashed_post_state(&bundle);
162+
let _ = state_provider.state_root_with_updates(hashed_post_state);
146163
let state_root_time = state_root_start.elapsed().as_micros();
147164

148165
let total_execution_time = execution_start.elapsed().as_micros();

crates/metering/src/rpc.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,11 @@ where
152152
)
153153
})?;
154154

155-
// If we have pending flashblocks, get the db_cache to apply state changes
156-
let db_cache = pending_blocks.as_ref().map(|pb| pb.get_db_cache());
155+
// If we have pending flashblocks, get the state to apply pending changes
156+
let flashblocks_state = pending_blocks.as_ref().map(|pb| crate::FlashblocksState {
157+
cache: pb.get_db_cache(),
158+
bundle_state: pb.get_bundle_state(),
159+
});
157160

158161
// Meter bundle using utility function
159162
let result = meter_bundle(
@@ -162,7 +165,7 @@ where
162165
decoded_txs,
163166
&header,
164167
&bundle_with_metadata,
165-
db_cache,
168+
flashblocks_state,
166169
)
167170
.map_err(|e| {
168171
error!(error = %e, "Bundle metering failed");

crates/metering/src/tests/meter.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ fn meter_bundle_empty_transactions() -> eyre::Result<()> {
161161
Vec::new(),
162162
&harness.header,
163163
&bundle_with_metadata,
164-
None, // No flashblocks cache in tests
164+
None,
165165
)?;
166166

167167
assert!(output.results.is_empty());
@@ -210,7 +210,7 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> {
210210
vec![envelope],
211211
&harness.header,
212212
&bundle_with_metadata,
213-
None, // No flashblocks cache in tests
213+
None,
214214
)?;
215215

216216
assert_eq!(output.results.len(), 1);
@@ -307,7 +307,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> {
307307
vec![envelope_1, envelope_2],
308308
&harness.header,
309309
&bundle_with_metadata,
310-
None, // No flashblocks cache in tests
310+
None,
311311
)?;
312312

313313
assert_eq!(output.results.len(), 2);
@@ -396,7 +396,7 @@ fn meter_bundle_state_root_time_invariant() -> eyre::Result<()> {
396396
vec![envelope],
397397
&harness.header,
398398
&bundle_with_metadata,
399-
None, // No flashblocks cache in tests
399+
None,
400400
)?;
401401

402402
// Verify invariant: total execution time must include state root time

0 commit comments

Comments
 (0)