From 6281681bcc3018ca58557a2fce7bcb1af8d92a94 Mon Sep 17 00:00:00 2001 From: Garand Tyson Date: Sat, 28 Feb 2026 08:24:42 -0800 Subject: [PATCH] TriggerTimreMixConcensus test --- doc/missions.md | 19 + src/App/Program.fs | 51 ++- src/FSLibrary.Tests/Tests.fs | 8 +- src/FSLibrary/FSLibrary.fsproj | 1 + src/FSLibrary/MaxTPSTest.fs | 10 +- src/FSLibrary/MinBlockTimeTest.fs | 10 +- src/FSLibrary/MissionMaxTPSClassic.fs | 2 +- src/FSLibrary/MissionMaxTPSMixed.fs | 2 +- .../MissionTriggerTimerMixConsensus.fs | 324 ++++++++++++++++++ src/FSLibrary/StellarCoreCfg.fs | 28 ++ src/FSLibrary/StellarCoreSet.fs | 4 + src/FSLibrary/StellarMission.fs | 2 + src/FSLibrary/StellarMissionContext.fs | 8 +- 13 files changed, 459 insertions(+), 10 deletions(-) create mode 100644 src/FSLibrary/MissionTriggerTimerMixConsensus.fs diff --git a/doc/missions.md b/doc/missions.md index a8b36f0d..0db28795 100644 --- a/doc/missions.md +++ b/doc/missions.md @@ -119,6 +119,10 @@ Simulate Public Network topology and throughput based on user-supplied distribut Stress test a network of simulated Tier1 topology with classic traffic and report maximum achieved throughput. +### Parameters + +- `--disable-trigger-timer`: Disable `EXPERIMENTAL_TRIGGER_TIMER` on all nodes. The trigger timer is enabled by default. + ## MissionSorobanLoadGeneration Test heavy Soroban load on a large network of nodes. This mission mostly focuses on high-bandwidth traffic, and its impact on SCP and overlay. Apply time is simulated to avoid noise from database backends. @@ -159,6 +163,21 @@ Find the minimum ledger target close time a simulated Tier1 network can sustain Same as `MissionMinBlockTimeClassic`, but drives an explicit `MIXED_PREGEN_*` overlay-only loadgen mode with pre-generated classic payments plus a selected synthetic Soroban transaction type. +## 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`. + +### Parameters + +- `--trigger-timer-flag-pct`: Percentage (0-100) of nodes with `EXPERIMENTAL_TRIGGER_TIMER` enabled. Default 100. +- `--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`). +- `--ledger-close-time-ms`: Target ledger close time in ms, upgraded before load is applied. Default 5000. +- `--min-block-time-mixed-mode`: The `MIXED_PREGEN_*` loadgen mode to drive. Default `mixed_pregen_sac_payment`. +- `--classic-tx-rate`: Classic TPS. Defaults to half of `--tx-rate`. +- `--soroban-tx-rate`: Soroban TPS. Defaults to half of `--tx-rate`. + ## MissionMixedNominationLeaderElectionWithOldMajority Run a network with a mix of nodes running the old and new nomination leader election algorithms. Contains a majority of nodes running the old algorithm. diff --git a/src/App/Program.fs b/src/App/Program.fs index 83ade839..72930635 100644 --- a/src/App/Program.fs +++ b/src/App/Program.fs @@ -137,7 +137,13 @@ type MissionOptions maxBlockTimeMs: int, minBlockTimeMixedMode: string, minBlockTimeMixedClassicTxRate: int option, - minBlockTimeMixedSorobanTxRate: int option + minBlockTimeMixedSorobanTxRate: int option, + triggerTimerFlagPct: int, + uniformDrift: seq, + bimodalDrift: seq, + driftPct: int, + ledgerCloseTimeMs: int option, + disableTriggerTimer: bool ) = [] @@ -631,6 +637,41 @@ type MissionOptions Required = false)>] member self.MinBlockTimeMixedSorobanTxRate = minBlockTimeMixedSorobanTxRate + [] + member self.TriggerTimerFlagPct = triggerTimerFlagPct + + [] + member self.UniformDrift = uniformDrift + + [] + member self.BimodalDrift = bimodalDrift + + [] + member self.DriftPct = driftPct + + [] + member self.LedgerCloseTimeMs = ledgerCloseTimeMs + + [] + member self.DisableTriggerTimer = disableTriggerTimer + let splitLabel (lab: string) : (string * string option) = match lab.Split ':' |> Array.toList with | [ x ] -> x, None @@ -869,7 +910,13 @@ let main argv = minBlockTimeMixedMode = mission.MinBlockTimeMixedMode minBlockTimeMixedClassicTxRate = mission.MinBlockTimeMixedClassicTxRate minBlockTimeMixedSorobanTxRate = mission.MinBlockTimeMixedSorobanTxRate - runForMinBlockTime = false } + runForMinBlockTime = false + triggerTimerFlagPct = mission.TriggerTimerFlagPct + uniformDrift = List.ofSeq mission.UniformDrift + bimodalDrift = List.ofSeq mission.BimodalDrift + driftPct = mission.DriftPct + ledgerCloseTimeMs = mission.LedgerCloseTimeMs + enableTriggerTimer = not mission.DisableTriggerTimer } allMissions.[m] missionContext diff --git a/src/FSLibrary.Tests/Tests.fs b/src/FSLibrary.Tests/Tests.fs index ce280d8b..35ee4b4e 100644 --- a/src/FSLibrary.Tests/Tests.fs +++ b/src/FSLibrary.Tests/Tests.fs @@ -143,7 +143,13 @@ let ctx : MissionContext = minBlockTimeMixedMode = "mixed_pregen_sac_payment" minBlockTimeMixedClassicTxRate = None minBlockTimeMixedSorobanTxRate = None - runForMinBlockTime = false } + runForMinBlockTime = false + triggerTimerFlagPct = 100 + uniformDrift = [] + bimodalDrift = [] + driftPct = 0 + ledgerCloseTimeMs = None + enableTriggerTimer = true } let netdata = __SOURCE_DIRECTORY__ + "/../../../data/public-network-data-2024-08-01.json" let pubkeys = __SOURCE_DIRECTORY__ + "/../../../data/tier1keys.json" diff --git a/src/FSLibrary/FSLibrary.fsproj b/src/FSLibrary/FSLibrary.fsproj index 135d1cec..e78c6ff5 100644 --- a/src/FSLibrary/FSLibrary.fsproj +++ b/src/FSLibrary/FSLibrary.fsproj @@ -81,6 +81,7 @@ + diff --git a/src/FSLibrary/MaxTPSTest.fs b/src/FSLibrary/MaxTPSTest.fs index dec4072b..8e9b3c93 100644 --- a/src/FSLibrary/MaxTPSTest.fs +++ b/src/FSLibrary/MaxTPSTest.fs @@ -124,7 +124,7 @@ let upgradeSorobanLedgerLimits peer.WaitForLedgerMaxTxCount multiplier -let maxTPSTest (context: MissionContext) (baseLoadGen: LoadGen) (setupCfg: LoadGen option) = +let maxTPSTest (context: MissionContext) (baseLoadGen: LoadGen) (setupCfg: LoadGen option) (enableTriggerTimer: bool) = let allNodes = if context.pubnetData.IsSome then FullPubnetCoreSets context true false @@ -133,6 +133,14 @@ let maxTPSTest (context: MissionContext) (baseLoadGen: LoadGen) (setupCfg: LoadG context.image (if context.flatQuorum.IsSome then context.flatQuorum.Value else false) + let allNodes = + if enableTriggerTimer then + allNodes + |> List.map + (fun (cs: CoreSet) -> { cs with options = { cs.options with experimentalTriggerTimer = Some true } }) + else + allNodes + // 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/MinBlockTimeTest.fs b/src/FSLibrary/MinBlockTimeTest.fs index 3401f5eb..571cb427 100644 --- a/src/FSLibrary/MinBlockTimeTest.fs +++ b/src/FSLibrary/MinBlockTimeTest.fs @@ -39,7 +39,9 @@ let private maxTxSetSizeForTarget (kind: string) (targetMs: int) (txRate: int) = max (int txSetSize) 100 -let private classicMaxTxSetSizeForTarget (targetMs: int) (classicTxRate: int) = +// Exposed for reuse by MissionTriggerTimerMixConsensus, which runs the same +// MIXED_PREGEN_* load without the binary search. +let classicMaxTxSetSizeForTarget (targetMs: int) (classicTxRate: int) = maxTxSetSizeForTarget "Classic" targetMs classicTxRate let private sorobanMaxTxSetSizeForTarget (targetMs: int) (sorobanTxRate: int) = @@ -139,7 +141,8 @@ let private waitForMixedPregenSorobanLimits (peer: Peer) (limits: MixedPregenSor && info.Tx.MaxContractEventsSizeBytes = limits.txMaxContractEventsSizeBytes) (fun _ -> LogInfo "Waiting for MIXED_PREGEN_* Soroban limits on %s" peer.ShortName.StringName) -let private upgradeMixedPregenSorobanLimits +// Exposed for reuse by MissionTriggerTimerMixConsensus. +let upgradeMixedPregenSorobanLimits (formation: StellarFormation) (coreSets: CoreSet list) (baseLoadGen: LoadGen) @@ -227,7 +230,8 @@ let private toggleOverlayOnlyMode (formation: StellarFormation) (coreSets: CoreS let res = peer.ToggleOverlayOnlyMode() LogInfo "Toggled overlay-only mode on %s: %s" peer.ShortName.StringName res) -let private withOverlayOnlyMode (formation: StellarFormation) (coreSets: CoreSet list) (f: unit -> unit) = +// Exposed for reuse by MissionTriggerTimerMixConsensus. +let withOverlayOnlyMode (formation: StellarFormation) (coreSets: CoreSet list) (f: unit -> unit) = LogInfo "Enabling overlay-only mode" toggleOverlayOnlyMode formation coreSets diff --git a/src/FSLibrary/MissionMaxTPSClassic.fs b/src/FSLibrary/MissionMaxTPSClassic.fs index 198a61ef..05d6c625 100644 --- a/src/FSLibrary/MissionMaxTPSClassic.fs +++ b/src/FSLibrary/MissionMaxTPSClassic.fs @@ -27,4 +27,4 @@ let maxTPSClassic (context: MissionContext) = maxfeerate = None skiplowfeetxs = false } - maxTPSTest context baseLoadGen None + maxTPSTest context baseLoadGen None context.enableTriggerTimer diff --git a/src/FSLibrary/MissionMaxTPSMixed.fs b/src/FSLibrary/MissionMaxTPSMixed.fs index 068e7ff3..2e02184c 100644 --- a/src/FSLibrary/MissionMaxTPSMixed.fs +++ b/src/FSLibrary/MissionMaxTPSMixed.fs @@ -47,4 +47,4 @@ let maxTPSMixed (baseContext: MissionContext) = let invokeSetupCfg = { baseLoadGen with mode = SorobanInvokeSetup } - maxTPSTest context baseLoadGen (Some invokeSetupCfg) + maxTPSTest context baseLoadGen (Some invokeSetupCfg) false diff --git a/src/FSLibrary/MissionTriggerTimerMixConsensus.fs b/src/FSLibrary/MissionTriggerTimerMixConsensus.fs new file mode 100644 index 00000000..1c66bea3 --- /dev/null +++ b/src/FSLibrary/MissionTriggerTimerMixConsensus.fs @@ -0,0 +1,324 @@ +// Copyright 2026 Stellar Development Foundation and contributors. Licensed +// 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. +// +// Load is always generated in the same MIXED_PREGEN_* (classic + synthetic +// Soroban) mode as MissionMinBlockTimeMixed, with the same node resources, +// account count, and load duration (see MinBlockTimeTest.fs). Unlike that +// mission we do NOT binary-search for a minimum block time; we run a single +// 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) +// --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 +// --ledger-close-time-ms N target ledger close time in ms (default 5000) +// --min-block-time-mixed-mode M MIXED_PREGEN_* loadgen mode (default mixed_pregen_sac_payment) +// --classic-tx-rate N classic TPS (defaults to half of --tx-rate) +// --soroban-tx-rate N soroban TPS (defaults to half of --tx-rate) + +module MissionTriggerTimerMixConsensus + +open Logging +open MinBlockTimeTest +open StellarCoreHTTP +open StellarCorePeer +open StellarCoreSet +open StellarFormation +open StellarMissionContext +open StellarNetworkData +open StellarStatefulSets +open StellarSupercluster + +type ClockDriftDistribution = + | NoDrift + | UniformDrift of lower: int * upper: int + | BimodalDrift of min1: int * max1: int * min2: int * max2: int + +// Networks larger than this drive load from tier1 only; smaller ones use every +// node. Matches MinBlockTimeTest.fs / MaxTPSTest.fs. +let private smallNetworkSize = 10 + +// Round ms to whole seconds, ceiling away from zero: 1500 -> 2, -800 -> -1 +let private ceilToSec (ms: int) = if ms >= 0 then (ms + 999) / 1000 else -((abs ms + 999) / 1000) + +// Drift suffix for a single offset: 0 -> "", 1500 -> "-p2", -800 -> "-m1" +let private driftSuffix (ms: int) = + let s = ceilToSec ms + + if s > 0 then sprintf "-p%d" s + elif s < 0 then sprintf "-m%d" (abs s) + else "" + +// Pod names are "-sts--" and Kubernetes rejects any name longer +// 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 +// 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 suffix = sprintf "-%d%s%s" idx flagPart (driftSuffix offsetMs) + let budget = maxCoreSetNameLen - suffix.Length + + let prefix = + if baseName.Length > budget then + baseName.Substring(0, max 0 budget).TrimEnd('-') + else + baseName + + CoreSetName(prefix + suffix) + +let private parseDrift (context: MissionContext) : ClockDriftDistribution = + match context.uniformDrift, context.bimodalDrift with + | [], [] -> NoDrift + | [ lower; upper ], [] -> + if upper < lower then + failwith (sprintf "uniform-drift requires lower <= upper, got %d,%d" lower upper) + + UniformDrift(lower, upper) + | [], [ min1; max1; min2; max2 ] -> + if max1 < min1 then + failwith (sprintf "bimodal-drift first range requires min <= max, got %d,%d" min1 max1) + + if max2 < min2 then + failwith (sprintf "bimodal-drift second range requires min <= max, got %d,%d" min2 max2) + + BimodalDrift(min1, max1, min2, max2) + | _ :: _, _ :: _ -> failwith "Cannot specify both --uniform-drift and --bimodal-drift" + | u, [] -> failwith (sprintf "--uniform-drift requires exactly 2 values (lower,upper), got %d" u.Length) + | [], b -> failwith (sprintf "--bimodal-drift requires exactly 4 values (min1,max1,min2,max2), got %d" b.Length) + +let triggerTimerMixConsensus (baseContext: MissionContext) = + let drift = parseDrift baseContext + let flagPct = baseContext.triggerTimerFlagPct + let driftPct = baseContext.driftPct + + if flagPct < 0 || flagPct > 100 then + failwith (sprintf "trigger-timer-flag-pct must be 0-100, got %d" flagPct) + + if driftPct < 0 || driftPct > 100 then + failwith (sprintf "drift-pct must be 0-100, got %d" driftPct) + + // Mixed (classic + synthetic Soroban) load, configured exactly like + // MissionMinBlockTimeMixed. Split --tx-rate evenly when neither component + // rate is given. + let mode = parseMixedPregenMode baseContext.minBlockTimeMixedMode + + let classicTxRate, sorobanTxRate = + match baseContext.minBlockTimeMixedClassicTxRate, baseContext.minBlockTimeMixedSorobanTxRate with + | None, None -> + let soroban = baseContext.txRate / 2 + baseContext.txRate - soroban, soroban + | Some classic, None -> classic, 0 + | None, Some soroban -> 0, soroban + | Some classic, Some soroban -> classic, soroban + + if classicTxRate < 0 || sorobanTxRate < 0 then + failwith "--classic-tx-rate and --soroban-tx-rate must be non-negative" + + if classicTxRate = 0 && sorobanTxRate = 0 then + failwith "At least one of --classic-tx-rate or --soroban-tx-rate must be non-zero" + + let txRate = classicTxRate + sorobanTxRate + + // Node resources and load are standardized with MissionMinBlockTime{Classic, + // Mixed} (see MinBlockTimeTest.fs) so the trigger timer is exercised under + // the same conditions as the minimum-block-time search. We keep this + // mission's own knobs (drift, trigger-timer flag pct, block time) but match + // the perf-test node size, account count, pregenerated tx pool, and load + // duration. + // + // Load duration is the ~5 min window MinBlockTimeTest uses to decide a + // "success" (txs = txRate * 300), so numTxs = txRate * loadGenDurationSec. + let loadGenDurationSec = 300 + + let genesisAccounts = baseContext.genesisTestAccountCount |> Option.defaultValue 100000 + + let numPregeneratedTxs = baseContext.numPregeneratedTxs |> Option.defaultValue 2500000 + + // Block time we run (and size tx-set limits) at. Always upgraded before + // load; defaults to 5000ms when --ledger-close-time-ms is unset. + let targetMs = baseContext.ledgerCloseTimeMs |> Option.defaultValue 5000 + + let context = + { baseContext with + txRate = txRate + numAccounts = genesisAccounts + numTxs = txRate * loadGenDurationSec + coreResources = SimulatePubnetTier1PerfResources + genesisTestAccountCount = Some genesisAccounts + numPregeneratedTxs = Some numPregeneratedTxs + enableTailLogging = false + installNetworkDelay = Some(baseContext.installNetworkDelay |> Option.defaultValue true) + maxConnections = Some(baseContext.maxConnections |> Option.defaultValue 65) } + + let baseLoadGen = + { LoadGen.GetDefault() with + mode = mode + spikesize = context.spikeSize + spikeinterval = context.spikeInterval + offset = 0 + maxfeerate = None + skiplowfeetxs = false + classicTxRate = Some classicTxRate + sorobanTxRate = Some sorobanTxRate } + + let baseCoreSets = FullPubnetCoreSets context true false + + let totalNodes = List.sumBy (fun (cs: CoreSet) -> cs.options.nodeCount) baseCoreSets + + match drift with + | NoDrift when driftPct > 0 -> + failwith "drift-pct > 0 but no drift distribution specified (use --uniform-drift or --bimodal-drift)" + | _ -> () + + LogInfo + "TriggerTimerMixConsensus: %d total nodes, flag-pct=%d%%, drift-pct=%d%%, mode=%s, classic-tps=%d, soroban-tps=%d, block-time=%dms" + totalNodes + flagPct + 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. + let rng = System.Random(context.randomSeed) + + let sampleFlag () = rng.Next(100) < flagPct + + let sampleOffset () = + match drift with + | NoDrift -> 0 + | _ when rng.Next(100) >= driftPct -> 0 + | UniformDrift (lower, upper) -> rng.Next(lower, upper + 1) + | BimodalDrift (min1, max1, min2, max2) -> + if rng.Next(2) = 0 then rng.Next(min1, max1 + 1) else rng.Next(min2, max2 + 1) + + // Walk through CoreSets, splitting each into single-node CoreSets so that + // each node gets its own name with flag/drift annotation. A monotonic index + // gives every node a globally-unique name even after truncation. + let mutable nodeIdx = 0 + + let modifiedCoreSets = + baseCoreSets + |> List.collect + (fun cs -> + let nc = cs.options.nodeCount + + [ for j in 0 .. nc - 1 do + let flagEnabled = sampleFlag () + let offset = sampleOffset () + let idx = nodeIdx + nodeIdx <- nodeIdx + 1 + + let annotatedName = annotateName cs.name.StringName idx flagEnabled offset + + LogInfo " Node %s: trigger_timer=%b, offset=%d" annotatedName.StringName flagEnabled offset + + { cs with + name = annotatedName + keys = [| cs.keys.[j] |] + options = + { cs.options with + nodeCount = 1 + nodeLocs = cs.options.nodeLocs |> Option.map (fun locs -> [ locs.[j] ]) + experimentalTriggerTimer = if flagEnabled 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 + + // Mirror MissionMinBlockTime{Classic,Mixed} (see MinBlockTimeTest.fs): on + // larger networks the tier1 nodes drive load; on tiny ones every node does. + let loadGenNodes = + if List.length modifiedCoreSets > smallNetworkSize then + tier1 + else + modifiedCoreSets + + // MIXED_PREGEN_* load runs on a subset of the loadgen nodes sized to the + // requested per-type TPS (at least one), as in MinBlockTimeTest. + let activeLoadGenNodes = + let requestedCount = max classicTxRate sorobanTxRate + + loadGenNodes + |> List.truncate (min (List.length loadGenNodes) (max 1 requestedCount)) + + let isLoadGenNode cs = List.exists (fun (cs': CoreSet) -> cs' = cs) loadGenNodes + + // Pre-generate signed txs on the loadgen nodes: partition the genesis + // accounts evenly across the active loadgen nodes and assign each one a + // distinct account slice via its offset (mirrors MinBlockTimeTest's mixed + // pregen partitioning). + let partitionCount = List.length activeLoadGenNodes + let accountsPerNode = genesisAccounts / partitionCount + let mutable partitionIdx = 0 + + let preparedCoreSets = + modifiedCoreSets + |> List.map + (fun cs -> + let pregenerateTxs = + if isLoadGenNode cs then + let i = partitionIdx % partitionCount + partitionIdx <- partitionIdx + 1 + Some(numPregeneratedTxs, accountsPerNode, accountsPerNode * i) + else + Some(0, 1, 0) + + { cs with + options = + { cs.options with + initialization = { cs.options.initialization with pregenerateTxs = pregenerateTxs } } }) + + // The fixed load pass: same shape as MinBlockTimeTest's evaluateAt, minus + // the binary search. RunMultiLoadgen splits accounts/txs/per-type TPS across + // the active nodes. + let loadGen = + { baseLoadGen with + accounts = genesisAccounts + txs = txRate * loadGenDurationSec + txrate = txRate } + + context.ExecuteWithOptionalConsistencyCheck + preparedCoreSets + None + false + (fun (formation: StellarFormation) -> + formation.WaitUntilConnected preparedCoreSets + formation.ManualClose tier1 + formation.WaitUntilSynced preparedCoreSets + + formation.UpgradeProtocolToLatest tier1 + + LogInfo "Upgrading target ledger close time to %d ms" targetMs + formation.UpgradeSCPTargetLedgerCloseTime tier1 targetMs + + // Raise classic and Soroban network limits to fit the requested TPS + // at the target block time before applying load. + formation.UpgradeMaxTxSetSize tier1 (classicMaxTxSetSizeForTarget targetMs classicTxRate) + upgradeMixedPregenSorobanLimits formation tier1 baseLoadGen targetMs + + // MIXED_PREGEN_* modes are overlay-only loadgen modes, so enable + // overlay-only mode for the duration of the load pass. + withOverlayOnlyMode + formation + preparedCoreSets + (fun () -> formation.RunMultiLoadgen activeLoadGenNodes loadGen) + + formation.CheckNoErrorsAndPairwiseConsistency() + formation.EnsureAllNodesInSync preparedCoreSets) diff --git a/src/FSLibrary/StellarCoreCfg.fs b/src/FSLibrary/StellarCoreCfg.fs index e4c2fd4a..85670636 100644 --- a/src/FSLibrary/StellarCoreCfg.fs +++ b/src/FSLibrary/StellarCoreCfg.fs @@ -169,6 +169,8 @@ type StellarCoreCfg = maxBatchWriteCount: int emitMeta: bool addArtificialDelayUsec: int option // optional delay for testing in microseconds + experimentalTriggerTimer: bool option + clockOffsetMs: int option surveyPhaseDuration: int option containerType: CoreContainerType skipHighCriticalValidatorChecks: bool } @@ -272,6 +274,16 @@ 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 + | None -> () + + match self.clockOffsetMs with + | Some offset -> + t.Add("ARTIFICIALLY_SET_SYSTEM_CLOCK_OFFSET_FOR_TESTING", int64 offset) + |> ignore + | None -> () + match self.network.missionContext.flowControlSendMoreBatchSize with | None -> () | Some batchSize -> t.Add("FLOW_CONTROL_SEND_MORE_BATCH_SIZE", batchSize) |> ignore @@ -644,6 +656,8 @@ type NetworkCfg with maxBatchWriteCount = opts.maxBatchWriteCount emitMeta = opts.emitMeta addArtificialDelayUsec = opts.addArtificialDelayUsec + experimentalTriggerTimer = opts.experimentalTriggerTimer + clockOffsetMs = None surveyPhaseDuration = opts.surveyPhaseDuration containerType = MainCoreContainer skipHighCriticalValidatorChecks = opts.skipHighCriticalValidatorChecks } @@ -685,6 +699,20 @@ type NetworkCfg with maxBatchWriteCount = c.options.maxBatchWriteCount emitMeta = c.options.emitMeta addArtificialDelayUsec = c.options.addArtificialDelayUsec + experimentalTriggerTimer = c.options.experimentalTriggerTimer + clockOffsetMs = + match c.options.clockOffsets with + | Some offsets -> + if offsets.Length <> c.options.nodeCount then + failwith ( + sprintf + "clockOffsets length %d does not match nodeCount %d" + offsets.Length + c.options.nodeCount + ) + + Some offsets.[i] + | None -> None surveyPhaseDuration = c.options.surveyPhaseDuration containerType = ctype skipHighCriticalValidatorChecks = c.options.skipHighCriticalValidatorChecks } diff --git a/src/FSLibrary/StellarCoreSet.fs b/src/FSLibrary/StellarCoreSet.fs index 34a2d84f..e1c1c3ca 100644 --- a/src/FSLibrary/StellarCoreSet.fs +++ b/src/FSLibrary/StellarCoreSet.fs @@ -220,6 +220,8 @@ type CoreSetOptions = maxBatchWriteCount: int emitMeta: bool addArtificialDelayUsec: int option + experimentalTriggerTimer: bool option + clockOffsets: int list option surveyPhaseDuration: int option updateSorobanCosts: bool option // `skipHighCriticalValidatorChecks` exists to allow supercluster to @@ -261,6 +263,8 @@ type CoreSetOptions = maxBatchWriteCount = 1024 emitMeta = false addArtificialDelayUsec = None + experimentalTriggerTimer = None + clockOffsets = None surveyPhaseDuration = None updateSorobanCosts = None skipHighCriticalValidatorChecks = true } diff --git a/src/FSLibrary/StellarMission.fs b/src/FSLibrary/StellarMission.fs index 70123e4a..6757a1bb 100644 --- a/src/FSLibrary/StellarMission.fs +++ b/src/FSLibrary/StellarMission.fs @@ -45,6 +45,7 @@ open MissionMaxTPSMixed open MissionSimulatePubnetMixedLoad open MissionPubnetNetworkLimitsBench open MissionMixedNominationLeaderElection +open MissionTriggerTimerMixConsensus open MissionUpgradeSCPSettings open MissionUpgradeTxClusters open MissionValidatorSetup @@ -97,6 +98,7 @@ let allMissions : Map = ("PubnetNetworkLimitsBench", pubnetNetworkLimitsBench) ("MixedNominationLeaderElectionWithOldMajority", mixedNominationLeaderElectionWithOldMajority) ("MixedNominationLeaderElectionWithNewMajority", mixedNominationLeaderElectionWithNewMajority) + ("TriggerTimerMixConsensus", triggerTimerMixConsensus) ("UpgradeSCPSettings", upgradeSCPSettings) ("UpgradeTxClusters", upgradeTxClusters) ("ValidatorSetup", validatorSetup) diff --git a/src/FSLibrary/StellarMissionContext.fs b/src/FSLibrary/StellarMissionContext.fs index c64b4cca..8fb5c3dc 100644 --- a/src/FSLibrary/StellarMissionContext.fs +++ b/src/FSLibrary/StellarMissionContext.fs @@ -138,4 +138,10 @@ type MissionContext = minBlockTimeMixedMode: string minBlockTimeMixedClassicTxRate: int option minBlockTimeMixedSorobanTxRate: int option - runForMinBlockTime: bool } + runForMinBlockTime: bool + triggerTimerFlagPct: int + uniformDrift: int list + bimodalDrift: int list + driftPct: int + ledgerCloseTimeMs: int option + enableTriggerTimer: bool }