diff --git a/doc/missions.md b/doc/missions.md index 7fc451eb..7d1f4d20 100644 --- a/doc/missions.md +++ b/doc/missions.md @@ -121,7 +121,7 @@ Stress test a network of simulated Tier1 topology with classic traffic and repor ### Parameters -- `--enable-trigger-timer=`: Enable or disable `EXPERIMENTAL_TRIGGER_TIMER` on all nodes. Defaults to enabled for this mission. +- `--force-old-style-trigger-timer=`: Set `FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER` on all nodes, forcing the pre-protocol-28 prepare-start trigger timer. Unset by default, in which case core picks the timer by protocol version (the consensus-close-time timer from protocol 28 on). ## MissionSorobanLoadGeneration @@ -157,7 +157,7 @@ Stress test a network of simulated Tier1 topology with a mix of classic and soro ### Parameters -- `--enable-trigger-timer=`: Enable or disable `EXPERIMENTAL_TRIGGER_TIMER` on all nodes. Defaults to enabled for this mission. +- `--force-old-style-trigger-timer=`: Set `FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER` on all nodes, forcing the pre-protocol-28 prepare-start trigger timer. Unset by default, in which case core picks the timer by protocol version (the consensus-close-time timer from protocol 28 on). ## MissionMinBlockTimeClassic @@ -169,11 +169,11 @@ Same as `MissionMinBlockTimeClassic`, but drives an explicit `MIXED_PREGEN_*` ov ## MissionTriggerTimerMixConsensus -Tests the `EXPERIMENTAL_TRIGGER_TIMER` feature on a simulated Public Network topology with a configurable mix of nodes that have the flag enabled versus disabled, under configurable clock-drift distributions. It drives the same `MIXED_PREGEN_*` (classic + synthetic Soroban) overlay-only load as `MissionMinBlockTimeMixed`, but instead of binary-searching for a minimum block time it runs a single load pass at a fixed ledger close time and verifies consensus stays healthy (no errors, pairwise-consistent, all nodes in sync). Requires a generated pubnet topology via `--pubnet-data`. +Tests the protocol-28 consensus-close-time trigger timer on a simulated Public Network topology with a configurable mix of nodes running it versus nodes forced back onto the older prepare-start timer via `FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER`, under configurable clock-drift distributions. It drives the same `MIXED_PREGEN_*` (classic + synthetic Soroban) overlay-only load as `MissionMinBlockTimeMixed`, but instead of binary-searching for a minimum block time it runs a single load pass at a fixed ledger close time and verifies consensus stays healthy (no errors, pairwise-consistent, all nodes in sync). Requires a generated pubnet topology via `--pubnet-data`. ### Parameters -- `--trigger-timer-flag-pct`: Percentage (0-100) of nodes with `EXPERIMENTAL_TRIGGER_TIMER` enabled. Default 100. This mission rejects `--enable-trigger-timer`; use this flag instead. +- `--force-old-style-trigger-timer-pct`: Percentage (0-100) of nodes with `FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER` set, i.e. forced onto the old prepare-start timer. The rest use the protocol default. Default 0. This mission rejects `--force-old-style-trigger-timer`; use this flag instead. - `--drift-pct`: Percentage (0-100) of nodes that receive clock drift. Default 0. - `--uniform-drift=lower,upper`: Uniform random clock drift, in signed ms, applied to each drifting node (e.g. `--uniform-drift=-2000,+2000`). - `--bimodal-drift=min1,max1,min2,max2`: Bimodal clock drift, in signed ms — the first half of the drifting nodes draw from `[min1,max1]`, the second half from `[min2,max2]` (e.g. `--bimodal-drift=-5000,-2000,+2000,+5000`). diff --git a/src/App/Program.fs b/src/App/Program.fs index 0dd5125a..7f8075a8 100644 --- a/src/App/Program.fs +++ b/src/App/Program.fs @@ -140,12 +140,12 @@ type MissionOptions minBlockTimeMixedMode: string, minBlockTimeMixedClassicTxRate: int option, minBlockTimeMixedSorobanTxRate: int option, - triggerTimerFlagPct: int, + forceOldStyleTriggerTimerPct: int, uniformDrift: seq, bimodalDrift: seq, driftPct: int, ledgerCloseTimeMs: int option, - enableTriggerTimer: bool option + forceOldStyleTriggerTimer: bool option ) = [] @@ -651,11 +651,11 @@ type MissionOptions Required = false)>] member self.MinBlockTimeMixedSorobanTxRate = minBlockTimeMixedSorobanTxRate - [] - member self.TriggerTimerFlagPct = triggerTimerFlagPct + Default = 0)>] + member self.ForceOldStyleTriggerTimerPct = forceOldStyleTriggerTimerPct [] member self.LedgerCloseTimeMs = ledgerCloseTimeMs - [] - member self.EnableTriggerTimer = enableTriggerTimer + member self.ForceOldStyleTriggerTimer = forceOldStyleTriggerTimer let splitLabel (lab: string) : (string * string option) = match lab.Split ':' |> Array.toList with @@ -929,12 +929,12 @@ let main argv = minBlockTimeMixedClassicTxRate = mission.MinBlockTimeMixedClassicTxRate minBlockTimeMixedSorobanTxRate = mission.MinBlockTimeMixedSorobanTxRate runForMinBlockTime = false - triggerTimerFlagPct = mission.TriggerTimerFlagPct + forceOldStyleTriggerTimerPct = mission.ForceOldStyleTriggerTimerPct uniformDrift = List.ofSeq mission.UniformDrift bimodalDrift = List.ofSeq mission.BimodalDrift driftPct = mission.DriftPct ledgerCloseTimeMs = mission.LedgerCloseTimeMs - enableTriggerTimer = mission.EnableTriggerTimer } + forceOldStyleTriggerTimer = mission.ForceOldStyleTriggerTimer } allMissions.[m] missionContext diff --git a/src/FSLibrary.Tests/Tests.fs b/src/FSLibrary.Tests/Tests.fs index 7a3c4f73..e82457e9 100644 --- a/src/FSLibrary.Tests/Tests.fs +++ b/src/FSLibrary.Tests/Tests.fs @@ -147,12 +147,12 @@ let ctx : MissionContext = minBlockTimeMixedClassicTxRate = None minBlockTimeMixedSorobanTxRate = None runForMinBlockTime = false - triggerTimerFlagPct = 100 + forceOldStyleTriggerTimerPct = 0 uniformDrift = [] bimodalDrift = [] driftPct = 0 ledgerCloseTimeMs = None - enableTriggerTimer = None } + forceOldStyleTriggerTimer = None } let netdata = __SOURCE_DIRECTORY__ + "/../../../data/public-network-data-2024-08-01.json" let pubkeys = __SOURCE_DIRECTORY__ + "/../../../data/tier1keys.json" @@ -195,14 +195,14 @@ type Tests(output: ITestOutputHelper) = Assert.Contains("HTTP_PORT = " + CfgVal.httpPort.ToString(), toml) // Trigger timer and clock offset settings must be omitted unless // explicitly configured on the CoreSet or the mission context. - Assert.DoesNotContain("EXPERIMENTAL_TRIGGER_TIMER", toml) + Assert.DoesNotContain("FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER", toml) Assert.DoesNotContain("ARTIFICIALLY_SET_SYSTEM_CLOCK_OFFSET_FOR_TESTING", toml) [] member __.``TOML Config emits trigger timer and per-node clock offsets``() = let opts = { coreSetOptions with - experimentalTriggerTimer = Some true + forceOldStyleTriggerTimer = Some true clockOffsets = Some [ 0; -800; 1500 ] } let cs = MakeLiveCoreSet "test" opts @@ -211,7 +211,7 @@ type Tests(output: ITestOutputHelper) = let tomlOfNode i = cfg.StellarCoreCfg(cs, i, MainCoreContainer).ToString() for i in 0 .. 2 do - Assert.Contains("EXPERIMENTAL_TRIGGER_TIMER = true", tomlOfNode i) + Assert.Contains("FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER = true", tomlOfNode i) Assert.Contains("ARTIFICIALLY_SET_SYSTEM_CLOCK_OFFSET_FOR_TESTING = 0", tomlOfNode 0) Assert.Contains("ARTIFICIALLY_SET_SYSTEM_CLOCK_OFFSET_FOR_TESTING = -800", tomlOfNode 1) @@ -225,18 +225,27 @@ type Tests(output: ITestOutputHelper) = // The CoreSet leaves the option unset, so the mission-level flag // decides whether (and with which value) the key is emitted. - Assert.Contains("EXPERIMENTAL_TRIGGER_TIMER = true", tomlWith { ctx with enableTriggerTimer = Some true }) - Assert.Contains("EXPERIMENTAL_TRIGGER_TIMER = false", tomlWith { ctx with enableTriggerTimer = Some false }) - Assert.DoesNotContain("EXPERIMENTAL_TRIGGER_TIMER", tomlWith ctx) + Assert.Contains( + "FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER = true", + tomlWith { ctx with forceOldStyleTriggerTimer = Some true } + ) + + Assert.Contains( + "FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER = false", + tomlWith { ctx with forceOldStyleTriggerTimer = Some false } + ) + + Assert.DoesNotContain("FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER", tomlWith ctx) // A CoreSet-level setting wins over the mission-level flag. let csOn = - MakeLiveCoreSet "test" { coreSetOptions with experimentalTriggerTimer = Some true } + MakeLiveCoreSet "test" { coreSetOptions with forceOldStyleTriggerTimer = Some true } - let cfgOn = MakeNetworkCfg { ctx with enableTriggerTimer = Some false } [ csOn ] passOpt + let cfgOn = + MakeNetworkCfg { ctx with forceOldStyleTriggerTimer = Some false } [ csOn ] passOpt Assert.Contains( - "EXPERIMENTAL_TRIGGER_TIMER = true", + "FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER = true", cfgOn.StellarCoreCfg(csOn, 0, MainCoreContainer).ToString() ) diff --git a/src/FSLibrary/MaxTPSTest.fs b/src/FSLibrary/MaxTPSTest.fs index b967a9ff..dec4072b 100644 --- a/src/FSLibrary/MaxTPSTest.fs +++ b/src/FSLibrary/MaxTPSTest.fs @@ -133,17 +133,6 @@ let maxTPSTest (context: MissionContext) (baseLoadGen: LoadGen) (setupCfg: LoadG context.image (if context.flatQuorum.IsSome then context.flatQuorum.Value else false) - // The max TPS missions enable EXPERIMENTAL_TRIGGER_TIMER unless - // --enable-trigger-timer=false is passed explicitly. - let triggerTimer = context.enableTriggerTimer |> Option.defaultValue true - - let allNodes = - allNodes - |> List.map - (fun (cs: CoreSet) -> - { cs with - options = { cs.options with experimentalTriggerTimer = Some triggerTimer } }) - // PayPregenerated requires node restart between failed iterations to ensure validity of the pregenerated transactions // However, large-scale simulation restarts can be slow, so for now only use the new mode on small networks let baseLoadGen = diff --git a/src/FSLibrary/MissionTriggerTimerMixConsensus.fs b/src/FSLibrary/MissionTriggerTimerMixConsensus.fs index e6a814d3..dda69d29 100644 --- a/src/FSLibrary/MissionTriggerTimerMixConsensus.fs +++ b/src/FSLibrary/MissionTriggerTimerMixConsensus.fs @@ -2,10 +2,12 @@ // under the Apache License, Version 2.0. See the COPYING file at the root // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 -// This mission tests the EXPERIMENTAL_TRIGGER_TIMER feature with a mix of -// nodes that have it enabled vs disabled, under configurable clock drift -// distributions. It uses generated pubnet topologies (--pubnet-data) and -// overlays trigger timer and clock offset settings onto the CoreSets. +// This mission tests the protocol-28 consensus-close-time trigger timer with a +// mix of nodes running it vs nodes forced back onto the older prepare-start +// timer via FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER, under configurable +// clock drift distributions. It uses generated pubnet topologies +// (--pubnet-data) and overlays trigger timer and clock offset settings onto the +// CoreSets. // // Load is always generated in the same MIXED_PREGEN_* (classic + synthetic // Soroban) mode as MissionMinBlockTimeMixed, with the same node resources, @@ -14,7 +16,7 @@ // load pass at a fixed block time and check consensus stays healthy. // // CLI parameters: -// --trigger-timer-flag-pct N percentage of nodes with the flag (0-100, default 100) +// --force-old-style-trigger-timer-pct N percentage of nodes forced onto the old timer (0-100, default 0) // --drift-pct N percentage of nodes that drift (0-100, default 0) // --uniform-drift=lower,upper uniform random drift in [lower,upper] signed ms (e.g. -2000,+2000) // --bimodal-drift=m1,M1,m2,M2 first half in [m1,M1], second half in [m2,M2] signed ms @@ -59,15 +61,15 @@ let private driftSuffix (ms: int) = // than 63 chars (it appends an 11-char nonce of its own; see StellarCoreSet.fs). // With a 16-char run nonce plus the "-sts-"/"-N" scaffolding, the CoreSet name // itself only has ~29 chars of headroom. Real pubnet home-domain names (e.g. -// "blockdaemon-non-tier1") plus our "-expr"/drift annotations overflow that, so +// "blockdaemon-non-tier1") plus our "-old"/drift annotations overflow that, so // we cap the name length here. let private maxCoreSetNameLen = 26 // Build an annotated CoreSet name: "-". The // globally-unique idx guarantees the name stays distinct even after the // descriptive home-domain prefix is truncated to fit the pod-name budget. -let private annotateName (baseName: string) (idx: int) (flagEnabled: bool) (offsetMs: int) = - let flagPart = if flagEnabled then "-expr" else "" +let private annotateName (baseName: string) (idx: int) (forceOldTimer: bool) (offsetMs: int) = + let flagPart = if forceOldTimer then "-old" else "" let suffix = sprintf "-%d%s%s" idx flagPart (driftSuffix offsetMs) let budget = maxCoreSetNameLen - suffix.Length @@ -113,16 +115,16 @@ let private parseDrift (context: MissionContext) : ClockDriftDistribution = let triggerTimerMixConsensus (baseContext: MissionContext) = // This mission assigns the trigger timer per node; a blanket setting for // all nodes would defeat its purpose. - if baseContext.enableTriggerTimer.IsSome then + if baseContext.forceOldStyleTriggerTimer.IsSome then failwith - "--enable-trigger-timer is not supported by TriggerTimerMixConsensus; use --trigger-timer-flag-pct instead" + "--force-old-style-trigger-timer is not supported by TriggerTimerMixConsensus; use --force-old-style-trigger-timer-pct instead" let drift = parseDrift baseContext - let flagPct = baseContext.triggerTimerFlagPct + let forceOldPct = baseContext.forceOldStyleTriggerTimerPct let driftPct = baseContext.driftPct - if flagPct < 0 || flagPct > 100 then - failwith (sprintf "trigger-timer-flag-pct must be 0-100, got %d" flagPct) + if forceOldPct < 0 || forceOldPct > 100 then + failwith (sprintf "force-old-style-trigger-timer-pct must be 0-100, got %d" forceOldPct) if driftPct < 0 || driftPct > 100 then failwith (sprintf "drift-pct must be 0-100, got %d" driftPct) @@ -204,21 +206,22 @@ let triggerTimerMixConsensus (baseContext: MissionContext) = let totalNodes = List.sumBy (fun (cs: CoreSet) -> cs.options.nodeCount) baseCoreSets LogInfo - "TriggerTimerMixConsensus: %d total nodes, flag-pct=%d%%, drift-pct=%d%%, mode=%s, classic-tps=%d, soroban-tps=%d, block-time=%dms" + "TriggerTimerMixConsensus: %d total nodes, force-old-timer-pct=%d%%, drift-pct=%d%%, mode=%s, classic-tps=%d, soroban-tps=%d, block-time=%dms" totalNodes - flagPct + forceOldPct driftPct (mode.ToString()) classicTxRate sorobanTxRate targetMs - // Each node independently has a flagPct% chance of having the trigger - // timer flag enabled, and a driftPct% chance of drifting. When drifting, - // bimodal nodes have a 50/50 chance of being in the first or second group. + // Each node independently has a forceOldPct% chance of being forced onto + // the old prepare-start trigger timer, and a driftPct% chance of drifting. + // When drifting, bimodal nodes have a 50/50 chance of being in the first or + // second group. let rng = System.Random(context.randomSeed) - let sampleFlag () = rng.Next(100) < flagPct + let sampleForceOldTimer () = rng.Next(100) < forceOldPct let sampleOffset () = match drift with @@ -240,14 +243,18 @@ let triggerTimerMixConsensus (baseContext: MissionContext) = let nc = cs.options.nodeCount [ for j in 0 .. nc - 1 do - let flagEnabled = sampleFlag () + let forceOldTimer = sampleForceOldTimer () let offset = sampleOffset () let idx = nodeIdx nodeIdx <- nodeIdx + 1 - let annotatedName = annotateName cs.name.StringName idx flagEnabled offset + let annotatedName = annotateName cs.name.StringName idx forceOldTimer offset - LogInfo " Node %s: trigger_timer=%b, offset=%d" annotatedName.StringName flagEnabled offset + LogInfo + " Node %s: force_old_trigger_timer=%b, offset=%d" + annotatedName.StringName + forceOldTimer + offset { cs with name = annotatedName @@ -256,7 +263,7 @@ let triggerTimerMixConsensus (baseContext: MissionContext) = { cs.options with nodeCount = 1 nodeLocs = cs.options.nodeLocs |> Option.map (fun locs -> [ locs.[j] ]) - experimentalTriggerTimer = if flagEnabled then Some true else None + forceOldStyleTriggerTimer = if forceOldTimer then Some true else None clockOffsets = if offset <> 0 then Some [ offset ] else None } } ]) let tier1 = List.filter (fun (cs: CoreSet) -> cs.options.tier1 = Some true) modifiedCoreSets diff --git a/src/FSLibrary/StellarCoreCfg.fs b/src/FSLibrary/StellarCoreCfg.fs index 190fea2b..75c5d6d3 100644 --- a/src/FSLibrary/StellarCoreCfg.fs +++ b/src/FSLibrary/StellarCoreCfg.fs @@ -180,7 +180,7 @@ type StellarCoreCfg = maxBatchWriteCount: int emitMeta: bool addArtificialDelayUsec: int option // optional delay for testing in microseconds - experimentalTriggerTimer: bool option + forceOldStyleTriggerTimer: bool option clockOffsetMs: int option surveyPhaseDuration: int option containerType: CoreContainerType @@ -285,8 +285,8 @@ type StellarCoreCfg = | None -> maybeAddGlobalDelay () | Some sleep -> t.Add("ARTIFICIALLY_SLEEP_MAIN_THREAD_FOR_TESTING", sleep) |> ignore - match self.experimentalTriggerTimer with - | Some v -> t.Add("EXPERIMENTAL_TRIGGER_TIMER", v) |> ignore + match self.forceOldStyleTriggerTimer with + | Some v -> t.Add("FORCE_OLD_STYLE_PREPARE_START_TRIGGER_TIMER", v) |> ignore | None -> () match self.clockOffsetMs with @@ -667,9 +667,9 @@ type NetworkCfg with maxBatchWriteCount = opts.maxBatchWriteCount emitMeta = opts.emitMeta addArtificialDelayUsec = opts.addArtificialDelayUsec - experimentalTriggerTimer = - opts.experimentalTriggerTimer - |> Option.orElse self.missionContext.enableTriggerTimer + forceOldStyleTriggerTimer = + opts.forceOldStyleTriggerTimer + |> Option.orElse self.missionContext.forceOldStyleTriggerTimer clockOffsetMs = None surveyPhaseDuration = opts.surveyPhaseDuration containerType = MainCoreContainer @@ -713,11 +713,12 @@ type NetworkCfg with emitMeta = c.options.emitMeta addArtificialDelayUsec = c.options.addArtificialDelayUsec // CoreSet-level setting wins; otherwise fall back to the - // mission-level --enable-trigger-timer flag (unset by default, in - // which case the key is omitted from the config entirely). - experimentalTriggerTimer = - c.options.experimentalTriggerTimer - |> Option.orElse self.missionContext.enableTriggerTimer + // mission-level --force-old-style-trigger-timer flag (unset by + // default, in which case the key is omitted from the config + // entirely and core selects the timer by protocol version). + forceOldStyleTriggerTimer = + c.options.forceOldStyleTriggerTimer + |> Option.orElse self.missionContext.forceOldStyleTriggerTimer clockOffsetMs = match c.options.clockOffsets with | Some offsets -> diff --git a/src/FSLibrary/StellarCoreSet.fs b/src/FSLibrary/StellarCoreSet.fs index e1c1c3ca..4ea5a819 100644 --- a/src/FSLibrary/StellarCoreSet.fs +++ b/src/FSLibrary/StellarCoreSet.fs @@ -220,7 +220,7 @@ type CoreSetOptions = maxBatchWriteCount: int emitMeta: bool addArtificialDelayUsec: int option - experimentalTriggerTimer: bool option + forceOldStyleTriggerTimer: bool option clockOffsets: int list option surveyPhaseDuration: int option updateSorobanCosts: bool option @@ -263,7 +263,7 @@ type CoreSetOptions = maxBatchWriteCount = 1024 emitMeta = false addArtificialDelayUsec = None - experimentalTriggerTimer = None + forceOldStyleTriggerTimer = None clockOffsets = None surveyPhaseDuration = None updateSorobanCosts = None diff --git a/src/FSLibrary/StellarMissionContext.fs b/src/FSLibrary/StellarMissionContext.fs index 828048ff..5641ea59 100644 --- a/src/FSLibrary/StellarMissionContext.fs +++ b/src/FSLibrary/StellarMissionContext.fs @@ -151,12 +151,9 @@ type MissionContext = minBlockTimeMixedClassicTxRate: int option minBlockTimeMixedSorobanTxRate: int option runForMinBlockTime: bool - triggerTimerFlagPct: int + forceOldStyleTriggerTimerPct: int uniformDrift: int list bimodalDrift: int list driftPct: int ledgerCloseTimeMs: int option - // Explicit EXPERIMENTAL_TRIGGER_TIMER setting for all nodes. When None, - // the max TPS missions default to enabled and everything else defers to - // the core image's default (the key is omitted from the config). - enableTriggerTimer: bool option } + forceOldStyleTriggerTimer: bool option }