From 98508f8eb5ea076f2d20f89fd6388456de15cac9 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 28 Mar 2024 13:27:57 -0500 Subject: [PATCH] feat(esplora): update to use SyncRequest and FullScanRequest structures --- crates/esplora/src/async_ext.rs | 119 ++++++++++++++------------- crates/esplora/src/blocking_ext.rs | 109 +++++++++++------------- crates/esplora/src/lib.rs | 22 +---- crates/esplora/tests/async_ext.rs | 76 +++++++++-------- crates/esplora/tests/blocking_ext.rs | 71 ++++++++++------ 5 files changed, 199 insertions(+), 198 deletions(-) diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs index 2657ebcff..86e469d0c 100644 --- a/crates/esplora/src/async_ext.rs +++ b/crates/esplora/src/async_ext.rs @@ -1,7 +1,9 @@ use std::collections::BTreeSet; +use std::fmt::Debug; use async_trait::async_trait; use bdk_chain::collections::btree_map; +use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}; use bdk_chain::Anchor; use bdk_chain::{ bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid}, @@ -12,7 +14,7 @@ use bdk_chain::{ use esplora_client::TxStatus; use futures::{stream::FuturesOrdered, TryStreamExt}; -use crate::{anchor_from_status, FullScanUpdate, SyncUpdate}; +use crate::anchor_from_status; /// [`esplora_client::Error`] type Error = Box; @@ -28,34 +30,24 @@ pub trait EsploraAsyncExt { /// Scan keychain scripts for transactions against Esplora, returning an update that can be /// applied to the receiving structures. /// - /// * `local_tip`: the previously seen tip from [`LocalChain::tip`]. - /// * `keychain_spks`: keychains that we want to scan transactions for - /// /// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no /// associated transactions. `parallel_requests` specifies the max number of HTTP requests to /// make in parallel. /// /// [`LocalChain::tip`]: local_chain::LocalChain::tip - async fn full_scan( + async fn full_scan< + K: Ord + Clone + Send + Debug + 'static, + I: Iterator + Send, + >( &self, - local_tip: CheckPoint, - keychain_spks: BTreeMap< - K, - impl IntoIterator + Send> + Send, - >, + request: FullScanRequest, stop_gap: usize, parallel_requests: usize, - ) -> Result, Error>; + ) -> Result, Error>; /// Sync a set of scripts with the blockchain (via an Esplora client) for the data /// specified and return a [`TxGraph`]. /// - /// * `local_tip`: the previously seen tip from [`LocalChain::tip`]. - /// * `misc_spks`: scripts that we want to sync transactions for - /// * `txids`: transactions for which we want updated [`ConfirmationTimeHeightAnchor`]s - /// * `outpoints`: transactions associated with these outpoints (residing, spending) that we - /// want to include in the update - /// /// If the scripts to sync are unknown, such as when restoring or importing a keychain that /// may include scripts that have been used, use [`full_scan`] with the keychain. /// @@ -63,55 +55,70 @@ pub trait EsploraAsyncExt { /// [`full_scan`]: EsploraAsyncExt::full_scan async fn sync( &self, - local_tip: CheckPoint, - misc_spks: impl IntoIterator + Send> + Send, - txids: impl IntoIterator + Send> + Send, - outpoints: impl IntoIterator + Send> + Send, + request: SyncRequest, parallel_requests: usize, - ) -> Result; + ) -> Result; } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl EsploraAsyncExt for esplora_client::AsyncClient { - async fn full_scan( + async fn full_scan< + K: Ord + Clone + Send + Debug + 'static, + I: Iterator + Send, + >( &self, - local_tip: CheckPoint, - keychain_spks: BTreeMap< - K, - impl IntoIterator + Send> + Send, - >, + mut request: FullScanRequest, stop_gap: usize, parallel_requests: usize, - ) -> Result, Error> { - let update_blocks = init_chain_update(self, &local_tip).await?; - let (tx_graph, last_active_indices) = - full_scan_for_index_and_graph(self, keychain_spks, stop_gap, parallel_requests).await?; - let local_chain = - finalize_chain_update(self, &local_tip, tx_graph.all_anchors(), update_blocks).await?; - Ok(FullScanUpdate { - local_chain, - tx_graph, + ) -> Result, Error> { + let update_blocks = init_chain_update(self, &request.chain_tip).await?; + let (graph_update, last_active_indices) = full_scan_for_index_and_graph( + self, + request.take_spks_by_keychain(), + stop_gap, + parallel_requests, + ) + .await?; + let chain_update = finalize_chain_update( + self, + &request.chain_tip, + graph_update.all_anchors(), + update_blocks, + ) + .await?; + + Ok(FullScanResult { + graph_update, + chain_update, last_active_indices, }) } async fn sync( &self, - local_tip: CheckPoint, - misc_spks: impl IntoIterator + Send> + Send, - txids: impl IntoIterator + Send> + Send, - outpoints: impl IntoIterator + Send> + Send, + mut request: SyncRequest, parallel_requests: usize, - ) -> Result { - let update_blocks = init_chain_update(self, &local_tip).await?; - let tx_graph = - sync_for_index_and_graph(self, misc_spks, txids, outpoints, parallel_requests).await?; - let local_chain = - finalize_chain_update(self, &local_tip, tx_graph.all_anchors(), update_blocks).await?; - Ok(SyncUpdate { - tx_graph, - local_chain, + ) -> Result { + let update_blocks = init_chain_update(self, &request.chain_tip).await?; + let graph_update = sync_for_index_and_graph( + self, + request.take_spks().map(|(_i, spk)| spk), + request.take_txids(), + request.take_outpoints(), + parallel_requests, + ) + .await?; + let chain_update = finalize_chain_update( + self, + &request.chain_tip, + graph_update.all_anchors(), + update_blocks, + ) + .await?; + Ok(SyncResult { + graph_update, + chain_update, }) } } @@ -238,7 +245,7 @@ pub async fn full_scan_for_index_and_graph( client: &esplora_client::AsyncClient, keychain_spks: BTreeMap< K, - impl IntoIterator + Send> + Send, + Box + Send> + Send>, >, stop_gap: usize, parallel_requests: usize, @@ -341,10 +348,12 @@ pub async fn sync_for_index_and_graph( client, [( (), - misc_spks - .into_iter() - .enumerate() - .map(|(i, spk)| (i as u32, spk)), + Box::new( + misc_spks + .into_iter() + .enumerate() + .map(|(i, spk)| (i as u32, spk)), + ), )] .into(), usize::MAX, diff --git a/crates/esplora/src/blocking_ext.rs b/crates/esplora/src/blocking_ext.rs index c3259ed58..da56250a8 100644 --- a/crates/esplora/src/blocking_ext.rs +++ b/crates/esplora/src/blocking_ext.rs @@ -1,9 +1,11 @@ use std::collections::BTreeSet; +use std::fmt::Debug; use std::thread::JoinHandle; use std::usize; use bdk_chain::collections::btree_map; use bdk_chain::collections::BTreeMap; +use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult}; use bdk_chain::Anchor; use bdk_chain::{ bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid}, @@ -13,8 +15,6 @@ use bdk_chain::{ use esplora_client::TxStatus; use crate::anchor_from_status; -use crate::FullScanUpdate; -use crate::SyncUpdate; /// [`esplora_client::Error`] pub type Error = Box; @@ -28,99 +28,83 @@ pub trait EsploraExt { /// Scan keychain scripts for transactions against Esplora, returning an update that can be /// applied to the receiving structures. /// - /// * `local_tip`: the previously seen tip from [`LocalChain::tip`]. - /// * `keychain_spks`: keychains that we want to scan transactions for - /// /// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no /// associated transactions. `parallel_requests` specifies the max number of HTTP requests to /// make in parallel. /// /// [`LocalChain::tip`]: local_chain::LocalChain::tip - fn full_scan( + fn full_scan< + K: Ord + Clone + Send + Debug + 'static, + I: Iterator + Send, + >( &self, - local_tip: CheckPoint, - keychain_spks: BTreeMap>, + request: FullScanRequest, stop_gap: usize, parallel_requests: usize, - ) -> Result, Error>; + ) -> Result, Error>; /// Sync a set of scripts with the blockchain (via an Esplora client) for the data /// specified and return a [`TxGraph`]. /// - /// * `local_tip`: the previously seen tip from [`LocalChain::tip`]. - /// * `misc_spks`: scripts that we want to sync transactions for - /// * `txids`: transactions for which we want updated [`ConfirmationTimeHeightAnchor`]s - /// * `outpoints`: transactions associated with these outpoints (residing, spending) that we - /// want to include in the update - /// /// If the scripts to sync are unknown, such as when restoring or importing a keychain that /// may include scripts that have been used, use [`full_scan`] with the keychain. /// - /// [`LocalChain::tip`]: local_chain::LocalChain::tip /// [`full_scan`]: EsploraExt::full_scan - fn sync( - &self, - local_tip: CheckPoint, - misc_spks: impl IntoIterator, - txids: impl IntoIterator, - outpoints: impl IntoIterator, - parallel_requests: usize, - ) -> Result; + fn sync(&self, request: SyncRequest, parallel_requests: usize) -> Result; } impl EsploraExt for esplora_client::BlockingClient { - fn full_scan( + fn full_scan< + K: Ord + Clone + Send + Debug + 'static, + I: Iterator + Send, + >( &self, - local_tip: CheckPoint, - keychain_spks: BTreeMap>, + mut request: FullScanRequest, stop_gap: usize, parallel_requests: usize, - ) -> Result, Error> { - let update_blocks = init_chain_update_blocking(self, &local_tip)?; - let (tx_graph, last_active_indices) = full_scan_for_index_and_graph_blocking( + ) -> Result, Error> { + let update_blocks = init_chain_update_blocking(self, &request.chain_tip)?; + let (graph_update, last_active_indices) = full_scan_for_index_and_graph_blocking( self, - keychain_spks, + request.take_spks_by_keychain(), stop_gap, parallel_requests, )?; - let local_chain = finalize_chain_update_blocking( + let chain_update = finalize_chain_update_blocking( self, - &local_tip, - tx_graph.all_anchors(), + &request.chain_tip, + graph_update.all_anchors(), update_blocks, )?; - Ok(FullScanUpdate { - local_chain, - tx_graph, + Ok(FullScanResult { + graph_update, + chain_update, last_active_indices, }) } fn sync( &self, - local_tip: CheckPoint, - misc_spks: impl IntoIterator, - txids: impl IntoIterator, - outpoints: impl IntoIterator, + mut request: SyncRequest, parallel_requests: usize, - ) -> Result { - let update_blocks = init_chain_update_blocking(self, &local_tip)?; - let tx_graph = sync_for_index_and_graph_blocking( + ) -> Result { + let update_blocks = init_chain_update_blocking(self, &request.chain_tip)?; + let graph_update = sync_for_index_and_graph_blocking( self, - misc_spks, - txids, - outpoints, + request.take_spks().map(|(_i, spk)| spk), + request.take_txids(), + request.take_outpoints(), parallel_requests, )?; - let local_chain = finalize_chain_update_blocking( + let chain_update = finalize_chain_update_blocking( self, - &local_tip, - tx_graph.all_anchors(), + &request.chain_tip, + graph_update.all_anchors(), update_blocks, )?; - Ok(SyncUpdate { - local_chain, - tx_graph, + Ok(SyncResult { + graph_update, + chain_update, }) } } @@ -242,9 +226,12 @@ pub fn finalize_chain_update_blocking( /// This performs a full scan to get an update for the [`TxGraph`] and /// [`KeychainTxOutIndex`](bdk_chain::keychain::KeychainTxOutIndex). #[doc(hidden)] -pub fn full_scan_for_index_and_graph_blocking( +pub fn full_scan_for_index_and_graph_blocking( client: &esplora_client::BlockingClient, - keychain_spks: BTreeMap>, + keychain_spks: BTreeMap< + K, + Box + Send> + Send>, + >, stop_gap: usize, parallel_requests: usize, ) -> Result<(TxGraph, BTreeMap), Error> { @@ -340,7 +327,7 @@ pub fn full_scan_for_index_and_graph_blocking( #[doc(hidden)] pub fn sync_for_index_and_graph_blocking( client: &esplora_client::BlockingClient, - misc_spks: impl IntoIterator, + misc_spks: impl IntoIterator + Send> + Send, txids: impl IntoIterator, outpoints: impl IntoIterator, parallel_requests: usize, @@ -351,10 +338,12 @@ pub fn sync_for_index_and_graph_blocking( let mut keychains = BTreeMap::new(); keychains.insert( (), - misc_spks - .into_iter() - .enumerate() - .map(|(i, spk)| (i as u32, spk)), + Box::new( + misc_spks + .into_iter() + .enumerate() + .map(|(i, spk)| (i as u32, spk)), + ), ); keychains }, diff --git a/crates/esplora/src/lib.rs b/crates/esplora/src/lib.rs index c422a0833..535167ff2 100644 --- a/crates/esplora/src/lib.rs +++ b/crates/esplora/src/lib.rs @@ -16,9 +16,7 @@ //! [`TxGraph`]: bdk_chain::tx_graph::TxGraph //! [`example_esplora`]: https://github.com/bitcoindevkit/bdk/tree/master/example-crates/example_esplora -use std::collections::BTreeMap; - -use bdk_chain::{local_chain, BlockId, ConfirmationTimeHeightAnchor, TxGraph}; +use bdk_chain::{BlockId, ConfirmationTimeHeightAnchor}; use esplora_client::TxStatus; pub use esplora_client; @@ -50,21 +48,3 @@ fn anchor_from_status(status: &TxStatus) -> Option None } } - -/// Update returns from a full scan. -pub struct FullScanUpdate { - /// The update to apply to the receiving [`LocalChain`](local_chain::LocalChain). - pub local_chain: local_chain::Update, - /// The update to apply to the receiving [`TxGraph`]. - pub tx_graph: TxGraph, - /// Last active indices for the corresponding keychains (`K`). - pub last_active_indices: BTreeMap, -} - -/// Update returned from a sync. -pub struct SyncUpdate { - /// The update to apply to the receiving [`LocalChain`](local_chain::LocalChain). - pub local_chain: local_chain::Update, - /// The update to apply to the receiving [`TxGraph`]. - pub tx_graph: TxGraph, -} diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs index 59df2b4c4..d81cb1261 100644 --- a/crates/esplora/tests/async_ext.rs +++ b/crates/esplora/tests/async_ext.rs @@ -11,6 +11,7 @@ use std::thread::sleep; use std::time::Duration; use bdk_chain::bitcoin::{Address, Amount, Txid}; +use bdk_chain::spk_client::{FullScanRequest, SyncRequest}; use bdk_testenv::TestEnv; macro_rules! h { @@ -227,20 +228,19 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { // use a full checkpoint linked list (since this is not what we are testing) let cp_tip = env.make_checkpoint_tip(); - let sync_update = client - .sync( - cp_tip.clone(), - misc_spks.into_iter(), - vec![].into_iter(), - vec![].into_iter(), - 1, - ) - .await?; + let mut request = SyncRequest::new(cp_tip.clone()); + request.add_spks( + misc_spks + .into_iter() + .enumerate() + .map(|(i, spk)| (i as u32, spk)), + ); + let sync_response = client.sync(request, 1).await?; assert!( { - let update_cps = sync_update - .local_chain + let update_cps = sync_response + .chain_update .tip .iter() .map(|cp| cp.block_id()) @@ -254,7 +254,7 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { "update should not alter original checkpoint tip since we already started with all checkpoints", ); - let graph_update = sync_update.tx_graph; + let graph_update = sync_response.graph_update; // Check to see if we have the floating txouts available from our two created transactions' // previous outputs in order to calculate transaction fees. for tx in graph_update.full_txs() { @@ -310,11 +310,10 @@ pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> { .into_iter() .map(|s| Address::from_str(s).unwrap().assume_checked()) .collect(); - let spks: Vec<_> = addresses + let spks = addresses .iter() .enumerate() - .map(|(i, addr)| (i as u32, addr.script_pubkey())) - .collect(); + .map(|(i, addr)| (i as u32, addr.script_pubkey())); let mut keychains = BTreeMap::new(); keychains.insert(0, spks); @@ -339,19 +338,24 @@ pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> { // A scan with a gap limit of 2 won't find the transaction, but a scan with a gap limit of 3 // will. - let full_scan_update = client - .full_scan(cp_tip.clone(), keychains.clone(), 2, 1) - .await?; - assert!(full_scan_update.tx_graph.full_txs().next().is_none()); - assert!(full_scan_update.last_active_indices.is_empty()); - let full_scan_update = client - .full_scan(cp_tip.clone(), keychains.clone(), 3, 1) - .await?; + let mut request = FullScanRequest::new(cp_tip.clone()); + request.add_spks_by_keychain(keychains.clone()); + let full_scan_response = client.full_scan(request, 2, 1).await?; + assert!(full_scan_response.graph_update.full_txs().next().is_none()); + assert!(full_scan_response.last_active_indices.is_empty()); + let mut request = FullScanRequest::new(cp_tip.clone()); + request.add_spks_by_keychain(keychains.clone()); + let full_scan_response = client.full_scan(request, 3, 1).await?; assert_eq!( - full_scan_update.tx_graph.full_txs().next().unwrap().txid, + full_scan_response + .graph_update + .full_txs() + .next() + .unwrap() + .txid, txid_4th_addr ); - assert_eq!(full_scan_update.last_active_indices[&0], 3); + assert_eq!(full_scan_response.last_active_indices[&0], 3); // Now receive a coin on the last address. let txid_last_addr = env.bitcoind.client.send_to_address( @@ -371,26 +375,28 @@ pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> { // A scan with gap limit 4 won't find the second transaction, but a scan with gap limit 5 will. // The last active indice won't be updated in the first case but will in the second one. - let full_scan_update = client - .full_scan(cp_tip.clone(), keychains.clone(), 4, 1) - .await?; - let txs: HashSet<_> = full_scan_update - .tx_graph + let mut request = FullScanRequest::new(cp_tip.clone()); + request.add_spks_by_keychain(keychains.clone()); + let full_scan_response = client.full_scan(request, 4, 1).await?; + let txs: HashSet<_> = full_scan_response + .graph_update .full_txs() .map(|tx| tx.txid) .collect(); assert_eq!(txs.len(), 1); assert!(txs.contains(&txid_4th_addr)); - assert_eq!(full_scan_update.last_active_indices[&0], 3); - let full_scan_update = client.full_scan(cp_tip, keychains, 5, 1).await?; - let txs: HashSet<_> = full_scan_update - .tx_graph + assert_eq!(full_scan_response.last_active_indices[&0], 3); + let mut request = FullScanRequest::new(cp_tip); + request.add_spks_by_keychain(keychains); + let full_scan_response = client.full_scan(request, 5, 1).await?; + let txs: HashSet<_> = full_scan_response + .graph_update .full_txs() .map(|tx| tx.txid) .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!(full_scan_response.last_active_indices[&0], 9); Ok(()) } diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs index fa299a51b..17ddbc8de 100644 --- a/crates/esplora/tests/blocking_ext.rs +++ b/crates/esplora/tests/blocking_ext.rs @@ -2,6 +2,7 @@ use bdk_chain::bitcoin::hashes::Hash; use bdk_chain::local_chain::LocalChain; use bdk_chain::BlockId; use bdk_esplora::EsploraExt; +use bitcoin::ScriptBuf; use electrsd::bitcoind::anyhow; use electrsd::bitcoind::bitcoincore_rpc::RpcApi; use esplora_client::{self, BlockHash, Builder}; @@ -11,6 +12,7 @@ use std::thread::sleep; use std::time::Duration; use bdk_chain::bitcoin::{Address, Amount, Txid}; +use bdk_chain::spk_client::{FullScanRequest, SyncRequest}; use bdk_testenv::TestEnv; macro_rules! h { @@ -239,18 +241,21 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { // use a full checkpoint linked list (since this is not what we are testing) let cp_tip = env.make_checkpoint_tip(); - let sync_update = client.sync( - cp_tip.clone(), - misc_spks.into_iter(), - vec![].into_iter(), - vec![].into_iter(), - 1, - )?; + let mut request = SyncRequest::new(cp_tip.clone()); + request.add_spks( + misc_spks + .into_iter() + .enumerate() + .map(|(i, spk)| (i as u32, spk)) + .collect::>(), + ); + + let result = client.sync(request, 1)?; assert!( { - let update_cps = sync_update - .local_chain + let update_cps = result + .chain_update .tip .iter() .map(|cp| cp.block_id()) @@ -264,7 +269,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { "update should not alter original checkpoint tip since we already started with all checkpoints", ); - let graph_update = sync_update.tx_graph; + let graph_update = result.graph_update; // Check to see if we have the floating txouts available from our two created transactions' // previous outputs in order to calculate transaction fees. for tx in graph_update.full_txs() { @@ -321,11 +326,10 @@ pub fn test_update_tx_graph_gap_limit() -> anyhow::Result<()> { .into_iter() .map(|s| Address::from_str(s).unwrap().assume_checked()) .collect(); - let spks: Vec<_> = addresses + let spks = addresses .iter() .enumerate() - .map(|(i, addr)| (i as u32, addr.script_pubkey())) - .collect(); + .map(|(i, addr)| (i as u32, addr.script_pubkey())); let mut keychains = BTreeMap::new(); keychains.insert(0, spks); @@ -350,15 +354,24 @@ pub fn test_update_tx_graph_gap_limit() -> anyhow::Result<()> { // A scan with a gap limit of 2 won't find the transaction, but a scan with a gap limit of 3 // will. - let full_scan_update = client.full_scan(cp_tip.clone(), keychains.clone(), 2, 1)?; - assert!(full_scan_update.tx_graph.full_txs().next().is_none()); - assert!(full_scan_update.last_active_indices.is_empty()); - let full_scan_update = client.full_scan(cp_tip.clone(), keychains.clone(), 3, 1)?; + let mut request = FullScanRequest::new(cp_tip.clone()); + request.add_spks_by_keychain(keychains.clone()); + let full_scan_response = client.full_scan(request, 2, 1)?; + assert!(full_scan_response.graph_update.full_txs().next().is_none()); + assert!(full_scan_response.last_active_indices.is_empty()); + let mut request = FullScanRequest::new(cp_tip.clone()); + request.add_spks_by_keychain(keychains.clone()); + let full_scan_response = client.full_scan(request, 3, 1)?; assert_eq!( - full_scan_update.tx_graph.full_txs().next().unwrap().txid, + full_scan_response + .graph_update + .full_txs() + .next() + .unwrap() + .txid, txid_4th_addr ); - assert_eq!(full_scan_update.last_active_indices[&0], 3); + assert_eq!(full_scan_response.last_active_indices[&0], 3); // Now receive a coin on the last address. let txid_last_addr = env.bitcoind.client.send_to_address( @@ -378,24 +391,28 @@ pub fn test_update_tx_graph_gap_limit() -> anyhow::Result<()> { // A scan with gap limit 4 won't find the second transaction, but a scan with gap limit 5 will. // The last active indice won't be updated in the first case but will in the second one. - let full_scan_update = client.full_scan(cp_tip.clone(), keychains.clone(), 4, 1)?; - let txs: HashSet<_> = full_scan_update - .tx_graph + let mut request = FullScanRequest::new(cp_tip.clone()); + request.add_spks_by_keychain(keychains.clone()); + let full_scan_response = client.full_scan(request, 4, 1)?; + let txs: HashSet<_> = full_scan_response + .graph_update .full_txs() .map(|tx| tx.txid) .collect(); assert_eq!(txs.len(), 1); assert!(txs.contains(&txid_4th_addr)); - assert_eq!(full_scan_update.last_active_indices[&0], 3); - let full_scan_update = client.full_scan(cp_tip.clone(), keychains, 5, 1)?; - let txs: HashSet<_> = full_scan_update - .tx_graph + assert_eq!(full_scan_response.last_active_indices[&0], 3); + let mut request = FullScanRequest::new(cp_tip.clone()); + request.add_spks_by_keychain(keychains.clone()); + let full_scan_response = client.full_scan(request, 5, 1)?; + let txs: HashSet<_> = full_scan_response + .graph_update .full_txs() .map(|tx| tx.txid) .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!(full_scan_response.last_active_indices[&0], 9); Ok(()) }