Sitelet https://github.com/init4tech/builder/commit/4edf3cb723c29f5fac10c87986d343f6899b23bf
Skip to content

Commit 4edf3cb

Browse files
EvalirclaudeFraser999
authored
fix(cache): rank ingested items with the live env basefee, not 0 (#280)
* fix(cache): rank ingested items with the live env basefee, not 0 CacheTask declared basefee as a loop-local reset to 0 on every select! iteration and assigned it only in the envs.changed() arm. Because add_bundle/add_tx run in the other select! arms (separate loop iterations), they always received basefee 0, so cached items were ranked by effective_gas_price(0) instead of the env basefee. The rank is only a simulation-order and cache-eviction heuristic, so the impact is limited to ordering/eviction quality (negligible at gouda's ~7 wei basefee, latent on higher-basefee chains); it does not affect block validity or execution. Fixed by reading the block number and basefee from the live env at ingest time via current_block_and_basefee. Adds cache_task_ranks_with_env_basefee as a regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update src/tasks/cache/task.rs Co-authored-by: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Fraser Hutchison <190532+Fraser999@users.noreply.github.com>
1 parent d1b1043 commit 4edf3cb

1 file changed

Lines changed: 96 additions & 6 deletions

File tree

‎src/tasks/cache/task.rs‎

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,29 @@ impl CacheTask {
3434
Self { envs: env, bundles, txns }
3535
}
3636

37+
/// Returns the rollup block number and basefee of the current sim env, or
38+
/// `(0, 0)` if no env has been published yet.
39+
///
40+
/// Read at ingest time rather than cached across `select!` iterations: the
41+
/// loop re-enters `select!` for every channel event, so a value assigned in
42+
/// the `envs.changed()` arm is not visible when a bundle or transaction is
43+
/// later received in a different arm. Reading the live env here ensures
44+
/// items are ranked against the correct basefee.
45+
fn current_block_and_basefee(&self) -> (u64, u64) {
46+
self.envs
47+
.borrow()
48+
.as_ref()
49+
.map(|env| {
50+
let rollup = env.rollup_env();
51+
(rollup.number.to::<u64>(), rollup.basefee)
52+
})
53+
.unwrap_or_default()
54+
}
55+
3756
async fn task_future(mut self, cache: SimCache) {
3857
let mut summary = IngestionSummary::default();
3958

4059
loop {
41-
let mut basefee = 0;
4260
tokio::select! {
4361
biased;
4462
res = self.envs.changed() => {
@@ -54,7 +72,7 @@ impl CacheTask {
5472

5573
summary.log_and_reset();
5674

57-
basefee = sim_env.basefee;
75+
let basefee = sim_env.basefee;
5876
info!(
5977
basefee,
6078
block_env_number = sim_env.number.to::<u64>(),
@@ -70,10 +88,9 @@ impl CacheTask {
7088
Some(bundle) = self.bundles.recv() => {
7189
summary.bundles_received += 1;
7290

73-
let env_block = self.envs.borrow()
74-
.as_ref()
75-
.map(|e| e.rollup_env().number.to::<u64>())
76-
.unwrap_or_default();
91+
// Read the block number and basefee from the live env at
92+
// ingest time (see `current_block_and_basefee`).
93+
let (env_block, basefee) = self.current_block_and_basefee();
7794
let bundle_block = bundle.bundle.block_number();
7895

7996
// Don't insert bundles for past blocks
@@ -106,6 +123,7 @@ impl CacheTask {
106123
continue;
107124
};
108125

126+
let (_, basefee) = self.current_block_and_basefee();
109127
match txn.try_into_recovered() {
110128
Ok(recovered_tx) => {
111129
cache.add_tx(recovered_tx, basefee);
@@ -186,3 +204,75 @@ impl IngestionSummary {
186204
*self = IngestionSummary { has_logged: true, ..IngestionSummary::default() };
187205
}
188206
}
207+
208+
#[cfg(test)]
209+
mod tests {
210+
use super::*;
211+
use crate::tasks::env::Environment;
212+
use crate::test_utils::{create_transfer_tx, scenarios_test_block_env as test_block_env};
213+
use alloy::consensus::TxEnvelope;
214+
use alloy::primitives::U256;
215+
use alloy::signers::local::PrivateKeySigner;
216+
use signet_sim::SimItem;
217+
use std::time::Duration;
218+
219+
// Regression for the `basefee = 0` ingest bug: `basefee` was re-initialised
220+
// to 0 at the top of every `select!` iteration and assigned only in the
221+
// `envs.changed()` arm, so `add_tx`/`add_bundle` (which run in other arms)
222+
// always ranked items with basefee 0. The ingest arms must read the basefee
223+
// from the live env instead.
224+
#[tokio::test]
225+
async fn cache_task_ranks_with_env_basefee() {
226+
const BASEFEE: u64 = 60_000_000_000; // 60 gwei, below the tx's 100 gwei max fee
227+
const PRIORITY: u128 = 50_000_000_000; // 50 gwei tip
228+
229+
// Env whose rollup block carries BASEFEE.
230+
let block_env = test_block_env(100, BASEFEE, 1_000, 30_000_000);
231+
let sim_env = SimEnv {
232+
rollup: Environment::new(block_env, Default::default()),
233+
host: Environment::for_testing(),
234+
span: tracing::Span::none(),
235+
};
236+
237+
let (_env_tx, env_rx) = watch::channel(Some(sim_env));
238+
let (_bundle_tx, bundle_rx) = mpsc::unbounded_channel();
239+
let (txn_tx, txn_rx) = mpsc::unbounded_channel();
240+
241+
let (cache, _jh) = CacheTask::new(env_rx, bundle_rx, txn_rx).spawn();
242+
243+
// A tx whose rank depends on the basefee: rank is effective_gas_price
244+
// (min(max_fee, basefee + tip)) * gas_limit, i.e. min(100, 60+50)=100 gwei
245+
// per gas at BASEFEE vs min(100, 0+50)=50 gwei at basefee 0.
246+
let signer = PrivateKeySigner::random();
247+
let recovered =
248+
create_transfer_tx(&signer, signer.address(), U256::from(1u64), 0, 1, PRIORITY)
249+
.unwrap();
250+
let envelope: TxEnvelope = recovered.inner().clone();
251+
252+
let item = SimItem::from(recovered);
253+
let expected = item.calculate_total_fee(BASEFEE);
254+
let with_zero = item.calculate_total_fee(0);
255+
assert_ne!(
256+
expected, with_zero,
257+
"basefee must affect the rank for this test to be meaningful"
258+
);
259+
260+
txn_tx.send(ReceivedTx::Tx(envelope)).unwrap();
261+
262+
// Wait for the task to ingest the tx.
263+
let mut rank = None;
264+
for _ in 0..200 {
265+
if let Some((r, _)) = cache.read_best(1).into_iter().next() {
266+
rank = Some(r);
267+
break;
268+
}
269+
tokio::time::sleep(Duration::from_millis(10)).await;
270+
}
271+
272+
assert_eq!(
273+
rank.expect("tx was never ingested into the cache"),
274+
expected,
275+
"tx must be ranked with the live env basefee, not 0",
276+
);
277+
}
278+
}

0 commit comments

Comments
 (0)