feat(electrum)!: use new sync/full-scan structs for ElectrumExt
`ElectrumResultExt` trait is also introduced that adds methods which can convert the `Anchor` type for the update `TxGraph`. We also make use of the new `TxCache` fields in `SyncRequest`/`FullScanRequest`. This way, we can avoid re-fetching full transactions from Electrum if not needed. Examples and tests are updated to use the new `ElectrumExt` API.
This commit is contained in:
parent
653e4fed6d
commit
a6fdfb2ae4
@ -12,7 +12,7 @@ readme = "README.md"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk_chain = { path = "../chain", version = "0.13.0", default-features = false }
|
bdk_chain = { path = "../chain", version = "0.13.0" }
|
||||||
electrum-client = { version = "0.19" }
|
electrum-client = { version = "0.19" }
|
||||||
#rustls = { version = "=0.21.1", optional = true, features = ["dangerous_configuration"] }
|
#rustls = { version = "=0.21.1", optional = true, features = ["dangerous_configuration"] }
|
||||||
|
|
||||||
|
@ -1,28 +1,18 @@
|
|||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{OutPoint, ScriptBuf, Txid},
|
bitcoin::{OutPoint, ScriptBuf, Transaction, Txid},
|
||||||
collections::{HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
local_chain::CheckPoint,
|
local_chain::CheckPoint,
|
||||||
|
spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult, TxCache},
|
||||||
tx_graph::TxGraph,
|
tx_graph::TxGraph,
|
||||||
Anchor, BlockId, ConfirmationHeightAnchor, ConfirmationTimeHeightAnchor,
|
BlockId, ConfirmationHeightAnchor, ConfirmationTimeHeightAnchor,
|
||||||
};
|
};
|
||||||
|
use core::str::FromStr;
|
||||||
use electrum_client::{ElectrumApi, Error, HeaderNotification};
|
use electrum_client::{ElectrumApi, Error, HeaderNotification};
|
||||||
use std::{collections::BTreeMap, fmt::Debug, str::FromStr};
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// We include a chain suffix of a certain length for the purpose of robustness.
|
/// We include a chain suffix of a certain length for the purpose of robustness.
|
||||||
const CHAIN_SUFFIX_LENGTH: u32 = 8;
|
const CHAIN_SUFFIX_LENGTH: u32 = 8;
|
||||||
|
|
||||||
/// Combination of chain and transactions updates from electrum
|
|
||||||
///
|
|
||||||
/// We have to update the chain and the txids at the same time since we anchor the txids to
|
|
||||||
/// the same chain tip that we check before and after we gather the txids.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ElectrumUpdate {
|
|
||||||
/// Chain update
|
|
||||||
pub chain_update: CheckPoint,
|
|
||||||
/// Tracks electrum updates in TxGraph
|
|
||||||
pub graph_update: TxGraph<ConfirmationTimeHeightAnchor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait to extend [`electrum_client::Client`] functionality.
|
/// Trait to extend [`electrum_client::Client`] functionality.
|
||||||
pub trait ElectrumExt {
|
pub trait ElectrumExt {
|
||||||
/// Full scan the keychain scripts specified with the blockchain (via an Electrum client) and
|
/// Full scan the keychain scripts specified with the blockchain (via an Electrum client) and
|
||||||
@ -35,14 +25,12 @@ pub trait ElectrumExt {
|
|||||||
/// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
|
/// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
|
||||||
/// transactions. `batch_size` specifies the max number of script pubkeys to request for in a
|
/// transactions. `batch_size` specifies the max number of script pubkeys to request for in a
|
||||||
/// single batch request.
|
/// single batch request.
|
||||||
fn full_scan<K: Ord + Clone, A: Anchor>(
|
fn full_scan<K: Ord + Clone>(
|
||||||
&self,
|
&self,
|
||||||
prev_tip: CheckPoint,
|
request: FullScanRequest<K>,
|
||||||
keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
|
|
||||||
full_txs: Option<&TxGraph<A>>,
|
|
||||||
stop_gap: usize,
|
stop_gap: usize,
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
) -> Result<(ElectrumUpdate, BTreeMap<K, u32>), Error>;
|
) -> Result<FullScanResult<K, ConfirmationHeightAnchor>, Error>;
|
||||||
|
|
||||||
/// Sync a set of scripts with the blockchain (via an Electrum client) for the data specified
|
/// Sync a set of scripts with the blockchain (via an Electrum client) for the data specified
|
||||||
/// and returns updates for [`bdk_chain`] data structures.
|
/// and returns updates for [`bdk_chain`] data structures.
|
||||||
@ -61,42 +49,33 @@ pub trait ElectrumExt {
|
|||||||
/// may include scripts that have been used, use [`full_scan`] with the keychain.
|
/// may include scripts that have been used, use [`full_scan`] with the keychain.
|
||||||
///
|
///
|
||||||
/// [`full_scan`]: ElectrumExt::full_scan
|
/// [`full_scan`]: ElectrumExt::full_scan
|
||||||
fn sync<A: Anchor>(
|
fn sync(
|
||||||
&self,
|
&self,
|
||||||
prev_tip: CheckPoint,
|
request: SyncRequest,
|
||||||
misc_spks: impl IntoIterator<Item = ScriptBuf>,
|
|
||||||
full_txs: Option<&TxGraph<A>>,
|
|
||||||
txids: impl IntoIterator<Item = Txid>,
|
|
||||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
) -> Result<ElectrumUpdate, Error>;
|
) -> Result<SyncResult<ConfirmationHeightAnchor>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: ElectrumApi> ElectrumExt for E {
|
impl<E: ElectrumApi> ElectrumExt for E {
|
||||||
fn full_scan<K: Ord + Clone, A: Anchor>(
|
fn full_scan<K: Ord + Clone>(
|
||||||
&self,
|
&self,
|
||||||
prev_tip: CheckPoint,
|
mut request: FullScanRequest<K>,
|
||||||
keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
|
|
||||||
full_txs: Option<&TxGraph<A>>,
|
|
||||||
stop_gap: usize,
|
stop_gap: usize,
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
) -> Result<(ElectrumUpdate, BTreeMap<K, u32>), Error> {
|
) -> Result<FullScanResult<K, ConfirmationHeightAnchor>, Error> {
|
||||||
let mut request_spks = keychain_spks
|
let mut request_spks = request.spks_by_keychain;
|
||||||
.into_iter()
|
|
||||||
.map(|(k, s)| (k, s.into_iter()))
|
// We keep track of already-scanned spks just in case a reorg happens and we need to do a
|
||||||
.collect::<BTreeMap<K, _>>();
|
// rescan. We need to keep track of this as iterators in `keychain_spks` are "unbounded" so
|
||||||
|
// cannot be collected. In addition, we keep track of whether an spk has an active tx
|
||||||
|
// history for determining the `last_active_index`.
|
||||||
|
// * key: (keychain, spk_index) that identifies the spk.
|
||||||
|
// * val: (script_pubkey, has_tx_history).
|
||||||
let mut scanned_spks = BTreeMap::<(K, u32), (ScriptBuf, bool)>::new();
|
let mut scanned_spks = BTreeMap::<(K, u32), (ScriptBuf, bool)>::new();
|
||||||
|
|
||||||
let (electrum_update, keychain_update) = loop {
|
let update = loop {
|
||||||
let (tip, _) = construct_update_tip(self, prev_tip.clone())?;
|
let (tip, _) = construct_update_tip(self, request.chain_tip.clone())?;
|
||||||
let mut tx_graph = TxGraph::<ConfirmationHeightAnchor>::default();
|
let mut graph_update = TxGraph::<ConfirmationHeightAnchor>::default();
|
||||||
if let Some(txs) = full_txs {
|
|
||||||
let _ =
|
|
||||||
tx_graph.apply_update(txs.clone().map_anchors(|a| ConfirmationHeightAnchor {
|
|
||||||
anchor_block: a.anchor_block(),
|
|
||||||
confirmation_height: a.confirmation_height_upper_bound(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
let cps = tip
|
let cps = tip
|
||||||
.iter()
|
.iter()
|
||||||
.take(10)
|
.take(10)
|
||||||
@ -108,7 +87,8 @@ impl<E: ElectrumApi> ElectrumExt for E {
|
|||||||
scanned_spks.append(&mut populate_with_spks(
|
scanned_spks.append(&mut populate_with_spks(
|
||||||
self,
|
self,
|
||||||
&cps,
|
&cps,
|
||||||
&mut tx_graph,
|
&mut request.tx_cache,
|
||||||
|
&mut graph_update,
|
||||||
&mut scanned_spks
|
&mut scanned_spks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(i, (spk, _))| (i.clone(), spk.clone())),
|
.map(|(i, (spk, _))| (i.clone(), spk.clone())),
|
||||||
@ -121,7 +101,8 @@ impl<E: ElectrumApi> ElectrumExt for E {
|
|||||||
populate_with_spks(
|
populate_with_spks(
|
||||||
self,
|
self,
|
||||||
&cps,
|
&cps,
|
||||||
&mut tx_graph,
|
&mut request.tx_cache,
|
||||||
|
&mut graph_update,
|
||||||
keychain_spks,
|
keychain_spks,
|
||||||
stop_gap,
|
stop_gap,
|
||||||
batch_size,
|
batch_size,
|
||||||
@ -140,8 +121,6 @@ impl<E: ElectrumApi> ElectrumExt for E {
|
|||||||
|
|
||||||
let chain_update = tip;
|
let chain_update = tip;
|
||||||
|
|
||||||
let graph_update = into_confirmation_time_tx_graph(self, &tx_graph)?;
|
|
||||||
|
|
||||||
let keychain_update = request_spks
|
let keychain_update = request_spks
|
||||||
.into_keys()
|
.into_keys()
|
||||||
.filter_map(|k| {
|
.filter_map(|k| {
|
||||||
@ -153,41 +132,29 @@ impl<E: ElectrumApi> ElectrumExt for E {
|
|||||||
})
|
})
|
||||||
.collect::<BTreeMap<_, _>>();
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
|
||||||
break (
|
break FullScanResult {
|
||||||
ElectrumUpdate {
|
graph_update,
|
||||||
chain_update,
|
chain_update,
|
||||||
graph_update,
|
last_active_indices: keychain_update,
|
||||||
},
|
};
|
||||||
keychain_update,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((electrum_update, keychain_update))
|
Ok(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync<A: Anchor>(
|
fn sync(
|
||||||
&self,
|
&self,
|
||||||
prev_tip: CheckPoint,
|
request: SyncRequest,
|
||||||
misc_spks: impl IntoIterator<Item = ScriptBuf>,
|
|
||||||
full_txs: Option<&TxGraph<A>>,
|
|
||||||
txids: impl IntoIterator<Item = Txid>,
|
|
||||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
) -> Result<ElectrumUpdate, Error> {
|
) -> Result<SyncResult<ConfirmationHeightAnchor>, Error> {
|
||||||
let spk_iter = misc_spks
|
let mut tx_cache = request.tx_cache.clone();
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, spk)| (i as u32, spk));
|
|
||||||
|
|
||||||
let (mut electrum_update, _) = self.full_scan(
|
let full_scan_req = FullScanRequest::from_chain_tip(request.chain_tip.clone())
|
||||||
prev_tip.clone(),
|
.cache_txs(request.tx_cache)
|
||||||
[((), spk_iter)].into(),
|
.set_spks_for_keychain((), request.spks.enumerate().map(|(i, spk)| (i as u32, spk)));
|
||||||
full_txs,
|
let full_scan_res = self.full_scan(full_scan_req, usize::MAX, batch_size)?;
|
||||||
usize::MAX,
|
|
||||||
batch_size,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let (tip, _) = construct_update_tip(self, prev_tip)?;
|
let (tip, _) = construct_update_tip(self, request.chain_tip)?;
|
||||||
let cps = tip
|
let cps = tip
|
||||||
.iter()
|
.iter()
|
||||||
.take(10)
|
.take(10)
|
||||||
@ -195,16 +162,88 @@ impl<E: ElectrumApi> ElectrumExt for E {
|
|||||||
.collect::<BTreeMap<u32, CheckPoint>>();
|
.collect::<BTreeMap<u32, CheckPoint>>();
|
||||||
|
|
||||||
let mut tx_graph = TxGraph::<ConfirmationHeightAnchor>::default();
|
let mut tx_graph = TxGraph::<ConfirmationHeightAnchor>::default();
|
||||||
populate_with_txids(self, &cps, &mut tx_graph, txids)?;
|
populate_with_txids(self, &cps, &mut tx_cache, &mut tx_graph, request.txids)?;
|
||||||
populate_with_outpoints(self, &cps, &mut tx_graph, outpoints)?;
|
populate_with_outpoints(self, &cps, &mut tx_cache, &mut tx_graph, request.outpoints)?;
|
||||||
let _ = electrum_update
|
|
||||||
.graph_update
|
|
||||||
.apply_update(into_confirmation_time_tx_graph(self, &tx_graph)?);
|
|
||||||
|
|
||||||
Ok(electrum_update)
|
Ok(SyncResult {
|
||||||
|
chain_update: full_scan_res.chain_update,
|
||||||
|
graph_update: full_scan_res.graph_update,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait that extends [`SyncResult`] and [`FullScanResult`] functionality.
|
||||||
|
///
|
||||||
|
/// Currently, only a single method exists that converts the update [`TxGraph`] to have an anchor
|
||||||
|
/// type of [`ConfirmationTimeHeightAnchor`].
|
||||||
|
pub trait ElectrumResultExt {
|
||||||
|
/// New result type with a [`TxGraph`] that contains the [`ConfirmationTimeHeightAnchor`].
|
||||||
|
type NewResult;
|
||||||
|
|
||||||
|
/// Convert result type to have an update [`TxGraph`] that contains the [`ConfirmationTimeHeightAnchor`] .
|
||||||
|
fn try_into_confirmation_time_result(
|
||||||
|
self,
|
||||||
|
client: &impl ElectrumApi,
|
||||||
|
) -> Result<Self::NewResult, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K> ElectrumResultExt for FullScanResult<K, ConfirmationHeightAnchor> {
|
||||||
|
type NewResult = FullScanResult<K, ConfirmationTimeHeightAnchor>;
|
||||||
|
|
||||||
|
fn try_into_confirmation_time_result(
|
||||||
|
self,
|
||||||
|
client: &impl ElectrumApi,
|
||||||
|
) -> Result<Self::NewResult, Error> {
|
||||||
|
Ok(FullScanResult::<K, ConfirmationTimeHeightAnchor> {
|
||||||
|
graph_update: try_into_confirmation_time_result(self.graph_update, client)?,
|
||||||
|
chain_update: self.chain_update,
|
||||||
|
last_active_indices: self.last_active_indices,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElectrumResultExt for SyncResult<ConfirmationHeightAnchor> {
|
||||||
|
type NewResult = SyncResult<ConfirmationTimeHeightAnchor>;
|
||||||
|
|
||||||
|
fn try_into_confirmation_time_result(
|
||||||
|
self,
|
||||||
|
client: &impl ElectrumApi,
|
||||||
|
) -> Result<Self::NewResult, Error> {
|
||||||
|
Ok(SyncResult {
|
||||||
|
graph_update: try_into_confirmation_time_result(self.graph_update, client)?,
|
||||||
|
chain_update: self.chain_update,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_into_confirmation_time_result(
|
||||||
|
graph_update: TxGraph<ConfirmationHeightAnchor>,
|
||||||
|
client: &impl ElectrumApi,
|
||||||
|
) -> Result<TxGraph<ConfirmationTimeHeightAnchor>, Error> {
|
||||||
|
let relevant_heights = graph_update
|
||||||
|
.all_anchors()
|
||||||
|
.iter()
|
||||||
|
.map(|(a, _)| a.confirmation_height)
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
let height_to_time = relevant_heights
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.zip(
|
||||||
|
client
|
||||||
|
.batch_block_header(relevant_heights)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|bh| bh.time as u64),
|
||||||
|
)
|
||||||
|
.collect::<HashMap<u32, u64>>();
|
||||||
|
|
||||||
|
Ok(graph_update.map_anchors(|a| ConfirmationTimeHeightAnchor {
|
||||||
|
anchor_block: a.anchor_block,
|
||||||
|
confirmation_height: a.confirmation_height,
|
||||||
|
confirmation_time: height_to_time[&a.confirmation_height],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a [`CheckPoint`] of the latest tip, that connects with `prev_tip`.
|
/// Return a [`CheckPoint`] of the latest tip, that connects with `prev_tip`.
|
||||||
fn construct_update_tip(
|
fn construct_update_tip(
|
||||||
client: &impl ElectrumApi,
|
client: &impl ElectrumApi,
|
||||||
@ -323,6 +362,7 @@ fn determine_tx_anchor(
|
|||||||
fn populate_with_outpoints(
|
fn populate_with_outpoints(
|
||||||
client: &impl ElectrumApi,
|
client: &impl ElectrumApi,
|
||||||
cps: &BTreeMap<u32, CheckPoint>,
|
cps: &BTreeMap<u32, CheckPoint>,
|
||||||
|
tx_cache: &mut TxCache,
|
||||||
tx_graph: &mut TxGraph<ConfirmationHeightAnchor>,
|
tx_graph: &mut TxGraph<ConfirmationHeightAnchor>,
|
||||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
outpoints: impl IntoIterator<Item = OutPoint>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@ -358,9 +398,9 @@ fn populate_with_outpoints(
|
|||||||
let res_tx = match tx_graph.get_tx(res.tx_hash) {
|
let res_tx = match tx_graph.get_tx(res.tx_hash) {
|
||||||
Some(tx) => tx,
|
Some(tx) => tx,
|
||||||
None => {
|
None => {
|
||||||
let res_tx = client.transaction_get(&res.tx_hash)?;
|
let res_tx = fetch_tx(client, tx_cache, res.tx_hash)?;
|
||||||
let _ = tx_graph.insert_tx(res_tx);
|
let _ = tx_graph.insert_tx(Arc::clone(&res_tx));
|
||||||
tx_graph.get_tx(res.tx_hash).expect("just inserted")
|
res_tx
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
has_spending = res_tx
|
has_spending = res_tx
|
||||||
@ -383,11 +423,12 @@ fn populate_with_outpoints(
|
|||||||
fn populate_with_txids(
|
fn populate_with_txids(
|
||||||
client: &impl ElectrumApi,
|
client: &impl ElectrumApi,
|
||||||
cps: &BTreeMap<u32, CheckPoint>,
|
cps: &BTreeMap<u32, CheckPoint>,
|
||||||
tx_graph: &mut TxGraph<ConfirmationHeightAnchor>,
|
tx_cache: &mut TxCache,
|
||||||
|
graph_update: &mut TxGraph<ConfirmationHeightAnchor>,
|
||||||
txids: impl IntoIterator<Item = Txid>,
|
txids: impl IntoIterator<Item = Txid>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
for txid in txids {
|
for txid in txids {
|
||||||
let tx = match client.transaction_get(&txid) {
|
let tx = match fetch_tx(client, tx_cache, txid) {
|
||||||
Ok(tx) => tx,
|
Ok(tx) => tx,
|
||||||
Err(electrum_client::Error::Protocol(_)) => continue,
|
Err(electrum_client::Error::Protocol(_)) => continue,
|
||||||
Err(other_err) => return Err(other_err),
|
Err(other_err) => return Err(other_err),
|
||||||
@ -408,20 +449,36 @@ fn populate_with_txids(
|
|||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
if tx_graph.get_tx(txid).is_none() {
|
if graph_update.get_tx(txid).is_none() {
|
||||||
let _ = tx_graph.insert_tx(tx);
|
// TODO: We need to be able to insert an `Arc` of a transaction.
|
||||||
|
let _ = graph_update.insert_tx(tx);
|
||||||
}
|
}
|
||||||
if let Some(anchor) = anchor {
|
if let Some(anchor) = anchor {
|
||||||
let _ = tx_graph.insert_anchor(txid, anchor);
|
let _ = graph_update.insert_anchor(txid, anchor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fetch_tx<C: ElectrumApi>(
|
||||||
|
client: &C,
|
||||||
|
tx_cache: &mut TxCache,
|
||||||
|
txid: Txid,
|
||||||
|
) -> Result<Arc<Transaction>, Error> {
|
||||||
|
use bdk_chain::collections::hash_map::Entry;
|
||||||
|
Ok(match tx_cache.entry(txid) {
|
||||||
|
Entry::Occupied(entry) => entry.get().clone(),
|
||||||
|
Entry::Vacant(entry) => entry
|
||||||
|
.insert(Arc::new(client.transaction_get(&txid)?))
|
||||||
|
.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn populate_with_spks<I: Ord + Clone>(
|
fn populate_with_spks<I: Ord + Clone>(
|
||||||
client: &impl ElectrumApi,
|
client: &impl ElectrumApi,
|
||||||
cps: &BTreeMap<u32, CheckPoint>,
|
cps: &BTreeMap<u32, CheckPoint>,
|
||||||
tx_graph: &mut TxGraph<ConfirmationHeightAnchor>,
|
tx_cache: &mut TxCache,
|
||||||
|
graph_update: &mut TxGraph<ConfirmationHeightAnchor>,
|
||||||
spks: &mut impl Iterator<Item = (I, ScriptBuf)>,
|
spks: &mut impl Iterator<Item = (I, ScriptBuf)>,
|
||||||
stop_gap: usize,
|
stop_gap: usize,
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
@ -453,51 +510,12 @@ fn populate_with_spks<I: Ord + Clone>(
|
|||||||
unused_spk_count = 0;
|
unused_spk_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for tx in spk_history {
|
for tx_res in spk_history {
|
||||||
let mut update = TxGraph::<ConfirmationHeightAnchor>::default();
|
let _ = graph_update.insert_tx(fetch_tx(client, tx_cache, tx_res.tx_hash)?);
|
||||||
|
if let Some(anchor) = determine_tx_anchor(cps, tx_res.height, tx_res.tx_hash) {
|
||||||
if tx_graph.get_tx(tx.tx_hash).is_none() {
|
let _ = graph_update.insert_anchor(tx_res.tx_hash, anchor);
|
||||||
let full_tx = client.transaction_get(&tx.tx_hash)?;
|
|
||||||
update = TxGraph::<ConfirmationHeightAnchor>::new([full_tx]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(anchor) = determine_tx_anchor(cps, tx.height, tx.tx_hash) {
|
|
||||||
let _ = update.insert_anchor(tx.tx_hash, anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = tx_graph.apply_update(update);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_confirmation_time_tx_graph(
|
|
||||||
client: &impl ElectrumApi,
|
|
||||||
tx_graph: &TxGraph<ConfirmationHeightAnchor>,
|
|
||||||
) -> Result<TxGraph<ConfirmationTimeHeightAnchor>, Error> {
|
|
||||||
let relevant_heights = tx_graph
|
|
||||||
.all_anchors()
|
|
||||||
.iter()
|
|
||||||
.map(|(a, _)| a.confirmation_height)
|
|
||||||
.collect::<HashSet<_>>();
|
|
||||||
|
|
||||||
let height_to_time = relevant_heights
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.zip(
|
|
||||||
client
|
|
||||||
.batch_block_header(relevant_heights)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|bh| bh.time as u64),
|
|
||||||
)
|
|
||||||
.collect::<HashMap<u32, u64>>();
|
|
||||||
|
|
||||||
let new_graph = tx_graph
|
|
||||||
.clone()
|
|
||||||
.map_anchors(|a| ConfirmationTimeHeightAnchor {
|
|
||||||
anchor_block: a.anchor_block,
|
|
||||||
confirmation_height: a.confirmation_height,
|
|
||||||
confirmation_time: height_to_time[&a.confirmation_height],
|
|
||||||
});
|
|
||||||
Ok(new_graph)
|
|
||||||
}
|
|
||||||
|
@ -2,9 +2,10 @@ use bdk_chain::{
|
|||||||
bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, WScriptHash},
|
bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, WScriptHash},
|
||||||
keychain::Balance,
|
keychain::Balance,
|
||||||
local_chain::LocalChain,
|
local_chain::LocalChain,
|
||||||
|
spk_client::SyncRequest,
|
||||||
ConfirmationTimeHeightAnchor, IndexedTxGraph, SpkTxOutIndex,
|
ConfirmationTimeHeightAnchor, IndexedTxGraph, SpkTxOutIndex,
|
||||||
};
|
};
|
||||||
use bdk_electrum::{ElectrumExt, ElectrumUpdate};
|
use bdk_electrum::{ElectrumExt, ElectrumResultExt};
|
||||||
use bdk_testenv::{anyhow, anyhow::Result, bitcoincore_rpc::RpcApi, TestEnv};
|
use bdk_testenv::{anyhow, anyhow::Result, bitcoincore_rpc::RpcApi, TestEnv};
|
||||||
|
|
||||||
fn get_balance(
|
fn get_balance(
|
||||||
@ -60,22 +61,18 @@ fn scan_detects_confirmed_tx() -> Result<()> {
|
|||||||
|
|
||||||
// Sync up to tip.
|
// Sync up to tip.
|
||||||
env.wait_until_electrum_sees_block()?;
|
env.wait_until_electrum_sees_block()?;
|
||||||
let ElectrumUpdate {
|
let update = client
|
||||||
chain_update,
|
.sync(
|
||||||
graph_update,
|
SyncRequest::from_chain_tip(recv_chain.tip())
|
||||||
} = client.sync::<ConfirmationTimeHeightAnchor>(
|
.chain_spks(core::iter::once(spk_to_track)),
|
||||||
recv_chain.tip(),
|
5,
|
||||||
[spk_to_track],
|
)?
|
||||||
Some(recv_graph.graph()),
|
.try_into_confirmation_time_result(&client)?;
|
||||||
None,
|
|
||||||
None,
|
|
||||||
5,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let _ = recv_chain
|
let _ = recv_chain
|
||||||
.apply_update(chain_update)
|
.apply_update(update.chain_update)
|
||||||
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
|
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
|
||||||
let _ = recv_graph.apply_update(graph_update);
|
let _ = recv_graph.apply_update(update.graph_update);
|
||||||
|
|
||||||
// Check to see if tx is confirmed.
|
// Check to see if tx is confirmed.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -131,25 +128,20 @@ fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
|
|||||||
|
|
||||||
// Sync up to tip.
|
// Sync up to tip.
|
||||||
env.wait_until_electrum_sees_block()?;
|
env.wait_until_electrum_sees_block()?;
|
||||||
let ElectrumUpdate {
|
let update = client
|
||||||
chain_update,
|
.sync(
|
||||||
graph_update,
|
SyncRequest::from_chain_tip(recv_chain.tip()).chain_spks([spk_to_track.clone()]),
|
||||||
} = client.sync::<ConfirmationTimeHeightAnchor>(
|
5,
|
||||||
recv_chain.tip(),
|
)?
|
||||||
[spk_to_track.clone()],
|
.try_into_confirmation_time_result(&client)?;
|
||||||
Some(recv_graph.graph()),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
5,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let _ = recv_chain
|
let _ = recv_chain
|
||||||
.apply_update(chain_update)
|
.apply_update(update.chain_update)
|
||||||
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
|
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
|
||||||
let _ = recv_graph.apply_update(graph_update.clone());
|
let _ = recv_graph.apply_update(update.graph_update.clone());
|
||||||
|
|
||||||
// Retain a snapshot of all anchors before reorg process.
|
// Retain a snapshot of all anchors before reorg process.
|
||||||
let initial_anchors = graph_update.all_anchors();
|
let initial_anchors = update.graph_update.all_anchors();
|
||||||
|
|
||||||
// Check if initial balance is correct.
|
// Check if initial balance is correct.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -166,27 +158,22 @@ fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
|
|||||||
env.reorg_empty_blocks(depth)?;
|
env.reorg_empty_blocks(depth)?;
|
||||||
|
|
||||||
env.wait_until_electrum_sees_block()?;
|
env.wait_until_electrum_sees_block()?;
|
||||||
let ElectrumUpdate {
|
let update = client
|
||||||
chain_update,
|
.sync(
|
||||||
graph_update,
|
SyncRequest::from_chain_tip(recv_chain.tip()).chain_spks([spk_to_track.clone()]),
|
||||||
} = client.sync::<ConfirmationTimeHeightAnchor>(
|
5,
|
||||||
recv_chain.tip(),
|
)?
|
||||||
[spk_to_track.clone()],
|
.try_into_confirmation_time_result(&client)?;
|
||||||
Some(recv_graph.graph()),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
5,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let _ = recv_chain
|
let _ = recv_chain
|
||||||
.apply_update(chain_update)
|
.apply_update(update.chain_update)
|
||||||
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
|
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
|
||||||
|
|
||||||
// Check to see if a new anchor is added during current reorg.
|
// Check to see if a new anchor is added during current reorg.
|
||||||
if !initial_anchors.is_superset(graph_update.all_anchors()) {
|
if !initial_anchors.is_superset(update.graph_update.all_anchors()) {
|
||||||
println!("New anchor added at reorg depth {}", depth);
|
println!("New anchor added at reorg depth {}", depth);
|
||||||
}
|
}
|
||||||
let _ = recv_graph.apply_update(graph_update);
|
let _ = recv_graph.apply_update(update.graph_update);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&recv_chain, &recv_graph)?,
|
get_balance(&recv_chain, &recv_graph)?,
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{constants::genesis_block, Address, Network, OutPoint, Txid},
|
bitcoin::{constants::genesis_block, Address, Network, Txid},
|
||||||
|
collections::BTreeSet,
|
||||||
indexed_tx_graph::{self, IndexedTxGraph},
|
indexed_tx_graph::{self, IndexedTxGraph},
|
||||||
keychain,
|
keychain,
|
||||||
local_chain::{self, LocalChain},
|
local_chain::{self, LocalChain},
|
||||||
|
spk_client::{FullScanRequest, SyncRequest},
|
||||||
Append, ConfirmationHeightAnchor,
|
Append, ConfirmationHeightAnchor,
|
||||||
};
|
};
|
||||||
use bdk_electrum::{
|
use bdk_electrum::{
|
||||||
electrum_client::{self, Client, ElectrumApi},
|
electrum_client::{self, Client, ElectrumApi},
|
||||||
ElectrumExt, ElectrumUpdate,
|
ElectrumExt,
|
||||||
};
|
};
|
||||||
use example_cli::{
|
use example_cli::{
|
||||||
anyhow::{self, Context},
|
anyhow::{self, Context},
|
||||||
@ -147,48 +148,55 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let client = electrum_cmd.electrum_args().client(args.network)?;
|
let client = electrum_cmd.electrum_args().client(args.network)?;
|
||||||
|
|
||||||
let response = match electrum_cmd.clone() {
|
let (chain_update, mut graph_update, keychain_update) = match electrum_cmd.clone() {
|
||||||
ElectrumCommands::Scan {
|
ElectrumCommands::Scan {
|
||||||
stop_gap,
|
stop_gap,
|
||||||
scan_options,
|
scan_options,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let (keychain_spks, tip) = {
|
let request = {
|
||||||
let graph = &*graph.lock().unwrap();
|
let graph = &*graph.lock().unwrap();
|
||||||
let chain = &*chain.lock().unwrap();
|
let chain = &*chain.lock().unwrap();
|
||||||
|
|
||||||
let keychain_spks = graph
|
FullScanRequest::from_chain_tip(chain.tip())
|
||||||
.index
|
.cache_graph_txs(graph.graph())
|
||||||
.all_unbounded_spk_iters()
|
.set_spks_for_keychain(
|
||||||
.into_iter()
|
Keychain::External,
|
||||||
.map(|(keychain, iter)| {
|
graph
|
||||||
let mut first = true;
|
.index
|
||||||
let spk_iter = iter.inspect(move |(i, _)| {
|
.unbounded_spk_iter(&Keychain::External)
|
||||||
if first {
|
.into_iter()
|
||||||
eprint!("\nscanning {}: ", keychain);
|
.flatten(),
|
||||||
first = false;
|
)
|
||||||
|
.set_spks_for_keychain(
|
||||||
|
Keychain::Internal,
|
||||||
|
graph
|
||||||
|
.index
|
||||||
|
.unbounded_spk_iter(&Keychain::Internal)
|
||||||
|
.into_iter()
|
||||||
|
.flatten(),
|
||||||
|
)
|
||||||
|
.inspect_spks_for_all_keychains({
|
||||||
|
let mut once = BTreeSet::new();
|
||||||
|
move |k, spk_i, _| {
|
||||||
|
if once.insert(k) {
|
||||||
|
eprint!("\nScanning {}: ", k);
|
||||||
|
} else {
|
||||||
|
eprint!("{} ", spk_i);
|
||||||
}
|
}
|
||||||
|
|
||||||
eprint!("{} ", i);
|
|
||||||
let _ = io::stdout().flush();
|
let _ = io::stdout().flush();
|
||||||
});
|
}
|
||||||
(keychain, spk_iter)
|
|
||||||
})
|
})
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
|
|
||||||
let tip = chain.tip();
|
|
||||||
(keychain_spks, tip)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
client
|
let res = client
|
||||||
.full_scan::<_, ConfirmationHeightAnchor>(
|
.full_scan::<_>(request, stop_gap, scan_options.batch_size)
|
||||||
tip,
|
.context("scanning the blockchain")?;
|
||||||
keychain_spks,
|
(
|
||||||
Some(graph.lock().unwrap().graph()),
|
res.chain_update,
|
||||||
stop_gap,
|
res.graph_update,
|
||||||
scan_options.batch_size,
|
Some(res.last_active_indices),
|
||||||
)
|
)
|
||||||
.context("scanning the blockchain")?
|
|
||||||
}
|
}
|
||||||
ElectrumCommands::Sync {
|
ElectrumCommands::Sync {
|
||||||
mut unused_spks,
|
mut unused_spks,
|
||||||
@ -201,7 +209,6 @@ fn main() -> anyhow::Result<()> {
|
|||||||
// Get a short lock on the tracker to get the spks we're interested in
|
// Get a short lock on the tracker to get the spks we're interested in
|
||||||
let graph = graph.lock().unwrap();
|
let graph = graph.lock().unwrap();
|
||||||
let chain = chain.lock().unwrap();
|
let chain = chain.lock().unwrap();
|
||||||
let chain_tip = chain.tip().block_id();
|
|
||||||
|
|
||||||
if !(all_spks || unused_spks || utxos || unconfirmed) {
|
if !(all_spks || unused_spks || utxos || unconfirmed) {
|
||||||
unused_spks = true;
|
unused_spks = true;
|
||||||
@ -211,18 +218,20 @@ fn main() -> anyhow::Result<()> {
|
|||||||
unused_spks = false;
|
unused_spks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut spks: Box<dyn Iterator<Item = bdk_chain::bitcoin::ScriptBuf>> =
|
let chain_tip = chain.tip();
|
||||||
Box::new(core::iter::empty());
|
let mut request =
|
||||||
|
SyncRequest::from_chain_tip(chain_tip.clone()).cache_graph_txs(graph.graph());
|
||||||
|
|
||||||
if all_spks {
|
if all_spks {
|
||||||
let all_spks = graph
|
let all_spks = graph
|
||||||
.index
|
.index
|
||||||
.revealed_spks(..)
|
.revealed_spks(..)
|
||||||
.map(|(k, i, spk)| (k.to_owned(), i, spk.to_owned()))
|
.map(|(k, i, spk)| (k.to_owned(), i, spk.to_owned()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| {
|
request = request.chain_spks(all_spks.into_iter().map(|(k, spk_i, spk)| {
|
||||||
eprintln!("scanning {}:{}", k, i);
|
eprintln!("scanning {}: {}", k, spk_i);
|
||||||
spk
|
spk
|
||||||
})));
|
}));
|
||||||
}
|
}
|
||||||
if unused_spks {
|
if unused_spks {
|
||||||
let unused_spks = graph
|
let unused_spks = graph
|
||||||
@ -230,82 +239,61 @@ fn main() -> anyhow::Result<()> {
|
|||||||
.unused_spks()
|
.unused_spks()
|
||||||
.map(|(k, i, spk)| (k, i, spk.to_owned()))
|
.map(|(k, i, spk)| (k, i, spk.to_owned()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
spks = Box::new(spks.chain(unused_spks.into_iter().map(|(k, i, spk)| {
|
request =
|
||||||
eprintln!(
|
request.chain_spks(unused_spks.into_iter().map(move |(k, spk_i, spk)| {
|
||||||
"Checking if address {} {}:{} has been used",
|
eprintln!(
|
||||||
Address::from_script(&spk, args.network).unwrap(),
|
"Checking if address {} {}:{} has been used",
|
||||||
k,
|
Address::from_script(&spk, args.network).unwrap(),
|
||||||
i,
|
k,
|
||||||
);
|
spk_i,
|
||||||
spk
|
);
|
||||||
})));
|
spk
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut outpoints: Box<dyn Iterator<Item = OutPoint>> = Box::new(core::iter::empty());
|
|
||||||
|
|
||||||
if utxos {
|
if utxos {
|
||||||
let init_outpoints = graph.index.outpoints();
|
let init_outpoints = graph.index.outpoints();
|
||||||
|
|
||||||
let utxos = graph
|
let utxos = graph
|
||||||
.graph()
|
.graph()
|
||||||
.filter_chain_unspents(&*chain, chain_tip, init_outpoints)
|
.filter_chain_unspents(&*chain, chain_tip.block_id(), init_outpoints)
|
||||||
.map(|(_, utxo)| utxo)
|
.map(|(_, utxo)| utxo)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
request = request.chain_outpoints(utxos.into_iter().map(|utxo| {
|
||||||
outpoints = Box::new(
|
eprintln!(
|
||||||
utxos
|
"Checking if outpoint {} (value: {}) has been spent",
|
||||||
.into_iter()
|
utxo.outpoint, utxo.txout.value
|
||||||
.inspect(|utxo| {
|
);
|
||||||
eprintln!(
|
utxo.outpoint
|
||||||
"Checking if outpoint {} (value: {}) has been spent",
|
}));
|
||||||
utxo.outpoint, utxo.txout.value
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.map(|utxo| utxo.outpoint),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut txids: Box<dyn Iterator<Item = Txid>> = Box::new(core::iter::empty());
|
|
||||||
|
|
||||||
if unconfirmed {
|
if unconfirmed {
|
||||||
let unconfirmed_txids = graph
|
let unconfirmed_txids = graph
|
||||||
.graph()
|
.graph()
|
||||||
.list_chain_txs(&*chain, chain_tip)
|
.list_chain_txs(&*chain, chain_tip.block_id())
|
||||||
.filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed())
|
.filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed())
|
||||||
.map(|canonical_tx| canonical_tx.tx_node.txid)
|
.map(|canonical_tx| canonical_tx.tx_node.txid)
|
||||||
.collect::<Vec<Txid>>();
|
.collect::<Vec<Txid>>();
|
||||||
|
|
||||||
txids = Box::new(unconfirmed_txids.into_iter().inspect(|txid| {
|
request = request.chain_txids(
|
||||||
eprintln!("Checking if {} is confirmed yet", txid);
|
unconfirmed_txids
|
||||||
}));
|
.into_iter()
|
||||||
|
.inspect(|txid| eprintln!("Checking if {} is confirmed yet", txid)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let electrum_update = client
|
let res = client
|
||||||
.sync::<ConfirmationHeightAnchor>(
|
.sync(request, scan_options.batch_size)
|
||||||
chain.tip(),
|
|
||||||
spks,
|
|
||||||
Some(graph.graph()),
|
|
||||||
txids,
|
|
||||||
outpoints,
|
|
||||||
scan_options.batch_size,
|
|
||||||
)
|
|
||||||
.context("scanning the blockchain")?;
|
.context("scanning the blockchain")?;
|
||||||
|
|
||||||
// drop lock on graph and chain
|
// drop lock on graph and chain
|
||||||
drop((graph, chain));
|
drop((graph, chain));
|
||||||
|
|
||||||
(electrum_update, BTreeMap::new())
|
(res.chain_update, res.graph_update, None)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (
|
|
||||||
ElectrumUpdate {
|
|
||||||
chain_update,
|
|
||||||
mut graph_update,
|
|
||||||
},
|
|
||||||
keychain_update,
|
|
||||||
) = response;
|
|
||||||
|
|
||||||
let now = std::time::UNIX_EPOCH
|
let now = std::time::UNIX_EPOCH
|
||||||
.elapsed()
|
.elapsed()
|
||||||
.expect("must get time")
|
.expect("must get time")
|
||||||
@ -316,26 +304,17 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let mut chain = chain.lock().unwrap();
|
let mut chain = chain.lock().unwrap();
|
||||||
let mut graph = graph.lock().unwrap();
|
let mut graph = graph.lock().unwrap();
|
||||||
|
|
||||||
let chain = chain.apply_update(chain_update)?;
|
let chain_changeset = chain.apply_update(chain_update)?;
|
||||||
|
|
||||||
let indexed_tx_graph = {
|
let mut indexed_tx_graph_changeset =
|
||||||
let mut changeset =
|
indexed_tx_graph::ChangeSet::<ConfirmationHeightAnchor, _>::default();
|
||||||
indexed_tx_graph::ChangeSet::<ConfirmationHeightAnchor, _>::default();
|
if let Some(keychain_update) = keychain_update {
|
||||||
let (_, indexer) = graph.index.reveal_to_target_multi(&keychain_update);
|
let (_, keychain_changeset) = graph.index.reveal_to_target_multi(&keychain_update);
|
||||||
changeset.append(indexed_tx_graph::ChangeSet {
|
indexed_tx_graph_changeset.append(keychain_changeset.into());
|
||||||
indexer,
|
}
|
||||||
..Default::default()
|
indexed_tx_graph_changeset.append(graph.apply_update(graph_update));
|
||||||
});
|
|
||||||
changeset.append(graph.apply_update(graph_update.map_anchors(|a| {
|
|
||||||
ConfirmationHeightAnchor {
|
|
||||||
anchor_block: a.anchor_block,
|
|
||||||
confirmation_height: a.confirmation_height,
|
|
||||||
}
|
|
||||||
})));
|
|
||||||
changeset
|
|
||||||
};
|
|
||||||
|
|
||||||
(chain, indexed_tx_graph)
|
(chain_changeset, indexed_tx_graph_changeset)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut db = db.lock().unwrap();
|
let mut db = db.lock().unwrap();
|
||||||
|
@ -3,17 +3,16 @@ const SEND_AMOUNT: Amount = Amount::from_sat(5000);
|
|||||||
const STOP_GAP: usize = 50;
|
const STOP_GAP: usize = 50;
|
||||||
const BATCH_SIZE: usize = 5;
|
const BATCH_SIZE: usize = 5;
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use bdk::bitcoin::{Address, Amount};
|
use bdk::bitcoin::{Address, Amount};
|
||||||
use bdk::chain::ConfirmationTimeHeightAnchor;
|
use bdk::chain::collections::HashSet;
|
||||||
use bdk::wallet::Update;
|
|
||||||
use bdk::{bitcoin::Network, Wallet};
|
use bdk::{bitcoin::Network, Wallet};
|
||||||
use bdk::{KeychainKind, SignOptions};
|
use bdk::{KeychainKind, SignOptions};
|
||||||
|
use bdk_electrum::ElectrumResultExt;
|
||||||
use bdk_electrum::{
|
use bdk_electrum::{
|
||||||
electrum_client::{self, ElectrumApi},
|
electrum_client::{self, ElectrumApi},
|
||||||
ElectrumExt, ElectrumUpdate,
|
ElectrumExt,
|
||||||
};
|
};
|
||||||
use bdk_file_store::Store;
|
use bdk_file_store::Store;
|
||||||
|
|
||||||
@ -39,48 +38,24 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
print!("Syncing...");
|
print!("Syncing...");
|
||||||
let client = electrum_client::Client::new("ssl://electrum.blockstream.info:60002")?;
|
let client = electrum_client::Client::new("ssl://electrum.blockstream.info:60002")?;
|
||||||
|
|
||||||
let prev_tip = wallet.latest_checkpoint();
|
let request = wallet.start_full_scan().inspect_spks_for_all_keychains({
|
||||||
let keychain_spks = wallet
|
let mut once = HashSet::<KeychainKind>::new();
|
||||||
.all_unbounded_spk_iters()
|
move |k, spk_i, _| match once.insert(k) {
|
||||||
.into_iter()
|
true => print!("\nScanning keychain [{:?}]", k),
|
||||||
.map(|(k, k_spks)| {
|
false => print!(" {:<3}", spk_i),
|
||||||
let mut once = Some(());
|
}
|
||||||
let mut stdout = std::io::stdout();
|
});
|
||||||
let k_spks = k_spks
|
|
||||||
.inspect(move |(spk_i, _)| match once.take() {
|
|
||||||
Some(_) => print!("\nScanning keychain [{:?}]", k),
|
|
||||||
None => print!(" {:<3}", spk_i),
|
|
||||||
})
|
|
||||||
.inspect(move |_| stdout.flush().expect("must flush"));
|
|
||||||
(k, k_spks)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let (
|
let mut update = client
|
||||||
ElectrumUpdate {
|
.full_scan(request, STOP_GAP, BATCH_SIZE)?
|
||||||
chain_update,
|
.try_into_confirmation_time_result(&client)?;
|
||||||
mut graph_update,
|
|
||||||
},
|
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||||
keychain_update,
|
let _ = update.graph_update.update_last_seen_unconfirmed(now);
|
||||||
) = client.full_scan::<_, ConfirmationTimeHeightAnchor>(
|
|
||||||
prev_tip,
|
|
||||||
keychain_spks,
|
|
||||||
Some(wallet.as_ref()),
|
|
||||||
STOP_GAP,
|
|
||||||
BATCH_SIZE,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
wallet.apply_update(update)?;
|
||||||
let _ = graph_update.update_last_seen_unconfirmed(now);
|
|
||||||
|
|
||||||
let wallet_update = Update {
|
|
||||||
last_active_indices: keychain_update,
|
|
||||||
graph: graph_update,
|
|
||||||
chain: Some(chain_update),
|
|
||||||
};
|
|
||||||
wallet.apply_update(wallet_update)?;
|
|
||||||
wallet.commit()?;
|
wallet.commit()?;
|
||||||
|
|
||||||
let balance = wallet.get_balance();
|
let balance = wallet.get_balance();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user