• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bitcoindevkit / bdk / 10537513186

24 Aug 2024 09:36AM UTC coverage: 82.091% (+0.2%) from 81.848%
10537513186

Pull #1569

github

web-flow
Merge 5144f7c71 into 9e6ac72a6
Pull Request #1569: `bdk_core` WIP WIP WIP

534 of 615 new or added lines in 16 files covered. (86.83%)

5 existing lines in 4 files now uncovered.

11230 of 13680 relevant lines covered (82.09%)

13567.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.78
/crates/electrum/src/bdk_electrum_client.rs
1
use bdk_core::{
2
    bitcoin::{block::Header, BlockHash, OutPoint, ScriptBuf, Transaction, Txid},
3
    collections::{BTreeMap, HashMap},
4
    spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
5
    tx_graph, BlockId, CheckPoint, ConfirmationBlockTime,
6
};
7
use electrum_client::{ElectrumApi, Error, HeaderNotification};
8
use std::{
9
    collections::HashSet,
10
    sync::{Arc, Mutex},
11
};
12

13
/// We include a chain suffix of a certain length for the purpose of robustness.
14
const CHAIN_SUFFIX_LENGTH: u32 = 8;
15

16
/// Wrapper around an [`electrum_client::ElectrumApi`] which includes an internal in-memory
17
/// transaction cache to avoid re-fetching already downloaded transactions.
18
#[derive(Debug)]
19
pub struct BdkElectrumClient<E> {
20
    /// The internal [`electrum_client::ElectrumApi`]
21
    pub inner: E,
22
    /// The transaction cache
23
    tx_cache: Mutex<HashMap<Txid, Arc<Transaction>>>,
24
    /// The header cache
25
    block_header_cache: Mutex<HashMap<u32, Header>>,
26
}
27

