{"record":{"id":"44df320a7a9fb9a4","repo":"nautechsystems/nautilus_trader","slug":"lighter-connection-or-nonce-state-changed-during-t","errorCode":null,"errorMessage":"Lighter connection or nonce state changed during transaction preparation","messagePattern":"Lighter connection or nonce state changed during transaction preparation","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/lighter/src/execution.rs","lineNumber":1470,"sourceCode":"    }\n\n    // Per-order `params[\"market_order_slippage_bps\"]` overrides the config default.\n    fn resolve_slippage_bps(&self, params: Option<&Params>) -> u32 {\n        params\n            .and_then(|p| p.get_u64(\"market_order_slippage_bps\"))\n            .map_or(self.config.market_order_slippage_bps, |v| v as u32)\n    }\n\n    fn build_tx_context(&self, credential: &Credential) -> anyhow::Result<ReservedTxContext> {\n        let connection_epoch = self.ws_client.connection_epoch();\n        anyhow::ensure!(\n            self.nonce_ready_connection_epoch.load(Ordering::Acquire) == connection_epoch,\n            \"Lighter nonce refresh is pending for connection epoch {connection_epoch}\",\n        );\n        let nonce_guard = Arc::clone(&self.nonce_submission_gate)\n            .try_read_owned()\n            .context(\"Lighter nonce refresh is in progress\")?;\n        anyhow::ensure!(\n            self.ws_client.connection_epoch() == connection_epoch\n                && self.nonce_ready_connection_epoch.load(Ordering::Acquire) == connection_epoch,\n            \"Lighter connection or nonce state changed during transaction preparation\",\n        );\n        let nonce = match self\n            .dispatch\n            .nonce_manager\n            .next_nonce(credential.account_index(), credential.api_key_index())\n        {\n            Ok(nonce) => nonce,\n            Err(e @ NonceError::SkipWindowExhausted { .. }) => {\n                // Lost acks leave the baseline stale; resync from the venue so\n                // later commands recover. The fetch is async; this command fails.\n                self.spawn_nonce_window_recovery(credential);\n                anyhow::bail!(\"failed to allocate Lighter nonce: {e}\");\n            }\n            Err(e) => anyhow::bail!(\"failed to allocate Lighter nonce: {e}\"),\n        };","sourceCodeStart":1452,"sourceCodeEnd":1488,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/lighter/src/execution.rs#L1452-L1488","documentation":"A TOCTOU guard in build_tx_context: after acquiring the nonce submission gate, the code re-checks that the connection epoch and nonce-ready epoch still match those observed at entry. If the WS reconnected or a nonce refresh began mid-preparation, the reserved context would be built on stale nonce state, so preparation is aborted.","triggerScenarios":"Concurrent reconnect/nonce refresh racing with any of update_leverage, prepare_signed_modify_order, or prepare_integrator_auto_approval: the epoch or nonce_ready_connection_epoch changed between the first check and gate acquisition.","commonSituations":"High-frequency order management during unstable network; multiple tasks issuing signed commands at reconnect time; automated loops calling modify/leverage APIs continuously across reconnect boundaries.","solutions":["Retry the operation from scratch — the next attempt will run against the settled connection state.","Pause command dispatch during reconnects and resume only after readiness is re-signaled.","Avoid issuing signed txs from multiple tasks concurrently at reconnect boundaries.","If persistent, investigate WS stability (keepalives, proxy timeouts)."],"exampleFix":"// before\nlet ctx = execution.prepare_signed_modify_order(&cmd, &credential)?;\n// after\nlet ctx = loop {\n    match execution.prepare_signed_modify_order(&cmd, &credential) {\n        Ok(ctx) => break ctx,\n        Err(e) if e.to_string().contains(\"state changed\") => {\n            tokio::time::sleep(Duration::from_millis(100)).await;\n        }\n        Err(e) => return Err(e),\n    }\n};","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"state changed during transaction preparation\") => retry_with_backoff(),\n    other => other,\n}","preventionTips":["Avoid issuing signed txs concurrently during reconnect windows","Use a single command queue serialized behind connection readiness","Retry idempotent preparation steps on this transient error"],"tags":["network","race-condition","nonce","retry"],"backgroundTag":"invalid-state-transition","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}