{"record":{"id":"5517dd708d6429a0","repo":"nautechsystems/nautilus_trader","slug":"persisted-transaction-hash-does-not-match-signe","errorCode":null,"errorMessage":"Persisted transaction hash {} does not match signed transaction hash {}","messagePattern":"Persisted transaction hash (.+?) does not match signed transaction hash (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/transaction.rs","lineNumber":245,"sourceCode":"        chain_id: tx.chain_id,\n        nonce: tx.nonce,\n        to,\n        value: tx.value,\n        input: tx.input.clone(),\n        gas_limit: tx.gas_limit,\n        max_fee_per_gas: tx.max_fee_per_gas,\n        max_priority_fee_per_gas: tx.max_priority_fee_per_gas,\n    })\n}\n\n/// Authenticates one complete signed EIP-1559 call against its durable intent and policy.\npub(super) fn validate_signed_transaction(\n    raw_transaction: &[u8],\n    intent: &SignedTransactionIntent,\n) -> anyhow::Result<()> {\n    let tx = decode_signed_transaction(raw_transaction)?;\n\n    anyhow::ensure!(\n        tx.hash == intent.hash,\n        \"Persisted transaction hash {} does not match signed transaction hash {}\",\n        intent.hash,\n        tx.hash\n    );\n    anyhow::ensure!(\n        intent.durable_signer == intent.signer,\n        \"Persisted transaction signer {} does not match configured wallet {}\",\n        intent.durable_signer,\n        intent.signer\n    );\n    anyhow::ensure!(\n        tx.signer == intent.signer,\n        \"Signed transaction signer {} does not match configured wallet {}\",\n        tx.signer,\n        intent.signer\n    );\n    anyhow::ensure!(","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/execution/transaction.rs#L227-L263","documentation":"validate_signed_transaction decodes the persisted raw transaction and compares its computed keccak hash against the hash stored in the SignedTransactionIntent. A mismatch means the persisted bytes do not correspond to the intent record — the stored transaction is not the one that was authorized, so authentication fails.","triggerScenarios":"Calling validate_signed_transaction (or authenticate_payload_identity_with_signer) where decode_signed_transaction(raw).hash != intent.hash — e.g. a different raw transaction was persisted than the intent, bytes were altered after persisting, or the intent record points at the wrong row/blob.","commonSituations":"Race where a transaction is replaced/re-signed but the intent hash was not updated; DB row pointing at the wrong blob; blob corruption altering bytes without changing the stored intent hash; copy-paste between environments.","solutions":["Recompute the keccak hash of the raw bytes and compare with intent.hash to confirm the mismatch on both sides","Re-persist the correct signed transaction matching intent.hash, or update the intent to reference the actual signed tx","Check for a replaced/cancelled transaction (same nonce, different hash) and reconcile with the nonce manager","Verify no storage corruption — compare the blob against any secondary checksum or re-derive from the tx signing payload"],"exampleFix":"// before\nvalidate_signed_transaction(&stored_raw, &intent)?; // stored_raw from wrong row\n// after\nlet decoded = decode_signed_transaction(&stored_raw)?;\nif decoded.hash != intent.hash {\n    // re-persist the signed tx that matches intent.hash before validating\n}\nvalidate_signed_transaction(&stored_raw, &intent)?;","handlingStrategy":"validation","validationCode":"fn hashes_match(raw: &[u8], intent: &SignedTransactionIntent) -> anyhow::Result<bool> {\n    let decoded = decode_signed_transaction(raw)?;\n    Ok(decoded.hash == intent.hash)\n}","typeGuard":"fn matches_intent(decoded: &DecodedSignedTransaction, intent: &SignedTransactionIntent) -> bool {\n    decoded.hash == intent.hash\n}","tryCatchPattern":"match validate_signed_transaction(&raw, &intent) {\n    Ok(()) => (),\n    Err(e) if e.to_string().contains(\"does not match signed transaction hash\") => {\n        // reconcile intent vs stored tx (replaced tx? wrong row?) before retrying\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Write the intent hash and raw bytes atomically in the same transaction","Verify hash == intent.hash immediately after persisting the signed transaction","Handle transaction replacement (same nonce) by updating the intent, not just the blob","Log both hashes on mismatch for fast forensics"],"tags":["blockchain","integrity","hash-mismatch"],"backgroundTag":"checksum-mismatch","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}