{"record":{"id":"8b13ec9571a58a11","repo":"nautechsystems/nautilus_trader","slug":"canonical-nonce-ledger-changed-during-finality-tra","errorCode":null,"errorMessage":"Canonical nonce ledger changed during finality transition","messagePattern":"Canonical nonce ledger changed during finality transition","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":7192,"sourceCode":"        .map_err(|e| anyhow::anyhow!(\"Failed to record verified finality transition: {e}\"))?;\n\n        let nonce_result = sqlx::query(\n            \"\n            UPDATE execution_verification_nonce\n            SET next_canonical_nonce = $3, revision = revision + 1, updated_at = NOW()\n            WHERE chain_id = $1 AND wallet_address = $2\n              AND next_canonical_nonce = $4 AND revision = $5\n            \",\n        )\n        .bind(chain_id)\n        .bind(finality.wallet_address)\n        .bind(next_nonce)\n        .bind(nonce)\n        .bind(revision)\n        .execute(&mut *transaction)\n        .await\n        .map_err(|e| anyhow::anyhow!(\"Failed to advance canonical nonce ledger: {e}\"))?;\n        anyhow::ensure!(\n            nonce_result.rows_affected() == 1,\n            \"Canonical nonce ledger changed during finality transition\"\n        );\n        transaction\n            .commit()\n            .await\n            .map_err(|e| anyhow::anyhow!(\"Failed to commit verified finality transition: {e}\"))?;\n        Ok(())\n    }\n\n    /// Loads one durable execution intent by ID.\n    pub(crate) async fn get_execution_intent(\n        &self,\n        intent_id: i64,\n    ) -> anyhow::Result<ExecutionIntentRow> {\n        sqlx::query_as::<_, ExecutionIntentRow>(\n            \"\n            SELECT","sourceCodeStart":7174,"sourceCodeEnd":7210,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L7174-L7210","documentation":"This is an optimistic-concurrency guard: after the nonce UPDATE, the code asserts exactly one row was affected (database.rs:7192). Zero rows means the ledger row for this chain/wallet was changed concurrently — either the expected next_canonical_nonce or the revision no longer matches — so committing would fork the canonical nonce sequence, and the library deliberately fails instead.","triggerScenarios":"Two concurrent finality transitions (or a nonce reservation) for the same chain_id + wallet_address read the same revision and one commits first; the ledger row was advanced or reset by another process between read and update; the wallet/chain has no ledger row at all (rows_affected == 0), e.g. after a reorg or manual data fix; the caller passed a stale nonce/revision pair from a cached read.","commonSituations":"Multiple strategy instances or replicas processing finality for the same signer wallet; a wallet's nonce ledger deleted/reseeded by an operator; retrying an old finality payload whose nonce/revision snapshot is outdated; the ledger row was never created for a newly configured wallet.","solutions":["Re-read the current execution_verification_nonce row (next_canonical_nonce and revision) and retry the finality transition with the fresh nonce/revision.","Check for concurrent workers processing finality for the same wallet and ensure only one actor (or a serialized queue / advisory lock) owns the signer.","Confirm a ledger row exists for the chain_id + wallet_address; create/initialize it if it is missing.","Refresh the finality payload's nonce/revision from the database rather than reusing a cached snapshot in retries.","Ensure all instances run against the same database and no manual edits reset revisions."],"exampleFix":"// before: blind retry with the stale snapshot\n// Err(Canonical nonce ledger changed during finality transition)\n// after: reload the ledger state, then retry\nlet ledger = db.get_verification_nonce(chain_id, wallet).await?;\nanyhow::ensure!(ledger.next_canonical_nonce == expected_nonce, \"nonce mismatch; refresh finality payload\");\ndb.record_verified_finality(&finality).await?;","handlingStrategy":"retry","validationCode":"let ledger = sqlx::query_as::<_, (i64, i64)>(\n    \"SELECT next_canonical_nonce, revision FROM execution_verification_nonce WHERE chain_id = $1 AND wallet_address = $2\")\n    .bind(chain_id).bind(wallet).fetch_one(&pool).await?;\nassert_eq!(ledger.0, expected_nonce, \"stale nonce snapshot; reload finality payload\");","typeGuard":"fn is_nonce_conflict(msg: &str) -> bool {\n    msg.contains(\"Canonical nonce ledger changed during finality transition\")\n}","tryCatchPattern":"loop {\n    match db.record_verified_finality(&finality).await {\n        Err(e) if is_nonce_conflict(&e.to_string()) => {\n            finality = refresh_nonce_state(finality).await?; // reload nonce+revision and continue\n            continue;\n        }\n        other => break other,\n    }\n}","preventionTips":["Ensure only one worker owns finality processing per signer wallet (leader election or advisory lock).","Never reuse cached nonce/revision snapshots across retries — re-read from the database.","Verify the ledger row exists for every configured chain/wallet pair.","Avoid manual edits to execution_verification_nonce; use the library's own flows."],"tags":["concurrency","optimistic-locking","nonce","database"],"backgroundTag":"internal-invariant-violation","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"}