diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2411fd7c..549e4a4353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ ### Fixes +* [FIX][rust] `parse_partial_blockchain_nodes` (sqlite-store) no longer panics when a `partial_blockchain_nodes.id` column reads back as `0` - `InOrderIndex` never constructs a zero index (its own constructor requires `NonZeroUsize`), so a stored `0` can only come from a corrupted or externally-tampered-with local database. Now surfaces as `StoreError::ParsingError` instead ([#2462](https://github.com/0xMiden/rust-sdk/pull/2462)). * [FIX][rust] `ChainAnchor` deserialization no longer panics on crafted input: a partial blockchain whose tracked leaf is missing an ancestor sibling, or whose block-map key disagrees with its header, is rejected as an invalid value, and anchors tracking more blocks than a transaction can reference are rejected early with the new `ChainAnchorError::TooManyTrackedBlocks` ([#2421](https://github.com/0xMiden/rust-sdk/pull/2421)). * [FIX][rust] `Client::execute_transaction_at` now fails with the new `ChainAnchorError::AnchoredTransactionExpired` when the executed transaction's expiration block has already been reached, instead of handing back a transaction the network would reject after proving ([#2421](https://github.com/0xMiden/rust-sdk/pull/2421)). * [FIX][rust] A request that sets `ignore_invalid_input_notes` but carries no input notes, or whose notes are all screened out, no longer fails with an out-of-range note-count error from the consumption checker ([#2421](https://github.com/0xMiden/rust-sdk/pull/2421)). diff --git a/crates/sqlite-store/src/chain_data.rs b/crates/sqlite-store/src/chain_data.rs index c61ac6d5f4..7ee422b171 100644 --- a/crates/sqlite-store/src/chain_data.rs +++ b/crates/sqlite-store/src/chain_data.rs @@ -366,12 +366,16 @@ fn parse_partial_blockchain_nodes_columns( fn parse_partial_blockchain_nodes( serialized_partial_blockchain_node_parts: &SerializedPartialBlockchainNodeParts, ) -> Result<(InOrderIndex, Word), StoreError> { + let raw_id = serialized_partial_blockchain_node_parts.id; let id = InOrderIndex::new( - NonZeroUsize::new( - usize::try_from(serialized_partial_blockchain_node_parts.id) - .expect("id is u64, should not fail"), - ) - .unwrap(), + NonZeroUsize::new(usize::try_from(raw_id).expect("id is u64, should not fail")).ok_or_else( + || { + StoreError::ParsingError(format!( + "partial_blockchain_nodes.id must be non-zero (0 is never a valid \ + InOrderIndex), got {raw_id}" + )) + }, + )?, ); let node: Word = Word::read_from_bytes(&serialized_partial_blockchain_node_parts.node)?; Ok((id, node)) @@ -409,6 +413,21 @@ mod test { use crate::SqliteStore; use crate::tests::create_test_store; + #[test] + fn parse_partial_blockchain_nodes_rejects_zero_id_instead_of_panicking() { + // id=0 is never a valid InOrderIndex (its constructor requires NonZeroUsize, and + // from_leaf_pos's minimum output is 1), so a row with id=0 can only come from a + // corrupted or externally-tampered-with local database. This must surface as a + // StoreError, not panic the client. + let parts = super::SerializedPartialBlockchainNodeParts { + id: 0, + node: Word::default().to_bytes(), + }; + + let result = super::parse_partial_blockchain_nodes(&parts); + assert!(matches!(result, Err(miden_client::store::StoreError::ParsingError(_)))); + } + async fn insert_dummy_block_headers(store: &mut SqliteStore) -> Vec { let block_headers: Vec = (0..5) .map(|block_num| {