28
impl<E: ElectrumApi> BdkElectrumClient<E> {
29
    /// Creates a new bdk client from a [`electrum_client::ElectrumApi`]
30
    pub fn new(client: E) -> Self {
4✔
31
        Self {
4✔
32
            inner: client,
4✔
33
            tx_cache: Default::default(),
4✔
34
            block_header_cache: Default::default(),
4✔
35
        }
4✔
36
    }
4✔
37

38
    /// Inserts transactions into the transaction cache so that the client will not fetch these
39
    /// transactions.
NEW
40
    pub fn populate_tx_cache(&self, txs: impl IntoIterator<Item = impl Into<Arc<Transaction>>>) {
×
41
        let mut tx_cache = self.tx_cache.lock().unwrap();
×
NEW
42
        for tx in txs {
×
NEW
43
            let tx = tx.into();
×
NEW
44
            let txid = tx.compute_txid();
×
45
            tx_cache.insert(txid, tx);
×
46
        }
×
47
    }
×
48

49
    /// Fetch transaction of given `txid`.
50
    ///
51
    /// If it hits the cache it will return the cached version and avoid making the request.
52
    pub fn fetch_tx(&self, txid: Txid) -> Result<Arc<Transaction>, Error> {
104✔
53
        let tx_cache = self.tx_cache.lock().unwrap();
104✔
54

55
        if let Some(tx) = tx_cache.get(&txid) {
104✔
56
            return Ok(Arc::clone(tx));
80✔
57
        }
24✔
58

24✔
59
        drop(tx_cache);
24✔
60

61
        let tx = Arc::new(self.inner.transaction_get(&txid)?);
24✔
62

63
        self.tx_cache.lock().unwrap().insert(txid, Arc::clone(&tx));
24✔
64

24✔
65
        Ok(tx)
24✔
66
    }
104✔
67

68
    /// Fetch block header of given `height`.
69
    ///
70
    /// If it hits the cache it will return the cached version and avoid making the request.
71
    fn fetch_header(&self, height: u32) -> Result<Header, Error> {
44✔
72
        let block_header_cache = self.block_header_cache.lock().unwrap();
44✔
73

74
        if let Some(header) = block_header_cache.get(&height) {
44✔
75
            return Ok(*header);
31✔
76
        }
13✔
77

13✔
78
        drop(block_header_cache);
13✔
79

13✔
80
        self.update_header(height)
13✔
81
    }
44✔
82

83
    /// Update a block header at given `height`. Returns the updated header.
84
    fn update_header(&self, height: u32) -> Result<Header, Error> {
13✔
85
        let header = self.inner.block_header(height as usize)?;
13✔
86

87
        self.block_header_cache
13✔
88
            .lock()
13✔
89
            .unwrap()
13✔
90
            .insert(height, header);
13✔
91

13✔
92
        Ok(header)
13✔
93
    }
13✔
94

95
    /// Broadcasts a transaction to the network.
96
    ///
97
    /// This is a re-export of [`ElectrumApi::transaction_broadcast`].
98
    pub fn transaction_broadcast(&self, tx: &Transaction) -> Result<Txid, Error> {
×
99
        self.inner.transaction_broadcast(tx)
×
100
    }
×
101

102
    /// Full scan the keychain scripts specified with the blockchain (via an Electrum client) and
103
    /// returns updates for [`bdk_chain`] data structures.
104
    ///
105
    /// - `request`: struct with data required to perform a spk-based blockchain client full scan,
106
    ///              see [`FullScanRequest`].
107
    /// - `stop_gap`: the full scan for each keychain stops after a gap of script pubkeys with no
108
    ///               associated transactions.
109
    /// - `batch_size`: specifies the max number of script pubkeys to request for in a single batch
110
    ///                 request.
111
    /// - `fetch_prev_txouts`: specifies whether we want previous `TxOut`s for fee calculation.
112
    ///                        Note that this requires additional calls to the Electrum server, but
113
    ///                        is necessary for calculating the fee on a transaction if your wallet
114
    ///                        does not own the inputs. Methods like [`Wallet.calculate_fee`] and
115
    ///                        [`Wallet.calculate_fee_rate`] will return a
116
    ///                        [`CalculateFeeError::MissingTxOut`] error if those `TxOut`s are not
117
    ///                        present in the transaction graph.
118
    ///
119
    /// [`CalculateFeeError::MissingTxOut`]: bdk_chain::tx_graph::CalculateFeeError::MissingTxOut
120
    /// [`Wallet.calculate_fee`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee
121
    /// [`Wallet.calculate_fee_rate`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee_rate
122
    pub fn full_scan<K: Ord + Clone>(
4✔
123
        &self,
4✔
124
        request: impl Into<FullScanRequest<K>>,
4✔
125
        stop_gap: usize,
4✔
126
        batch_size: usize,
4✔
127
        fetch_prev_txouts: bool,
4✔
128
    ) -> Result<FullScanResult<K>, Error> {
4✔
129
        let mut request: FullScanRequest<K> = request.into();
4✔
130

131
        let tip_and_latest_blocks = match request.chain_tip() {
4✔
132
            Some(chain_tip) => Some(fetch_tip_and_latest_blocks(&self.inner, chain_tip)?),
4✔
133
            None => None,
×
134
        };
135

136
        let mut graph_update = tx_graph::Update::<ConfirmationBlockTime>::default();
4✔
137
        let mut last_active_indices = BTreeMap::<K, u32>::default();
4✔
138
        for keychain in request.keychains() {
4✔
139
            let spks = request.iter_spks(keychain.clone());
4✔
140
            if let Some(last_active_index) =
3✔
141
                self.populate_with_spks(&mut graph_update, spks, stop_gap, batch_size)?
4✔
142
            {
3✔
143
                last_active_indices.insert(keychain, last_active_index);
3✔
144
            }
3✔
145
        }
146

147
        // Fetch previous `TxOut`s for fee calculation if flag is enabled.
148
        if fetch_prev_txouts {
4✔
149
            self.fetch_prev_txout(&mut graph_update)?;
×
150
        }
4✔
151

152
        let chain_update = match tip_and_latest_blocks {
4✔
153
            Some((chain_tip, latest_blocks)) => Some(chain_update(
4✔
154
                chain_tip,
4✔
155
                &latest_blocks,
4✔
156
                graph_update.anchors.iter().cloned(),
4✔
157
            )?),
4✔
158
            _ => None,
×
159
        };
160

161
        Ok(FullScanResult {
4✔
162
            graph_update,
4✔
163
            chain_update,
4✔
164
            last_active_indices,
4✔
165
        })
4✔
166
    }
4✔
167

168
    /// Sync a set of scripts with the blockchain (via an Electrum client) for the data specified
169
    /// and returns updates for [`bdk_chain`] data structures.
170
    ///
171
    /// - `request`: struct with data required to perform a spk-based blockchain client sync,
172
    ///              see [`SyncRequest`]
173
    /// - `batch_size`: specifies the max number of script pubkeys to request for in a single batch
174
    ///                 request
175
    /// - `fetch_prev_txouts`: specifies whether we want previous `TxOut`s for fee calculation.
176
    ///                        Note that this requires additional calls to the Electrum server, but
177
    ///                        is necessary for calculating the fee on a transaction if your wallet
178
    ///                        does not own the inputs. Methods like [`Wallet.calculate_fee`] and
179
    ///                        [`Wallet.calculate_fee_rate`] will return a
180
    ///                        [`CalculateFeeError::MissingTxOut`] error if those `TxOut`s are not
181
    ///                        present in the transaction graph.
182
    ///
183
    /// If the scripts to sync are unknown, such as when restoring or importing a keychain that
184
    /// may include scripts that have been used, use [`full_scan`] with the keychain.
185
    ///
186
    /// [`full_scan`]: Self::full_scan
187
    /// [`CalculateFeeError::MissingTxOut`]: bdk_chain::tx_graph::CalculateFeeError::MissingTxOut
188
    /// [`Wallet.calculate_fee`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee
189
    /// [`Wallet.calculate_fee_rate`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.Wallet.html#method.calculate_fee_rate
190
    pub fn sync<I: 'static>(
14✔
191
        &self,
14✔
192
        request: impl Into<SyncRequest<I>>,
14✔
193
        batch_size: usize,
14✔
194
        fetch_prev_txouts: bool,
14✔
195
    ) -> Result<SyncResult, Error> {
14✔
196
        let mut request: SyncRequest<I> = request.into();
14✔
197

198
        let tip_and_latest_blocks = match request.chain_tip() {
14✔
199
            Some(chain_tip) => Some(fetch_tip_and_latest_blocks(&self.inner, chain_tip)?),
14✔
200
            None => None,
×
201
        };
202

203
        let mut graph_update = tx_graph::Update::<ConfirmationBlockTime>::default();
14✔
204
        self.populate_with_spks(
14✔
205
            &mut graph_update,
14✔
206
            request
14✔
207
                .iter_spks()
14✔
208
                .enumerate()
14✔
209
                .map(|(i, spk)| (i as u32, spk)),
15✔
210
            usize::MAX,
14✔
211
            batch_size,
14✔
212
        )?;
14✔
213
        self.populate_with_txids(&mut graph_update, request.iter_txids())?;
14✔
214
        self.populate_with_outpoints(&mut graph_update, request.iter_outpoints())?;
14✔
215

216
        // Fetch previous `TxOut`s for fee calculation if flag is enabled.
217
        if fetch_prev_txouts {
14✔
218
            self.fetch_prev_txout(&mut graph_update)?;
14✔
219
        }
×
220

221
        let chain_update = match tip_and_latest_blocks {
14✔
222
            Some((chain_tip, latest_blocks)) => Some(chain_update(
14✔
223
                chain_tip,
14✔
224
                &latest_blocks,
14✔
225
                graph_update.anchors.iter().cloned(),
14✔
226
            )?),
14✔
227
            None => None,
×
228
        };
229

230
        Ok(SyncResult {
14✔
231
            graph_update,
14✔
232
            chain_update,
14✔
233
        })
14✔
234
    }
