Skip to content

Commit 44e2a79

Browse files
committed
feat!: rework FullScanRequest and SyncRequest
Change `FullScanRequest` and `SyncRequest` take in a `chain_tip` as an option. In turn, `FullScanResult` and `SyncResult` are also changed to return the update `chain_tip` as an option. This allows the caller to opt-out of getting a `LocalChain` update. Rework `FullScanRequest` and `SyncRequest` to have better ergonomics when inspecting the progress of items of a sync request. Richer progress data is provided to the inspect closure. Introduce `FullScanRequestBuilder` and `SyncRequestBuilder`. Separating out request-construction and request-consumption in different structs simplifies the API and method names. Simplify `EsploraExt` and `EsploraAsyncExt` back to having two methods (`full_scan` and `sync`). The caller can still opt out of fetching a `LocalChain` update with the new `FullScanRequest` and `SyncRequest`.
1 parent 16c1c2c commit 44e2a79

14 files changed

Lines changed: 1276 additions & 1153 deletions

File tree

crates/chain/src/spk_client.rs

Lines changed: 462 additions & 284 deletions
Large diffs are not rendered by default.

crates/electrum/src/bdk_electrum_client.rs

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -126,31 +126,43 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
126126
/// [`Wallet.calculate_fee_rate`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee_rate
127127
pub fn full_scan<K: Ord + Clone>(
128128
&self,
129-
request: FullScanRequest<K>,
129+
request: impl Into<FullScanRequest<K>>,
130130
stop_gap: usize,
131131
batch_size: usize,
132132
fetch_prev_txouts: bool,
133133
) -> Result<FullScanResult<K>, Error> {
134-
let (tip, latest_blocks) =
135-
fetch_tip_and_latest_blocks(&self.inner, request.chain_tip.clone())?;
136-
let mut graph_update = TxGraph::<ConfirmationBlockTime>::default();
137-
let mut last_active_indices = BTreeMap::<K, u32>::new();
134+
let mut request: FullScanRequest<K> = request.into();
135+
136+
let tip_and_latest_blocks = match request.chain_tip() {
137+
Some(chain_tip) => Some(fetch_tip_and_latest_blocks(&self.inner, chain_tip)?),
138+
None => None,
139+
};
138140

139-
for (keychain, spks) in request.spks_by_keychain {
141+
let mut graph_update = TxGraph::<ConfirmationBlockTime>::default();
142+
let mut last_active_indices = BTreeMap::<K, u32>::default();
143+
for keychain in request.keychains() {
144+
let spks = request.iter_spks(keychain.clone());
140145
if let Some(last_active_index) =
141146
self.populate_with_spks(&mut graph_update, spks, stop_gap, batch_size)?
142147
{
143148
last_active_indices.insert(keychain, last_active_index);
144149
}
145150
}
146151

147-
let chain_update = chain_update(tip, &latest_blocks, graph_update.all_anchors())?;
148-
149152
// Fetch previous `TxOut`s for fee calculation if flag is enabled.
150153
if fetch_prev_txouts {
151154
self.fetch_prev_txout(&mut graph_update)?;
152155
}
153156

157+
let chain_update = match tip_and_latest_blocks {
158+
Some((chain_tip, latest_blocks)) => Some(chain_update(
159+
chain_tip,
160+
&latest_blocks,
161+
graph_update.all_anchors(),
162+
)?),
163+
_ => None,
164+
};
165+
154166
Ok(FullScanResult {
155167
graph_update,
156168
chain_update,
@@ -180,35 +192,52 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
180192
/// [`CalculateFeeError::MissingTxOut`]: bdk_chain::tx_graph::CalculateFeeError::MissingTxOut
181193
/// [`Wallet.calculate_fee`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee
182194
/// [`Wallet.calculate_fee_rate`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee_rate
183-
pub fn sync(
195+
pub fn sync<I: 'static>(
184196
&self,
185-
request: SyncRequest,
197+
request: impl Into<SyncRequest<I>>,
186198
batch_size: usize,
187199
fetch_prev_txouts: bool,
188200
) -> Result<SyncResult, Error> {
189-
let full_scan_req = FullScanRequest::from_chain_tip(request.chain_tip.clone())
190-
.set_spks_for_keychain((), request.spks.enumerate().map(|(i, spk)| (i as u32, spk)));
191-
let mut full_scan_res = self.full_scan(full_scan_req, usize::MAX, batch_size, false)?;
192-
let (tip, latest_blocks) =
193-
fetch_tip_and_latest_blocks(&self.inner, request.chain_tip.clone())?;
194-
195-
self.populate_with_txids(&mut full_scan_res.graph_update, request.txids)?;
196-
self.populate_with_outpoints(&mut full_scan_res.graph_update, request.outpoints)?;
197-
198-
let chain_update = chain_update(
199-
tip,
200-
&latest_blocks,
201-
full_scan_res.graph_update.all_anchors(),
202-
)?;
201+
let mut request: SyncRequest<I> = request.into();
202+
203+
let tip_and_latest_blocks = match request.chain_tip() {
204+
Some(chain_tip) => Some(fetch_tip_and_latest_blocks(&self.inner, chain_tip)?),
205+
None => None,
206+
};
207+
208+
let full_scan_request = FullScanRequest::builder()
209+
.spks_for_keychain(
210+
(),
211+
request
212+
.iter_spks()
213+
.enumerate()
214+
.map(|(i, spk)| (i as u32, spk))
215+
.collect::<Vec<_>>(),
216+
)
217+
.build();
218+
let mut graph_update = self
219+
.full_scan(full_scan_request, usize::MAX, batch_size, false)?
220+
.graph_update;
221+
self.populate_with_txids(&mut graph_update, request.iter_txids())?;
222+
self.populate_with_outpoints(&mut graph_update, request.iter_outpoints())?;
203223

204224
// Fetch previous `TxOut`s for fee calculation if flag is enabled.
205225
if fetch_prev_txouts {
206-
self.fetch_prev_txout(&mut full_scan_res.graph_update)?;
226+
self.fetch_prev_txout(&mut graph_update)?;
207227
}
208228

229+
let chain_update = match tip_and_latest_blocks {
230+
Some((chain_tip, latest_blocks)) => Some(chain_update(
231+
chain_tip,
232+
&latest_blocks,
233+
graph_update.all_anchors(),
234+
)?),
235+
None => None,
236+
};
237+
209238
Ok(SyncResult {
239+
graph_update,
210240
chain_update,
211-
graph_update: full_scan_res.graph_update,
212241
})
213242
}
214243

crates/electrum/tests/test_electrum.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ where
3939
Spks::IntoIter: ExactSizeIterator + Send + 'static,
4040
{
4141
let mut update = client.sync(
42-
SyncRequest::from_chain_tip(chain.tip()).chain_spks(spks),
42+
SyncRequest::builder().chain_tip(chain.tip()).spks(spks),
4343
BATCH_SIZE,
4444
true,
4545
)?;
@@ -51,9 +51,11 @@ where
5151
.as_secs();
5252
let _ = update.graph_update.update_last_seen_unconfirmed(now);
5353

54-
let _ = chain
55-
.apply_update(update.chain_update.clone())
56-
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
54+
if let Some(chain_update) = update.chain_update.clone() {
55+
let _ = chain
56+
.apply_update(chain_update)
57+
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
58+
}
5759
let _ = graph.apply_update(update.graph_update.clone());
5860

5961
Ok(update)
@@ -103,7 +105,9 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
103105
let cp_tip = env.make_checkpoint_tip();
104106

105107
let sync_update = {
106-
let request = SyncRequest::from_chain_tip(cp_tip.clone()).set_spks(misc_spks);
108+
let request = SyncRequest::builder()
109+
.chain_tip(cp_tip.clone())
110+
.spks(misc_spks);
107111
client.sync(request, 1, true)?
108112
};
109113

@@ -207,15 +211,17 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
207211
// A scan with a stop_gap of 3 won't find the transaction, but a scan with a gap limit of 4
208212
// will.
209213
let full_scan_update = {
210-
let request =
211-
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
214+
let request = FullScanRequest::builder()
215+
.chain_tip(cp_tip.clone())
216+
.spks_for_keychain(0, spks.clone());
212217
client.full_scan(request, 3, 1, false)?
213218
};
214219
assert!(full_scan_update.graph_update.full_txs().next().is_none());
215220
assert!(full_scan_update.last_active_indices.is_empty());
216221
let full_scan_update = {
217-
let request =
218-
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
222+
let request = FullScanRequest::builder()
223+
.chain_tip(cp_tip.clone())
224+
.spks_for_keychain(0, spks.clone());
219225
client.full_scan(request, 4, 1, false)?
220226
};
221227
assert_eq!(
@@ -246,8 +252,9 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
246252
// A scan with gap limit 5 won't find the second transaction, but a scan with gap limit 6 will.
247253
// The last active indice won't be updated in the first case but will in the second one.
248254
let full_scan_update = {
249-
let request =
250-
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
255+
let request = FullScanRequest::builder()
256+
.chain_tip(cp_tip.clone())
257+
.spks_for_keychain(0, spks.clone());
251258
client.full_scan(request, 5, 1, false)?
252259
};
253260
let txs: HashSet<_> = full_scan_update
@@ -259,8 +266,9 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
259266
assert!(txs.contains(&txid_4th_addr));
260267
assert_eq!(full_scan_update.last_active_indices[&0], 3);
261268
let full_scan_update = {
262-
let request =
263-
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
269+
let request = FullScanRequest::builder()
270+
.chain_tip(cp_tip.clone())
271+
.spks_for_keychain(0, spks.clone());
264272
client.full_scan(request, 6, 1, false)?
265273
};
266274
let txs: HashSet<_> = full_scan_update
@@ -311,7 +319,7 @@ fn test_sync() -> anyhow::Result<()> {
311319
let txid = env.send(&addr_to_track, SEND_AMOUNT)?;
312320
env.wait_until_electrum_sees_txid(txid, Duration::from_secs(6))?;
313321

314-
sync_with_electrum(
322+
let _ = sync_with_electrum(
315323
&client,
316324
[spk_to_track.clone()],
317325
&mut recv_chain,
@@ -332,7 +340,7 @@ fn test_sync() -> anyhow::Result<()> {
332340
env.mine_blocks(1, None)?;
333341
env.wait_until_electrum_sees_block(Duration::from_secs(6))?;
334342

335-
sync_with_electrum(
343+
let _ = sync_with_electrum(
336344
&client,
337345
[spk_to_track.clone()],
338346
&mut recv_chain,
@@ -353,7 +361,7 @@ fn test_sync() -> anyhow::Result<()> {
353361
env.reorg_empty_blocks(1)?;
354362
env.wait_until_electrum_sees_block(Duration::from_secs(6))?;
355363

356-
sync_with_electrum(
364+
let _ = sync_with_electrum(
357365
&client,
358366
[spk_to_track.clone()],
359367
&mut recv_chain,
@@ -373,7 +381,7 @@ fn test_sync() -> anyhow::Result<()> {
373381
env.mine_blocks(1, None)?;
374382
env.wait_until_electrum_sees_block(Duration::from_secs(6))?;
375383

376-
sync_with_electrum(&client, [spk_to_track], &mut recv_chain, &mut recv_graph)?;
384+
let _ = sync_with_electrum(&client, [spk_to_track], &mut recv_chain, &mut recv_graph)?;
377385

378386
// Check if balance is correct once transaction is confirmed again.
379387
assert_eq!(

0 commit comments

Comments
 (0)