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

bitcoindevkit / bdk / 10539621204

24 Aug 2024 03:32PM UTC coverage: 82.079% (+0.2%) from 81.848%
10539621204

Pull #1569

github

web-flow
Merge 48259deeb into 9e6ac72a6
Pull Request #1569: `bdk_core` WIP WIP WIP

533 of 616 new or added lines in 16 files covered. (86.53%)

5 existing lines in 4 files now uncovered.

11230 of 13682 relevant lines covered (82.08%)

13503.08 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
    /// [`bdk_chain`]: ../bdk_chain/index.html
120
    /// [`CalculateFeeError::MissingTxOut`]: ../bdk_chain/tx_graph/enum.CalculateFeeError.html#variant.MissingTxOut
121
    /// [`Wallet.calculate_fee`]: ../bdk_wallet/struct.Wallet.html#method.calculate_fee
122
    /// [`Wallet.calculate_fee_rate`]: ../bdk_wallet/struct.Wallet.html#method.calculate_fee_rate
123
    pub fn full_scan<K: Ord + Clone>(
4✔
124
        &self,
4✔
125
        request: impl Into<FullScanRequest<K>>,
4✔
126
        stop_gap: usize,
4✔
127
        batch_size: usize,
4✔
128
        fetch_prev_txouts: bool,
4✔
129
    ) -> Result<FullScanResult<K>, Error> {
4✔
130
        let mut request: FullScanRequest<K> = request.into();
4✔
131

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

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

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

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

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

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

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

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

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

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

232
        Ok(SyncResult {
14✔
233
            graph_update,
14✔
234
            chain_update,
14✔
235
        })
14✔
236
    }
14✔
237

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

18✔
491
    let agreement_height = agreement_cp.as_ref().map(CheckPoint::height);
18✔
492

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

18✔
509
    Ok((new_tip, new_blocks))
18✔
510
}
18✔
511

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

44✔
522
        // Checkpoint uses the `BlockHash` from `latest_blocks` so that the hash will be consistent
44✔
523
        // in case of a re-org.
44✔
524
        if tip.get(height).is_none() && height <= tip.height() {
44✔
525
            let hash = match latest_blocks.get(&height) {
×
526
                Some(&hash) => hash,
×
NEW
527
                None => anchor.block_id.hash,
×
528
            };
529
            tip = tip.insert(BlockId { hash, height });
×
530
        }
44✔
531
    }
532
    Ok(tip)
18✔
533
}
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