14✔
235

236
    /// Populate the `graph_update` with transactions/anchors associated with the given `spks`.
237
    ///
238
    /// Transactions that contains an output with requested spk, or spends form an output with
239
    /// requested spk will be added to `graph_update`. Anchors of the aforementioned transactions are
240
    /// also included.
241
    fn populate_with_spks(
18✔
242
        &self,
18✔
243
        graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
18✔
244
        mut spks: impl Iterator<Item = (u32, ScriptBuf)>,
18✔
245
        stop_gap: usize,
18✔
246
        batch_size: usize,
18✔
247
    ) -> Result<Option<u32>, Error> {
18✔
248
        let mut unused_spk_count = 0_usize;
18✔
249
        let mut last_active_index = Option::<u32>::None;
18✔
250

251
        loop {
60✔
252
            let spks = (0..batch_size)
60✔
253
                .map_while(|_| spks.next())
73✔
254
                .collect::<Vec<_>>();
60✔
255
            if spks.is_empty() {
60✔
256
                return Ok(last_active_index);
15✔
257
            }
45✔
258

259
            let spk_histories = self
45✔
260
                .inner
45✔
261
                .batch_script_get_history(spks.iter().map(|(_, s)| s.as_script()))?;
45✔
262

263
            for ((spk_index, _spk), spk_history) in spks.into_iter().zip(spk_histories) {
45✔
264
                if spk_history.is_empty() {
45✔
265
                    unused_spk_count = unused_spk_count.saturating_add(1);
26✔
266
                    if unused_spk_count >= stop_gap {
26✔
267
                        return Ok(last_active_index);
3✔
268
                    }
23✔
269
                    continue;
23✔
270
                } else {
19✔
271
                    last_active_index = Some(spk_index);
19✔
272
                    unused_spk_count = 0;
19✔
273
                }
19✔
274

275
                for tx_res in spk_history {
73✔
276
                    graph_update.txs.push(self.fetch_tx(tx_res.tx_hash)?);
54✔
277
                    self.validate_merkle_for_anchor(graph_update, tx_res.tx_hash, tx_res.height)?;
54✔
278
                }
279
            }
280
        }
281
    }
