Merge bitcoindevkit/bdk#1084: Enhance bdk chain structures
1ff806c67f4da9ba58b7c7689fde0fe41a34a6f5 fix(chain)!: rm weird `From` impl (志宇) d43ae0231fa4670b98780cad84466c14ae087292 refactor: improve docs, cleanup unnecessary types and improve code (Vladimir Fomene) 41042069809e3eeb4a8cc8a5a8db1af2c57c4a11 feat: impl Append for lots of tuples (LLFourn) c56728ff1315e0deaf256af07fd1ff5e18fced8a refactor: Remove `scan` and `scan_txout` from SpkTxoutIndex and KeychainTxoutIndex (Vladimir Fomene) 32c40ac939bc514ac7f1d3f1d7cd1080011e20ba feat(electrum)!: change signature of `ElectrumExt` (志宇) a28748c33976312b9e6671636ab7e305323efb03 refactor: Implement Default for WalletUpdate (Vladimir Fomene) f42f8b8ff19c2e67888b476487e4e5c9edb0d0ff refactor: Allow for no chain update (Vladimir Fomene) 68572bfd2e32efdeefaa46618e8e248d3a87f143 refactor: move WalletChangeset to wallet module (Vladimir Fomene) 2392e50fd9793902d480556caa4ec225085c82d6 refactor: Move WalletUpdate to wallet module (Vladimir Fomene) 7c12dc994242bf2d7e35c2723f6e7000de97a388 refactor: Remove ForEachTxout trait (Vladimir Fomene) 6bcbb93233824ec391689191b8ca1f5459cec930 refactor: Edit ElectrumExt not to use WalletUpdate (Vladimir Fomene) Pull request description: ### Description Fixes #1061 ### Changelog notice - Move WalletUpdate to the wallet module - Remove ForEachTxout trait completely - Refactor ElectrumExt to not use WalletUpdate. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing ACKs for top commit: evanlinjin: ACK 1ff806c67f4da9ba58b7c7689fde0fe41a34a6f5 Tree-SHA512: 05349713af9d2efa14a522ceaabb7513bb437d786adf2f93055765589a67e4eb68bda36ff415aeba07816c4d30988d4d55bac018e7697019270a219105ed65a2
This commit is contained in:
commit
c20a4da9fc
@ -22,7 +22,7 @@ use alloc::{
|
|||||||
pub use bdk_chain::keychain::Balance;
|
pub use bdk_chain::keychain::Balance;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
indexed_tx_graph,
|
indexed_tx_graph,
|
||||||
keychain::{KeychainTxOutIndex, WalletChangeSet, WalletUpdate},
|
keychain::{self, KeychainTxOutIndex},
|
||||||
local_chain::{self, CannotConnectError, CheckPoint, CheckPointIter, LocalChain},
|
local_chain::{self, CannotConnectError, CheckPoint, CheckPointIter, LocalChain},
|
||||||
tx_graph::{CanonicalTx, TxGraph},
|
tx_graph::{CanonicalTx, TxGraph},
|
||||||
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut,
|
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut,
|
||||||
@ -95,11 +95,74 @@ pub struct Wallet<D = ()> {
|
|||||||
secp: SecpCtx,
|
secp: SecpCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources.
|
/// An update to [`Wallet`].
|
||||||
pub type Update = WalletUpdate<KeychainKind, ConfirmationTimeAnchor>;
|
///
|
||||||
|
/// It updates [`bdk_chain::keychain::KeychainTxOutIndex`], [`bdk_chain::TxGraph`] and [`local_chain::LocalChain`] atomically.
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Update {
|
||||||
|
/// Contains the last active derivation indices per keychain (`K`), which is used to update the
|
||||||
|
/// [`KeychainTxOutIndex`].
|
||||||
|
pub last_active_indices: BTreeMap<KeychainKind, u32>,
|
||||||
|
|
||||||
/// The changeset produced internally by [`Wallet`] when mutated.
|
/// Update for the wallet's internal [`TxGraph`].
|
||||||
pub type ChangeSet = WalletChangeSet<KeychainKind, ConfirmationTimeAnchor>;
|
pub graph: TxGraph<ConfirmationTimeAnchor>,
|
||||||
|
|
||||||
|
/// Update for the wallet's internal [`LocalChain`].
|
||||||
|
///
|
||||||
|
/// [`LocalChain`]: local_chain::LocalChain
|
||||||
|
pub chain: Option<local_chain::Update>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The changes made to a wallet by applying an [`Update`].
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
|
||||||
|
pub struct ChangeSet {
|
||||||
|
/// Changes to the [`LocalChain`].
|
||||||
|
///
|
||||||
|
/// [`LocalChain`]: local_chain::LocalChain
|
||||||
|
pub chain: local_chain::ChangeSet,
|
||||||
|
|
||||||
|
/// Changes to [`IndexedTxGraph`].
|
||||||
|
///
|
||||||
|
/// [`IndexedTxGraph`]: bdk_chain::indexed_tx_graph::IndexedTxGraph
|
||||||
|
pub indexed_tx_graph:
|
||||||
|
indexed_tx_graph::ChangeSet<ConfirmationTimeAnchor, keychain::ChangeSet<KeychainKind>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Append for ChangeSet {
|
||||||
|
fn append(&mut self, other: Self) {
|
||||||
|
Append::append(&mut self.chain, other.chain);
|
||||||
|
Append::append(&mut self.indexed_tx_graph, other.indexed_tx_graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.chain.is_empty() && self.indexed_tx_graph.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<local_chain::ChangeSet> for ChangeSet {
|
||||||
|
fn from(chain: local_chain::ChangeSet) -> Self {
|
||||||
|
Self {
|
||||||
|
chain,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<indexed_tx_graph::ChangeSet<ConfirmationTimeAnchor, keychain::ChangeSet<KeychainKind>>>
|
||||||
|
for ChangeSet
|
||||||
|
{
|
||||||
|
fn from(
|
||||||
|
indexed_tx_graph: indexed_tx_graph::ChangeSet<
|
||||||
|
ConfirmationTimeAnchor,
|
||||||
|
keychain::ChangeSet<KeychainKind>,
|
||||||
|
>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
indexed_tx_graph,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The address index selection strategy to use to derived an address from the wallet's external
|
/// The address index selection strategy to use to derived an address from the wallet's external
|
||||||
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
|
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
|
||||||
@ -1857,7 +1920,11 @@ impl<D> Wallet<D> {
|
|||||||
where
|
where
|
||||||
D: PersistBackend<ChangeSet>,
|
D: PersistBackend<ChangeSet>,
|
||||||
{
|
{
|
||||||
let mut changeset = ChangeSet::from(self.chain.apply_update(update.chain)?);
|
let mut changeset = match update.chain {
|
||||||
|
Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?),
|
||||||
|
None => ChangeSet::default(),
|
||||||
|
};
|
||||||
|
|
||||||
let (_, index_changeset) = self
|
let (_, index_changeset) = self
|
||||||
.indexed_graph
|
.indexed_graph
|
||||||
.index
|
.index
|
||||||
|
@ -225,7 +225,10 @@ impl<A, K> From<keychain::ChangeSet<K>> for ChangeSet<A, keychain::ChangeSet<K>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a structure that can index transaction data.
|
/// Utilities for indexing transaction data.
|
||||||
|
///
|
||||||
|
/// Types which implement this trait can be used to construct an [`IndexedTxGraph`].
|
||||||
|
/// This trait's methods should rarely be called directly.
|
||||||
pub trait Indexer {
|
pub trait Indexer {
|
||||||
/// The resultant "changeset" when new transaction data is indexed.
|
/// The resultant "changeset" when new transaction data is indexed.
|
||||||
type ChangeSet;
|
type ChangeSet;
|
||||||
@ -233,7 +236,7 @@ pub trait Indexer {
|
|||||||
/// Scan and index the given `outpoint` and `txout`.
|
/// Scan and index the given `outpoint` and `txout`.
|
||||||
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet;
|
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet;
|
||||||
|
|
||||||
/// Scan and index the given transaction.
|
/// Scans a transaction for relevant outpoints, which are stored and indexed internally.
|
||||||
fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet;
|
fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet;
|
||||||
|
|
||||||
/// Apply changeset to itself.
|
/// Apply changeset to itself.
|
||||||
|
@ -10,9 +10,7 @@
|
|||||||
//!
|
//!
|
||||||
//! [`SpkTxOutIndex`]: crate::SpkTxOutIndex
|
//! [`SpkTxOutIndex`]: crate::SpkTxOutIndex
|
||||||
|
|
||||||
use crate::{
|
use crate::{collections::BTreeMap, Append};
|
||||||
collections::BTreeMap, indexed_tx_graph, local_chain, tx_graph::TxGraph, Anchor, Append,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
#[cfg(feature = "miniscript")]
|
||||||
mod txout_index;
|
mod txout_index;
|
||||||
@ -82,98 +80,6 @@ impl<K> AsRef<BTreeMap<K, u32>> for ChangeSet<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structure to update [`KeychainTxOutIndex`], [`TxGraph`] and [`LocalChain`] atomically.
|
|
||||||
///
|
|
||||||
/// [`LocalChain`]: local_chain::LocalChain
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct WalletUpdate<K, A> {
|
|
||||||
/// Contains the last active derivation indices per keychain (`K`), which is used to update the
|
|
||||||
/// [`KeychainTxOutIndex`].
|
|
||||||
pub last_active_indices: BTreeMap<K, u32>,
|
|
||||||
|
|
||||||
/// Update for the [`TxGraph`].
|
|
||||||
pub graph: TxGraph<A>,
|
|
||||||
|
|
||||||
/// Update for the [`LocalChain`].
|
|
||||||
///
|
|
||||||
/// [`LocalChain`]: local_chain::LocalChain
|
|
||||||
pub chain: local_chain::Update,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, A> WalletUpdate<K, A> {
|
|
||||||
/// Construct a [`WalletUpdate`] with a given [`local_chain::Update`].
|
|
||||||
pub fn new(chain_update: local_chain::Update) -> Self {
|
|
||||||
Self {
|
|
||||||
last_active_indices: BTreeMap::new(),
|
|
||||||
graph: TxGraph::default(),
|
|
||||||
chain: chain_update,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A structure that records the corresponding changes as result of applying an [`WalletUpdate`].
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "serde",
|
|
||||||
derive(serde::Deserialize, serde::Serialize),
|
|
||||||
serde(
|
|
||||||
crate = "serde_crate",
|
|
||||||
bound(
|
|
||||||
deserialize = "K: Ord + serde::Deserialize<'de>, A: Ord + serde::Deserialize<'de>",
|
|
||||||
serialize = "K: Ord + serde::Serialize, A: Ord + serde::Serialize",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
pub struct WalletChangeSet<K, A> {
|
|
||||||
/// Changes to the [`LocalChain`].
|
|
||||||
///
|
|
||||||
/// [`LocalChain`]: local_chain::LocalChain
|
|
||||||
pub chain: local_chain::ChangeSet,
|
|
||||||
|
|
||||||
/// ChangeSet to [`IndexedTxGraph`].
|
|
||||||
///
|
|
||||||
/// [`IndexedTxGraph`]: crate::indexed_tx_graph::IndexedTxGraph
|
|
||||||
pub indexed_tx_graph: indexed_tx_graph::ChangeSet<A, ChangeSet<K>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, A> Default for WalletChangeSet<K, A> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
chain: Default::default(),
|
|
||||||
indexed_tx_graph: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, A: Anchor> Append for WalletChangeSet<K, A> {
|
|
||||||
fn append(&mut self, other: Self) {
|
|
||||||
Append::append(&mut self.chain, other.chain);
|
|
||||||
Append::append(&mut self.indexed_tx_graph, other.indexed_tx_graph);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.chain.is_empty() && self.indexed_tx_graph.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, A> From<local_chain::ChangeSet> for WalletChangeSet<K, A> {
|
|
||||||
fn from(chain: local_chain::ChangeSet) -> Self {
|
|
||||||
Self {
|
|
||||||
chain,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, A> From<indexed_tx_graph::ChangeSet<A, ChangeSet<K>>> for WalletChangeSet<K, A> {
|
|
||||||
fn from(indexed_tx_graph: indexed_tx_graph::ChangeSet<A, ChangeSet<K>>) -> Self {
|
|
||||||
Self {
|
|
||||||
indexed_tx_graph,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Balance, differentiated into various categories.
|
/// Balance, differentiated into various categories.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
@ -3,7 +3,7 @@ use crate::{
|
|||||||
indexed_tx_graph::Indexer,
|
indexed_tx_graph::Indexer,
|
||||||
miniscript::{Descriptor, DescriptorPublicKey},
|
miniscript::{Descriptor, DescriptorPublicKey},
|
||||||
spk_iter::BIP32_MAX_INDEX,
|
spk_iter::BIP32_MAX_INDEX,
|
||||||
ForEachTxOut, SpkIterator, SpkTxOutIndex,
|
SpkIterator, SpkTxOutIndex,
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bitcoin::{OutPoint, Script, TxOut};
|
use bitcoin::{OutPoint, Script, TxOut};
|
||||||
@ -91,11 +91,18 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
|
|||||||
type ChangeSet = super::ChangeSet<K>;
|
type ChangeSet = super::ChangeSet<K>;
|
||||||
|
|
||||||
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
|
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
|
||||||
self.scan_txout(outpoint, txout)
|
match self.inner.scan_txout(outpoint, txout).cloned() {
|
||||||
|
Some((keychain, index)) => self.reveal_to_target(&keychain, index).1,
|
||||||
|
None => super::ChangeSet::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet {
|
fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet {
|
||||||
self.scan(tx)
|
let mut changeset = super::ChangeSet::<K>::default();
|
||||||
|
for (op, txout) in tx.output.iter().enumerate() {
|
||||||
|
changeset.append(self.index_txout(OutPoint::new(tx.txid(), op as u32), txout));
|
||||||
|
}
|
||||||
|
changeset
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initial_changeset(&self) -> Self::ChangeSet {
|
fn initial_changeset(&self) -> Self::ChangeSet {
|
||||||
@ -112,38 +119,6 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
||||||
/// Scans an object for relevant outpoints, which are stored and indexed internally.
|
|
||||||
///
|
|
||||||
/// If the matched script pubkey is part of the lookahead, the last stored index is updated for
|
|
||||||
/// the script pubkey's keychain and the [`super::ChangeSet`] returned will reflect the
|
|
||||||
/// change.
|
|
||||||
///
|
|
||||||
/// Typically, this method is used in two situations:
|
|
||||||
///
|
|
||||||
/// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all
|
|
||||||
/// your txouts.
|
|
||||||
/// 2. When getting new data from the chain, you usually scan it before incorporating it into
|
|
||||||
/// your chain state (i.e., `SparseChain`, `ChainGraph`).
|
|
||||||
///
|
|
||||||
/// See [`ForEachTxout`] for the types that support this.
|
|
||||||
///
|
|
||||||
/// [`ForEachTxout`]: crate::ForEachTxOut
|
|
||||||
pub fn scan(&mut self, txouts: &impl ForEachTxOut) -> super::ChangeSet<K> {
|
|
||||||
let mut changeset = super::ChangeSet::<K>::default();
|
|
||||||
txouts.for_each_txout(|(op, txout)| changeset.append(self.scan_txout(op, txout)));
|
|
||||||
changeset
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scan a single outpoint for a matching script pubkey.
|
|
||||||
///
|
|
||||||
/// If it matches, this will store and index it.
|
|
||||||
pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> super::ChangeSet<K> {
|
|
||||||
match self.inner.scan_txout(op, txout).cloned() {
|
|
||||||
Some((keychain, index)) => self.reveal_to_target(&keychain, index).1,
|
|
||||||
None => super::ChangeSet::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a reference to the internal [`SpkTxOutIndex`].
|
/// Return a reference to the internal [`SpkTxOutIndex`].
|
||||||
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
|
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
|
||||||
&self.inner
|
&self.inner
|
||||||
@ -199,15 +174,14 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
|||||||
|
|
||||||
/// Set the lookahead count for `keychain`.
|
/// Set the lookahead count for `keychain`.
|
||||||
///
|
///
|
||||||
/// The lookahead is the number of scripts to cache ahead of the last stored script index. This
|
/// The lookahead is the number of scripts to cache ahead of the last revealed script index. This
|
||||||
/// is useful during a scan via [`scan`] or [`scan_txout`].
|
/// is useful to find outputs you own when processing block data that lie beyond the last revealed
|
||||||
|
/// index. In certain situations, such as when performing an initial scan of the blockchain during
|
||||||
|
/// wallet import, it may be uncertain or unknown what the last revealed index is.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// This will panic if the `keychain` does not exist.
|
/// This will panic if the `keychain` does not exist.
|
||||||
///
|
|
||||||
/// [`scan`]: Self::scan
|
|
||||||
/// [`scan_txout`]: Self::scan_txout
|
|
||||||
pub fn set_lookahead(&mut self, keychain: &K, lookahead: u32) {
|
pub fn set_lookahead(&mut self, keychain: &K, lookahead: u32) {
|
||||||
self.lookahead.insert(keychain.clone(), lookahead);
|
self.lookahead.insert(keychain.clone(), lookahead);
|
||||||
self.replenish_lookahead(keychain);
|
self.replenish_lookahead(keychain);
|
||||||
|
@ -3,15 +3,15 @@ use core::ops::RangeBounds;
|
|||||||
use crate::{
|
use crate::{
|
||||||
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
|
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
|
||||||
indexed_tx_graph::Indexer,
|
indexed_tx_graph::Indexer,
|
||||||
ForEachTxOut,
|
|
||||||
};
|
};
|
||||||
use bitcoin::{self, OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid};
|
use bitcoin::{self, OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid};
|
||||||
|
|
||||||
/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
|
/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
|
||||||
///
|
///
|
||||||
/// The basic idea is that you insert script pubkeys you care about into the index with
|
/// The basic idea is that you insert script pubkeys you care about into the index with
|
||||||
/// [`insert_spk`] and then when you call [`scan`], the index will look at any txouts you pass in and
|
/// [`insert_spk`] and then when you call [`Indexer::index_tx`] or [`Indexer::index_txout`], the
|
||||||
/// store and index any txouts matching one of its script pubkeys.
|
/// index will look at any txouts you pass in and store and index any txouts matching one of its
|
||||||
|
/// script pubkeys.
|
||||||
///
|
///
|
||||||
/// Each script pubkey is associated with an application-defined index script index `I`, which must be
|
/// Each script pubkey is associated with an application-defined index script index `I`, which must be
|
||||||
/// [`Ord`]. Usually, this is used to associate the derivation index of the script pubkey or even a
|
/// [`Ord`]. Usually, this is used to associate the derivation index of the script pubkey or even a
|
||||||
@ -25,7 +25,6 @@ use bitcoin::{self, OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid};
|
|||||||
/// [`TxOut`]: bitcoin::TxOut
|
/// [`TxOut`]: bitcoin::TxOut
|
||||||
/// [`insert_spk`]: Self::insert_spk
|
/// [`insert_spk`]: Self::insert_spk
|
||||||
/// [`Ord`]: core::cmp::Ord
|
/// [`Ord`]: core::cmp::Ord
|
||||||
/// [`scan`]: Self::scan
|
|
||||||
/// [`TxGraph`]: crate::tx_graph::TxGraph
|
/// [`TxGraph`]: crate::tx_graph::TxGraph
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SpkTxOutIndex<I> {
|
pub struct SpkTxOutIndex<I> {
|
||||||
@ -77,41 +76,23 @@ impl<I: Clone + Ord> Indexer for SpkTxOutIndex<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This macro is used instead of a member function of `SpkTxOutIndex`, which would result in a
|
|
||||||
/// compiler error[E0521]: "borrowed data escapes out of closure" when we attempt to take a
|
|
||||||
/// reference out of the `ForEachTxOut` closure during scanning.
|
|
||||||
macro_rules! scan_txout {
|
|
||||||
($self:ident, $op:expr, $txout:expr) => {{
|
|
||||||
let spk_i = $self.spk_indices.get(&$txout.script_pubkey);
|
|
||||||
if let Some(spk_i) = spk_i {
|
|
||||||
$self.txouts.insert($op, (spk_i.clone(), $txout.clone()));
|
|
||||||
$self.spk_txouts.insert((spk_i.clone(), $op));
|
|
||||||
$self.unused.remove(&spk_i);
|
|
||||||
}
|
|
||||||
spk_i
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Clone + Ord> SpkTxOutIndex<I> {
|
impl<I: Clone + Ord> SpkTxOutIndex<I> {
|
||||||
/// Scans an object containing many txouts.
|
/// Scans a transaction's outputs for matching script pubkeys.
|
||||||
///
|
///
|
||||||
/// Typically, this is used in two situations:
|
/// Typically, this is used in two situations:
|
||||||
///
|
///
|
||||||
/// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all
|
/// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all
|
||||||
/// your txouts.
|
/// your txouts.
|
||||||
/// 2. When getting new data from the chain, you usually scan it before incorporating it into your chain state.
|
/// 2. When getting new data from the chain, you usually scan it before incorporating it into your chain state.
|
||||||
///
|
pub fn scan(&mut self, tx: &Transaction) -> BTreeSet<I> {
|
||||||
/// See [`ForEachTxout`] for the types that support this.
|
|
||||||
///
|
|
||||||
/// [`ForEachTxout`]: crate::ForEachTxOut
|
|
||||||
pub fn scan(&mut self, txouts: &impl ForEachTxOut) -> BTreeSet<I> {
|
|
||||||
let mut scanned_indices = BTreeSet::new();
|
let mut scanned_indices = BTreeSet::new();
|
||||||
|
let txid = tx.txid();
|
||||||
txouts.for_each_txout(|(op, txout)| {
|
for (i, txout) in tx.output.iter().enumerate() {
|
||||||
if let Some(spk_i) = scan_txout!(self, op, txout) {
|
let op = OutPoint::new(txid, i as u32);
|
||||||
|
if let Some(spk_i) = self.scan_txout(op, txout) {
|
||||||
scanned_indices.insert(spk_i.clone());
|
scanned_indices.insert(spk_i.clone());
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
scanned_indices
|
scanned_indices
|
||||||
}
|
}
|
||||||
@ -119,7 +100,13 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
|
|||||||
/// Scan a single `TxOut` for a matching script pubkey and returns the index that matches the
|
/// Scan a single `TxOut` for a matching script pubkey and returns the index that matches the
|
||||||
/// script pubkey (if any).
|
/// script pubkey (if any).
|
||||||
pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> Option<&I> {
|
pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> Option<&I> {
|
||||||
scan_txout!(self, op, txout)
|
let spk_i = self.spk_indices.get(&txout.script_pubkey);
|
||||||
|
if let Some(spk_i) = spk_i {
|
||||||
|
self.txouts.insert(op, (spk_i.clone(), txout.clone()));
|
||||||
|
self.spk_txouts.insert((spk_i.clone(), op));
|
||||||
|
self.unused.remove(spk_i);
|
||||||
|
}
|
||||||
|
spk_i
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the set of indexed outpoints.
|
/// Get a reference to the set of indexed outpoints.
|
||||||
|
@ -2,39 +2,6 @@ use crate::collections::BTreeMap;
|
|||||||
use crate::collections::BTreeSet;
|
use crate::collections::BTreeSet;
|
||||||
use crate::BlockId;
|
use crate::BlockId;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bitcoin::{Block, OutPoint, Transaction, TxOut};
|
|
||||||
|
|
||||||
/// Trait to do something with every txout contained in a structure.
|
|
||||||
///
|
|
||||||
/// We would prefer to just work with things that can give us an `Iterator<Item=(OutPoint, &TxOut)>`
|
|
||||||
/// here, but rust's type system makes it extremely hard to do this (without trait objects).
|
|
||||||
pub trait ForEachTxOut {
|
|
||||||
/// The provided closure `f` will be called with each `outpoint/txout` pair.
|
|
||||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut)));
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ForEachTxOut for Block {
|
|
||||||
fn for_each_txout(&self, mut f: impl FnMut((OutPoint, &TxOut))) {
|
|
||||||
for tx in self.txdata.iter() {
|
|
||||||
tx.for_each_txout(&mut f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ForEachTxOut for Transaction {
|
|
||||||
fn for_each_txout(&self, mut f: impl FnMut((OutPoint, &TxOut))) {
|
|
||||||
let txid = self.txid();
|
|
||||||
for (i, txout) in self.output.iter().enumerate() {
|
|
||||||
f((
|
|
||||||
OutPoint {
|
|
||||||
txid,
|
|
||||||
vout: i as u32,
|
|
||||||
},
|
|
||||||
txout,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait that "anchors" blockchain data to a specific block of height and hash.
|
/// Trait that "anchors" blockchain data to a specific block of height and hash.
|
||||||
///
|
///
|
||||||
@ -124,14 +91,6 @@ pub trait Append {
|
|||||||
fn is_empty(&self) -> bool;
|
fn is_empty(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Append for () {
|
|
||||||
fn append(&mut self, _other: Self) {}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ord, V> Append for BTreeMap<K, V> {
|
impl<K: Ord, V> Append for BTreeMap<K, V> {
|
||||||
fn append(&mut self, mut other: Self) {
|
fn append(&mut self, mut other: Self) {
|
||||||
BTreeMap::append(self, &mut other)
|
BTreeMap::append(self, &mut other)
|
||||||
@ -162,13 +121,30 @@ impl<T> Append for Vec<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Append, B: Append> Append for (A, B) {
|
macro_rules! impl_append_for_tuple {
|
||||||
fn append(&mut self, other: Self) {
|
($($a:ident $b:tt)*) => {
|
||||||
Append::append(&mut self.0, other.0);
|
impl<$($a),*> Append for ($($a,)*) where $($a: Append),* {
|
||||||
Append::append(&mut self.1, other.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
fn append(&mut self, _other: Self) {
|
||||||
Append::is_empty(&self.0) && Append::is_empty(&self.1)
|
$(Append::append(&mut self.$b, _other.$b) );*
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
$(Append::is_empty(&self.$b) && )* true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_append_for_tuple!();
|
||||||
|
impl_append_for_tuple!(T0 0);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1 T2 2);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T9 9);
|
||||||
|
impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T9 9 T10 10);
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId,
|
collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId,
|
||||||
ChainOracle, ChainPosition, ForEachTxOut, FullTxOut,
|
ChainOracle, ChainPosition, FullTxOut,
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
|
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
|
||||||
@ -1137,18 +1137,6 @@ impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A> ForEachTxOut for ChangeSet<A> {
|
|
||||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
|
||||||
self.txouts().for_each(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A> ForEachTxOut for TxGraph<A> {
|
|
||||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
|
||||||
self.all_txouts().for_each(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator that traverses transaction descendants.
|
/// An iterator that traverses transaction descendants.
|
||||||
///
|
///
|
||||||
/// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`].
|
/// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`].
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
mod common;
|
mod common;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
|
indexed_tx_graph::Indexer,
|
||||||
keychain::{self, KeychainTxOutIndex},
|
keychain::{self, KeychainTxOutIndex},
|
||||||
Append,
|
Append,
|
||||||
};
|
};
|
||||||
@ -194,7 +195,7 @@ fn test_lookahead() {
|
|||||||
],
|
],
|
||||||
..common::new_tx(external_index)
|
..common::new_tx(external_index)
|
||||||
};
|
};
|
||||||
assert_eq!(txout_index.scan(&tx), keychain::ChangeSet::default());
|
assert_eq!(txout_index.index_tx(&tx), keychain::ChangeSet::default());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
txout_index.last_revealed_index(&TestKeychain::External),
|
txout_index.last_revealed_index(&TestKeychain::External),
|
||||||
Some(last_external_index)
|
Some(last_external_index)
|
||||||
@ -248,7 +249,7 @@ fn test_scan_with_lookahead() {
|
|||||||
value: 0,
|
value: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let changeset = txout_index.scan_txout(op, &txout);
|
let changeset = txout_index.index_txout(op, &txout);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
changeset.as_inner(),
|
changeset.as_inner(),
|
||||||
&[(TestKeychain::External, spk_i)].into()
|
&[(TestKeychain::External, spk_i)].into()
|
||||||
@ -273,7 +274,7 @@ fn test_scan_with_lookahead() {
|
|||||||
script_pubkey: spk_41,
|
script_pubkey: spk_41,
|
||||||
value: 0,
|
value: 0,
|
||||||
};
|
};
|
||||||
let changeset = txout_index.scan_txout(op, &txout);
|
let changeset = txout_index.index_txout(op, &txout);
|
||||||
assert!(changeset.is_empty());
|
assert!(changeset.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use bdk_chain::SpkTxOutIndex;
|
use bdk_chain::{indexed_tx_graph::Indexer, SpkTxOutIndex};
|
||||||
use bitcoin::{absolute, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
|
use bitcoin::{absolute, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -22,7 +22,7 @@ fn spk_txout_sent_and_received() {
|
|||||||
|
|
||||||
assert_eq!(index.sent_and_received(&tx1), (0, 42_000));
|
assert_eq!(index.sent_and_received(&tx1), (0, 42_000));
|
||||||
assert_eq!(index.net_value(&tx1), 42_000);
|
assert_eq!(index.net_value(&tx1), 42_000);
|
||||||
index.scan(&tx1);
|
index.index_tx(&tx1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
index.sent_and_received(&tx1),
|
index.sent_and_received(&tx1),
|
||||||
(0, 42_000),
|
(0, 42_000),
|
||||||
@ -82,7 +82,7 @@ fn mark_used() {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
spk_index.scan(&tx1);
|
spk_index.index_tx(&tx1);
|
||||||
spk_index.unmark_used(&1);
|
spk_index.unmark_used(&1);
|
||||||
assert!(
|
assert!(
|
||||||
spk_index.is_used(&1),
|
spk_index.is_used(&1),
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{OutPoint, ScriptBuf, Transaction, Txid},
|
bitcoin::{OutPoint, ScriptBuf, Transaction, Txid},
|
||||||
keychain::WalletUpdate,
|
|
||||||
local_chain::{self, CheckPoint},
|
local_chain::{self, CheckPoint},
|
||||||
tx_graph::{self, TxGraph},
|
tx_graph::{self, TxGraph},
|
||||||
Anchor, BlockId, ConfirmationHeightAnchor, ConfirmationTimeAnchor,
|
Anchor, BlockId, ConfirmationHeightAnchor, ConfirmationTimeAnchor,
|
||||||
@ -15,90 +14,65 @@ use std::{
|
|||||||
/// We assume that a block of this depth and deeper cannot be reorged.
|
/// We assume that a block of this depth and deeper cannot be reorged.
|
||||||
const ASSUME_FINAL_DEPTH: u32 = 8;
|
const ASSUME_FINAL_DEPTH: u32 = 8;
|
||||||
|
|
||||||
/// Represents an update fetched from an Electrum server, but excludes full transactions.
|
/// Represents updates fetched from an Electrum server, but excludes full transactions.
|
||||||
///
|
///
|
||||||
/// To provide a complete update to [`TxGraph`], you'll need to call [`Self::missing_full_txs`] to
|
/// To provide a complete update to [`TxGraph`], you'll need to call [`Self::missing_full_txs`] to
|
||||||
/// determine the full transactions missing from [`TxGraph`]. Then call [`Self::finalize`] to fetch
|
/// determine the full transactions missing from [`TxGraph`]. Then call [`Self::into_tx_graph`] to
|
||||||
/// the full transactions from Electrum and finalize the update.
|
/// fetch the full transactions from Electrum and finalize the update.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct ElectrumUpdate<K, A> {
|
pub struct RelevantTxids(HashMap<Txid, BTreeSet<ConfirmationHeightAnchor>>);
|
||||||
/// Map of [`Txid`]s to associated [`Anchor`]s.
|
|
||||||
pub graph_update: HashMap<Txid, BTreeSet<A>>,
|
|
||||||
/// The latest chain tip, as seen by the Electrum server.
|
|
||||||
pub new_tip: local_chain::CheckPoint,
|
|
||||||
/// Last-used index update for [`KeychainTxOutIndex`](bdk_chain::keychain::KeychainTxOutIndex).
|
|
||||||
pub keychain_update: BTreeMap<K, u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, A: Anchor> ElectrumUpdate<K, A> {
|
|
||||||
fn new(new_tip: local_chain::CheckPoint) -> Self {
|
|
||||||
Self {
|
|
||||||
new_tip,
|
|
||||||
graph_update: HashMap::new(),
|
|
||||||
keychain_update: BTreeMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl RelevantTxids {
|
||||||
/// Determine the full transactions that are missing from `graph`.
|
/// Determine the full transactions that are missing from `graph`.
|
||||||
///
|
///
|
||||||
/// Refer to [`ElectrumUpdate`].
|
/// Refer to [`RelevantTxids`] for more details.
|
||||||
pub fn missing_full_txs<A2>(&self, graph: &TxGraph<A2>) -> Vec<Txid> {
|
pub fn missing_full_txs<A: Anchor>(&self, graph: &TxGraph<A>) -> Vec<Txid> {
|
||||||
self.graph_update
|
self.0
|
||||||
.keys()
|
.keys()
|
||||||
.filter(move |&&txid| graph.as_ref().get_tx(txid).is_none())
|
.filter(move |&&txid| graph.as_ref().get_tx(txid).is_none())
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finalizes update with `missing` txids to fetch from `client`.
|
/// Finalizes the [`TxGraph`] update by fetching `missing` txids from the `client`.
|
||||||
///
|
///
|
||||||
/// Refer to [`ElectrumUpdate`].
|
/// Refer to [`RelevantTxids`] for more details.
|
||||||
pub fn finalize(
|
pub fn into_tx_graph(
|
||||||
self,
|
self,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
seen_at: Option<u64>,
|
seen_at: Option<u64>,
|
||||||
missing: Vec<Txid>,
|
missing: Vec<Txid>,
|
||||||
) -> Result<WalletUpdate<K, A>, Error> {
|
) -> Result<TxGraph<ConfirmationHeightAnchor>, Error> {
|
||||||
let new_txs = client.batch_transaction_get(&missing)?;
|
let new_txs = client.batch_transaction_get(&missing)?;
|
||||||
let mut graph_update = TxGraph::<A>::new(new_txs);
|
let mut graph = TxGraph::<ConfirmationHeightAnchor>::new(new_txs);
|
||||||
for (txid, anchors) in self.graph_update {
|
for (txid, anchors) in self.0 {
|
||||||
if let Some(seen_at) = seen_at {
|
if let Some(seen_at) = seen_at {
|
||||||
let _ = graph_update.insert_seen_at(txid, seen_at);
|
let _ = graph.insert_seen_at(txid, seen_at);
|
||||||
}
|
}
|
||||||
for anchor in anchors {
|
for anchor in anchors {
|
||||||
let _ = graph_update.insert_anchor(txid, anchor);
|
let _ = graph.insert_anchor(txid, anchor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(WalletUpdate {
|
Ok(graph)
|
||||||
last_active_indices: self.keychain_update,
|
|
||||||
graph: graph_update,
|
|
||||||
chain: local_chain::Update {
|
|
||||||
tip: self.new_tip,
|
|
||||||
introduce_older_blocks: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<K> ElectrumUpdate<K, ConfirmationHeightAnchor> {
|
/// Finalizes [`RelevantTxids`] with `new_txs` and anchors of type
|
||||||
/// Finalizes the [`ElectrumUpdate`] with `new_txs` and anchors of type
|
|
||||||
/// [`ConfirmationTimeAnchor`].
|
/// [`ConfirmationTimeAnchor`].
|
||||||
///
|
///
|
||||||
/// **Note:** The confirmation time might not be precisely correct if there has been a reorg.
|
/// **Note:** The confirmation time might not be precisely correct if there has been a reorg.
|
||||||
/// Electrum's API intends that we use the merkle proof API, we should change `bdk_electrum` to
|
/// Electrum's API intends that we use the merkle proof API, we should change `bdk_electrum` to
|
||||||
/// use it.
|
/// use it.
|
||||||
pub fn finalize_as_confirmation_time(
|
pub fn into_confirmation_time_tx_graph(
|
||||||
self,
|
self,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
seen_at: Option<u64>,
|
seen_at: Option<u64>,
|
||||||
missing: Vec<Txid>,
|
missing: Vec<Txid>,
|
||||||
) -> Result<WalletUpdate<K, ConfirmationTimeAnchor>, Error> {
|
) -> Result<TxGraph<ConfirmationTimeAnchor>, Error> {
|
||||||
let update = self.finalize(client, seen_at, missing)?;
|
let graph = self.into_tx_graph(client, seen_at, missing)?;
|
||||||
|
|
||||||
let relevant_heights = {
|
let relevant_heights = {
|
||||||
let mut visited_heights = HashSet::new();
|
let mut visited_heights = HashSet::new();
|
||||||
update
|
graph
|
||||||
.graph
|
|
||||||
.all_anchors()
|
.all_anchors()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(a, _)| a.confirmation_height_upper_bound())
|
.map(|(a, _)| a.confirmation_height_upper_bound())
|
||||||
@ -118,7 +92,7 @@ impl<K> ElectrumUpdate<K, ConfirmationHeightAnchor> {
|
|||||||
.collect::<HashMap<u32, u64>>();
|
.collect::<HashMap<u32, u64>>();
|
||||||
|
|
||||||
let graph_changeset = {
|
let graph_changeset = {
|
||||||
let old_changeset = TxGraph::default().apply_update(update.graph.clone());
|
let old_changeset = TxGraph::default().apply_update(graph);
|
||||||
tx_graph::ChangeSet {
|
tx_graph::ChangeSet {
|
||||||
txs: old_changeset.txs,
|
txs: old_changeset.txs,
|
||||||
txouts: old_changeset.txouts,
|
txouts: old_changeset.txouts,
|
||||||
@ -140,21 +114,28 @@ impl<K> ElectrumUpdate<K, ConfirmationHeightAnchor> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(WalletUpdate {
|
let mut new_graph = TxGraph::default();
|
||||||
last_active_indices: update.last_active_indices,
|
new_graph.apply_changeset(graph_changeset);
|
||||||
graph: {
|
Ok(new_graph)
|
||||||
let mut graph = TxGraph::default();
|
|
||||||
graph.apply_changeset(graph_changeset);
|
|
||||||
graph
|
|
||||||
},
|
|
||||||
chain: update.chain,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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: local_chain::Update,
|
||||||
|
/// Transaction updates from electrum
|
||||||
|
pub relevant_txids: RelevantTxids,
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait to extend [`Client`] functionality.
|
/// Trait to extend [`Client`] functionality.
|
||||||
pub trait ElectrumExt<A> {
|
pub trait ElectrumExt {
|
||||||
/// Scan the blockchain (via electrum) for the data specified and returns a [`ElectrumUpdate`].
|
/// Scan the blockchain (via electrum) for the data specified and returns updates for
|
||||||
|
/// [`bdk_chain`] data structures.
|
||||||
///
|
///
|
||||||
/// - `prev_tip`: the most recent blockchain tip present locally
|
/// - `prev_tip`: the most recent blockchain tip present locally
|
||||||
/// - `keychain_spks`: keychains that we want to scan transactions for
|
/// - `keychain_spks`: keychains that we want to scan transactions for
|
||||||
@ -173,7 +154,7 @@ pub trait ElectrumExt<A> {
|
|||||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
outpoints: impl IntoIterator<Item = OutPoint>,
|
||||||
stop_gap: usize,
|
stop_gap: usize,
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
) -> Result<ElectrumUpdate<K, A>, Error>;
|
) -> Result<(ElectrumUpdate, BTreeMap<K, u32>), Error>;
|
||||||
|
|
||||||
/// Convenience method to call [`scan`] without requiring a keychain.
|
/// Convenience method to call [`scan`] without requiring a keychain.
|
||||||
///
|
///
|
||||||
@ -185,24 +166,26 @@ pub trait ElectrumExt<A> {
|
|||||||
txids: impl IntoIterator<Item = Txid>,
|
txids: impl IntoIterator<Item = Txid>,
|
||||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
outpoints: impl IntoIterator<Item = OutPoint>,
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
) -> Result<ElectrumUpdate<(), A>, Error> {
|
) -> Result<ElectrumUpdate, Error> {
|
||||||
let spk_iter = misc_spks
|
let spk_iter = misc_spks
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, spk)| (i as u32, spk));
|
.map(|(i, spk)| (i as u32, spk));
|
||||||
|
|
||||||
self.scan(
|
let (electrum_update, _) = self.scan(
|
||||||
prev_tip,
|
prev_tip,
|
||||||
[((), spk_iter)].into(),
|
[((), spk_iter)].into(),
|
||||||
txids,
|
txids,
|
||||||
outpoints,
|
outpoints,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
batch_size,
|
batch_size,
|
||||||
)
|
)?;
|
||||||
|
|
||||||
|
Ok(electrum_update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElectrumExt<ConfirmationHeightAnchor> for Client {
|
impl ElectrumExt for Client {
|
||||||
fn scan<K: Ord + Clone>(
|
fn scan<K: Ord + Clone>(
|
||||||
&self,
|
&self,
|
||||||
prev_tip: Option<CheckPoint>,
|
prev_tip: Option<CheckPoint>,
|
||||||
@ -211,7 +194,7 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
|
|||||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
outpoints: impl IntoIterator<Item = OutPoint>,
|
||||||
stop_gap: usize,
|
stop_gap: usize,
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
) -> Result<ElectrumUpdate<K, ConfirmationHeightAnchor>, Error> {
|
) -> Result<(ElectrumUpdate, BTreeMap<K, u32>), Error> {
|
||||||
let mut request_spks = keychain_spks
|
let mut request_spks = keychain_spks
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, s)| (k, s.into_iter()))
|
.map(|(k, s)| (k, s.into_iter()))
|
||||||
@ -221,11 +204,10 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
|
|||||||
let txids = txids.into_iter().collect::<Vec<_>>();
|
let txids = txids.into_iter().collect::<Vec<_>>();
|
||||||
let outpoints = outpoints.into_iter().collect::<Vec<_>>();
|
let outpoints = outpoints.into_iter().collect::<Vec<_>>();
|
||||||
|
|
||||||
let update = loop {
|
let (electrum_update, keychain_update) = loop {
|
||||||
let (tip, _) = construct_update_tip(self, prev_tip.clone())?;
|
let (tip, _) = construct_update_tip(self, prev_tip.clone())?;
|
||||||
let mut update = ElectrumUpdate::<K, ConfirmationHeightAnchor>::new(tip.clone());
|
let mut relevant_txids = RelevantTxids::default();
|
||||||
let cps = update
|
let cps = tip
|
||||||
.new_tip
|
|
||||||
.iter()
|
.iter()
|
||||||
.take(10)
|
.take(10)
|
||||||
.map(|cp| (cp.height(), cp))
|
.map(|cp| (cp.height(), cp))
|
||||||
@ -236,7 +218,7 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
|
|||||||
scanned_spks.append(&mut populate_with_spks(
|
scanned_spks.append(&mut populate_with_spks(
|
||||||
self,
|
self,
|
||||||
&cps,
|
&cps,
|
||||||
&mut update,
|
&mut relevant_txids,
|
||||||
&mut scanned_spks
|
&mut scanned_spks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(i, (spk, _))| (i.clone(), spk.clone())),
|
.map(|(i, (spk, _))| (i.clone(), spk.clone())),
|
||||||
@ -249,7 +231,7 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
|
|||||||
populate_with_spks(
|
populate_with_spks(
|
||||||
self,
|
self,
|
||||||
&cps,
|
&cps,
|
||||||
&mut update,
|
&mut relevant_txids,
|
||||||
keychain_spks,
|
keychain_spks,
|
||||||
stop_gap,
|
stop_gap,
|
||||||
batch_size,
|
batch_size,
|
||||||
@ -260,10 +242,14 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
populate_with_txids(self, &cps, &mut update, &mut txids.iter().cloned())?;
|
populate_with_txids(self, &cps, &mut relevant_txids, &mut txids.iter().cloned())?;
|
||||||
|
|
||||||
let _txs =
|
let _txs = populate_with_outpoints(
|
||||||
populate_with_outpoints(self, &cps, &mut update, &mut outpoints.iter().cloned())?;
|
self,
|
||||||
|
&cps,
|
||||||
|
&mut relevant_txids,
|
||||||
|
&mut outpoints.iter().cloned(),
|
||||||
|
)?;
|
||||||
|
|
||||||
// check for reorgs during scan process
|
// check for reorgs during scan process
|
||||||
let server_blockhash = self.block_header(tip.height() as usize)?.block_hash();
|
let server_blockhash = self.block_header(tip.height() as usize)?.block_hash();
|
||||||
@ -271,7 +257,12 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
|
|||||||
continue; // reorg
|
continue; // reorg
|
||||||
}
|
}
|
||||||
|
|
||||||
update.keychain_update = request_spks
|
let chain_update = local_chain::Update {
|
||||||
|
tip,
|
||||||
|
introduce_older_blocks: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let keychain_update = request_spks
|
||||||
.into_keys()
|
.into_keys()
|
||||||
.filter_map(|k| {
|
.filter_map(|k| {
|
||||||
scanned_spks
|
scanned_spks
|
||||||
@ -281,10 +272,17 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
|
|||||||
.map(|((_, i), _)| (k, *i))
|
.map(|((_, i), _)| (k, *i))
|
||||||
})
|
})
|
||||||
.collect::<BTreeMap<_, _>>();
|
.collect::<BTreeMap<_, _>>();
|
||||||
break update;
|
|
||||||
|
break (
|
||||||
|
ElectrumUpdate {
|
||||||
|
chain_update,
|
||||||
|
relevant_txids,
|
||||||
|
},
|
||||||
|
keychain_update,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(update)
|
Ok((electrum_update, keychain_update))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,10 +403,10 @@ fn determine_tx_anchor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn populate_with_outpoints<K>(
|
fn populate_with_outpoints(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
cps: &BTreeMap<u32, CheckPoint>,
|
cps: &BTreeMap<u32, CheckPoint>,
|
||||||
update: &mut ElectrumUpdate<K, ConfirmationHeightAnchor>,
|
relevant_txids: &mut RelevantTxids,
|
||||||
outpoints: &mut impl Iterator<Item = OutPoint>,
|
outpoints: &mut impl Iterator<Item = OutPoint>,
|
||||||
) -> Result<HashMap<Txid, Transaction>, Error> {
|
) -> Result<HashMap<Txid, Transaction>, Error> {
|
||||||
let mut full_txs = HashMap::new();
|
let mut full_txs = HashMap::new();
|
||||||
@ -457,8 +455,7 @@ fn populate_with_outpoints<K>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let anchor = determine_tx_anchor(cps, res.height, res.tx_hash);
|
let anchor = determine_tx_anchor(cps, res.height, res.tx_hash);
|
||||||
|
let tx_entry = relevant_txids.0.entry(res.tx_hash).or_default();
|
||||||
let tx_entry = update.graph_update.entry(res.tx_hash).or_default();
|
|
||||||
if let Some(anchor) = anchor {
|
if let Some(anchor) = anchor {
|
||||||
tx_entry.insert(anchor);
|
tx_entry.insert(anchor);
|
||||||
}
|
}
|
||||||
@ -467,10 +464,10 @@ fn populate_with_outpoints<K>(
|
|||||||
Ok(full_txs)
|
Ok(full_txs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn populate_with_txids<K>(
|
fn populate_with_txids(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
cps: &BTreeMap<u32, CheckPoint>,
|
cps: &BTreeMap<u32, CheckPoint>,
|
||||||
update: &mut ElectrumUpdate<K, ConfirmationHeightAnchor>,
|
relevant_txids: &mut RelevantTxids,
|
||||||
txids: &mut impl Iterator<Item = Txid>,
|
txids: &mut impl Iterator<Item = Txid>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
for txid in txids {
|
for txid in txids {
|
||||||
@ -495,7 +492,7 @@ fn populate_with_txids<K>(
|
|||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let tx_entry = update.graph_update.entry(txid).or_default();
|
let tx_entry = relevant_txids.0.entry(txid).or_default();
|
||||||
if let Some(anchor) = anchor {
|
if let Some(anchor) = anchor {
|
||||||
tx_entry.insert(anchor);
|
tx_entry.insert(anchor);
|
||||||
}
|
}
|
||||||
@ -503,10 +500,10 @@ fn populate_with_txids<K>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn populate_with_spks<K, I: Ord + Clone>(
|
fn populate_with_spks<I: Ord + Clone>(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
cps: &BTreeMap<u32, CheckPoint>,
|
cps: &BTreeMap<u32, CheckPoint>,
|
||||||
update: &mut ElectrumUpdate<K, ConfirmationHeightAnchor>,
|
relevant_txids: &mut RelevantTxids,
|
||||||
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,
|
||||||
@ -539,7 +536,7 @@ fn populate_with_spks<K, I: Ord + Clone>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for tx in spk_history {
|
for tx in spk_history {
|
||||||
let tx_entry = update.graph_update.entry(tx.tx_hash).or_default();
|
let tx_entry = relevant_txids.0.entry(tx.tx_hash).or_default();
|
||||||
if let Some(anchor) = determine_tx_anchor(cps, tx.height, tx.tx_hash) {
|
if let Some(anchor) = determine_tx_anchor(cps, tx.height, tx.tx_hash) {
|
||||||
tx_entry.insert(anchor);
|
tx_entry.insert(anchor);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
//! This crate is used for updating structures of the [`bdk_chain`] crate with data from electrum.
|
//! This crate is used for updating structures of the [`bdk_chain`] crate with data from electrum.
|
||||||
//!
|
//!
|
||||||
//! The star of the show is the [`ElectrumExt::scan`] method, which scans for relevant blockchain
|
//! The star of the show is the [`ElectrumExt::scan`] method, which scans for relevant blockchain
|
||||||
//! data (via electrum) and outputs an [`ElectrumUpdate`].
|
//! data (via electrum) and outputs updates for [`bdk_chain`] structures as a tuple of form:
|
||||||
//!
|
//!
|
||||||
//! An [`ElectrumUpdate`] only includes `txid`s and no full transactions. The caller is responsible
|
//! ([`bdk_chain::local_chain::Update`], [`RelevantTxids`], `keychain_update`)
|
||||||
//! for obtaining full transactions before applying. This can be done with
|
//!
|
||||||
|
//! An [`RelevantTxids`] only includes `txid`s and no full transactions. The caller is
|
||||||
|
//! responsible for obtaining full transactions before applying. This can be done with
|
||||||
//! these steps:
|
//! these steps:
|
||||||
//!
|
//!
|
||||||
//! 1. Determine which full transactions are missing. The method [`missing_full_txs`] of
|
//! 1. Determine which full transactions are missing. The method [`missing_full_txs`] of
|
||||||
//! [`ElectrumUpdate`] can be used.
|
//! [`RelevantTxids`] can be used.
|
||||||
//!
|
//!
|
||||||
//! 2. Obtaining the full transactions. To do this via electrum, the method
|
//! 2. Obtaining the full transactions. To do this via electrum, the method
|
||||||
//! [`batch_transaction_get`] can be used.
|
//! [`batch_transaction_get`] can be used.
|
||||||
@ -16,7 +18,7 @@
|
|||||||
//! Refer to [`bdk_electrum_example`] for a complete example.
|
//! Refer to [`bdk_electrum_example`] for a complete example.
|
||||||
//!
|
//!
|
||||||
//! [`ElectrumClient::scan`]: electrum_client::ElectrumClient::scan
|
//! [`ElectrumClient::scan`]: electrum_client::ElectrumClient::scan
|
||||||
//! [`missing_full_txs`]: ElectrumUpdate::missing_full_txs
|
//! [`missing_full_txs`]: RelevantTxids::missing_full_txs
|
||||||
//! [`batch_transaction_get`]: electrum_client::ElectrumApi::batch_transaction_get
|
//! [`batch_transaction_get`]: electrum_client::ElectrumApi::batch_transaction_get
|
||||||
//! [`bdk_electrum_example`]: https://github.com/LLFourn/bdk_core_staging/tree/master/bdk_electrum_example
|
//! [`bdk_electrum_example`]: https://github.com/LLFourn/bdk_core_staging/tree/master/bdk_electrum_example
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ use bdk_chain::{
|
|||||||
},
|
},
|
||||||
indexed_tx_graph::{self, IndexedTxGraph},
|
indexed_tx_graph::{self, IndexedTxGraph},
|
||||||
keychain::{self, KeychainTxOutIndex},
|
keychain::{self, KeychainTxOutIndex},
|
||||||
|
local_chain,
|
||||||
miniscript::{
|
miniscript::{
|
||||||
descriptor::{DescriptorSecretKey, KeyMap},
|
descriptor::{DescriptorSecretKey, KeyMap},
|
||||||
Descriptor, DescriptorPublicKey,
|
Descriptor, DescriptorPublicKey,
|
||||||
@ -24,7 +25,10 @@ pub use clap;
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
|
pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
|
||||||
pub type KeychainChangeSet<A> = indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<Keychain>>;
|
pub type KeychainChangeSet<A> = (
|
||||||
|
local_chain::ChangeSet,
|
||||||
|
indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<Keychain>>,
|
||||||
|
);
|
||||||
pub type Database<'m, C> = Persist<Store<'m, C>, C>;
|
pub type Database<'m, C> = Persist<Store<'m, C>, C>;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@ -200,7 +204,10 @@ where
|
|||||||
|
|
||||||
let ((spk_i, spk), index_changeset) = spk_chooser(index, &Keychain::External);
|
let ((spk_i, spk), index_changeset) = spk_chooser(index, &Keychain::External);
|
||||||
let db = &mut *db.lock().unwrap();
|
let db = &mut *db.lock().unwrap();
|
||||||
db.stage(C::from(KeychainChangeSet::from(index_changeset)));
|
db.stage(C::from((
|
||||||
|
local_chain::ChangeSet::default(),
|
||||||
|
indexed_tx_graph::ChangeSet::from(index_changeset),
|
||||||
|
)));
|
||||||
db.commit()?;
|
db.commit()?;
|
||||||
let addr = Address::from_script(spk, network).context("failed to derive address")?;
|
let addr = Address::from_script(spk, network).context("failed to derive address")?;
|
||||||
println!("[address @ {}] {}", spk_i, addr);
|
println!("[address @ {}] {}", spk_i, addr);
|
||||||
@ -353,7 +360,10 @@ where
|
|||||||
// If we're unable to persist this, then we don't want to broadcast.
|
// If we're unable to persist this, then we don't want to broadcast.
|
||||||
{
|
{
|
||||||
let db = &mut *db.lock().unwrap();
|
let db = &mut *db.lock().unwrap();
|
||||||
db.stage(C::from(KeychainChangeSet::from(index_changeset)));
|
db.stage(C::from((
|
||||||
|
local_chain::ChangeSet::default(),
|
||||||
|
indexed_tx_graph::ChangeSet::from(index_changeset),
|
||||||
|
)));
|
||||||
db.commit()?;
|
db.commit()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +386,10 @@ where
|
|||||||
// We know the tx is at least unconfirmed now. Note if persisting here fails,
|
// We know the tx is at least unconfirmed now. Note if persisting here fails,
|
||||||
// it's not a big deal since we can always find it again form
|
// it's not a big deal since we can always find it again form
|
||||||
// blockchain.
|
// blockchain.
|
||||||
db.lock().unwrap().stage(C::from(keychain_changeset));
|
db.lock().unwrap().stage(C::from((
|
||||||
|
local_chain::ChangeSet::default(),
|
||||||
|
keychain_changeset,
|
||||||
|
)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -7,8 +7,8 @@ use std::{
|
|||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid},
|
bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid},
|
||||||
indexed_tx_graph::{self, IndexedTxGraph},
|
indexed_tx_graph::{self, IndexedTxGraph},
|
||||||
keychain::WalletChangeSet,
|
keychain,
|
||||||
local_chain::LocalChain,
|
local_chain::{self, LocalChain},
|
||||||
Append, ConfirmationHeightAnchor,
|
Append, ConfirmationHeightAnchor,
|
||||||
};
|
};
|
||||||
use bdk_electrum::{
|
use bdk_electrum::{
|
||||||
@ -60,19 +60,22 @@ pub struct ScanOptions {
|
|||||||
pub batch_size: usize,
|
pub batch_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChangeSet = WalletChangeSet<Keychain, ConfirmationHeightAnchor>;
|
type ChangeSet = (
|
||||||
|
local_chain::ChangeSet,
|
||||||
|
indexed_tx_graph::ChangeSet<ConfirmationHeightAnchor, keychain::ChangeSet<Keychain>>,
|
||||||
|
);
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let (args, keymap, index, db, init_changeset) =
|
let (args, keymap, index, db, (disk_local_chain, disk_tx_graph)) =
|
||||||
example_cli::init::<ElectrumCommands, ChangeSet>(DB_MAGIC, DB_PATH)?;
|
example_cli::init::<ElectrumCommands, ChangeSet>(DB_MAGIC, DB_PATH)?;
|
||||||
|
|
||||||
let graph = Mutex::new({
|
let graph = Mutex::new({
|
||||||
let mut graph = IndexedTxGraph::new(index);
|
let mut graph = IndexedTxGraph::new(index);
|
||||||
graph.apply_changeset(init_changeset.indexed_tx_graph);
|
graph.apply_changeset(disk_tx_graph);
|
||||||
graph
|
graph
|
||||||
});
|
});
|
||||||
|
|
||||||
let chain = Mutex::new(LocalChain::from_changeset(init_changeset.chain));
|
let chain = Mutex::new(LocalChain::from_changeset(disk_local_chain));
|
||||||
|
|
||||||
let electrum_url = match args.network {
|
let electrum_url = match args.network {
|
||||||
Network::Bitcoin => "ssl://electrum.blockstream.info:50002",
|
Network::Bitcoin => "ssl://electrum.blockstream.info:50002",
|
||||||
@ -248,20 +251,24 @@ fn main() -> anyhow::Result<()> {
|
|||||||
// drop lock on graph and chain
|
// drop lock on graph and chain
|
||||||
drop((graph, chain));
|
drop((graph, chain));
|
||||||
|
|
||||||
let update = client
|
let electrum_update = client
|
||||||
.scan_without_keychain(tip, spks, txids, outpoints, scan_options.batch_size)
|
.scan_without_keychain(tip, spks, txids, outpoints, scan_options.batch_size)
|
||||||
.context("scanning the blockchain")?;
|
.context("scanning the blockchain")?;
|
||||||
ElectrumUpdate {
|
(electrum_update, BTreeMap::new())
|
||||||
graph_update: update.graph_update,
|
|
||||||
new_tip: update.new_tip,
|
|
||||||
keychain_update: BTreeMap::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (
|
||||||
|
ElectrumUpdate {
|
||||||
|
chain_update,
|
||||||
|
relevant_txids,
|
||||||
|
},
|
||||||
|
keychain_update,
|
||||||
|
) = response;
|
||||||
|
|
||||||
let missing_txids = {
|
let missing_txids = {
|
||||||
let graph = &*graph.lock().unwrap();
|
let graph = &*graph.lock().unwrap();
|
||||||
response.missing_full_txs(graph.graph())
|
relevant_txids.missing_full_txs(graph.graph())
|
||||||
};
|
};
|
||||||
|
|
||||||
let now = std::time::UNIX_EPOCH
|
let now = std::time::UNIX_EPOCH
|
||||||
@ -269,32 +276,27 @@ fn main() -> anyhow::Result<()> {
|
|||||||
.expect("must get time")
|
.expect("must get time")
|
||||||
.as_secs();
|
.as_secs();
|
||||||
|
|
||||||
let final_update = response.finalize(&client, Some(now), missing_txids)?;
|
let graph_update = relevant_txids.into_tx_graph(&client, Some(now), missing_txids)?;
|
||||||
|
|
||||||
let db_changeset = {
|
let db_changeset = {
|
||||||
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(final_update.chain)?;
|
let chain = chain.apply_update(chain_update)?;
|
||||||
|
|
||||||
let indexed_tx_graph = {
|
let indexed_tx_graph = {
|
||||||
let mut changeset =
|
let mut changeset =
|
||||||
indexed_tx_graph::ChangeSet::<ConfirmationHeightAnchor, _>::default();
|
indexed_tx_graph::ChangeSet::<ConfirmationHeightAnchor, _>::default();
|
||||||
let (_, indexer) = graph
|
let (_, indexer) = graph.index.reveal_to_target_multi(&keychain_update);
|
||||||
.index
|
|
||||||
.reveal_to_target_multi(&final_update.last_active_indices);
|
|
||||||
changeset.append(indexed_tx_graph::ChangeSet {
|
changeset.append(indexed_tx_graph::ChangeSet {
|
||||||
indexer,
|
indexer,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
changeset.append(graph.apply_update(final_update.graph));
|
changeset.append(graph.apply_update(graph_update));
|
||||||
changeset
|
changeset
|
||||||
};
|
};
|
||||||
|
|
||||||
ChangeSet {
|
(chain, indexed_tx_graph)
|
||||||
indexed_tx_graph,
|
|
||||||
chain,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut db = db.lock().unwrap();
|
let mut db = db.lock().unwrap();
|
||||||
|
@ -6,9 +6,9 @@ use std::{
|
|||||||
|
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid},
|
bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid},
|
||||||
indexed_tx_graph::IndexedTxGraph,
|
indexed_tx_graph::{self, IndexedTxGraph},
|
||||||
keychain::WalletChangeSet,
|
keychain,
|
||||||
local_chain::{CheckPoint, LocalChain},
|
local_chain::{self, CheckPoint, LocalChain},
|
||||||
Append, ConfirmationTimeAnchor,
|
Append, ConfirmationTimeAnchor,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,6 +23,11 @@ use example_cli::{
|
|||||||
const DB_MAGIC: &[u8] = b"bdk_example_esplora";
|
const DB_MAGIC: &[u8] = b"bdk_example_esplora";
|
||||||
const DB_PATH: &str = ".bdk_esplora_example.db";
|
const DB_PATH: &str = ".bdk_esplora_example.db";
|
||||||
|
|
||||||
|
type ChangeSet = (
|
||||||
|
local_chain::ChangeSet,
|
||||||
|
indexed_tx_graph::ChangeSet<ConfirmationTimeAnchor, keychain::ChangeSet<Keychain>>,
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Subcommand, Debug, Clone)]
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
enum EsploraCommands {
|
enum EsploraCommands {
|
||||||
/// Scans the addresses in the wallet using the esplora API.
|
/// Scans the addresses in the wallet using the esplora API.
|
||||||
@ -60,22 +65,22 @@ pub struct ScanOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let (args, keymap, index, db, init_changeset) = example_cli::init::<
|
let (args, keymap, index, db, init_changeset) =
|
||||||
EsploraCommands,
|
example_cli::init::<EsploraCommands, ChangeSet>(DB_MAGIC, DB_PATH)?;
|
||||||
WalletChangeSet<Keychain, ConfirmationTimeAnchor>,
|
|
||||||
>(DB_MAGIC, DB_PATH)?;
|
let (init_chain_changeset, init_indexed_tx_graph_changeset) = init_changeset;
|
||||||
|
|
||||||
// Contruct `IndexedTxGraph` and `LocalChain` with our initial changeset. They are wrapped in
|
// Contruct `IndexedTxGraph` and `LocalChain` with our initial changeset. They are wrapped in
|
||||||
// `Mutex` to display how they can be used in a multithreaded context. Technically the mutexes
|
// `Mutex` to display how they can be used in a multithreaded context. Technically the mutexes
|
||||||
// aren't strictly needed here.
|
// aren't strictly needed here.
|
||||||
let graph = Mutex::new({
|
let graph = Mutex::new({
|
||||||
let mut graph = IndexedTxGraph::new(index);
|
let mut graph = IndexedTxGraph::new(index);
|
||||||
graph.apply_changeset(init_changeset.indexed_tx_graph);
|
graph.apply_changeset(init_indexed_tx_graph_changeset);
|
||||||
graph
|
graph
|
||||||
});
|
});
|
||||||
let chain = Mutex::new({
|
let chain = Mutex::new({
|
||||||
let mut chain = LocalChain::default();
|
let mut chain = LocalChain::default();
|
||||||
chain.apply_changeset(&init_changeset.chain);
|
chain.apply_changeset(&init_chain_changeset);
|
||||||
chain
|
chain
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -303,18 +308,17 @@ fn main() -> anyhow::Result<()> {
|
|||||||
println!("missing block heights: {:?}", missing_block_heights);
|
println!("missing block heights: {:?}", missing_block_heights);
|
||||||
|
|
||||||
// Here, we actually fetch the missing blocks and create a `local_chain::Update`.
|
// Here, we actually fetch the missing blocks and create a `local_chain::Update`.
|
||||||
let chain_update = client
|
let chain_changeset = {
|
||||||
.update_local_chain(tip, missing_block_heights)
|
let chain_update = client
|
||||||
.context("scanning for blocks")?;
|
.update_local_chain(tip, missing_block_heights)
|
||||||
|
.context("scanning for blocks")?;
|
||||||
println!("new tip: {}", chain_update.tip.height());
|
println!("new tip: {}", chain_update.tip.height());
|
||||||
|
chain.lock().unwrap().apply_update(chain_update)?
|
||||||
|
};
|
||||||
|
|
||||||
// We persist the changes
|
// We persist the changes
|
||||||
let mut db = db.lock().unwrap();
|
let mut db = db.lock().unwrap();
|
||||||
db.stage(WalletChangeSet {
|
db.stage((chain_changeset, indexed_tx_graph_changeset));
|
||||||
chain: chain.lock().unwrap().apply_update(chain_update)?,
|
|
||||||
indexed_tx_graph: indexed_tx_graph_changeset,
|
|
||||||
});
|
|
||||||
db.commit()?;
|
db.commit()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,13 @@ use std::io::Write;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use bdk::bitcoin::Address;
|
use bdk::bitcoin::Address;
|
||||||
|
use bdk::wallet::Update;
|
||||||
use bdk::SignOptions;
|
use bdk::SignOptions;
|
||||||
use bdk::{bitcoin::Network, Wallet};
|
use bdk::{bitcoin::Network, Wallet};
|
||||||
use bdk_electrum::electrum_client::{self, ElectrumApi};
|
use bdk_electrum::{
|
||||||
use bdk_electrum::ElectrumExt;
|
electrum_client::{self, ElectrumApi},
|
||||||
|
ElectrumExt, ElectrumUpdate,
|
||||||
|
};
|
||||||
use bdk_file_store::Store;
|
use bdk_file_store::Store;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@ -52,14 +55,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let electrum_update = client.scan(prev_tip, keychain_spks, None, None, STOP_GAP, BATCH_SIZE)?;
|
let (
|
||||||
|
ElectrumUpdate {
|
||||||
|
chain_update,
|
||||||
|
relevant_txids,
|
||||||
|
},
|
||||||
|
keychain_update,
|
||||||
|
) = client.scan(prev_tip, keychain_spks, None, None, STOP_GAP, BATCH_SIZE)?;
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let missing = electrum_update.missing_full_txs(wallet.as_ref());
|
let missing = relevant_txids.missing_full_txs(wallet.as_ref());
|
||||||
let update = electrum_update.finalize_as_confirmation_time(&client, None, missing)?;
|
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?;
|
||||||
|
|
||||||
wallet.apply_update(update)?;
|
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();
|
||||||
|
@ -2,8 +2,7 @@ use std::{io::Write, str::FromStr};
|
|||||||
|
|
||||||
use bdk::{
|
use bdk::{
|
||||||
bitcoin::{Address, Network},
|
bitcoin::{Address, Network},
|
||||||
chain::keychain::WalletUpdate,
|
wallet::{AddressIndex, Update},
|
||||||
wallet::AddressIndex,
|
|
||||||
SignOptions, Wallet,
|
SignOptions, Wallet,
|
||||||
};
|
};
|
||||||
use bdk_esplora::{esplora_client, EsploraAsyncExt};
|
use bdk_esplora::{esplora_client, EsploraAsyncExt};
|
||||||
@ -59,10 +58,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.await?;
|
.await?;
|
||||||
let missing_heights = wallet.tx_graph().missing_heights(wallet.local_chain());
|
let missing_heights = wallet.tx_graph().missing_heights(wallet.local_chain());
|
||||||
let chain_update = client.update_local_chain(prev_tip, missing_heights).await?;
|
let chain_update = client.update_local_chain(prev_tip, missing_heights).await?;
|
||||||
let update = WalletUpdate {
|
let update = Update {
|
||||||
last_active_indices,
|
last_active_indices,
|
||||||
graph: update_graph,
|
graph: update_graph,
|
||||||
..WalletUpdate::new(chain_update)
|
chain: Some(chain_update),
|
||||||
};
|
};
|
||||||
wallet.apply_update(update)?;
|
wallet.apply_update(update)?;
|
||||||
wallet.commit()?;
|
wallet.commit()?;
|
||||||
|
@ -7,8 +7,7 @@ use std::{io::Write, str::FromStr};
|
|||||||
|
|
||||||
use bdk::{
|
use bdk::{
|
||||||
bitcoin::{Address, Network},
|
bitcoin::{Address, Network},
|
||||||
chain::keychain::WalletUpdate,
|
wallet::{AddressIndex, Update},
|
||||||
wallet::AddressIndex,
|
|
||||||
SignOptions, Wallet,
|
SignOptions, Wallet,
|
||||||
};
|
};
|
||||||
use bdk_esplora::{esplora_client, EsploraExt};
|
use bdk_esplora::{esplora_client, EsploraExt};
|
||||||
@ -58,10 +57,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
client.scan_txs_with_keychains(keychain_spks, None, None, STOP_GAP, PARALLEL_REQUESTS)?;
|
client.scan_txs_with_keychains(keychain_spks, None, None, STOP_GAP, PARALLEL_REQUESTS)?;
|
||||||
let missing_heights = wallet.tx_graph().missing_heights(wallet.local_chain());
|
let missing_heights = wallet.tx_graph().missing_heights(wallet.local_chain());
|
||||||
let chain_update = client.update_local_chain(prev_tip, missing_heights)?;
|
let chain_update = client.update_local_chain(prev_tip, missing_heights)?;
|
||||||
let update = WalletUpdate {
|
let update = Update {
|
||||||
last_active_indices,
|
last_active_indices,
|
||||||
graph: update_graph,
|
graph: update_graph,
|
||||||
..WalletUpdate::new(chain_update)
|
chain: Some(chain_update),
|
||||||
};
|
};
|
||||||
|
|
||||||
wallet.apply_update(update)?;
|
wallet.apply_update(update)?;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user