{"record":{"id":"f0a5063ad982c936","repo":"nautechsystems/nautilus_trader","slug":"finalized-block-changed-from-to-before-in","errorCode":null,"errorMessage":"Finalized block {} changed from {} to {} before intent validation","messagePattern":"Finalized block (.+?) changed from (.+?) to (.+?) before intent validation","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/execution/client.rs","lineNumber":2129,"sourceCode":"            executor.release_slot();\n            Ok(())\n        }\n        Ok(InclusionOutcome::Pending(message)) => anyhow::bail!(message),\n        Err(e) => Err(e),\n    }\n}\n\nasync fn finalized_transaction_matches(\n    included: &IncludedTransaction,\n    intent: &ExecutionIntentRow,\n    nonce: u64,\n    executor: &TransactionExecutor,\n) -> anyhow::Result<bool> {\n    let block = executor\n        .http_rpc_client\n        .block_by_number(included.block_number, true)\n        .await?;\n    anyhow::ensure!(\n        block.hash == included.receipt.block_hash,\n        \"Finalized block {} changed from {} to {} before intent validation\",\n        included.block_number,\n        included.receipt.block_hash,\n        block.hash\n    );\n    let Some(transaction) = block\n        .transactions\n        .iter()\n        .find(|transaction| transaction.hash == included.tx_hash)\n    else {\n        anyhow::bail!(\n            \"Finalized block {} does not contain transaction {}\",\n            included.block_number,\n            included.tx_hash\n        );\n    };\n    let (expected_to, expected_input, expected_value) = persisted_call_fields(intent)?;","sourceCodeStart":2111,"sourceCodeEnd":2147,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/2114cf6f761429e0adb5ca9596fcd7b895b16011/crates/adapters/blockchain/src/execution/client.rs#L2111-L2147","documentation":"finalized_transaction_matches re-fetches the block at the receipt's block_number and compares its hash to the receipt's block_hash; a mismatch means a block previously reported as finalized now has a different hash. The validation aborts because the included-transaction evidence (receipt) no longer matches the canonical chain the node is serving.","triggerScenarios":"Intent validation during reconciliation while the RPC node's view of a finalized block changes: deep reorgs crossing the finalized boundary (rare but possible on some networks), or inconsistent load-balanced RPC backends disagreeing about the canonical chain.","commonSituations":"Post-incident reconciliation after a network-level reorg event; RPC load balancers routing the second request to a node on a different fork; misconfigured/attacking endpoints serving non-canonical blocks.","solutions":["Retry reconciliation against a trusted, canonical RPC provider; a genuinely finalized block will not change again on the canonical chain","Cross-check the block hash across multiple independent providers (two different companies) to identify the inconsistent endpoint","If providers agree the block really did change, follow the network's incident guidance and reconcile the intent against the new canonical state","Pin the client to a single high-quality endpoint rather than a rotating pool during reconciliation"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"from web3 import Web3\n\n# Cross-check the finalized block across providers before reconciling\nh1 = Web3(Web3.HTTPProvider(rpc_a)).eth.get_block(block_number)['hash']\nh2 = Web3(Web3.HTTPProvider(rpc_b)).eth.get_block(block_number)['hash']\nassert h1 == h2 == receipt_block_hash, 'providers disagree on finalized block; pick a canonical endpoint'","typeGuard":null,"tryCatchPattern":"for attempt in range(5):\n    try:\n        client.connect()\n        break\n    except Exception as e:\n        if 'changed from' not in str(e) or 'before intent validation' not in str(e):\n            raise\n        time.sleep(20 * (attempt + 1))  # inconsistent/forked node view; retry or switch endpoint\nelse:\n    raise RuntimeError('finalized-block view never stabilized')","preventionTips":["Reconcile against a single trusted, canonical RPC endpoint rather than rotating multi-provider pools","After any network reorg incident, let finality settle before reconnecting a wallet with active intents"],"tags":["blockchain","chain-reorg","finality","reconciliation","rpc"],"backgroundTag":"chain-reorg","analyzedSha":"2114cf6f761429e0adb5ca9596fcd7b895b16011","analyzedAt":"2026-08-21T11:28:30.864Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}