18✔
282

283
    /// Populate the `graph_update` with associated transactions/anchors of `outpoints`.
284
    ///
285
    /// Transactions in which the outpoint resides, and transactions that spend from the outpoint are
286
    /// included. Anchors of the aforementioned transactions are included.
287
    fn populate_with_outpoints(
14✔
288
        &self,
14✔
289
        graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
14✔
290
        outpoints: impl IntoIterator<Item = OutPoint>,
14✔
291
    ) -> Result<(), Error> {
14✔
292
        for outpoint in outpoints {
14✔
293
            let op_txid = outpoint.txid;
×
294
            let op_tx = self.fetch_tx(op_txid)?;
×
295
            let op_txout = match op_tx.output.get(outpoint.vout as usize) {
×
296
                Some(txout) => txout,
×
297
                None => continue,
×
298
            };
299
            debug_assert_eq!(op_tx.compute_txid(), op_txid);
×
300

301
            // attempt to find the following transactions (alongside their chain positions), and
302
            // add to our sparsechain `update`:
303
            let mut has_residing = false; // tx in which the outpoint resides
×
304
            let mut has_spending = false; // tx that spends the outpoint
×
305
            for res in self.inner.script_get_history(&op_txout.script_pubkey)? {
×
306
                if has_residing && has_spending {
×
307
                    break;
×
308
                }
×
309

×
310
                if !has_residing && res.tx_hash == op_txid {
×
311
                    has_residing = true;
×
NEW
312
                    graph_update.txs.push(Arc::clone(&op_tx));
×
313
                    self.validate_merkle_for_anchor(graph_update, res.tx_hash, res.height)?;
×
314
                }
×
315

316
                if !has_spending && res.tx_hash != op_txid {
×
317
                    let res_tx = self.fetch_tx(res.tx_hash)?;
×
318
                    // we exclude txs/anchors that do not spend our specified outpoint(s)
319
                    has_spending = res_tx
×
320
                        .input
×
321
                        .iter()
×
322
                        .any(|txin| txin.previous_output == outpoint);
×
323
                    if !has_spending {
×
324
                        continue;
×
325
                    }
×
NEW
326
                    graph_update.txs.push(Arc::clone(&res_tx));
×
327
                    self.validate_merkle_for_anchor(graph_update, res.tx_hash, res.height)?;
×
328
                }
×
329
            }
330
        }
331
        Ok(())
14✔
332
    }
14✔
333

334
    /// Populate the `graph_update` with transactions/anchors of the provided `txids`.
