diff --git a/doc/missions.md b/doc/missions.md
index dad337e2..f009fa26 100644
--- a/doc/missions.md
+++ b/doc/missions.md
@@ -159,3 +159,18 @@ Run a network with a mix of nodes running the old and new nomination leader elec
## MissionMixedNominationLeaderElectionWithNewMajority
Run a network with a mix of nodes running the old and new nomination leader election algorithms. Contains a majority of nodes running the new algorithm.
+
+## MissionPubnetNetworkLimitsBench
+
+This mission simulates the full pubnet topology and tests how the network performs under specific transaction size limits, currently targeting SLP-4. For future SLP evaluations, adjust mission's SLP_TX_SIZE_LIMIT and SLP_TX_SIZE_MULTIPLIER parameters as needed.
+
+### Required Parameters
+
+- `--tx-size-bytes`: **Required**. Specifies the distribution of transaction sizes to generate.
+- `--tx-size-bytes-weights`: **Required**. Specifies the weights for each transaction size in the distribution.
+- `--tx-rate`: Specifies the transaction rate to generate. 200 TPS will be dedicated to classic, the rest is for Soroban invokes. Default is 230 TPS.
+
+ Examples:
+ - `--tx-size-bytes 120000 --tx-size-bytes-weights 1` - Generate only large transactions (~120KB)
+ - `--tx-size-bytes 40000,120000 --tx-size-bytes-weights 1,1` - Generate a 50/50 mix of medium (~40KB) and large (~120KB) transactions
+ - `--tx-size-bytes 20000,80000,120000 --tx-size-bytes-weights 3,2,1` - Weighted distribution: 50% small, 33% medium, 17% large
diff --git a/src/FSLibrary/FSLibrary.fsproj b/src/FSLibrary/FSLibrary.fsproj
index 57a17bcd..b33c5785 100644
--- a/src/FSLibrary/FSLibrary.fsproj
+++ b/src/FSLibrary/FSLibrary.fsproj
@@ -44,6 +44,7 @@
+
diff --git a/src/FSLibrary/MaxTPSTest.fs b/src/FSLibrary/MaxTPSTest.fs
index a07ad819..5cddbe5b 100644
--- a/src/FSLibrary/MaxTPSTest.fs
+++ b/src/FSLibrary/MaxTPSTest.fs
@@ -18,13 +18,13 @@ open StellarSupercluster
// Get the maximum value from a distribution. Returns `None` if `distribution`
// is the empty list
-let private maxDistributionValue (distribution: (int * int) list) =
+let maxDistributionValue (distribution: (int * int) list) =
match distribution with
| [] -> None
| _ -> Some(fst (List.maxBy fst distribution))
// Get the max of two optional values.
-let private maxOption (x: int option) (y: int option) =
+let maxOption (x: int option) (y: int option) =
match x, y with
| Some x, Some y -> Some(max x y)
| Some x, None -> Some x
diff --git a/src/FSLibrary/MissionPubnetNetworkLimitsBench.fs b/src/FSLibrary/MissionPubnetNetworkLimitsBench.fs
new file mode 100644
index 00000000..7266e70d
--- /dev/null
+++ b/src/FSLibrary/MissionPubnetNetworkLimitsBench.fs
@@ -0,0 +1,209 @@
+// 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
+
+module MissionPubnetNetworkLimitsBench
+
+// The point of this mission is to simulate pubnet as closely as possible under
+// a mix of classic and soroban load.
+
+open MaxTPSTest
+open PubnetData
+open StellarCoreSet
+open StellarMissionContext
+open StellarFormation
+open StellarStatefulSets
+open StellarNetworkData
+open StellarSupercluster
+open StellarCoreHTTP
+open StellarCorePeer
+
+let largeMultiplier = 100
+// Note: SLP4-specific tx size limits, update as needed
+let SLP_TX_SIZE_LIMIT = 132_096
+let PRE_SLP_TX_SIZE_LEDGER_LIMIT = 133_120
+let POST_SLP_TX_SIZE_LEDGER_LIMIT = PRE_SLP_TX_SIZE_LEDGER_LIMIT * 2
+
+let upgradeSorobanTxLimits (context: MissionContext) (formation: StellarFormation) (coreSetList: CoreSet list) =
+ formation.SetupUpgradeContract coreSetList.Head
+
+ let instructions =
+ Option.map (fun x -> (int64 x * int64 largeMultiplier)) (maxDistributionValue context.instructionsDistribution)
+
+ let txBytes =
+ Option.map ((*) (largeMultiplier * 1024)) (maxDistributionValue context.totalKiloBytesDistribution)
+
+ let entries =
+ Option.map ((*) largeMultiplier) (maxDistributionValue context.dataEntriesDistribution)
+
+ let txSizeBytes = Some(SLP_TX_SIZE_LIMIT)
+ let wasmBytes = Some(SLP_TX_SIZE_LIMIT)
+
+ formation.DeployUpgradeEntriesAndArm
+ coreSetList
+ { LoadGen.GetDefault() with
+ mode = CreateSorobanUpgrade
+ txMaxInstructions = instructions
+ txMaxReadBytes = txBytes
+ txMaxWriteBytes = maxOption txBytes wasmBytes
+ txMaxReadLedgerEntries = entries
+ txMaxWriteLedgerEntries = entries
+ txMaxFootprintSize = entries
+ txMaxSizeBytes = txSizeBytes
+ maxContractSizeBytes =
+ Option.map ((*) largeMultiplier) (maxDistributionValue context.wasmBytesDistribution)
+ // Memory limit must be reasonably high
+ txMemoryLimit = Some 200000000 }
+ (System.DateTime.UtcNow)
+
+ let peer = formation.NetworkCfg.GetPeer coreSetList.Head 0
+
+ match instructions with
+ | Some instructions -> peer.WaitForTxMaxInstructions instructions
+ | None ->
+ // Wait a little
+ peer.WaitForFewLedgers 3
+
+// Multiplier to use when increasing ledger limits. Expected
+// ledger close time (5 seconds) multiplied by some factor to add headroom (2x)
+let private ledgerSecondsMultiplier = 5 * 2
+
+let upgradeSorobanLedgerLimits
+ (context: MissionContext)
+ (formation: StellarFormation)
+ (coreSetList: CoreSet list)
+ (txrate: int)
+ =
+ formation.SetupUpgradeContract coreSetList.Head
+
+ // Make the multiplier for all limits exception txSize really large to only surge on tx size
+ let allOtherLimitsMultiplier = txrate * ledgerSecondsMultiplier * largeMultiplier
+
+ let instructions =
+ Option.map
+ (fun x -> (int64 x * int64 allOtherLimitsMultiplier))
+ (maxDistributionValue context.instructionsDistribution)
+
+ let txBytes =
+ Option.map ((*) (allOtherLimitsMultiplier * 1024)) (maxDistributionValue context.totalKiloBytesDistribution)
+
+ let entries =
+ Option.map ((*) allOtherLimitsMultiplier) (maxDistributionValue context.dataEntriesDistribution)
+
+ // Note: pass the desired network limit here. Multiplier is not needed in order to enforce the limit to test.
+ let txSizeBytes = Some(POST_SLP_TX_SIZE_LEDGER_LIMIT)
+
+ let wasmBytes = txSizeBytes
+
+ formation.DeployUpgradeEntriesAndArm
+ coreSetList
+ { LoadGen.GetDefault() with
+ mode = CreateSorobanUpgrade
+ ledgerMaxInstructions = instructions
+ ledgerMaxReadBytes = txBytes
+ ledgerMaxWriteBytes = maxOption txBytes wasmBytes
+ ledgerMaxTxCount = Some allOtherLimitsMultiplier
+ ledgerMaxReadLedgerEntries = entries
+ ledgerMaxWriteLedgerEntries = entries
+ ledgerMaxTransactionsSizeBytes = maxOption txSizeBytes wasmBytes }
+ (System.DateTime.UtcNow)
+
+ let peer = formation.NetworkCfg.GetPeer coreSetList.Head 0
+ peer.WaitForLedgerMaxTxCount allOtherLimitsMultiplier
+
+let pubnetNetworkLimitsBench (baseContext: MissionContext) =
+ let context =
+ { baseContext with
+ numAccounts = 30000
+ coreResources = SimulatePubnetResources
+ genesisTestAccountCount = Some 30000
+ // As the goal of `SimulatePubnet` is to simulate a pubnet,
+ // network delays are, in general, indispensable.
+ // Therefore, unless explicitly told otherwise, we will use
+ // network delays.
+ installNetworkDelay = Some(baseContext.installNetworkDelay |> Option.defaultValue true)
+ enableTailLogging = false
+ byteCountDistribution = defaultListValue pubnetByteCounts baseContext.byteCountDistribution
+ wasmBytesDistribution = defaultListValue pubnetWasmBytes baseContext.wasmBytesDistribution
+ dataEntriesDistribution = defaultListValue pubnetDataEntries baseContext.dataEntriesDistribution
+ totalKiloBytesDistribution = defaultListValue pubnetTotalKiloBytes baseContext.totalKiloBytesDistribution
+
+ // Note: try different distributions here to benchmark different transactions profiles
+ // e.g. [120_000, 1] for large txs only, or
+ // [(40_000, 1); (120_000, 1)] for a mix of medium and large txs
+ txSizeBytesDistribution =
+ match baseContext.txSizeBytesDistribution with
+ | [] -> failwith "PubnetNetworkLimitsBench mission requires tx size distribution to be set"
+ | _ -> baseContext.txSizeBytesDistribution
+
+ instructionsDistribution = defaultListValue pubnetInstructions baseContext.instructionsDistribution
+
+ // Unless otherwise specified, cap max connections at 65 for
+ // performance.
+ maxConnections = Some(baseContext.maxConnections |> Option.defaultValue 65)
+
+ updateSorobanCosts = Some(true)
+
+ // Transactions per second. Default is ~200 payment TPS and ~30 invoke load TPS.
+ txRate = if baseContext.txRate > 200 then baseContext.txRate else 230
+
+ skipLowFeeTxs = true }
+
+ let fullCoreSet = FullPubnetCoreSets context true false
+
+ let tier1 = List.filter (fun (cs: CoreSet) -> cs.options.tier1 = Some true) fullCoreSet
+
+ let nonTier1 = List.filter (fun (cs: CoreSet) -> cs.options.tier1 <> Some true) fullCoreSet
+
+ let numLoadGenerators = 20
+ let loadGenerators = List.take (min numLoadGenerators (List.length nonTier1)) nonTier1
+
+
+ let loadGen =
+ { LoadGen.GetDefault() with
+ mode = MixedClassicSoroban
+ offset = 0
+ maxfeerate = None
+ skiplowfeetxs = context.skipLowFeeTxs
+ accounts = context.numAccounts
+ wasms = context.numWasms
+ instances = context.numInstances
+ txrate = context.txRate
+
+ // ~10 minutes of load
+ txs = context.txRate * 60 * 10
+
+ // Weights: 200 TPS and (txRate - 200) TPS
+ payWeight = Some(200 * 5)
+ sorobanInvokeWeight = Some((context.txRate - 200) * 5)
+ sorobanUploadWeight = Some(0)
+
+ // Require all Soroban transactions to succeed.
+ minSorobanPercentSuccess = Some(100) }
+
+ context.Execute
+ fullCoreSet
+ None
+ (fun (formation: StellarFormation) ->
+ // Setup overlay connections first before manually closing
+ // ledger, which kick off consensus
+ formation.WaitUntilConnected fullCoreSet
+ formation.ManualClose tier1
+
+ // Wait until the whole network is synced before proceeding,
+ // to fail asap in case of a misconfiguration
+ formation.WaitUntilSynced fullCoreSet
+
+ // Setup
+ formation.UpgradeProtocolToLatest tier1
+ formation.UpgradeMaxTxSetSize tier1 (context.txRate * 10)
+
+ upgradeSorobanLedgerLimits context formation tier1 context.txRate
+ upgradeSorobanTxLimits context formation tier1
+
+ for lg in loadGenerators do
+ // Run setup on nodes one at a time
+ formation.RunLoadgen lg context.SetupSorobanInvoke
+
+ formation.RunMultiLoadgen loadGenerators loadGen
+ formation.EnsureAllNodesInSync fullCoreSet)
diff --git a/src/FSLibrary/StellarMission.fs b/src/FSLibrary/StellarMission.fs
index 128bf80d..2738cb0c 100644
--- a/src/FSLibrary/StellarMission.fs
+++ b/src/FSLibrary/StellarMission.fs
@@ -43,6 +43,7 @@ open MissionSorobanCatchupWithPrevAndCurr
open MissionMixedImageNetworkSurvey
open MissionMaxTPSMixed
open MissionSimulatePubnetMixedLoad
+open MissionPubnetNetworkLimitsBench
open MissionMixedNominationLeaderElection
open MissionUpgradeSCPSettings
open MissionUpgradeTxClusters
@@ -90,6 +91,7 @@ let allMissions : Map =
("MixedImageNetworkSurvey", mixedImageNetworkSurvey)
("MaxTPSMixed", maxTPSMixed)
("SimulatePubnetMixedLoad", simulatePubnetMixedLoad)
+ ("PubnetNetworkLimitsBench", pubnetNetworkLimitsBench)
("MixedNominationLeaderElectionWithOldMajority", mixedNominationLeaderElectionWithOldMajority)
("MixedNominationLeaderElectionWithNewMajority", mixedNominationLeaderElectionWithNewMajority)
("UpgradeSCPSettings", upgradeSCPSettings)