From b4935c0e5f00d17b56ddc2c1191bc939a0560f2a Mon Sep 17 00:00:00 2001 From: busayo-OD Date: Tue, 25 Aug 2026 21:40:20 +0100 Subject: [PATCH] fix(chain): clamp derivation index before apply_changeset KeychainTxOutIndex::apply_changeset accepts ChangeSet::last_revealed values above BIP32_MAX_INDEX, violating an invariant relied on by the indexer. Clamp each last_revealed value to BIP32_MAX_INDEX before storing it, keeping changeset application infallible and monotone. --- crates/chain/src/indexer/keychain_txout.rs | 1 + .../chain/tests/test_keychain_txout_index.rs | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/chain/src/indexer/keychain_txout.rs b/crates/chain/src/indexer/keychain_txout.rs index 7973a0254e..72ef5d9d27 100644 --- a/crates/chain/src/indexer/keychain_txout.rs +++ b/crates/chain/src/indexer/keychain_txout.rs @@ -969,6 +969,7 @@ impl KeychainTxOutIndex { } } for (did, index) in changeset.last_revealed { + let index = index.min(BIP32_MAX_INDEX); let v = self.last_revealed.entry(did).or_default(); *v = index.max(*v); self.replenish_inner_index_did(did, self.lookahead); diff --git a/crates/chain/tests/test_keychain_txout_index.rs b/crates/chain/tests/test_keychain_txout_index.rs index 263a0fa86f..badbbecd6b 100644 --- a/crates/chain/tests/test_keychain_txout_index.rs +++ b/crates/chain/tests/test_keychain_txout_index.rs @@ -3,7 +3,7 @@ use bdk_chain::{ collections::BTreeMap, indexer::keychain_txout::{ChangeSet, KeychainTxOutIndex}, - DescriptorExt, DescriptorId, Indexer, Merge, SpkIterator, + DescriptorExt, DescriptorId, Indexer, Merge, SpkIterator, BIP32_MAX_INDEX, }; use bdk_testenv::{ hash, @@ -689,6 +689,26 @@ fn applying_changesets_one_by_one_vs_aggregate_must_have_same_result() { ); } +#[test] +fn apply_changeset_clamps_out_of_range_index() { + let desc = parse_descriptor(DESCRIPTORS[6]); + let mut indexer = KeychainTxOutIndex::::new(0, false); + let _ = indexer + .insert_descriptor(TestKeychain::External, desc.clone()) + .expect("must insert keychain"); + + let changeset = ChangeSet { + last_revealed: [(desc.descriptor_id(), BIP32_MAX_INDEX + 1)].into(), + ..Default::default() + }; + indexer.apply_changeset(changeset); + + assert_eq!( + indexer.last_revealed_index(TestKeychain::External), + Some(BIP32_MAX_INDEX) + ); +} + #[test] fn assigning_same_descriptor_to_multiple_keychains_should_error() { let desc = parse_descriptor(DESCRIPTORS[0]);