Skip to content

Commit d6a8698

Browse files
committed
feat(core): simulate realistic network conditions
Signed-off-by: AvhiMaz <avhimazumder5@outlook.com>
1 parent 9095435 commit d6a8698

7 files changed

Lines changed: 70 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/src/cli/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ lazy_static::lazy_static! {
6363
/// We set out snap build to set this environment variable to the real home directory,
6464
/// because by default, snaps run in a confined environment where the home directory is not
6565
/// the user's actual home directory.
66+
fn parse_drop_rate(s: &str) -> Result<f64, String> {
67+
let v: f64 = s
68+
.parse()
69+
.map_err(|_| format!("'{}' is not a valid number", s))?;
70+
if !(0.0..=1.0).contains(&v) {
71+
return Err(format!("drop rate must be between 0.0 and 1.0, got {}", v));
72+
}
73+
Ok(v)
74+
}
75+
6676
pub fn get_home_dir() -> String {
6777
if let Ok(real_home) = env::var("SNAP_REAL_HOME") {
6878
let path_buf = PathBuf::from(real_home);
@@ -276,6 +286,14 @@ pub struct StartSimnet {
276286
/// Skip signature verification for all transactions (eg. surfpool start --skip-signature-verification)
277287
#[clap(long = "skip-signature-verification", action=ArgAction::SetTrue, default_value = "false")]
278288
pub skip_signature_verification: bool,
289+
/// Probability (0.0–1.0) that a transaction is randomly dropped, simulating packet loss or leader rejection.
290+
/// E.g. 0.1 means 10% of transactions will be dropped. (eg. surfpool start --transaction-drop-rate 0.1)
291+
#[arg(long = "transaction-drop-rate", value_parser = parse_drop_rate)]
292+
pub transaction_drop_rate: Option<f64>,
293+
/// Maximum random delay in milliseconds before executing a transaction, simulating out-of-order execution.
294+
/// Each transaction waits a random duration from 0 to this value. (eg. surfpool start --transaction-execution-delay-ms 200)
295+
#[arg(long = "transaction-execution-delay-ms")]
296+
pub transaction_execution_delay_ms: Option<u64>,
279297
}
280298

281299
#[derive(clap::ValueEnum, PartialEq, Clone, Debug)]
@@ -426,6 +444,8 @@ impl StartSimnet {
426444
skip_signature_verification: self.skip_signature_verification,
427445
surfnet_id: self.surfnet_id.clone(),
428446
snapshot,
447+
transaction_drop_rate: self.transaction_drop_rate,
448+
transaction_execution_delay_ms: self.transaction_execution_delay_ms,
429449
}
430450
}
431451

crates/core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ libloading = { workspace = true }
4242
litesvm = { workspace = true }
4343
litesvm-token = { workspace = true }
4444
log = { workspace = true }
45+
rand = "0.8"
4546
reqwest = { workspace = true }
4647
serde = { workspace = true }
4748
serde_derive = { workspace = true } # must match the serde version, see https://github.com/serde-rs/serde/issues/2584#issuecomment-1685252251

crates/core/src/rpc/full.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,18 @@ impl Full for SurfpoolFullRpc {
16791679
m.record_transaction(true, rpc_start.elapsed().as_millis() as u64);
16801680
}
16811681
}
1682+
Ok(TransactionStatusEvent::Dropped) => {
1683+
#[cfg(feature = "prometheus")]
1684+
if let Some(m) = crate::telemetry::metrics() {
1685+
m.record_transaction(false, rpc_start.elapsed().as_millis() as u64);
1686+
m.record_rpc_request("sendTransaction", rpc_start.elapsed().as_millis() as u64);
1687+
}
1688+
return Err(Error {
1689+
data: None,
1690+
message: "Transaction dropped: simulated network condition".to_string(),
1691+
code: jsonrpc_core::ErrorCode::ServerError(-32002),
1692+
});
1693+
}
16821694
}
16831695

16841696
#[cfg(feature = "prometheus")]

