Sitelet https://github.com/ethereum-optimism/optimism/commit/45a80414637dbd2c0772c7260469dbf81c8fdbeb
Skip to content

Commit 45a8041

Browse files
authored
kona-sp1-proposer: make game discovery lag configurable (#23044)
1 parent ba941e1 commit 45a8041

5 files changed

Lines changed: 106 additions & 20 deletions

File tree

‎rust/kona/sp1/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ Optional core and operational configuration:
343343
| `KONA_SP1_PROPOSER_FETCH_INTERVAL` | loop interval in seconds (default `30`) |
344344
| `KONA_SP1_PROPOSER_METRICS_PORT` | `0` disables metrics; `auto` selects a free port (default `0`) |
345345
| `KONA_SP1_PROPOSER_SYNC_L1_CONFIRMATIONS` | L1 confirmation lag for pinned reads (default `0`) |
346+
| `KONA_SP1_PROPOSER_MAX_GAME_DEADLINE_LAG_SECONDS` | Startup discovery and pending-game eviction cutoff relative to the anchor deadline (default `1209600`, 14 days) |
346347
| `KONA_SP1_PROPOSER_TX_CONFIRMATION_TIMEOUT` | transaction confirmation timeout in seconds (default `180`) |
347348
| `KONA_SP1_PROPOSER_MAX_FEE_PER_GAS` | L1 max-fee cap in wei (default uncapped) |
348349
| `KONA_SP1_PROPOSER_MAX_PRIORITY_FEE_PER_GAS` | L1 priority-fee cap in wei (default uncapped) |

‎rust/kona/sp1/crates/proposer/src/config.rs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use kona_registry::{CHAINS, OPCHAINS};
2020
use kona_sp1_host_utils::{metrics::MetricsListen, network::parse_fulfillment_strategy};
2121
use sp1_sdk::network::FulfillmentStrategy;
2222

23-
use crate::env_var;
23+
use crate::{env_var, proposer::MAX_GAME_DEADLINE_LAG};
2424

2525
/// Safety level gating how far proposals may advance.
2626
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -116,6 +116,9 @@ pub struct ProposerConfig {
116116
/// Number of L1 blocks behind `latest` to pin reads during sync cycles.
117117
pub sync_l1_confirmations: u64,
118118

119+
/// Maximum game-deadline lag behind the anchor, in seconds.
120+
pub max_game_deadline_lag: u64,
121+
119122
/// Maximum time (in seconds) to wait for an L1 transaction to reach the
120123
/// required confirmations before the watcher gives up.
121124
pub tx_confirmation_timeout: u64,
@@ -290,6 +293,13 @@ impl ProposerConfig {
290293
let factory_address =
291294
resolve_factory_address(explicit_factory_address.as_deref(), network)?;
292295
let tx_confirmation_timeout = parsed_env_or("TX_CONFIRMATION_TIMEOUT", 180u64)?;
296+
let max_game_deadline_lag =
297+
parsed_env_or("MAX_GAME_DEADLINE_LAG_SECONDS", MAX_GAME_DEADLINE_LAG)?;
298+
anyhow::ensure!(
299+
max_game_deadline_lag > 0,
300+
"{} must be positive",
301+
env_var("MAX_GAME_DEADLINE_LAG_SECONDS")
302+
);
293303
anyhow::ensure!(
294304
tx_confirmation_timeout > 0,
295305
"{} must be positive (0 would time out every transaction immediately)",
@@ -321,6 +331,7 @@ impl ProposerConfig {
321331
fetch_interval: parsed_env_or("FETCH_INTERVAL", 30u64)?,
322332
metrics_listen: parsed_env_or("METRICS_PORT", MetricsListen::default())?,
323333
sync_l1_confirmations: parsed_env_or("SYNC_L1_CONFIRMATIONS", 0u64)?,
334+
max_game_deadline_lag,
324335
tx_confirmation_timeout,
325336
max_fee_per_gas: parsed_optional_env("MAX_FEE_PER_GAS")?,
326337
max_priority_fee_per_gas: parsed_optional_env("MAX_PRIORITY_FEE_PER_GAS")?,
@@ -935,6 +946,10 @@ mod tests {
935946
assert_eq!(config.l2_rpcs.len(), 2);
936947
assert!(config.rollup_config_paths.is_none());
937948
assert_eq!(config.tx_confirmation_timeout, 180);
949+
assert_eq!(config.max_game_deadline_lag, MAX_GAME_DEADLINE_LAG);
950+
set_proposer_env("MAX_GAME_DEADLINE_LAG_SECONDS", "2419200");
951+
assert_eq!(ProposerConfig::from_env(None).unwrap().max_game_deadline_lag, 2_419_200);
952+
unsafe { env::remove_var(env_var("MAX_GAME_DEADLINE_LAG_SECONDS")) };
938953
assert_eq!(config.range_split_count.to_usize(), 16);
939954
set_proposer_env("RANGE_SPLIT_COUNT", "128");
940955
assert_eq!(ProposerConfig::from_env(None).unwrap().range_split_count.to_usize(), 128);

‎rust/kona/sp1/crates/proposer/src/proposer.rs‎

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,13 @@ use crate::{
5858
#[cfg(test)]
5959
mod scenario;
6060

61-
/// Max allowed time (secs) between a game's deadline and the anchor game's deadline.
62-
///
63-
/// Games beyond this threshold are skipped during incremental syncs to cut startup latency and
64-
/// avoid caching stale data.
65-
///
66-
/// The 14-day window is chosen with a 7-day challenge period in mind, plus a 7-day buffer,
67-
/// ensuring all actionable games are included under normal conditions.
61+
/// Default cutoff between a game's deadline and the anchor game's deadline, in seconds.
62+
/// Configurable for factory discovery and pending-game eviction.
6863
pub const MAX_GAME_DEADLINE_LAG: u64 = 60 * 60 * 24 * 14; // 14 days
6964

65+
/// Maximum lead of a game's L2 timestamp over local safe, in seconds.
66+
const MAX_FUTURE_GAME_TIMESTAMP_LAG: u64 = 60 * 60 * 24 * 14; // 14 days
67+
7068
/// Nonzero identifier assigned to a proposer task.
7169
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
7270
pub(crate) struct TaskId(NonZeroU64);
@@ -1609,7 +1607,11 @@ impl Proposer {
16091607
anchor_deadline = Some(deadline);
16101608
}
16111609
if let Some(anchor_d) = anchor_deadline &&
1612-
beyond_deadline_lag(anchor_d, deadline)
1610+
beyond_deadline_lag(
1611+
anchor_d,
1612+
deadline,
1613+
self.config.max_game_deadline_lag,
1614+
)
16131615
{
16141616
tracing::debug!(
16151617
game_index = %index,
@@ -1875,7 +1877,11 @@ impl Proposer {
18751877
"Keeping pending owned game re-checkable (eviction exempt)"
18761878
);
18771879
} else if let Some(anchor_deadline) = anchor_deadline &&
1878-
beyond_deadline_lag(anchor_deadline, deadline)
1880+
beyond_deadline_lag(
1881+
anchor_deadline,
1882+
deadline,
1883+
self.config.max_game_deadline_lag,
1884+
)
18791885
{
18801886
tracing::warn!(
18811887
game_index = %index,
@@ -2654,7 +2660,7 @@ impl Proposer {
26542660
// nearer is pending and re-validated next sync.
26552661
let local_safe = super_root_at.response.current_local_safe_timestamp;
26562662
if local_safe > 0 &&
2657-
sequence_number > local_safe.saturating_add(MAX_GAME_DEADLINE_LAG)
2663+
sequence_number > local_safe.saturating_add(MAX_FUTURE_GAME_TIMESTAMP_LAG)
26582664
{
26592665
tracing::warn!(
26602666
game_index = %index,
@@ -4440,8 +4446,8 @@ pub fn withdrawal_matured(withdrawal_ts: u64, weth_delay: u64, l1_now: u64) -> b
44404446

44414447
/// Returns whether a game deadline is more than the maximum allowed lag
44424448
/// behind the anchor deadline.
4443-
pub const fn beyond_deadline_lag(anchor_deadline: u64, game_deadline: u64) -> bool {
4444-
game_deadline.saturating_add(MAX_GAME_DEADLINE_LAG) < anchor_deadline
4449+
pub const fn beyond_deadline_lag(anchor_deadline: u64, game_deadline: u64, max_lag: u64) -> bool {
4450+
game_deadline.saturating_add(max_lag) < anchor_deadline
44454451
}
44464452

44474453
/// Policy for game creation when the registered prestate's programs cannot
@@ -5444,6 +5450,7 @@ mod tests {
54445450
fetch_interval: 30,
54455451
metrics_listen: MetricsListen::Disabled,
54465452
sync_l1_confirmations: 0,
5453+
max_game_deadline_lag: MAX_GAME_DEADLINE_LAG,
54475454
tx_confirmation_timeout: 60,
54485455
max_fee_per_gas: None,
54495456
max_priority_fee_per_gas: None,
@@ -7095,7 +7102,7 @@ mod tests {
70957102
let cases = [
70967103
(100, absent_super_root_at_timestamp(99), canonical, false, Expected::Pending),
70977104
(
7098-
super::MAX_GAME_DEADLINE_LAG + 101,
7105+
super::MAX_FUTURE_GAME_TIMESTAMP_LAG + 101,
70997106
absent_super_root_at_timestamp(100),
71007107
canonical,
71017108
false,
@@ -9184,11 +9191,15 @@ mod tests {
91849191
#[test]
91859192
fn cutoff_only_fires_behind_the_anchor() {
91869193
let anchor = 1_000_000 + MAX_GAME_DEADLINE_LAG + 1;
9187-
assert!(beyond_deadline_lag(anchor, 1_000_000));
9188-
assert!(!beyond_deadline_lag(anchor - 1, 1_000_000));
9189-
assert!(!beyond_deadline_lag(1_000_000, 1_000_000));
9190-
assert!(!beyond_deadline_lag(1_000_000, 1_000_000 + MAX_GAME_DEADLINE_LAG + 1));
9191-
assert!(!beyond_deadline_lag(1_000_000, u64::MAX));
9194+
assert!(beyond_deadline_lag(anchor, 1_000_000, MAX_GAME_DEADLINE_LAG));
9195+
assert!(!beyond_deadline_lag(anchor - 1, 1_000_000, MAX_GAME_DEADLINE_LAG));
9196+
assert!(!beyond_deadline_lag(1_000_000, 1_000_000, MAX_GAME_DEADLINE_LAG));
9197+
assert!(!beyond_deadline_lag(
9198+
1_000_000,
9199+
1_000_000 + MAX_GAME_DEADLINE_LAG + 1,
9200+
MAX_GAME_DEADLINE_LAG,
9201+
));
9202+
assert!(!beyond_deadline_lag(1_000_000, u64::MAX, MAX_GAME_DEADLINE_LAG));
91929203
}
91939204
}
91949205
}

‎rust/kona/sp1/crates/proposer/src/proposer/scenario/tests.rs‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ fn test_config(fetch_interval: u64) -> ProposerConfig {
366366
fetch_interval,
367367
metrics_listen: MetricsListen::Disabled,
368368
sync_l1_confirmations: 0,
369+
max_game_deadline_lag: MAX_GAME_DEADLINE_LAG,
369370
tx_confirmation_timeout: 60,
370371
max_fee_per_gas: None,
371372
max_priority_fee_per_gas: None,
@@ -3096,6 +3097,60 @@ async fn incremental_discovery_stops_before_old_history_and_resumes_for_new_entr
30963097
scenario.settle_scheduled(&resumed).await.unwrap();
30973098
}
30983099

3100+
#[tokio::test]
3101+
async fn widened_startup_discovery_keeps_old_ancestor_until_bond_payout() {
3102+
let world = ScenarioWorld::new();
3103+
let root = ScenarioGame::new(0, u32::MAX, 10, ScenarioWorld::default_prestate()).claimable(0);
3104+
let mut ancestor =
3105+
ScenarioGame::new(1, 0, 20, ScenarioWorld::default_prestate()).provable_for_resolution();
3106+
ancestor.creator = ScenarioWorld::proposer_address();
3107+
ancestor.deadline = 2_000_000 - MAX_GAME_DEADLINE_LAG - 1;
3108+
let ancestor_target = ancestor.target();
3109+
let child =
3110+
ScenarioGame::new(2, 1, 22, ScenarioWorld::default_prestate()).provable_for_resolution();
3111+
let child_target = child.target();
3112+
let mut boundary = ScenarioGame::new(3, u32::MAX, 25, ScenarioWorld::default_prestate());
3113+
boundary.deadline = ancestor.deadline;
3114+
let mut anchor = ScenarioGame::new(4, u32::MAX, 30, ScenarioWorld::default_prestate());
3115+
anchor.deadline = 2_000_000;
3116+
let anchor_target = anchor.target();
3117+
for game in [root, ancestor, child, boundary, anchor] {
3118+
world.add_game(game);
3119+
}
3120+
world.set_anchor_game(&anchor_target);
3121+
world.set_horizons(30, 30);
3122+
3123+
let mut config = scenario_config();
3124+
config.proposal_interval_seconds = 100;
3125+
config.max_game_deadline_lag = 28 * 24 * 60 * 60;
3126+
let mut widened = ScenarioHarness::new(world.clone(), config).await.unwrap();
3127+
let discovered = widened.tick().await.unwrap();
3128+
widened.settle_scheduled(&discovered).await.unwrap();
3129+
assert!(world.action_record(&ActionTarget::Resolve(child_target.clone()), 1).is_none());
3130+
3131+
world.set_latest_l1_time(
3132+
world.observation().latest_l1.timestamp + SCENARIO_GAME_FINALITY_DELAY + 1,
3133+
);
3134+
let finalized = widened.tick().await.unwrap();
3135+
widened.settle_scheduled(&finalized).await.unwrap();
3136+
assert!(matches!(
3137+
world.action_record(&ActionTarget::Resolve(child_target), 1).unwrap().effect,
3138+
CommittedEffect::Resolved { .. }
3139+
));
3140+
assert!(matches!(
3141+
world.action_record(&ActionTarget::ClaimCredit(ancestor_target.clone()), 1).unwrap().effect,
3142+
CommittedEffect::ClaimUnlocked { game, .. } if game == ancestor_target.address
3143+
));
3144+
3145+
world.set_latest_l1_time(world.observation().latest_l1.timestamp + 20);
3146+
let matured = widened.tick().await.unwrap();
3147+
widened.settle_scheduled(&matured).await.unwrap();
3148+
assert!(matches!(
3149+
world.action_record(&ActionTarget::ClaimCredit(ancestor_target.clone()), 2).unwrap().effect,
3150+
CommittedEffect::ClaimPaid { game, .. } if game == ancestor_target.address
3151+
));
3152+
}
3153+
30993154
#[tokio::test]
31003155
async fn empty_and_truncated_factories_reset_cached_history() {
31013156
let empty_world = ScenarioWorld::new();

‎rust/kona/sp1/crates/proposer/src/proposer/scenario/world.rs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ use crate::{
3636
L1View, NonceState, ProofEngine, ProofInputs, ProposalHorizon, QueryTime,
3737
SuperRootAtTimestamp, SuperRootSource, WithdrawalState,
3838
},
39-
proposer::{CycleResult, OperationSummary, PrestateCache, Proposer, TaskCompletion, TaskId},
39+
proposer::{
40+
CycleResult, MAX_GAME_DEADLINE_LAG, OperationSummary, PrestateCache, Proposer,
41+
TaskCompletion, TaskId,
42+
},
4043
prover::ProofKeys,
4144
proving::GameProofInputs,
4245
signer::NUM_CONFIRMATIONS,
@@ -2439,6 +2442,7 @@ pub(super) fn scenario_config() -> ProposerConfig {
24392442
fetch_interval: 86_400,
24402443
metrics_listen: MetricsListen::Disabled,
24412444
sync_l1_confirmations: 0,
2445+
max_game_deadline_lag: MAX_GAME_DEADLINE_LAG,
24422446
tx_confirmation_timeout: 60,
24432447
max_fee_per_gas: None,
24442448
max_priority_fee_per_gas: None,

0 commit comments

Comments
 (0)