Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions crates/chain/src/indexer/keychain_txout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -816,11 +816,17 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
}

/// Convenience method to call [`Self::reveal_to_target`] on multiple keychains.
pub fn reveal_to_target_multi(&mut self, keychains: &BTreeMap<K, u32>) -> ChangeSet {
///
/// `keychains` may repeat a keychain and need not be ordered, as revealing to a target at or
/// below the keychain's last revealed index is a no-op.
pub fn reveal_to_target_multi(
&mut self,
keychains: impl IntoIterator<Item = (K, u32)>,
) -> ChangeSet {
let mut changeset = ChangeSet::default();

for (keychain, &index) in keychains {
self._reveal_to_target(&mut changeset, keychain.clone(), index);
for (keychain, index) in keychains {
self._reveal_to_target(&mut changeset, keychain, index);
}

self._empty_stage_into_changeset(&mut changeset);
Expand Down
4 changes: 2 additions & 2 deletions crates/chain/tests/test_keychain_txout_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,15 @@ fn test_set_all_derivation_indices() {
]
.into();
assert_eq!(
txout_index.reveal_to_target_multi(&derive_to),
txout_index.reveal_to_target_multi(derive_to.clone()),
ChangeSet {
last_revealed: last_revealed.clone(),
spk_cache: spk_cache.clone(),
}
);
assert_eq!(txout_index.last_revealed_indices(), derive_to);
assert_eq!(
txout_index.reveal_to_target_multi(&derive_to),
txout_index.reveal_to_target_multi(derive_to.clone()),
ChangeSet::default(),
"no changes if we set to the same thing"
);
Expand Down
7 changes: 6 additions & 1 deletion crates/core/src/spk_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,12 @@ pub struct FullScanResponse<K, A = ConfirmationBlockTime, D = BlockHash> {
pub tx_update: crate::TxUpdate<A>,
/// Last active indices for the corresponding keychains (`K`). An index is active if it had a
/// transaction associated with the script pubkey at that index.
pub last_active_indices: BTreeMap<K, u32>,
///
/// Entries are observations, so a keychain may appear multiple times and entries need not be
/// ordered. The consumer only ever reveals upwards (see
/// [`reveal_to_target_multi`](https://docs.rs/bdk_chain/latest/bdk_chain/indexer/keychain_txout/struct.KeychainTxOutIndex.html#method.reveal_to_target_multi)),
/// so an entry that targets an index at or below what is already revealed is a no-op.
pub last_active_indices: Vec<(K, u32)>,
/// Changes to the chain discovered during the scan.
pub chain_update: Option<CheckPoint<D>>,
}
Expand Down
4 changes: 2 additions & 2 deletions crates/electrum/src/bdk_electrum_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
};

let mut tx_update = TxUpdate::<ConfirmationBlockTime>::default();
let mut last_active_indices = BTreeMap::<K, u32>::default();
let mut last_active_indices = Vec::<(K, u32)>::new();
let mut pending_anchors = Vec::new();
for keychain in request.keychains() {
let last_revealed = request.last_revealed(&keychain);
Expand All @@ -153,7 +153,7 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
batch_size,
&mut pending_anchors,
)? {
last_active_indices.insert(keychain, last_active_index);
last_active_indices.push((keychain, last_active_index));
}
}

Expand Down
29 changes: 25 additions & 4 deletions crates/electrum/tests/test_electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ use std::str::FromStr;
// Batch size for `sync_with_electrum`.
const BATCH_SIZE: usize = 5;

/// The last active index reported for `keychain`.
///
/// Entries of `last_active_indices` are observations, so they may repeat a keychain and need not
/// be ordered. The highest index reported for the keychain is the meaningful one.
fn last_active_index(last_active_indices: &[(u32, u32)], keychain: u32) -> Option<u32> {
last_active_indices
.iter()
.filter(|(k, _)| *k == keychain)
.map(|(_, i)| *i)
.max()
}

pub fn get_test_spk() -> ScriptBuf {
const PK_BYTES: &[u8] = &[
12, 244, 72, 4, 163, 4, 211, 81, 159, 82, 153, 123, 125, 74, 142, 40, 55, 237, 191, 231,
Expand Down Expand Up @@ -449,7 +461,10 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.compute_txid(),
txid_4th_addr
);
assert_eq!(full_scan_update.last_active_indices[&0], 3);
assert_eq!(
last_active_index(&full_scan_update.last_active_indices, 0),
Some(3)
);

// Now receive a coin on the last address.
let txid_last_addr = env
Expand All @@ -476,7 +491,10 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.collect();
assert_eq!(txs.len(), 1);
assert!(txs.contains(&txid_4th_addr));
assert_eq!(full_scan_update.last_active_indices[&0], 3);
assert_eq!(
last_active_index(&full_scan_update.last_active_indices, 0),
Some(3)
);
let full_scan_update = {
let request = FullScanRequest::builder()
.chain_tip(cp_tip.clone())
Expand All @@ -491,7 +509,10 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.collect();
assert_eq!(txs.len(), 2);
assert!(txs.contains(&txid_4th_addr) && txs.contains(&txid_last_addr));
assert_eq!(full_scan_update.last_active_indices[&0], 9);
assert_eq!(
last_active_index(&full_scan_update.last_active_indices, 0),
Some(9)
);

Ok(())
}
Expand Down Expand Up @@ -538,7 +559,7 @@ pub fn test_stop_gap_past_last_revealed() -> anyhow::Result<()> {
response.tx_update.txs.first().unwrap().compute_txid(),
txid_last_addr
);
assert_eq!(response.last_active_indices[&0], 9);
assert_eq!(last_active_index(&response.last_active_indices, 0), Some(9));

// Tx sits beyond `last_revealed + stop_gap`. So tx should not be found.
let request = FullScanRequest::builder()
Expand Down
4 changes: 2 additions & 2 deletions crates/esplora/src/async_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ where

let mut tx_update = TxUpdate::<ConfirmationBlockTime>::default();
let mut inserted_txs = HashSet::<Txid>::new();
let mut last_active_indices = BTreeMap::<K, u32>::new();
let mut last_active_indices = Vec::<(K, u32)>::new();
for keychain in keychains {
let last_revealed = request.last_revealed(&keychain);
let keychain_spks = request
Expand All @@ -95,7 +95,7 @@ where
.await?;
tx_update.extend(update);
if let Some(last_active_index) = last_active_index {
last_active_indices.insert(keychain, last_active_index);
last_active_indices.push((keychain, last_active_index));
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/esplora/src/blocking_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl EsploraExt for esplora_client::BlockingClient {

let mut tx_update = TxUpdate::default();
let mut inserted_txs = HashSet::<Txid>::new();
let mut last_active_indices = BTreeMap::<K, u32>::new();
let mut last_active_indices = Vec::<(K, u32)>::new();
for keychain in request.keychains() {
let last_revealed = request.last_revealed(&keychain);
let keychain_spks = request
Expand All @@ -84,7 +84,7 @@ impl EsploraExt for esplora_client::BlockingClient {
)?;
tx_update.extend(update);
if let Some(last_active_index) = last_active_index {
last_active_indices.insert(keychain, last_active_index);
last_active_indices.push((keychain, last_active_index));
}
}

Expand Down
20 changes: 16 additions & 4 deletions crates/esplora/tests/async_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,10 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.compute_txid(),
txid_4th_addr
);
assert_eq!(full_scan_update.last_active_indices[&0], 3);
assert_eq!(
common::last_active_index(&full_scan_update.last_active_indices, 0),
Some(3)
);

// Now receive a coin on the last address.
let txid_last_addr = env
Expand Down Expand Up @@ -332,7 +335,10 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.collect();
assert_eq!(txs.len(), 1);
assert!(txs.contains(&txid_4th_addr));
assert_eq!(full_scan_update.last_active_indices[&0], 3);
assert_eq!(
common::last_active_index(&full_scan_update.last_active_indices, 0),
Some(3)
);
let full_scan_update = {
let request = FullScanRequest::builder()
.chain_tip(cp_tip.clone())
Expand All @@ -347,7 +353,10 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.collect();
assert_eq!(txs.len(), 2);
assert!(txs.contains(&txid_4th_addr) && txs.contains(&txid_last_addr));
assert_eq!(full_scan_update.last_active_indices[&0], 9);
assert_eq!(
common::last_active_index(&full_scan_update.last_active_indices, 0),
Some(9)
);

Ok(())
}
Expand Down Expand Up @@ -396,7 +405,10 @@ pub async fn test_async_stop_gap_past_last_revealed() -> anyhow::Result<()> {
response.tx_update.txs.first().unwrap().compute_txid(),
txid_last_addr
);
assert_eq!(response.last_active_indices[&0], 9);
assert_eq!(
common::last_active_index(&response.last_active_indices, 0),
Some(9)
);

// Tx sits beyond `last_revealed + stop_gap`. So `stop_gap` must cut the scan off.
let request = FullScanRequest::builder()
Expand Down
20 changes: 16 additions & 4 deletions crates/esplora/tests/blocking_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,10 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.compute_txid(),
txid_4th_addr
);
assert_eq!(full_scan_update.last_active_indices[&0], 3);
assert_eq!(
common::last_active_index(&full_scan_update.last_active_indices, 0),
Some(3)
);

// Now receive a coin on the last address.
let txid_last_addr = env
Expand Down Expand Up @@ -331,7 +334,10 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.collect();
assert_eq!(txs.len(), 1);
assert!(txs.contains(&txid_4th_addr));
assert_eq!(full_scan_update.last_active_indices[&0], 3);
assert_eq!(
common::last_active_index(&full_scan_update.last_active_indices, 0),
Some(3)
);
let full_scan_update = {
let request = FullScanRequest::builder()
.chain_tip(cp_tip.clone())
Expand All @@ -346,7 +352,10 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.collect();
assert_eq!(txs.len(), 2);
assert!(txs.contains(&txid_4th_addr) && txs.contains(&txid_last_addr));
assert_eq!(full_scan_update.last_active_indices[&0], 9);
assert_eq!(
common::last_active_index(&full_scan_update.last_active_indices, 0),
Some(9)
);

Ok(())
}
Expand Down Expand Up @@ -395,7 +404,10 @@ pub fn test_stop_gap_past_last_revealed() -> anyhow::Result<()> {
response.tx_update.txs.first().unwrap().compute_txid(),
txid_last_addr
);
assert_eq!(response.last_active_indices[&0], 9);
assert_eq!(
common::last_active_index(&response.last_active_indices, 0),
Some(9)
);

// Tx sits beyond `last_revealed + stop_gap`. So `stop_gap` must cut the scan off.
let request = FullScanRequest::builder()
Expand Down
12 changes: 12 additions & 0 deletions crates/esplora/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,15 @@ pub fn test_addresses() -> Vec<Address> {
.map(|s| Address::from_str(s).unwrap().assume_checked())
.collect()
}

/// The last active index reported for `keychain`.
///
/// Entries of `last_active_indices` are observations, so they may repeat a keychain and need not
/// be ordered. The highest index reported for the keychain is the meaningful one.
pub fn last_active_index(last_active_indices: &[(u32, u32)], keychain: u32) -> Option<u32> {
last_active_indices
.iter()
.filter(|(k, _)| *k == keychain)
.map(|(_, i)| *i)
.max()
}
Loading