crates/core/src/runloops/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@ pub async fn start_block_production_runloop(
346346
expiry_duration_ms.map(|expiry_val| Utc::now().timestamp_millis() as u64 + expiry_val);
347347
let global_skip_sig_verify = simnet_config.skip_signature_verification;
348348
let ix_profiling_initially_enabled = simnet_config.instruction_profiling_enabled;
349+
let transaction_drop_rate = simnet_config.transaction_drop_rate;
350+
let transaction_execution_delay_ms = simnet_config.transaction_execution_delay_ms;
349351
loop {
350352
let mut do_produce_block = false;
351353

@@ -481,6 +483,23 @@ pub async fn start_block_production_runloop(
481483
continue
482484
}
483485
SimnetCommand::ProcessTransaction(_key, transaction, status_tx, skip_preflight, skip_sig_verify_override) => {
486+
// Randomly drop the transaction to simulate real-world packet loss / leader rejection
487+
if let Some(drop_rate) = transaction_drop_rate {
488+
if rand::random::<f64>() < drop_rate {
489+
let _ = svm_locker.simnet_events_tx().send(SimnetEvent::warn(
490+
format!("Transaction dropped (simulated, drop_rate={})", drop_rate)
491+
));
492+
let _ = status_tx.send(surfpool_types::TransactionStatusEvent::Dropped);
493+
continue;
494+
}
495+
}
496+
// Apply random execution delay to simulate out-of-order processing
497+
if let Some(max_delay_ms) = transaction_execution_delay_ms {
498+
if max_delay_ms > 0 {
499+
let delay = rand::random::<u64>() % (max_delay_ms + 1);
500+
tokio::time::sleep(std::time::Duration::from_millis(delay)).await;
501+
}
502+
}
484503
let skip_sig_verify = skip_sig_verify_override.unwrap_or(global_skip_sig_verify);
485504
let sigverify = !skip_sig_verify;
486505
if let Err(e) = svm_locker.process_transaction(&remote_client_with_commitment, transaction, status_tx, skip_preflight, sigverify).await {

crates/core/src/tests/integration.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,9 @@ async fn test_transaction_with_ed25519_instruction(test_type: TestType) {
825825
Ok(TransactionStatusEvent::VerificationFailure(error)) => {
826826
panic!("Transaction verification failed: {}", error);
827827
}
828+
Ok(TransactionStatusEvent::Dropped) => {
829+
panic!("Transaction was unexpectedly dropped");
830+
}
828831
Err(e) => {
829832
panic!("Failed to receive transaction status: {:?}", e);
830833
}
@@ -1553,6 +1556,9 @@ async fn test_get_transaction_profile(test_type: TestType) {
15531556
Ok(TransactionStatusEvent::VerificationFailure(error)) => {
15541557
panic!("Transaction verification failed: {}", error);
15551558
}
1559+
Ok(TransactionStatusEvent::Dropped) => {
1560+
panic!("Transaction was unexpectedly dropped");
1561+
}
15561562
Err(e) => {
15571563
panic!("Failed to receive transaction status: {:?}", e);
15581564
}

crates/types/src/types.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ pub enum TransactionStatusEvent {
537537
SimulationFailure((TransactionError, TransactionMetadata)),
538538
ExecutionFailure((TransactionError, TransactionMetadata)),
539539
VerificationFailure(String),
540+
Dropped,
540541
}
541542

542543
#[derive(Debug)]
@@ -615,6 +616,14 @@ pub struct SimnetConfig {
615616
/// Snapshot accounts to preload at startup.
616617
/// Keys are pubkey strings, values can be None to fetch from remote RPC.
617618
pub snapshot: BTreeMap<String, Option<AccountSnapshot>>,
619+
/// Probability (0.0–1.0) that any given transaction will be randomly dropped,
620+
/// simulating real-world packet loss or leader rejection.
621+
#[serde(default)]
622+
pub transaction_drop_rate: Option<f64>,
623+
/// Maximum random delay in milliseconds applied before executing each transaction,
624+
/// simulating out-of-order execution under high throughput.
625+
#[serde(default)]
626+
pub transaction_execution_delay_ms: Option<u64>,
618627
}
619628

620629
impl Default for SimnetConfig {
@@ -633,6 +642,8 @@ impl Default for SimnetConfig {
633642
skip_signature_verification: false,
634643
surfnet_id: "default".to_string(),
635644
snapshot: BTreeMap::new(),
645+
transaction_drop_rate: None,
646+
transaction_execution_delay_ms: None,
636647
}
637648
}
638649
}

0 commit comments

Comments
 (0)