335
    fn populate_with_txids(
14✔
336
        &self,
14✔
337
        graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
14✔
338
        txids: impl IntoIterator<Item = Txid>,
14✔
339
    ) -> Result<(), Error> {
14✔
340
        for txid in txids {
14✔
341
            let tx = match self.fetch_tx(txid) {
×
342
                Ok(tx) => tx,
×
343
                Err(electrum_client::Error::Protocol(_)) => continue,
×
344
                Err(other_err) => return Err(other_err),
×
345
            };
346

347
            let spk = tx
×
348
                .output
×
349
                .first()
×
350
                .map(|txo| &txo.script_pubkey)
×
351
                .expect("tx must have an output");
×
352

353
            // because of restrictions of the Electrum API, we have to use the `script_get_history`
354
            // call to get confirmation status of our transaction
355
            if let Some(r) = self
×
356
                .inner
×
357
                .script_get_history(spk)?
×
358
                .into_iter()
×
359
                .find(|r| r.tx_hash == txid)
×
360
            {
361
                self.validate_merkle_for_anchor(graph_update, txid, r.height)?;
×
362
            }
×
363

NEW
364
            graph_update.txs.push(tx);
×
365
        }
366
        Ok(())
14✔
367
    }
14✔
368

369
    // Helper function which checks if a transaction is confirmed by validating the merkle proof.
370
    // An anchor is inserted if the transaction is validated to be in a confirmed block.
371
    fn validate_merkle_for_anchor(
54✔
372
        &self,
54✔
373
        graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
54✔
374
        txid: Txid,
54✔
375
        confirmation_height: i32,
54✔
376
    ) -> Result<(), Error> {
54✔
377
        if let Ok(merkle_res) = self
54✔
378
            .inner
54✔
379
            .transaction_get_merkle(&txid, confirmation_height as usize)
54✔
380
        {
381
            let mut header = self.fetch_header(merkle_res.block_height as u32)?;
44✔
382
            let mut is_confirmed_tx = electrum_client::utils::validate_merkle_proof(
44✔
383
                &txid,
44✔
384
                &header.merkle_root,
44✔
385
                &merkle_res,
44✔
386
            );
44✔
387

44✔
388
            // Merkle validation will fail if the header in `block_header_cache` is outdated, so we
44✔
389
            // want to check if there is a new header and validate against the new one.
44✔
390
            if !is_confirmed_tx {
44✔
391
                header = self.update_header(merkle_res.block_height as u32)?;
×
392
                is_confirmed_tx = electrum_client::utils::validate_merkle_proof(
×
393
                    &txid,
×
394
                    &header.merkle_root,
×
395
                    &merkle_res,
×
396
                );
×
397
            }
44✔
398

399
            if is_confirmed_tx {
44✔
400
                graph_update.anchors.insert((
44✔
401
                    ConfirmationBlockTime {
44✔
402
                        confirmation_time: header.time as u64,
44✔
403
                        block_id: BlockId {
44✔
404
                            height: merkle_res.block_height as u32,
44✔
405
                            hash: header.block_hash(),
44✔
406
                        },
44✔
407
                    },
44✔
408
                    txid,
44✔
409
                ));
44✔
410
            }
44✔
411
        }
10✔
412
        Ok(())
54✔
413
    }
54✔
414

415
    // Helper function which fetches the `TxOut`s of our relevant transactions' previous transactions,
416
    // which we do not have by default. This data is needed to calculate the transaction fee.
417
    fn fetch_prev_txout(
14✔
418
        &self,
14✔
419
        graph_update: &mut tx_graph::Update<ConfirmationBlockTime>,
14✔
420
    ) -> Result<(), Error> {
14✔
421
        let mut no_dup = HashSet::<Txid>::new();
14✔
422
        for tx in &graph_update.txs {
64✔
423
            if no_dup.insert(tx.compute_txid()) {
50✔
424
                for vin in &tx.input {
50✔
425
                    let outpoint = vin.previous_output;
50✔
426
                    let vout = outpoint.vout;
50✔
427
                    let prev_tx = self.fetch_tx(outpoint.txid)?;
50✔
428
                    let txout = prev_tx.output[vout as usize].clone();
50✔
429
                    let _ = graph_update.txouts.insert(outpoint, txout);
50✔
430
                }
UNCOV
431
            }
×
432
        }
433
        Ok(())
14✔
434
    }
14✔
435
}
436

