{"record":{"id":"195d8eec0d728300","repo":"nautechsystems/nautilus_trader","slug":"book-snapshot-hash-mismatch-for-expected-expe","errorCode":null,"errorMessage":"Book snapshot hash mismatch for {}: expected {expected}, computed {computed}","messagePattern":"Book snapshot hash mismatch for (.+?): expected (.+?), computed (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/polymarket/src/websocket/parse.rs","lineNumber":82,"sourceCode":"    })?;\n    Quantity::from_decimal_dp(value, precision)\n}\n\npub(crate) fn verify_book_snapshot_hash(\n    snap: &PolymarketBookSnapshot,\n    min_order_size: Option<&str>,\n    neg_risk: Option<bool>,\n) -> anyhow::Result<bool> {\n    let Some(expected) = snap.hash.as_deref() else {\n        return Ok(false);\n    };\n\n    let Some(computed) = book_snapshot_hash(snap, min_order_size, neg_risk)? else {\n        return Ok(false);\n    };\n\n    if computed != expected {\n        anyhow::bail!(\n            \"Book snapshot hash mismatch for {}: expected {expected}, computed {computed}\",\n            snap.asset_id\n        );\n    }\n\n    Ok(true)\n}\n\nfn book_snapshot_hash(\n    snap: &PolymarketBookSnapshot,\n    min_order_size: Option<&str>,\n    neg_risk: Option<bool>,\n) -> anyhow::Result<Option<String>> {\n    let Some(min_order_size) = snap.min_order_size.as_deref().or(min_order_size) else {\n        return Ok(None);\n    };\n\n    let Some(tick_size) = snap.tick_size.as_deref() else {","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/polymarket/src/websocket/parse.rs#L64-L100","documentation":"Polymarket book snapshots carry a content hash computed over bids/asks with min_order_size and neg_risk adjustments. verify_book_snapshot_hash recomputes the hash; when the computed value differs from the snapshot's declared hash the snapshot cannot be trusted and processing fails loudly instead of accepting a corrupted or stale book.","triggerScenarios":"A market message of type snapshot whose asset_id's bids/asks hash to a different value than snap.hash — e.g. hash algorithm parameters (min_order_size, neg_risk) wrong for the market, or the snapshot payload was altered/truncated in transit.","commonSituations":"Hard-coded or stale min_order_size/neg_risk values for a market whose parameters changed; negative-risk (neg_risk) markets hashed with non-neg-risk logic; proxy/middleman corrupting frames; adapter version lagging a venue hash change.","solutions":["Verify the min_order_size and neg_risk parameters passed for that asset_id match the venue's current market definition (fetch fresh market metadata)","Re-fetch the snapshot over REST and reconcile before applying deltas","Check for a venue-side hash algorithm change and update book_snapshot_hash accordingly","Log the asset_id, expected, and computed hashes to confirm whether it is a parameter bug or transport corruption"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// fetch fresh market metadata before processing snapshots\nlet meta = fetch_market_meta(&snap.asset_id)?; // includes tick_size/min_order_size, neg_risk\nverify_book_snapshot_hash(&snap, meta.min_order_size, meta.neg_risk)?;","typeGuard":null,"tryCatchPattern":"match verify_book_snapshot_hash(&snap, min_order_size, neg_risk) {\n    Ok(true) => apply_snapshot(snap),\n    Ok(false) => log::warn!(\"hash not computable; requesting REST snapshot\"),\n    Err(e) if e.to_string().contains(\"hash mismatch\") => resync_book_from_rest(&snap.asset_id).await?,\n    Err(e) => return Err(e),\n}","preventionTips":["Always pass market-fresh min_order_size and neg_risk, never hard-coded values","Resync the book from REST after any hash mismatch","Keep the adapter updated for venue hash-algorithm changes"],"tags":["rust","websocket","hash","order-book","polymarket"],"backgroundTag":"checksum-mismatch","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"}