437
/// Return a [`CheckPoint`] of the latest tip, that connects with `prev_tip`. The latest blocks are
438
/// fetched to construct checkpoint updates with the proper [`BlockHash`] in case of re-org.
439
fn fetch_tip_and_latest_blocks(
18✔
440
    client: &impl ElectrumApi,
18✔
441
    prev_tip: CheckPoint,
18✔
442
) -> Result<(CheckPoint, BTreeMap<u32, BlockHash>), Error> {
18✔
443
    let HeaderNotification { height, .. } = client.block_headers_subscribe()?;
18✔
444
    let new_tip_height = height as u32;
18✔
445

18✔
446
    // If electrum returns a tip height that is lower than our previous tip, then checkpoints do
18✔
447
    // not need updating. We just return the previous tip and use that as the point of agreement.
18✔
448
    if new_tip_height < prev_tip.height() {
18✔
449
        return Ok((prev_tip, BTreeMap::new()));
×
450
    }
18✔
451

452
    // Atomically fetch the latest `CHAIN_SUFFIX_LENGTH` count of blocks from Electrum. We use this
453
    // to construct our checkpoint update.
454
    let mut new_blocks = {
18✔
455
        let start_height = new_tip_height.saturating_sub(CHAIN_SUFFIX_LENGTH - 1);
18✔
456
        let hashes = client
18✔
457
            .block_headers(start_height as _, CHAIN_SUFFIX_LENGTH as _)?
18✔
458
            .headers
459
            .into_iter()
18✔
460
            .map(|h| h.block_hash());
144✔
461
        (start_height..).zip(hashes).collect::<BTreeMap<u32, _>>()
18✔
462
    };
463

464
    // Find the "point of agreement" (if any).
465
    let agreement_cp = {
18✔
466
        let mut agreement_cp = Option::<CheckPoint>::None;
18✔
467
        for cp in prev_tip.iter() {
55✔
468
            let cp_block = cp.block_id();
55✔
469
            let hash = match new_blocks.get(&cp_block.height) {
55✔
470
                Some(&hash) => hash,
52✔
471
                None => {
472
                    assert!(
3✔
473
                        new_tip_height >= cp_block.height,
3✔
474
                        "already checked that electrum's tip cannot be smaller"
×
475
                    );
476
                    let hash = client.block_header(cp_block.height as _)?.block_hash();
3✔
477
                    new_blocks.insert(cp_block.height, hash);
3✔
478
                    hash
3✔
479
                }
480
            };
481
            if hash == cp_block.hash {
55✔
482
                agreement_cp = Some(cp);
18✔
483
                break;
18✔
484
            }
37✔
485
        }
486
        agreement_cp
18✔
487
    };
18✔
488

18✔
489
    let agreement_height = agreement_cp.as_ref().map(CheckPoint::height);
18✔
490

18✔
491
    let new_tip = new_blocks
18✔
492
        .iter()
18✔
493
        // Prune `new_blocks` to only include blocks that are actually new.
18✔
494
        .filter(|(height, _)| Some(*<&u32>::clone(height)) > agreement_height)
147✔
495
        .map(|(height, hash)| BlockId {
57✔
496
            height: *height,
57✔
497
            hash: *hash,
57✔
498
        })
57✔
499
        .fold(agreement_cp, |prev_cp, block| {
57✔
500
            Some(match prev_cp {
57✔
501
                Some(cp) => cp.push(block).expect("must extend checkpoint"),
57✔
502
                None => CheckPoint::new(block),
×
503
            })
504
        })
57✔
505
        .expect("must have at least one checkpoint");
18✔
506

18✔
507
    Ok((new_tip, new_blocks))
18✔
508
}
18✔
509

510
// Add a corresponding checkpoint per anchor height if it does not yet exist. Checkpoints should not
511
// surpass `latest_blocks`.
512
fn chain_update(
18✔
513
    mut tip: CheckPoint,
18✔
514
    latest_blocks: &BTreeMap<u32, BlockHash>,
18✔
515
    anchors: impl Iterator<Item = (ConfirmationBlockTime, Txid)>,
18✔
516
) -> Result<CheckPoint, Error> {
18✔
517
    for (anchor, _txid) in anchors {
62✔
518
        let height = anchor.block_id.height;
44✔
519

44✔
520
        // Checkpoint uses the `BlockHash` from `latest_blocks` so that the hash will be consistent
44✔
521
        // in case of a re-org.
44✔
522
        if tip.get(height).is_none() && height <= tip.height() {
44✔
523
            let hash = match latest_blocks.get(&height) {
×
524
                Some(&hash) => hash,
×
NEW
525
                None => anchor.block_id.hash,
×
526
            };
527
            tip = tip.insert(BlockId { hash, height });
×
528
        }
44✔
529
    }
530
    Ok(tip)
18✔
531
}
18✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc