[wallet_redesign] Update Wallet
with redesigned structures
This commit is contained in:
parent
8641847e6c
commit
e69fccb15f
@ -56,7 +56,6 @@
|
|||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
|
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use bdk_chain::sparse_chain::ChainPosition;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use miniscript::descriptor::{ShInner, WshInner};
|
use miniscript::descriptor::{ShInner, WshInner};
|
||||||
@ -130,8 +129,10 @@ impl FullyNodedExport {
|
|||||||
wallet
|
wallet
|
||||||
.transactions()
|
.transactions()
|
||||||
.next()
|
.next()
|
||||||
.and_then(|(pos, _)| pos.height().into())
|
.map_or(0, |canonical_tx| match canonical_tx.observed_as {
|
||||||
.unwrap_or(0)
|
bdk_chain::ObservedAs::Confirmed(a) => a.confirmation_height,
|
||||||
|
bdk_chain::ObservedAs::Unconfirmed(_) => 0,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
@ -246,6 +247,7 @@ mod test {
|
|||||||
height: 5000,
|
height: 5000,
|
||||||
time: 0,
|
time: 0,
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
wallet
|
wallet
|
||||||
|
@ -21,9 +21,12 @@ use alloc::{
|
|||||||
};
|
};
|
||||||
pub use bdk_chain::keychain::Balance;
|
pub use bdk_chain::keychain::Balance;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
chain_graph,
|
indexed_tx_graph::{IndexedAdditions, IndexedTxGraph},
|
||||||
keychain::{persist, KeychainChangeSet, KeychainScan, KeychainTracker},
|
keychain::{DerivationAdditions, KeychainTxOutIndex},
|
||||||
sparse_chain, BlockId, ConfirmationTime,
|
local_chain::{self, LocalChain, UpdateNotConnectedError},
|
||||||
|
tx_graph::{CanonicalTx, TxGraph},
|
||||||
|
Anchor, Append, BlockId, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, ObservedAs,
|
||||||
|
Persist, PersistBackend,
|
||||||
};
|
};
|
||||||
use bitcoin::consensus::encode::serialize;
|
use bitcoin::consensus::encode::serialize;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
@ -83,19 +86,83 @@ const COINBASE_MATURITY: u32 = 100;
|
|||||||
pub struct Wallet<D = ()> {
|
pub struct Wallet<D = ()> {
|
||||||
signers: Arc<SignersContainer>,
|
signers: Arc<SignersContainer>,
|
||||||
change_signers: Arc<SignersContainer>,
|
change_signers: Arc<SignersContainer>,
|
||||||
keychain_tracker: KeychainTracker<KeychainKind, ConfirmationTime>,
|
chain: LocalChain,
|
||||||
persist: persist::Persist<KeychainKind, ConfirmationTime, D>,
|
indexed_graph: IndexedTxGraph<ConfirmationTimeAnchor, KeychainTxOutIndex<KeychainKind>>,
|
||||||
|
persist: Persist<D, ChangeSet>, // [TODO] Use a different `ChangeSet`
|
||||||
network: Network,
|
network: Network,
|
||||||
secp: SecpCtx,
|
secp: SecpCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources.
|
/// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources.
|
||||||
/// The type parameter `T` indicates the kind of transaction contained in the update. It's usually a [`bitcoin::Transaction`].
|
/// The type parameter `T` indicates the kind of transaction contained in the update. It's usually a [`bitcoin::Transaction`].
|
||||||
pub type Update = KeychainScan<KeychainKind, ConfirmationTime>;
|
#[derive(Debug, Default, PartialEq)]
|
||||||
/// Error indicating that something was wrong with an [`Update<T>`].
|
pub struct Update<K = KeychainKind, A = ConfirmationTimeAnchor> {
|
||||||
pub type UpdateError = chain_graph::UpdateError<ConfirmationTime>;
|
keychain: BTreeMap<K, u32>,
|
||||||
|
graph: TxGraph<A>,
|
||||||
|
chain: LocalChain,
|
||||||
|
}
|
||||||
|
|
||||||
/// The changeset produced internally by applying an update
|
/// The changeset produced internally by applying an update
|
||||||
pub(crate) type ChangeSet = KeychainChangeSet<KeychainKind, ConfirmationTime>;
|
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(bound(
|
||||||
|
deserialize = "A: Ord + serde::Deserialize<'de>, K: Ord + serde::Deserialize<'de>",
|
||||||
|
serialize = "A: Ord + serde::Serialize, K: Ord + serde::Serialize"
|
||||||
|
))]
|
||||||
|
// #[cfg_attr(predicate, attr)]
|
||||||
|
pub struct ChangeSet<K = KeychainKind, A = ConfirmationTimeAnchor> {
|
||||||
|
pub chain_changeset: local_chain::ChangeSet,
|
||||||
|
pub indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, A> Default for ChangeSet<K, A> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
chain_changeset: Default::default(),
|
||||||
|
indexed_additions: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Ord, A: Anchor> Append for ChangeSet<K, A> {
|
||||||
|
fn append(&mut self, other: Self) {
|
||||||
|
Append::append(&mut self.chain_changeset, other.chain_changeset);
|
||||||
|
Append::append(&mut self.indexed_additions, other.indexed_additions);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.chain_changeset.is_empty() && self.indexed_additions.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, A> From<IndexedAdditions<A, DerivationAdditions<K>>> for ChangeSet<K, A> {
|
||||||
|
fn from(indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>) -> Self {
|
||||||
|
Self {
|
||||||
|
indexed_additions,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, A> From<DerivationAdditions<K>> for ChangeSet<K, A> {
|
||||||
|
fn from(index_additions: DerivationAdditions<K>) -> Self {
|
||||||
|
Self {
|
||||||
|
indexed_additions: IndexedAdditions {
|
||||||
|
index_additions,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, A> From<local_chain::ChangeSet> for ChangeSet<K, A> {
|
||||||
|
fn from(chain_changeset: local_chain::ChangeSet) -> Self {
|
||||||
|
Self {
|
||||||
|
chain_changeset,
|
||||||
|
..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`.
|
||||||
@ -182,6 +249,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InsertTxError {
|
||||||
|
ConfirmationHeightCannotBeGreaterThanTip {
|
||||||
|
tip_height: Option<u32>,
|
||||||
|
tx_height: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl<P: core::fmt::Display + core::fmt::Debug> std::error::Error for NewError<P> {}
|
impl<P: core::fmt::Display + core::fmt::Debug> std::error::Error for NewError<P> {}
|
||||||
|
|
||||||
@ -195,15 +270,17 @@ impl<D> Wallet<D> {
|
|||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<Self, NewError<D::LoadError>>
|
) -> Result<Self, NewError<D::LoadError>>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: PersistBackend<ChangeSet>,
|
||||||
{
|
{
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
let mut chain = LocalChain::default();
|
||||||
|
let mut indexed_graph =
|
||||||
|
IndexedTxGraph::<ConfirmationTimeAnchor, KeychainTxOutIndex<KeychainKind>>::default();
|
||||||
|
|
||||||
let mut keychain_tracker = KeychainTracker::default();
|
|
||||||
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)
|
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)
|
||||||
.map_err(NewError::Descriptor)?;
|
.map_err(NewError::Descriptor)?;
|
||||||
keychain_tracker
|
indexed_graph
|
||||||
.txout_index
|
.index
|
||||||
.add_keychain(KeychainKind::External, descriptor.clone());
|
.add_keychain(KeychainKind::External, descriptor.clone());
|
||||||
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, &secp));
|
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, &secp));
|
||||||
let change_signers = match change_descriptor {
|
let change_signers = match change_descriptor {
|
||||||
@ -218,8 +295,8 @@ impl<D> Wallet<D> {
|
|||||||
&secp,
|
&secp,
|
||||||
));
|
));
|
||||||
|
|
||||||
keychain_tracker
|
indexed_graph
|
||||||
.txout_index
|
.index
|
||||||
.add_keychain(KeychainKind::Internal, change_descriptor);
|
.add_keychain(KeychainKind::Internal, change_descriptor);
|
||||||
|
|
||||||
change_signers
|
change_signers
|
||||||
@ -227,18 +304,20 @@ impl<D> Wallet<D> {
|
|||||||
None => Arc::new(SignersContainer::new()),
|
None => Arc::new(SignersContainer::new()),
|
||||||
};
|
};
|
||||||
|
|
||||||
db.load_into_keychain_tracker(&mut keychain_tracker)
|
let changeset = db.load_from_persistence().map_err(NewError::Persist)?;
|
||||||
.map_err(NewError::Persist)?;
|
chain.apply_changeset(changeset.chain_changeset);
|
||||||
|
indexed_graph.apply_additions(changeset.indexed_additions);
|
||||||
|
|
||||||
let persist = persist::Persist::new(db);
|
let persist = Persist::new(db);
|
||||||
|
|
||||||
Ok(Wallet {
|
Ok(Wallet {
|
||||||
signers,
|
signers,
|
||||||
change_signers,
|
change_signers,
|
||||||
network,
|
network,
|
||||||
|
chain,
|
||||||
|
indexed_graph,
|
||||||
persist,
|
persist,
|
||||||
secp,
|
secp,
|
||||||
keychain_tracker,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +328,7 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
/// Iterator over all keychains in this wallet
|
/// Iterator over all keychains in this wallet
|
||||||
pub fn keychains(&self) -> &BTreeMap<KeychainKind, ExtendedDescriptor> {
|
pub fn keychains(&self) -> &BTreeMap<KeychainKind, ExtendedDescriptor> {
|
||||||
self.keychain_tracker.txout_index.keychains()
|
self.indexed_graph.index.keychains()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a derived address using the external descriptor, see [`AddressIndex`] for
|
/// Return a derived address using the external descriptor, see [`AddressIndex`] for
|
||||||
@ -257,7 +336,7 @@ impl<D> Wallet<D> {
|
|||||||
/// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
|
/// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
|
||||||
pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo
|
pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: PersistBackend<ChangeSet>,
|
||||||
{
|
{
|
||||||
self._get_address(address_index, KeychainKind::External)
|
self._get_address(address_index, KeychainKind::External)
|
||||||
}
|
}
|
||||||
@ -271,17 +350,17 @@ impl<D> Wallet<D> {
|
|||||||
/// be returned for any [`AddressIndex`].
|
/// be returned for any [`AddressIndex`].
|
||||||
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo
|
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: PersistBackend<ChangeSet>,
|
||||||
{
|
{
|
||||||
self._get_address(address_index, KeychainKind::Internal)
|
self._get_address(address_index, KeychainKind::Internal)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo
|
fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: PersistBackend<ChangeSet>,
|
||||||
{
|
{
|
||||||
let keychain = self.map_keychain(keychain);
|
let keychain = self.map_keychain(keychain);
|
||||||
let txout_index = &mut self.keychain_tracker.txout_index;
|
let txout_index = &mut self.indexed_graph.index;
|
||||||
let (index, spk) = match address_index {
|
let (index, spk) = match address_index {
|
||||||
AddressIndex::New => {
|
AddressIndex::New => {
|
||||||
let ((index, spk), changeset) = txout_index.reveal_next_spk(&keychain);
|
let ((index, spk), changeset) = txout_index.reveal_next_spk(&keychain);
|
||||||
@ -320,42 +399,36 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
/// Return whether or not a `script` is part of this wallet (either internal or external)
|
/// Return whether or not a `script` is part of this wallet (either internal or external)
|
||||||
pub fn is_mine(&self, script: &Script) -> bool {
|
pub fn is_mine(&self, script: &Script) -> bool {
|
||||||
self.keychain_tracker
|
self.indexed_graph.index.index_of_spk(script).is_some()
|
||||||
.txout_index
|
|
||||||
.index_of_spk(script)
|
|
||||||
.is_some()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds how the wallet derived the script pubkey `spk`.
|
/// Finds how the wallet derived the script pubkey `spk`.
|
||||||
///
|
///
|
||||||
/// Will only return `Some(_)` if the wallet has given out the spk.
|
/// Will only return `Some(_)` if the wallet has given out the spk.
|
||||||
pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> {
|
pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> {
|
||||||
self.keychain_tracker.txout_index.index_of_spk(spk).copied()
|
self.indexed_graph.index.index_of_spk(spk).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the list of unspent outputs of this wallet
|
/// Return the list of unspent outputs of this wallet
|
||||||
pub fn list_unspent(&self) -> Vec<LocalUtxo> {
|
pub fn list_unspent(&self) -> impl Iterator<Item = LocalUtxo> + '_ {
|
||||||
self.keychain_tracker
|
self.indexed_graph
|
||||||
.full_utxos()
|
.graph()
|
||||||
.map(|(&(keychain, derivation_index), utxo)| LocalUtxo {
|
.filter_chain_unspents(
|
||||||
outpoint: utxo.outpoint,
|
&self.chain,
|
||||||
txout: utxo.txout,
|
self.chain.tip().unwrap_or_default(),
|
||||||
keychain,
|
self.indexed_graph.index.outpoints().iter().cloned(),
|
||||||
is_spent: false,
|
)
|
||||||
derivation_index,
|
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
|
||||||
confirmation_time: utxo.chain_position,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all the checkpoints the wallet is currently storing indexed by height.
|
/// Get all the checkpoints the wallet is currently storing indexed by height.
|
||||||
pub fn checkpoints(&self) -> &BTreeMap<u32, BlockHash> {
|
pub fn checkpoints(&self) -> &BTreeMap<u32, BlockHash> {
|
||||||
self.keychain_tracker.chain().checkpoints()
|
self.chain.blocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the latest checkpoint.
|
/// Returns the latest checkpoint.
|
||||||
pub fn latest_checkpoint(&self) -> Option<BlockId> {
|
pub fn latest_checkpoint(&self) -> Option<BlockId> {
|
||||||
self.keychain_tracker.chain().latest_checkpoint()
|
self.chain.tip()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a iterators of all the script pubkeys for the `Internal` and External` variants in `KeychainKind`.
|
/// Returns a iterators of all the script pubkeys for the `Internal` and External` variants in `KeychainKind`.
|
||||||
@ -369,7 +442,7 @@ impl<D> Wallet<D> {
|
|||||||
pub fn spks_of_all_keychains(
|
pub fn spks_of_all_keychains(
|
||||||
&self,
|
&self,
|
||||||
) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, Script)> + Clone> {
|
) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, Script)> + Clone> {
|
||||||
self.keychain_tracker.txout_index.spks_of_all_keychains()
|
self.indexed_graph.index.spks_of_all_keychains()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets an iterator over all the script pubkeys in a single keychain.
|
/// Gets an iterator over all the script pubkeys in a single keychain.
|
||||||
@ -381,30 +454,22 @@ impl<D> Wallet<D> {
|
|||||||
&self,
|
&self,
|
||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
) -> impl Iterator<Item = (u32, Script)> + Clone {
|
) -> impl Iterator<Item = (u32, Script)> + Clone {
|
||||||
self.keychain_tracker
|
self.indexed_graph.index.spks_of_keychain(&keychain)
|
||||||
.txout_index
|
|
||||||
.spks_of_keychain(&keychain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
|
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
|
||||||
/// wallet's database.
|
/// wallet's database.
|
||||||
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalUtxo> {
|
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalUtxo> {
|
||||||
self.keychain_tracker
|
let (&spk_i, _) = self.indexed_graph.index.txout(op)?;
|
||||||
.full_utxos()
|
self.indexed_graph
|
||||||
.find_map(|(&(keychain, derivation_index), txo)| {
|
.graph()
|
||||||
if op == txo.outpoint {
|
.filter_chain_unspents(
|
||||||
Some(LocalUtxo {
|
&self.chain,
|
||||||
outpoint: txo.outpoint,
|
self.chain.tip().unwrap_or_default(),
|
||||||
txout: txo.txout,
|
core::iter::once((spk_i, op)),
|
||||||
keychain,
|
)
|
||||||
is_spent: txo.spent_by.is_none(),
|
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
|
||||||
derivation_index,
|
.next()
|
||||||
confirmation_time: txo.chain_position,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a single transactions made and received by the wallet
|
/// Return a single transactions made and received by the wallet
|
||||||
@ -412,54 +477,22 @@ impl<D> Wallet<D> {
|
|||||||
/// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
|
/// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
|
||||||
/// `include_raw` is `true`.
|
/// `include_raw` is `true`.
|
||||||
pub fn get_tx(&self, txid: Txid, include_raw: bool) -> Option<TransactionDetails> {
|
pub fn get_tx(&self, txid: Txid, include_raw: bool) -> Option<TransactionDetails> {
|
||||||
let (&confirmation_time, tx) = self.keychain_tracker.chain_graph().get_tx_in_chain(txid)?;
|
let graph = self.indexed_graph.graph();
|
||||||
let graph = self.keychain_tracker.graph();
|
|
||||||
let txout_index = &self.keychain_tracker.txout_index;
|
|
||||||
|
|
||||||
let received = tx
|
let canonical_tx = CanonicalTx {
|
||||||
.output
|
observed_as: graph.get_chain_position(
|
||||||
.iter()
|
&self.chain,
|
||||||
.map(|txout| {
|
self.chain.tip().unwrap_or_default(),
|
||||||
if txout_index.index_of_spk(&txout.script_pubkey).is_some() {
|
txid,
|
||||||
txout.value
|
)?,
|
||||||
} else {
|
node: graph.get_tx_node(txid)?,
|
||||||
0
|
};
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
let sent = tx
|
Some(new_tx_details(
|
||||||
.input
|
&self.indexed_graph,
|
||||||
.iter()
|
canonical_tx,
|
||||||
.map(|txin| {
|
include_raw,
|
||||||
if let Some((_, txout)) = txout_index.txout(txin.previous_output) {
|
))
|
||||||
txout.value
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
let inputs = tx
|
|
||||||
.input
|
|
||||||
.iter()
|
|
||||||
.map(|txin| {
|
|
||||||
graph
|
|
||||||
.get_txout(txin.previous_output)
|
|
||||||
.map(|txout| txout.value)
|
|
||||||
})
|
|
||||||
.sum::<Option<u64>>();
|
|
||||||
let outputs = tx.output.iter().map(|txout| txout.value).sum();
|
|
||||||
let fee = inputs.map(|inputs| inputs.saturating_sub(outputs));
|
|
||||||
|
|
||||||
Some(TransactionDetails {
|
|
||||||
transaction: if include_raw { Some(tx.clone()) } else { None },
|
|
||||||
txid,
|
|
||||||
received,
|
|
||||||
sent,
|
|
||||||
fee,
|
|
||||||
confirmation_time,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new checkpoint to the wallet's internal view of the chain.
|
/// Add a new checkpoint to the wallet's internal view of the chain.
|
||||||
@ -472,10 +505,15 @@ impl<D> Wallet<D> {
|
|||||||
pub fn insert_checkpoint(
|
pub fn insert_checkpoint(
|
||||||
&mut self,
|
&mut self,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
) -> Result<bool, sparse_chain::InsertCheckpointError> {
|
) -> Result<bool, local_chain::InsertBlockNotMatchingError>
|
||||||
let changeset = self.keychain_tracker.insert_checkpoint(block_id)?;
|
where
|
||||||
let changed = changeset.is_empty();
|
D: PersistBackend<ChangeSet>,
|
||||||
self.persist.stage(changeset);
|
{
|
||||||
|
let changeset = self.chain.insert_block(block_id)?;
|
||||||
|
let changed = !changeset.is_empty();
|
||||||
|
if changed {
|
||||||
|
self.persist.stage(changeset.into());
|
||||||
|
}
|
||||||
Ok(changed)
|
Ok(changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,41 +535,80 @@ impl<D> Wallet<D> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
position: ConfirmationTime,
|
position: ConfirmationTime,
|
||||||
) -> Result<bool, chain_graph::InsertTxError<ConfirmationTime>> {
|
seen_at: Option<u64>,
|
||||||
let changeset = self.keychain_tracker.insert_tx(tx, position)?;
|
) -> Result<bool, InsertTxError>
|
||||||
let changed = changeset.is_empty();
|
where
|
||||||
self.persist.stage(changeset);
|
D: PersistBackend<ChangeSet>,
|
||||||
|
{
|
||||||
|
let tip = self.chain.tip();
|
||||||
|
|
||||||
|
if let ConfirmationTime::Confirmed { height, .. } = position {
|
||||||
|
let tip_height = tip.map(|b| b.height);
|
||||||
|
if Some(height) > tip_height {
|
||||||
|
return Err(InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
|
||||||
|
tip_height,
|
||||||
|
tx_height: height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let anchor = match position {
|
||||||
|
ConfirmationTime::Confirmed { height, time } => {
|
||||||
|
let tip_height = tip.map(|b| b.height);
|
||||||
|
if Some(height) > tip_height {
|
||||||
|
return Err(InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
|
||||||
|
tip_height,
|
||||||
|
tx_height: height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(ConfirmationTimeAnchor {
|
||||||
|
anchor_block: tip.expect("already checked if tip_height > height"),
|
||||||
|
confirmation_height: height,
|
||||||
|
confirmation_time: time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ConfirmationTime::Unconfirmed => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let changeset: ChangeSet = self.indexed_graph.insert_tx(&tx, anchor, seen_at).into();
|
||||||
|
let changed = !changeset.is_empty();
|
||||||
|
if changed {
|
||||||
|
self.persist.stage(changeset);
|
||||||
|
}
|
||||||
Ok(changed)
|
Ok(changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deprecated(note = "use Wallet::transactions instead")]
|
#[deprecated(note = "use Wallet::transactions instead")]
|
||||||
/// Deprecated. use `Wallet::transactions` instead.
|
/// Deprecated. use `Wallet::transactions` instead.
|
||||||
pub fn list_transactions(&self, include_raw: bool) -> Vec<TransactionDetails> {
|
pub fn list_transactions(
|
||||||
self.keychain_tracker
|
&self,
|
||||||
.chain()
|
include_raw: bool,
|
||||||
.txids()
|
) -> impl Iterator<Item = TransactionDetails> + '_ {
|
||||||
.map(|&(_, txid)| self.get_tx(txid, include_raw).expect("must exist"))
|
self.indexed_graph
|
||||||
.collect()
|
.graph()
|
||||||
|
.list_chain_txs(&self.chain, self.chain.tip().unwrap_or_default())
|
||||||
|
.map(move |canonical_tx| new_tx_details(&self.indexed_graph, canonical_tx, include_raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the transactions in the wallet in order of ascending confirmation time with
|
/// Iterate over the transactions in the wallet in order of ascending confirmation time with
|
||||||
/// unconfirmed transactions last.
|
/// unconfirmed transactions last.
|
||||||
pub fn transactions(
|
pub fn transactions(
|
||||||
&self,
|
&self,
|
||||||
) -> impl DoubleEndedIterator<Item = (ConfirmationTime, &Transaction)> + '_ {
|
) -> impl Iterator<Item = CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>> + '_ {
|
||||||
self.keychain_tracker
|
self.indexed_graph
|
||||||
.chain_graph()
|
.graph()
|
||||||
.transactions_in_chain()
|
.list_chain_txs(&self.chain, self.chain.tip().unwrap_or_default())
|
||||||
.map(|(pos, tx)| (*pos, tx))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
|
/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
|
||||||
/// values.
|
/// values.
|
||||||
pub fn get_balance(&self) -> Balance {
|
pub fn get_balance(&self) -> Balance {
|
||||||
self.keychain_tracker.balance(|keychain| match keychain {
|
self.indexed_graph.graph().balance(
|
||||||
KeychainKind::External => false,
|
&self.chain,
|
||||||
KeychainKind::Internal => true,
|
self.chain.tip().unwrap_or_default(),
|
||||||
})
|
self.indexed_graph.index.outpoints().iter().cloned(),
|
||||||
|
|&(k, _), _| k == KeychainKind::Internal,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an external signer
|
/// Add an external signer
|
||||||
@ -613,17 +690,17 @@ impl<D> Wallet<D> {
|
|||||||
params: TxParams,
|
params: TxParams,
|
||||||
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
|
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: PersistBackend<ChangeSet>,
|
||||||
{
|
{
|
||||||
let external_descriptor = self
|
let external_descriptor = self
|
||||||
.keychain_tracker
|
.indexed_graph
|
||||||
.txout_index
|
.index
|
||||||
.keychains()
|
.keychains()
|
||||||
.get(&KeychainKind::External)
|
.get(&KeychainKind::External)
|
||||||
.expect("must exist");
|
.expect("must exist");
|
||||||
let internal_descriptor = self
|
let internal_descriptor = self
|
||||||
.keychain_tracker
|
.indexed_graph
|
||||||
.txout_index
|
.index
|
||||||
.keychains()
|
.keychains()
|
||||||
.get(&KeychainKind::Internal);
|
.get(&KeychainKind::Internal);
|
||||||
|
|
||||||
@ -700,9 +777,8 @@ impl<D> Wallet<D> {
|
|||||||
let current_height = match params.current_height {
|
let current_height = match params.current_height {
|
||||||
// If they didn't tell us the current height, we assume it's the latest sync height.
|
// If they didn't tell us the current height, we assume it's the latest sync height.
|
||||||
None => self
|
None => self
|
||||||
.keychain_tracker
|
.chain
|
||||||
.chain()
|
.tip()
|
||||||
.latest_checkpoint()
|
|
||||||
.and_then(|cp| cp.height.into())
|
.and_then(|cp| cp.height.into())
|
||||||
.map(|height| LockTime::from_height(height).expect("Invalid height")),
|
.map(|height| LockTime::from_height(height).expect("Invalid height")),
|
||||||
h => h,
|
h => h,
|
||||||
@ -874,14 +950,10 @@ impl<D> Wallet<D> {
|
|||||||
Some(ref drain_recipient) => drain_recipient.clone(),
|
Some(ref drain_recipient) => drain_recipient.clone(),
|
||||||
None => {
|
None => {
|
||||||
let change_keychain = self.map_keychain(KeychainKind::Internal);
|
let change_keychain = self.map_keychain(KeychainKind::Internal);
|
||||||
let ((index, spk), changeset) = self
|
let ((index, spk), changeset) =
|
||||||
.keychain_tracker
|
self.indexed_graph.index.next_unused_spk(&change_keychain);
|
||||||
.txout_index
|
|
||||||
.next_unused_spk(&change_keychain);
|
|
||||||
let spk = spk.clone();
|
let spk = spk.clone();
|
||||||
self.keychain_tracker
|
self.indexed_graph.index.mark_used(&change_keychain, index);
|
||||||
.txout_index
|
|
||||||
.mark_used(&change_keychain, index);
|
|
||||||
self.persist.stage(changeset.into());
|
self.persist.stage(changeset.into());
|
||||||
self.persist.commit().expect("TODO");
|
self.persist.commit().expect("TODO");
|
||||||
spk
|
spk
|
||||||
@ -1019,16 +1091,21 @@ impl<D> Wallet<D> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
txid: Txid,
|
txid: Txid,
|
||||||
) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
|
) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
|
||||||
let graph = self.keychain_tracker.graph();
|
let graph = self.indexed_graph.graph();
|
||||||
let txout_index = &self.keychain_tracker.txout_index;
|
let txout_index = &self.indexed_graph.index;
|
||||||
let tx_and_height = self.keychain_tracker.chain_graph().get_tx_in_chain(txid);
|
let chain_tip = self.chain.tip().unwrap_or_default();
|
||||||
let mut tx = match tx_and_height {
|
|
||||||
None => return Err(Error::TransactionNotFound),
|
let mut tx = graph
|
||||||
Some((ConfirmationTime::Confirmed { .. }, _tx)) => {
|
.get_tx(txid)
|
||||||
return Err(Error::TransactionConfirmed)
|
.ok_or(Error::TransactionNotFound)?
|
||||||
}
|
.clone();
|
||||||
Some((_, tx)) => tx.clone(),
|
|
||||||
};
|
let pos = graph
|
||||||
|
.get_chain_position(&self.chain, chain_tip, txid)
|
||||||
|
.ok_or(Error::TransactionNotFound)?;
|
||||||
|
if let ObservedAs::Confirmed(_) = pos {
|
||||||
|
return Err(Error::TransactionConfirmed);
|
||||||
|
}
|
||||||
|
|
||||||
if !tx
|
if !tx
|
||||||
.input
|
.input
|
||||||
@ -1051,13 +1128,17 @@ impl<D> Wallet<D> {
|
|||||||
let original_utxos = original_txin
|
let original_utxos = original_txin
|
||||||
.iter()
|
.iter()
|
||||||
.map(|txin| -> Result<_, Error> {
|
.map(|txin| -> Result<_, Error> {
|
||||||
let (&confirmation_time, prev_tx) = self
|
let prev_tx = graph
|
||||||
.keychain_tracker
|
.get_tx(txin.previous_output.txid)
|
||||||
.chain_graph()
|
|
||||||
.get_tx_in_chain(txin.previous_output.txid)
|
|
||||||
.ok_or(Error::UnknownUtxo)?;
|
.ok_or(Error::UnknownUtxo)?;
|
||||||
let txout = &prev_tx.output[txin.previous_output.vout as usize];
|
let txout = &prev_tx.output[txin.previous_output.vout as usize];
|
||||||
|
|
||||||
|
let confirmation_time: ConfirmationTime = graph
|
||||||
|
.get_chain_position(&self.chain, chain_tip, txin.previous_output.txid)
|
||||||
|
.ok_or(Error::UnknownUtxo)?
|
||||||
|
.cloned()
|
||||||
|
.into();
|
||||||
|
|
||||||
let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
|
let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
|
||||||
Some(&(keychain, derivation_index)) => {
|
Some(&(keychain, derivation_index)) => {
|
||||||
let satisfaction_weight = self
|
let satisfaction_weight = self
|
||||||
@ -1231,7 +1312,7 @@ impl<D> Wallet<D> {
|
|||||||
///
|
///
|
||||||
/// This can be used to build a watch-only version of a wallet
|
/// This can be used to build a watch-only version of a wallet
|
||||||
pub fn public_descriptor(&self, keychain: KeychainKind) -> Option<&ExtendedDescriptor> {
|
pub fn public_descriptor(&self, keychain: KeychainKind) -> Option<&ExtendedDescriptor> {
|
||||||
self.keychain_tracker.txout_index.keychains().get(&keychain)
|
self.indexed_graph.index.keychains().get(&keychain)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finalize a PSBT, i.e., for each input determine if sufficient data is available to pass
|
/// Finalize a PSBT, i.e., for each input determine if sufficient data is available to pass
|
||||||
@ -1247,6 +1328,8 @@ impl<D> Wallet<D> {
|
|||||||
psbt: &mut psbt::PartiallySignedTransaction,
|
psbt: &mut psbt::PartiallySignedTransaction,
|
||||||
sign_options: SignOptions,
|
sign_options: SignOptions,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
|
let chain_tip = self.chain.tip().unwrap_or_default();
|
||||||
|
|
||||||
let tx = &psbt.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
let mut finished = true;
|
let mut finished = true;
|
||||||
|
|
||||||
@ -1259,19 +1342,16 @@ impl<D> Wallet<D> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let confirmation_height = self
|
let confirmation_height = self
|
||||||
.keychain_tracker
|
.indexed_graph
|
||||||
.chain()
|
.graph()
|
||||||
.tx_position(input.previous_output.txid)
|
.get_chain_position(&self.chain, chain_tip, input.previous_output.txid)
|
||||||
.map(|conftime| match conftime {
|
.map(|observed_as| match observed_as {
|
||||||
&ConfirmationTime::Confirmed { height, .. } => height,
|
ObservedAs::Confirmed(a) => a.confirmation_height,
|
||||||
ConfirmationTime::Unconfirmed => u32::MAX,
|
ObservedAs::Unconfirmed(_) => u32::MAX,
|
||||||
});
|
});
|
||||||
let last_sync_height = self
|
let current_height = sign_options
|
||||||
.keychain_tracker
|
.assume_height
|
||||||
.chain()
|
.or(self.chain.tip().map(|b| b.height));
|
||||||
.latest_checkpoint()
|
|
||||||
.map(|block_id| block_id.height);
|
|
||||||
let current_height = sign_options.assume_height.or(last_sync_height);
|
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Input #{} - {}, using `confirmation_height` = {:?}, `current_height` = {:?}",
|
"Input #{} - {}, using `confirmation_height` = {:?}, `current_height` = {:?}",
|
||||||
@ -1288,8 +1368,8 @@ impl<D> Wallet<D> {
|
|||||||
.get_utxo_for(n)
|
.get_utxo_for(n)
|
||||||
.and_then(|txout| self.get_descriptor_for_txout(&txout))
|
.and_then(|txout| self.get_descriptor_for_txout(&txout))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.keychain_tracker
|
self.indexed_graph
|
||||||
.txout_index
|
.index
|
||||||
.keychains()
|
.keychains()
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|(_, desc)| {
|
.find_map(|(_, desc)| {
|
||||||
@ -1347,14 +1427,12 @@ impl<D> Wallet<D> {
|
|||||||
/// The derivation index of this wallet. It will return `None` if it has not derived any addresses.
|
/// The derivation index of this wallet. It will return `None` if it has not derived any addresses.
|
||||||
/// Otherwise, it will return the index of the highest address it has derived.
|
/// Otherwise, it will return the index of the highest address it has derived.
|
||||||
pub fn derivation_index(&self, keychain: KeychainKind) -> Option<u32> {
|
pub fn derivation_index(&self, keychain: KeychainKind) -> Option<u32> {
|
||||||
self.keychain_tracker
|
self.indexed_graph.index.last_revealed_index(&keychain)
|
||||||
.txout_index
|
|
||||||
.last_revealed_index(&keychain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The index of the next address that you would get if you were to ask the wallet for a new address
|
/// The index of the next address that you would get if you were to ask the wallet for a new address
|
||||||
pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 {
|
pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 {
|
||||||
self.keychain_tracker.txout_index.next_index(&keychain).0
|
self.indexed_graph.index.next_index(&keychain).0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Informs the wallet that you no longer intend to broadcast a tx that was built from it.
|
/// Informs the wallet that you no longer intend to broadcast a tx that was built from it.
|
||||||
@ -1362,7 +1440,7 @@ impl<D> Wallet<D> {
|
|||||||
/// This frees up the change address used when creating the tx for use in future transactions.
|
/// This frees up the change address used when creating the tx for use in future transactions.
|
||||||
// TODO: Make this free up reserved utxos when that's implemented
|
// TODO: Make this free up reserved utxos when that's implemented
|
||||||
pub fn cancel_tx(&mut self, tx: &Transaction) {
|
pub fn cancel_tx(&mut self, tx: &Transaction) {
|
||||||
let txout_index = &mut self.keychain_tracker.txout_index;
|
let txout_index = &mut self.indexed_graph.index;
|
||||||
for txout in &tx.output {
|
for txout in &tx.output {
|
||||||
if let Some(&(keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
|
if let Some(&(keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
|
||||||
// NOTE: unmark_used will **not** make something unused if it has actually been used
|
// NOTE: unmark_used will **not** make something unused if it has actually been used
|
||||||
@ -1384,8 +1462,8 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
|
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
|
||||||
let &(keychain, child) = self
|
let &(keychain, child) = self
|
||||||
.keychain_tracker
|
.indexed_graph
|
||||||
.txout_index
|
.index
|
||||||
.index_of_spk(&txout.script_pubkey)?;
|
.index_of_spk(&txout.script_pubkey)?;
|
||||||
let descriptor = self.get_descriptor_for_keychain(keychain);
|
let descriptor = self.get_descriptor_for_keychain(keychain);
|
||||||
Some(descriptor.at_derivation_index(child))
|
Some(descriptor.at_derivation_index(child))
|
||||||
@ -1393,7 +1471,6 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
fn get_available_utxos(&self) -> Vec<(LocalUtxo, usize)> {
|
fn get_available_utxos(&self) -> Vec<(LocalUtxo, usize)> {
|
||||||
self.list_unspent()
|
self.list_unspent()
|
||||||
.into_iter()
|
|
||||||
.map(|utxo| {
|
.map(|utxo| {
|
||||||
let keychain = utxo.keychain;
|
let keychain = utxo.keychain;
|
||||||
(
|
(
|
||||||
@ -1419,6 +1496,7 @@ impl<D> Wallet<D> {
|
|||||||
must_only_use_confirmed_tx: bool,
|
must_only_use_confirmed_tx: bool,
|
||||||
current_height: Option<u32>,
|
current_height: Option<u32>,
|
||||||
) -> (Vec<WeightedUtxo>, Vec<WeightedUtxo>) {
|
) -> (Vec<WeightedUtxo>, Vec<WeightedUtxo>) {
|
||||||
|
let chain_tip = self.chain.tip().unwrap_or_default();
|
||||||
// must_spend <- manually selected utxos
|
// must_spend <- manually selected utxos
|
||||||
// may_spend <- all other available utxos
|
// may_spend <- all other available utxos
|
||||||
let mut may_spend = self.get_available_utxos();
|
let mut may_spend = self.get_available_utxos();
|
||||||
@ -1438,39 +1516,43 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
let satisfies_confirmed = may_spend
|
let satisfies_confirmed = may_spend
|
||||||
.iter()
|
.iter()
|
||||||
.map(|u| {
|
.map(|u| -> bool {
|
||||||
let txid = u.0.outpoint.txid;
|
let txid = u.0.outpoint.txid;
|
||||||
let tx = self.keychain_tracker.chain_graph().get_tx_in_chain(txid);
|
let tx = match self.indexed_graph.graph().get_tx(txid) {
|
||||||
match tx {
|
Some(tx) => tx,
|
||||||
// We don't have the tx in the db for some reason,
|
None => return false,
|
||||||
// so we can't know for sure if it's mature or not.
|
};
|
||||||
// We prefer not to spend it.
|
let confirmation_time: ConfirmationTime = match self
|
||||||
None => false,
|
.indexed_graph
|
||||||
Some((confirmation_time, tx)) => {
|
.graph()
|
||||||
// Whether the UTXO is mature and, if needed, confirmed
|
.get_chain_position(&self.chain, chain_tip, txid)
|
||||||
let mut spendable = true;
|
{
|
||||||
if must_only_use_confirmed_tx && !confirmation_time.is_confirmed() {
|
Some(observed_as) => observed_as.cloned().into(),
|
||||||
return false;
|
None => return false,
|
||||||
}
|
};
|
||||||
if tx.is_coin_base() {
|
|
||||||
debug_assert!(
|
// Whether the UTXO is mature and, if needed, confirmed
|
||||||
confirmation_time.is_confirmed(),
|
let mut spendable = true;
|
||||||
"coinbase must always be confirmed"
|
if must_only_use_confirmed_tx && !confirmation_time.is_confirmed() {
|
||||||
);
|
return false;
|
||||||
if let Some(current_height) = current_height {
|
}
|
||||||
match confirmation_time {
|
if tx.is_coin_base() {
|
||||||
ConfirmationTime::Confirmed { height, .. } => {
|
debug_assert!(
|
||||||
// https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
|
confirmation_time.is_confirmed(),
|
||||||
spendable &= (current_height.saturating_sub(*height))
|
"coinbase must always be confirmed"
|
||||||
>= COINBASE_MATURITY;
|
);
|
||||||
}
|
if let Some(current_height) = current_height {
|
||||||
ConfirmationTime::Unconfirmed => spendable = false,
|
match confirmation_time {
|
||||||
}
|
ConfirmationTime::Confirmed { height, .. } => {
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
|
||||||
|
spendable &=
|
||||||
|
(current_height.saturating_sub(height)) >= COINBASE_MATURITY;
|
||||||
}
|
}
|
||||||
|
ConfirmationTime::Unconfirmed => spendable = false,
|
||||||
}
|
}
|
||||||
spendable
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
spendable
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@ -1590,8 +1672,8 @@ impl<D> Wallet<D> {
|
|||||||
// Try to find the prev_script in our db to figure out if this is internal or external,
|
// Try to find the prev_script in our db to figure out if this is internal or external,
|
||||||
// and the derivation index
|
// and the derivation index
|
||||||
let &(keychain, child) = self
|
let &(keychain, child) = self
|
||||||
.keychain_tracker
|
.indexed_graph
|
||||||
.txout_index
|
.index
|
||||||
.index_of_spk(&utxo.txout.script_pubkey)
|
.index_of_spk(&utxo.txout.script_pubkey)
|
||||||
.ok_or(Error::UnknownUtxo)?;
|
.ok_or(Error::UnknownUtxo)?;
|
||||||
|
|
||||||
@ -1608,7 +1690,7 @@ impl<D> Wallet<D> {
|
|||||||
.map_err(MiniscriptPsbtError::Conversion)?;
|
.map_err(MiniscriptPsbtError::Conversion)?;
|
||||||
|
|
||||||
let prev_output = utxo.outpoint;
|
let prev_output = utxo.outpoint;
|
||||||
if let Some(prev_tx) = self.keychain_tracker.graph().get_tx(prev_output.txid) {
|
if let Some(prev_tx) = self.indexed_graph.graph().get_tx(prev_output.txid) {
|
||||||
if desc.is_witness() || desc.is_taproot() {
|
if desc.is_witness() || desc.is_taproot() {
|
||||||
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
|
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
|
||||||
}
|
}
|
||||||
@ -1641,10 +1723,8 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
// Try to figure out the keychain and derivation for every input and output
|
// Try to figure out the keychain and derivation for every input and output
|
||||||
for (is_input, index, out) in utxos.into_iter() {
|
for (is_input, index, out) in utxos.into_iter() {
|
||||||
if let Some(&(keychain, child)) = self
|
if let Some(&(keychain, child)) =
|
||||||
.keychain_tracker
|
self.indexed_graph.index.index_of_spk(&out.script_pubkey)
|
||||||
.txout_index
|
|
||||||
.index_of_spk(&out.script_pubkey)
|
|
||||||
{
|
{
|
||||||
debug!(
|
debug!(
|
||||||
"Found descriptor for input #{} {:?}/{}",
|
"Found descriptor for input #{} {:?}/{}",
|
||||||
@ -1685,52 +1765,62 @@ impl<D> Wallet<D> {
|
|||||||
/// transactions related to your wallet into it.
|
/// transactions related to your wallet into it.
|
||||||
///
|
///
|
||||||
/// [`commit`]: Self::commit
|
/// [`commit`]: Self::commit
|
||||||
pub fn apply_update(&mut self, update: Update) -> Result<(), UpdateError>
|
pub fn apply_update(&mut self, update: Update) -> Result<bool, UpdateNotConnectedError>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: PersistBackend<ChangeSet>,
|
||||||
{
|
{
|
||||||
let changeset = self.keychain_tracker.apply_update(update)?;
|
let mut changeset: ChangeSet = self.chain.apply_update(update.chain)?.into();
|
||||||
self.persist.stage(changeset);
|
let (_, derivation_additions) = self
|
||||||
Ok(())
|
.indexed_graph
|
||||||
|
.index
|
||||||
|
.reveal_to_target_multi(&update.keychain);
|
||||||
|
changeset.append(derivation_additions.into());
|
||||||
|
changeset.append(self.indexed_graph.apply_update(update.graph).into());
|
||||||
|
|
||||||
|
let changed = !changeset.is_empty();
|
||||||
|
if changed {
|
||||||
|
self.persist.stage(changeset);
|
||||||
|
}
|
||||||
|
Ok(changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commits all curently [`staged`] changed to the persistence backend returning and error when this fails.
|
/// Commits all curently [`staged`] changed to the persistence backend returning and error when this fails.
|
||||||
///
|
///
|
||||||
/// [`staged`]: Self::staged
|
/// [`staged`]: Self::staged
|
||||||
pub fn commit(&mut self) -> Result<(), D::WriteError>
|
pub fn commit(&mut self) -> Result<bool, D::WriteError>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: PersistBackend<ChangeSet>,
|
||||||
{
|
{
|
||||||
self.persist.commit()
|
self.persist.commit().map(|c| c.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the changes that will be staged with the next call to [`commit`].
|
/// Returns the changes that will be staged with the next call to [`commit`].
|
||||||
///
|
///
|
||||||
/// [`commit`]: Self::commit
|
/// [`commit`]: Self::commit
|
||||||
pub fn staged(&self) -> &ChangeSet {
|
pub fn staged(&self) -> &ChangeSet
|
||||||
|
where
|
||||||
|
D: PersistBackend<ChangeSet>,
|
||||||
|
{
|
||||||
self.persist.staged()
|
self.persist.staged()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the inner [`TxGraph`](bdk_chain::tx_graph::TxGraph).
|
/// Get a reference to the inner [`TxGraph`](bdk_chain::tx_graph::TxGraph).
|
||||||
pub fn as_graph(&self) -> &bdk_chain::tx_graph::TxGraph {
|
pub fn as_graph(&self) -> &TxGraph<ConfirmationTimeAnchor> {
|
||||||
self.keychain_tracker.graph()
|
self.indexed_graph.graph()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the inner [`ChainGraph`](bdk_chain::chain_graph::ChainGraph).
|
pub fn as_index(&self) -> &KeychainTxOutIndex<KeychainKind> {
|
||||||
pub fn as_chain_graph(&self) -> &bdk_chain::chain_graph::ChainGraph<ConfirmationTime> {
|
&self.indexed_graph.index
|
||||||
self.keychain_tracker.chain_graph()
|
}
|
||||||
|
|
||||||
|
pub fn as_chain(&self) -> &LocalChain {
|
||||||
|
&self.chain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> AsRef<bdk_chain::tx_graph::TxGraph> for Wallet<D> {
|
impl<D> AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationTimeAnchor>> for Wallet<D> {
|
||||||
fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph {
|
fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph<ConfirmationTimeAnchor> {
|
||||||
self.keychain_tracker.graph()
|
self.indexed_graph.graph()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D> AsRef<bdk_chain::chain_graph::ChainGraph<ConfirmationTime>> for Wallet<D> {
|
|
||||||
fn as_ref(&self) -> &bdk_chain::chain_graph::ChainGraph<ConfirmationTime> {
|
|
||||||
self.keychain_tracker.chain_graph()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1765,6 +1855,76 @@ where
|
|||||||
Ok(wallet_name)
|
Ok(wallet_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_local_utxo(
|
||||||
|
keychain: KeychainKind,
|
||||||
|
derivation_index: u32,
|
||||||
|
full_txo: FullTxOut<ObservedAs<ConfirmationTimeAnchor>>,
|
||||||
|
) -> LocalUtxo {
|
||||||
|
LocalUtxo {
|
||||||
|
outpoint: full_txo.outpoint,
|
||||||
|
txout: full_txo.txout,
|
||||||
|
is_spent: full_txo.spent_by.is_some(),
|
||||||
|
confirmation_time: full_txo.chain_position.into(),
|
||||||
|
keychain,
|
||||||
|
derivation_index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_tx_details(
|
||||||
|
indexed_graph: &IndexedTxGraph<ConfirmationTimeAnchor, KeychainTxOutIndex<KeychainKind>>,
|
||||||
|
canonical_tx: CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>,
|
||||||
|
include_raw: bool,
|
||||||
|
) -> TransactionDetails {
|
||||||
|
let graph = indexed_graph.graph();
|
||||||
|
let index = &indexed_graph.index;
|
||||||
|
let tx = canonical_tx.node.tx;
|
||||||
|
|
||||||
|
let received = tx
|
||||||
|
.output
|
||||||
|
.iter()
|
||||||
|
.map(|txout| {
|
||||||
|
if index.index_of_spk(&txout.script_pubkey).is_some() {
|
||||||
|
txout.value
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let sent = tx
|
||||||
|
.input
|
||||||
|
.iter()
|
||||||
|
.map(|txin| {
|
||||||
|
if let Some((_, txout)) = index.txout(txin.previous_output) {
|
||||||
|
txout.value
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let inputs = tx
|
||||||
|
.input
|
||||||
|
.iter()
|
||||||
|
.map(|txin| {
|
||||||
|
graph
|
||||||
|
.get_txout(txin.previous_output)
|
||||||
|
.map(|txout| txout.value)
|
||||||
|
})
|
||||||
|
.sum::<Option<u64>>();
|
||||||
|
let outputs = tx.output.iter().map(|txout| txout.value).sum();
|
||||||
|
let fee = inputs.map(|inputs| inputs.saturating_sub(outputs));
|
||||||
|
|
||||||
|
TransactionDetails {
|
||||||
|
transaction: if include_raw { Some(tx.clone()) } else { None },
|
||||||
|
txid: canonical_tx.node.txid,
|
||||||
|
received,
|
||||||
|
sent,
|
||||||
|
fee,
|
||||||
|
confirmation_time: canonical_tx.observed_as.cloned().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Macro for getting a wallet for use in a doctest
|
/// Macro for getting a wallet for use in a doctest
|
||||||
@ -1796,7 +1956,7 @@ macro_rules! doctest_wallet {
|
|||||||
let _ = wallet.insert_tx(tx.clone(), ConfirmationTime::Confirmed {
|
let _ = wallet.insert_tx(tx.clone(), ConfirmationTime::Confirmed {
|
||||||
height: 500,
|
height: 500,
|
||||||
time: 50_000
|
time: 50_000
|
||||||
});
|
}, None);
|
||||||
|
|
||||||
wallet
|
wallet
|
||||||
}}
|
}}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
use crate::collections::BTreeMap;
|
use crate::collections::BTreeMap;
|
||||||
use crate::collections::HashSet;
|
use crate::collections::HashSet;
|
||||||
use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
|
use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
|
||||||
use bdk_chain::ConfirmationTime;
|
use bdk_chain::PersistBackend;
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt};
|
|||||||
use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction};
|
use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction};
|
||||||
|
|
||||||
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
|
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
|
||||||
use super::persist;
|
use super::ChangeSet;
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo},
|
types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo},
|
||||||
TransactionDetails,
|
TransactionDetails,
|
||||||
@ -529,7 +529,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
|
|||||||
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
|
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
|
||||||
pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error>
|
pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: PersistBackend<ChangeSet>,
|
||||||
{
|
{
|
||||||
self.wallet
|
self.wallet
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
@ -35,6 +35,7 @@ pub fn get_funded_wallet_with_change(
|
|||||||
height: 1_000,
|
height: 1_000,
|
||||||
time: 100,
|
time: 100,
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ fn receive_output(wallet: &mut Wallet, value: u64, height: TxHeight) -> OutPoint
|
|||||||
},
|
},
|
||||||
TxHeight::Unconfirmed => ConfirmationTime::Unconfirmed,
|
TxHeight::Unconfirmed => ConfirmationTime::Unconfirmed,
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -811,7 +812,7 @@ fn test_create_tx_add_utxo() {
|
|||||||
lock_time: PackedLockTime(0),
|
lock_time: PackedLockTime(0),
|
||||||
};
|
};
|
||||||
wallet
|
wallet
|
||||||
.insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed)
|
.insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed, None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
@ -848,7 +849,7 @@ fn test_create_tx_manually_selected_insufficient() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
wallet
|
wallet
|
||||||
.insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed)
|
.insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed, None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
@ -889,7 +890,9 @@ fn test_create_tx_policy_path_no_csv() {
|
|||||||
script_pubkey: wallet.get_address(New).script_pubkey(),
|
script_pubkey: wallet.get_address(New).script_pubkey(),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
|
let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
|
||||||
let root_id = external_policy.id;
|
let root_id = external_policy.id;
|
||||||
@ -972,7 +975,7 @@ fn test_add_foreign_utxo() {
|
|||||||
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
|
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
|
||||||
|
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let utxo = wallet2.list_unspent().remove(0);
|
let utxo = wallet2.list_unspent().next().expect("must take!");
|
||||||
let foreign_utxo_satisfaction = wallet2
|
let foreign_utxo_satisfaction = wallet2
|
||||||
.get_descriptor_for_keychain(KeychainKind::External)
|
.get_descriptor_for_keychain(KeychainKind::External)
|
||||||
.max_satisfaction_weight()
|
.max_satisfaction_weight()
|
||||||
@ -1036,7 +1039,7 @@ fn test_add_foreign_utxo() {
|
|||||||
#[should_panic(expected = "Generic(\"Foreign utxo missing witness_utxo or non_witness_utxo\")")]
|
#[should_panic(expected = "Generic(\"Foreign utxo missing witness_utxo or non_witness_utxo\")")]
|
||||||
fn test_add_foreign_utxo_invalid_psbt_input() {
|
fn test_add_foreign_utxo_invalid_psbt_input() {
|
||||||
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
|
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let outpoint = wallet.list_unspent()[0].outpoint;
|
let outpoint = wallet.list_unspent().next().expect("must exist").outpoint;
|
||||||
let foreign_utxo_satisfaction = wallet
|
let foreign_utxo_satisfaction = wallet
|
||||||
.get_descriptor_for_keychain(KeychainKind::External)
|
.get_descriptor_for_keychain(KeychainKind::External)
|
||||||
.max_satisfaction_weight()
|
.max_satisfaction_weight()
|
||||||
@ -1054,7 +1057,7 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
|
|||||||
let (wallet2, txid2) =
|
let (wallet2, txid2) =
|
||||||
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
|
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
|
||||||
|
|
||||||
let utxo2 = wallet2.list_unspent().remove(0);
|
let utxo2 = wallet2.list_unspent().next().unwrap();
|
||||||
let tx1 = wallet1.get_tx(txid1, true).unwrap().transaction.unwrap();
|
let tx1 = wallet1.get_tx(txid1, true).unwrap().transaction.unwrap();
|
||||||
let tx2 = wallet2.get_tx(txid2, true).unwrap().transaction.unwrap();
|
let tx2 = wallet2.get_tx(txid2, true).unwrap().transaction.unwrap();
|
||||||
|
|
||||||
@ -1098,7 +1101,7 @@ fn test_add_foreign_utxo_only_witness_utxo() {
|
|||||||
let (wallet2, txid2) =
|
let (wallet2, txid2) =
|
||||||
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
|
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let utxo2 = wallet2.list_unspent().remove(0);
|
let utxo2 = wallet2.list_unspent().next().unwrap();
|
||||||
|
|
||||||
let satisfaction_weight = wallet2
|
let satisfaction_weight = wallet2
|
||||||
.get_descriptor_for_keychain(KeychainKind::External)
|
.get_descriptor_for_keychain(KeychainKind::External)
|
||||||
@ -1214,7 +1217,9 @@ fn test_bump_fee_irreplaceable_tx() {
|
|||||||
|
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
wallet.build_fee_bump(txid).unwrap().finish().unwrap();
|
wallet.build_fee_bump(txid).unwrap().finish().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1237,6 +1242,7 @@ fn test_bump_fee_confirmed_tx() {
|
|||||||
height: 42,
|
height: 42,
|
||||||
time: 42_000,
|
time: 42_000,
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1257,7 +1263,9 @@ fn test_bump_fee_low_fee_rate() {
|
|||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
|
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder.fee_rate(FeeRate::from_sat_per_vb(1.0));
|
builder.fee_rate(FeeRate::from_sat_per_vb(1.0));
|
||||||
@ -1278,7 +1286,9 @@ fn test_bump_fee_low_abs() {
|
|||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
|
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder.fee_absolute(10);
|
builder.fee_absolute(10);
|
||||||
@ -1298,7 +1308,9 @@ fn test_bump_fee_zero_abs() {
|
|||||||
|
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder.fee_absolute(0);
|
builder.fee_absolute(0);
|
||||||
@ -1316,7 +1328,9 @@ fn test_bump_fee_reduce_change() {
|
|||||||
let (psbt, original_details) = builder.finish().unwrap();
|
let (psbt, original_details) = builder.finish().unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder.fee_rate(FeeRate::from_sat_per_vb(2.5)).enable_rbf();
|
builder.fee_rate(FeeRate::from_sat_per_vb(2.5)).enable_rbf();
|
||||||
@ -1401,7 +1415,9 @@ fn test_bump_fee_reduce_single_recipient() {
|
|||||||
let (psbt, original_details) = builder.finish().unwrap();
|
let (psbt, original_details) = builder.finish().unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder
|
builder
|
||||||
@ -1432,7 +1448,9 @@ fn test_bump_fee_absolute_reduce_single_recipient() {
|
|||||||
let (psbt, original_details) = builder.finish().unwrap();
|
let (psbt, original_details) = builder.finish().unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder
|
builder
|
||||||
@ -1471,6 +1489,7 @@ fn test_bump_fee_drain_wallet() {
|
|||||||
height: wallet.latest_checkpoint().unwrap().height,
|
height: wallet.latest_checkpoint().unwrap().height,
|
||||||
time: 42_000,
|
time: 42_000,
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
@ -1488,7 +1507,9 @@ fn test_bump_fee_drain_wallet() {
|
|||||||
let (psbt, original_details) = builder.finish().unwrap();
|
let (psbt, original_details) = builder.finish().unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(original_details.sent, 25_000);
|
assert_eq!(original_details.sent, 25_000);
|
||||||
|
|
||||||
// for the new feerate, it should be enough to reduce the output, but since we specify
|
// for the new feerate, it should be enough to reduce the output, but since we specify
|
||||||
@ -1523,7 +1544,17 @@ fn test_bump_fee_remove_output_manually_selected_only() {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
wallet
|
wallet
|
||||||
.insert_tx(init_tx.clone(), wallet.transactions().last().unwrap().0)
|
.insert_tx(
|
||||||
|
init_tx.clone(),
|
||||||
|
wallet
|
||||||
|
.transactions()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.observed_as
|
||||||
|
.cloned()
|
||||||
|
.into(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let outpoint = OutPoint {
|
let outpoint = OutPoint {
|
||||||
txid: init_tx.txid(),
|
txid: init_tx.txid(),
|
||||||
@ -1540,7 +1571,9 @@ fn test_bump_fee_remove_output_manually_selected_only() {
|
|||||||
let (psbt, original_details) = builder.finish().unwrap();
|
let (psbt, original_details) = builder.finish().unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(original_details.sent, 25_000);
|
assert_eq!(original_details.sent, 25_000);
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
@ -1562,9 +1595,14 @@ fn test_bump_fee_add_input() {
|
|||||||
value: 25_000,
|
value: 25_000,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
wallet
|
let pos = wallet
|
||||||
.insert_tx(init_tx, wallet.transactions().last().unwrap().0)
|
.transactions()
|
||||||
.unwrap();
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.observed_as
|
||||||
|
.cloned()
|
||||||
|
.into();
|
||||||
|
wallet.insert_tx(init_tx, pos, None).unwrap();
|
||||||
|
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
|
let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
|
||||||
@ -1574,7 +1612,9 @@ fn test_bump_fee_add_input() {
|
|||||||
let (psbt, original_details) = builder.finish().unwrap();
|
let (psbt, original_details) = builder.finish().unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder.fee_rate(FeeRate::from_sat_per_vb(50.0));
|
builder.fee_rate(FeeRate::from_sat_per_vb(50.0));
|
||||||
@ -1618,7 +1658,9 @@ fn test_bump_fee_absolute_add_input() {
|
|||||||
let (psbt, original_details) = builder.finish().unwrap();
|
let (psbt, original_details) = builder.finish().unwrap();
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder.fee_absolute(6_000);
|
builder.fee_absolute(6_000);
|
||||||
@ -1668,7 +1710,9 @@ fn test_bump_fee_no_change_add_input_and_change() {
|
|||||||
|
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// now bump the fees without using `allow_shrinking`. the wallet should add an
|
// now bump the fees without using `allow_shrinking`. the wallet should add an
|
||||||
// extra input and a change output, and leave the original output untouched
|
// extra input and a change output, and leave the original output untouched
|
||||||
@ -1724,7 +1768,9 @@ fn test_bump_fee_add_input_change_dust() {
|
|||||||
assert_eq!(tx.input.len(), 1);
|
assert_eq!(tx.input.len(), 1);
|
||||||
assert_eq!(tx.output.len(), 2);
|
assert_eq!(tx.output.len(), 2);
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
// We set a fee high enough that during rbf we are forced to add
|
// We set a fee high enough that during rbf we are forced to add
|
||||||
@ -1784,7 +1830,7 @@ fn test_bump_fee_force_add_input() {
|
|||||||
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
||||||
}
|
}
|
||||||
wallet
|
wallet
|
||||||
.insert_tx(tx.clone(), ConfirmationTime::Unconfirmed)
|
.insert_tx(tx.clone(), ConfirmationTime::Unconfirmed, None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// the new fee_rate is low enough that just reducing the change would be fine, but we force
|
// the new fee_rate is low enough that just reducing the change would be fine, but we force
|
||||||
// the addition of an extra input with `add_utxo()`
|
// the addition of an extra input with `add_utxo()`
|
||||||
@ -1839,7 +1885,7 @@ fn test_bump_fee_absolute_force_add_input() {
|
|||||||
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
||||||
}
|
}
|
||||||
wallet
|
wallet
|
||||||
.insert_tx(tx.clone(), ConfirmationTime::Unconfirmed)
|
.insert_tx(tx.clone(), ConfirmationTime::Unconfirmed, None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// the new fee_rate is low enough that just reducing the change would be fine, but we force
|
// the new fee_rate is low enough that just reducing the change would be fine, but we force
|
||||||
@ -1899,7 +1945,9 @@ fn test_bump_fee_unconfirmed_inputs_only() {
|
|||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
||||||
}
|
}
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder.fee_rate(FeeRate::from_sat_per_vb(25.0));
|
builder.fee_rate(FeeRate::from_sat_per_vb(25.0));
|
||||||
builder.finish().unwrap();
|
builder.finish().unwrap();
|
||||||
@ -1928,7 +1976,9 @@ fn test_bump_fee_unconfirmed_input() {
|
|||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
||||||
}
|
}
|
||||||
wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
|
wallet
|
||||||
|
.insert_tx(tx, ConfirmationTime::Unconfirmed, None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||||
builder
|
builder
|
||||||
@ -2660,7 +2710,7 @@ fn test_taproot_foreign_utxo() {
|
|||||||
let (wallet2, _) = get_funded_wallet(get_test_tr_single_sig());
|
let (wallet2, _) = get_funded_wallet(get_test_tr_single_sig());
|
||||||
|
|
||||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||||
let utxo = wallet2.list_unspent().remove(0);
|
let utxo = wallet2.list_unspent().next().unwrap();
|
||||||
let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
|
let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
|
||||||
let foreign_utxo_satisfaction = wallet2
|
let foreign_utxo_satisfaction = wallet2
|
||||||
.get_descriptor_for_keychain(KeychainKind::External)
|
.get_descriptor_for_keychain(KeychainKind::External)
|
||||||
@ -3022,6 +3072,7 @@ fn test_spend_coinbase() {
|
|||||||
height: confirmation_height,
|
height: confirmation_height,
|
||||||
time: 30_000,
|
time: 30_000,
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -137,6 +137,18 @@ impl ConfirmationTime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ObservedAs<ConfirmationTimeAnchor>> for ConfirmationTime {
|
||||||
|
fn from(observed_as: ObservedAs<ConfirmationTimeAnchor>) -> Self {
|
||||||
|
match observed_as {
|
||||||
|
ObservedAs::Confirmed(a) => Self::Confirmed {
|
||||||
|
height: a.confirmation_height,
|
||||||
|
time: a.confirmation_time,
|
||||||
|
},
|
||||||
|
ObservedAs::Unconfirmed(_) => Self::Unconfirmed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A reference to a block in the canonical chain.
|
/// A reference to a block in the canonical chain.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
@ -1,104 +1,105 @@
|
|||||||
use std::{io::Write, str::FromStr};
|
// use std::{io::Write, str::FromStr};
|
||||||
|
|
||||||
use bdk::{
|
// use bdk::{
|
||||||
bitcoin::{Address, Network},
|
// bitcoin::{Address, Network},
|
||||||
SignOptions, Wallet,
|
// SignOptions, Wallet,
|
||||||
};
|
// };
|
||||||
use bdk_electrum::{
|
// use bdk_electrum::{
|
||||||
electrum_client::{self, ElectrumApi},
|
// electrum_client::{self, ElectrumApi},
|
||||||
ElectrumExt,
|
// ElectrumExt,
|
||||||
};
|
// };
|
||||||
use bdk_file_store::KeychainStore;
|
// use bdk_file_store::KeychainStore;
|
||||||
|
|
||||||
const SEND_AMOUNT: u64 = 5000;
|
// const SEND_AMOUNT: u64 = 5000;
|
||||||
const STOP_GAP: usize = 50;
|
// const STOP_GAP: usize = 50;
|
||||||
const BATCH_SIZE: usize = 5;
|
// const BATCH_SIZE: usize = 5;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("Hello, world!");
|
todo!("update this example!");
|
||||||
|
// println!("Hello, world!");
|
||||||
|
|
||||||
let db_path = std::env::temp_dir().join("bdk-electrum-example");
|
// let db_path = std::env::temp_dir().join("bdk-electrum-example");
|
||||||
let db = KeychainStore::new_from_path(db_path)?;
|
// let db = KeychainStore::new_from_path(db_path)?;
|
||||||
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/0/*)";
|
// let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/0/*)";
|
||||||
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/1/*)";
|
// let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/1/*)";
|
||||||
|
|
||||||
let mut wallet = Wallet::new(
|
// let mut wallet = Wallet::new(
|
||||||
external_descriptor,
|
// external_descriptor,
|
||||||
Some(internal_descriptor),
|
// Some(internal_descriptor),
|
||||||
db,
|
// db,
|
||||||
Network::Testnet,
|
// Network::Testnet,
|
||||||
)?;
|
// )?;
|
||||||
|
|
||||||
let address = wallet.get_address(bdk::wallet::AddressIndex::New);
|
// let address = wallet.get_address(bdk::wallet::AddressIndex::New);
|
||||||
println!("Generated Address: {}", address);
|
// println!("Generated Address: {}", address);
|
||||||
|
|
||||||
let balance = wallet.get_balance();
|
// let balance = wallet.get_balance();
|
||||||
println!("Wallet balance before syncing: {} sats", balance.total());
|
// println!("Wallet balance before syncing: {} sats", balance.total());
|
||||||
|
|
||||||
print!("Syncing...");
|
// print!("Syncing...");
|
||||||
// Scanning the chain...
|
// // Scanning the chain...
|
||||||
let electrum_url = "ssl://electrum.blockstream.info:60002";
|
// let electrum_url = "ssl://electrum.blockstream.info:60002";
|
||||||
let client = electrum_client::Client::new(electrum_url)?;
|
// let client = electrum_client::Client::new(electrum_url)?;
|
||||||
let local_chain = wallet.checkpoints();
|
// let local_chain = wallet.checkpoints();
|
||||||
let spks = wallet
|
// let spks = wallet
|
||||||
.spks_of_all_keychains()
|
// .spks_of_all_keychains()
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
.map(|(k, spks)| {
|
// .map(|(k, spks)| {
|
||||||
let mut first = true;
|
// let mut first = true;
|
||||||
(
|
// (
|
||||||
k,
|
// k,
|
||||||
spks.inspect(move |(spk_i, _)| {
|
// spks.inspect(move |(spk_i, _)| {
|
||||||
if first {
|
// if first {
|
||||||
first = false;
|
// first = false;
|
||||||
print!("\nScanning keychain [{:?}]:", k);
|
// print!("\nScanning keychain [{:?}]:", k);
|
||||||
}
|
// }
|
||||||
print!(" {}", spk_i);
|
// print!(" {}", spk_i);
|
||||||
let _ = std::io::stdout().flush();
|
// let _ = std::io::stdout().flush();
|
||||||
}),
|
// }),
|
||||||
)
|
// )
|
||||||
})
|
// })
|
||||||
.collect();
|
// .collect();
|
||||||
let electrum_update = client
|
// let electrum_update = client
|
||||||
.scan(
|
// .scan(
|
||||||
local_chain,
|
// local_chain,
|
||||||
spks,
|
// spks,
|
||||||
core::iter::empty(),
|
// core::iter::empty(),
|
||||||
core::iter::empty(),
|
// core::iter::empty(),
|
||||||
STOP_GAP,
|
// STOP_GAP,
|
||||||
BATCH_SIZE,
|
// BATCH_SIZE,
|
||||||
)?
|
// )?
|
||||||
.into_confirmation_time_update(&client)?;
|
// .into_confirmation_time_update(&client)?;
|
||||||
println!();
|
// println!();
|
||||||
let new_txs = client.batch_transaction_get(electrum_update.missing_full_txs(&wallet))?;
|
// let new_txs = client.batch_transaction_get(electrum_update.missing_full_txs(&wallet))?;
|
||||||
let update = electrum_update.into_keychain_scan(new_txs, &wallet)?;
|
// let update = electrum_update.into_keychain_scan(new_txs, &wallet)?;
|
||||||
wallet.apply_update(update)?;
|
// wallet.apply_update(update)?;
|
||||||
wallet.commit()?;
|
// wallet.commit()?;
|
||||||
|
|
||||||
let balance = wallet.get_balance();
|
// let balance = wallet.get_balance();
|
||||||
println!("Wallet balance after syncing: {} sats", balance.total());
|
// println!("Wallet balance after syncing: {} sats", balance.total());
|
||||||
|
|
||||||
if balance.total() < SEND_AMOUNT {
|
// if balance.total() < SEND_AMOUNT {
|
||||||
println!(
|
// println!(
|
||||||
"Please send at least {} sats to the receiving address",
|
// "Please send at least {} sats to the receiving address",
|
||||||
SEND_AMOUNT
|
// SEND_AMOUNT
|
||||||
);
|
// );
|
||||||
std::process::exit(0);
|
// std::process::exit(0);
|
||||||
}
|
// }
|
||||||
|
|
||||||
let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?;
|
// let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?;
|
||||||
|
|
||||||
let mut tx_builder = wallet.build_tx();
|
// let mut tx_builder = wallet.build_tx();
|
||||||
tx_builder
|
// tx_builder
|
||||||
.add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
|
// .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
|
||||||
.enable_rbf();
|
// .enable_rbf();
|
||||||
|
|
||||||
let (mut psbt, _) = tx_builder.finish()?;
|
// let (mut psbt, _) = tx_builder.finish()?;
|
||||||
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
||||||
assert!(finalized);
|
// assert!(finalized);
|
||||||
|
|
||||||
let tx = psbt.extract_tx();
|
// let tx = psbt.extract_tx();
|
||||||
client.transaction_broadcast(&tx)?;
|
// client.transaction_broadcast(&tx)?;
|
||||||
println!("Tx broadcasted! Txid: {}", tx.txid());
|
// println!("Tx broadcasted! Txid: {}", tx.txid());
|
||||||
|
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,96 +1,97 @@
|
|||||||
use bdk::{
|
// use bdk::{
|
||||||
bitcoin::{Address, Network},
|
// bitcoin::{Address, Network},
|
||||||
wallet::AddressIndex,
|
// wallet::AddressIndex,
|
||||||
SignOptions, Wallet,
|
// SignOptions, Wallet,
|
||||||
};
|
// };
|
||||||
use bdk_esplora::esplora_client;
|
// use bdk_esplora::esplora_client;
|
||||||
use bdk_esplora::EsploraExt;
|
// use bdk_esplora::EsploraExt;
|
||||||
use bdk_file_store::KeychainStore;
|
// use bdk_file_store::KeychainStore;
|
||||||
use std::{io::Write, str::FromStr};
|
// use std::{io::Write, str::FromStr};
|
||||||
|
|
||||||
const SEND_AMOUNT: u64 = 5000;
|
// const SEND_AMOUNT: u64 = 5000;
|
||||||
const STOP_GAP: usize = 50;
|
// const STOP_GAP: usize = 50;
|
||||||
const PARALLEL_REQUESTS: usize = 5;
|
// const PARALLEL_REQUESTS: usize = 5;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let db_path = std::env::temp_dir().join("bdk-esplora-example");
|
todo!("update this exampe!");
|
||||||
let db = KeychainStore::new_from_path(db_path)?;
|
// let db_path = std::env::temp_dir().join("bdk-esplora-example");
|
||||||
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/0/*)";
|
// let db = KeychainStore::new_from_path(db_path)?;
|
||||||
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/1/*)";
|
// let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/0/*)";
|
||||||
|
// let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/1/*)";
|
||||||
|
|
||||||
let mut wallet = Wallet::new(
|
// let mut wallet = Wallet::new(
|
||||||
external_descriptor,
|
// external_descriptor,
|
||||||
Some(internal_descriptor),
|
// Some(internal_descriptor),
|
||||||
db,
|
// db,
|
||||||
Network::Testnet,
|
// Network::Testnet,
|
||||||
)?;
|
// )?;
|
||||||
|
|
||||||
let address = wallet.get_address(AddressIndex::New);
|
// let address = wallet.get_address(AddressIndex::New);
|
||||||
println!("Generated Address: {}", address);
|
// println!("Generated Address: {}", address);
|
||||||
|
|
||||||
let balance = wallet.get_balance();
|
// let balance = wallet.get_balance();
|
||||||
println!("Wallet balance before syncing: {} sats", balance.total());
|
// println!("Wallet balance before syncing: {} sats", balance.total());
|
||||||
|
|
||||||
print!("Syncing...");
|
// print!("Syncing...");
|
||||||
// Scanning the chain...
|
// // Scanning the chain...
|
||||||
let esplora_url = "https://mempool.space/testnet/api";
|
// let esplora_url = "https://mempool.space/testnet/api";
|
||||||
let client = esplora_client::Builder::new(esplora_url).build_blocking()?;
|
// let client = esplora_client::Builder::new(esplora_url).build_blocking()?;
|
||||||
let checkpoints = wallet.checkpoints();
|
// let checkpoints = wallet.checkpoints();
|
||||||
let spks = wallet
|
// let spks = wallet
|
||||||
.spks_of_all_keychains()
|
// .spks_of_all_keychains()
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
.map(|(k, spks)| {
|
// .map(|(k, spks)| {
|
||||||
let mut first = true;
|
// let mut first = true;
|
||||||
(
|
// (
|
||||||
k,
|
// k,
|
||||||
spks.inspect(move |(spk_i, _)| {
|
// spks.inspect(move |(spk_i, _)| {
|
||||||
if first {
|
// if first {
|
||||||
first = false;
|
// first = false;
|
||||||
print!("\nScanning keychain [{:?}]:", k);
|
// print!("\nScanning keychain [{:?}]:", k);
|
||||||
}
|
// }
|
||||||
print!(" {}", spk_i);
|
// print!(" {}", spk_i);
|
||||||
let _ = std::io::stdout().flush();
|
// let _ = std::io::stdout().flush();
|
||||||
}),
|
// }),
|
||||||
)
|
// )
|
||||||
})
|
// })
|
||||||
.collect();
|
// .collect();
|
||||||
let update = client.scan(
|
// let update = client.scan(
|
||||||
checkpoints,
|
// checkpoints,
|
||||||
spks,
|
// spks,
|
||||||
core::iter::empty(),
|
// core::iter::empty(),
|
||||||
core::iter::empty(),
|
// core::iter::empty(),
|
||||||
STOP_GAP,
|
// STOP_GAP,
|
||||||
PARALLEL_REQUESTS,
|
// PARALLEL_REQUESTS,
|
||||||
)?;
|
// )?;
|
||||||
println!();
|
// println!();
|
||||||
wallet.apply_update(update)?;
|
// wallet.apply_update(update)?;
|
||||||
wallet.commit()?;
|
// wallet.commit()?;
|
||||||
|
|
||||||
let balance = wallet.get_balance();
|
// let balance = wallet.get_balance();
|
||||||
println!("Wallet balance after syncing: {} sats", balance.total());
|
// println!("Wallet balance after syncing: {} sats", balance.total());
|
||||||
|
|
||||||
if balance.total() < SEND_AMOUNT {
|
// if balance.total() < SEND_AMOUNT {
|
||||||
println!(
|
// println!(
|
||||||
"Please send at least {} sats to the receiving address",
|
// "Please send at least {} sats to the receiving address",
|
||||||
SEND_AMOUNT
|
// SEND_AMOUNT
|
||||||
);
|
// );
|
||||||
std::process::exit(0);
|
// std::process::exit(0);
|
||||||
}
|
// }
|
||||||
|
|
||||||
let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?;
|
// let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?;
|
||||||
|
|
||||||
let mut tx_builder = wallet.build_tx();
|
// let mut tx_builder = wallet.build_tx();
|
||||||
tx_builder
|
// tx_builder
|
||||||
.add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
|
// .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
|
||||||
.enable_rbf();
|
// .enable_rbf();
|
||||||
|
|
||||||
let (mut psbt, _) = tx_builder.finish()?;
|
// let (mut psbt, _) = tx_builder.finish()?;
|
||||||
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
||||||
assert!(finalized);
|
// assert!(finalized);
|
||||||
|
|
||||||
let tx = psbt.extract_tx();
|
// let tx = psbt.extract_tx();
|
||||||
client.broadcast(&tx)?;
|
// client.broadcast(&tx)?;
|
||||||
println!("Tx broadcasted! Txid: {}", tx.txid());
|
// println!("Tx broadcasted! Txid: {}", tx.txid());
|
||||||
|
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,99 +1,100 @@
|
|||||||
use std::{io::Write, str::FromStr};
|
// use std::{io::Write, str::FromStr};
|
||||||
|
|
||||||
use bdk::{
|
// use bdk::{
|
||||||
bitcoin::{Address, Network},
|
// bitcoin::{Address, Network},
|
||||||
wallet::AddressIndex,
|
// wallet::AddressIndex,
|
||||||
SignOptions, Wallet,
|
// SignOptions, Wallet,
|
||||||
};
|
// };
|
||||||
use bdk_esplora::{esplora_client, EsploraAsyncExt};
|
// use bdk_esplora::{esplora_client, EsploraAsyncExt};
|
||||||
use bdk_file_store::KeychainStore;
|
// use bdk_file_store::KeychainStore;
|
||||||
|
|
||||||
const SEND_AMOUNT: u64 = 5000;
|
// const SEND_AMOUNT: u64 = 5000;
|
||||||
const STOP_GAP: usize = 50;
|
// const STOP_GAP: usize = 50;
|
||||||
const PARALLEL_REQUESTS: usize = 5;
|
// const PARALLEL_REQUESTS: usize = 5;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let db_path = std::env::temp_dir().join("bdk-esplora-example");
|
todo!("update this example!");
|
||||||
let db = KeychainStore::new_from_path(db_path)?;
|
// let db_path = std::env::temp_dir().join("bdk-esplora-example");
|
||||||
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/0/*)";
|
// let db = KeychainStore::new_from_path(db_path)?;
|
||||||
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/1/*)";
|
// let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/0/*)";
|
||||||
|
// let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/0'/0'/1/*)";
|
||||||
|
|
||||||
let mut wallet = Wallet::new(
|
// let mut wallet = Wallet::new(
|
||||||
external_descriptor,
|
// external_descriptor,
|
||||||
Some(internal_descriptor),
|
// Some(internal_descriptor),
|
||||||
db,
|
// db,
|
||||||
Network::Testnet,
|
// Network::Testnet,
|
||||||
)?;
|
// )?;
|
||||||
|
|
||||||
let address = wallet.get_address(AddressIndex::New);
|
// let address = wallet.get_address(AddressIndex::New);
|
||||||
println!("Generated Address: {}", address);
|
// println!("Generated Address: {}", address);
|
||||||
|
|
||||||
let balance = wallet.get_balance();
|
// let balance = wallet.get_balance();
|
||||||
println!("Wallet balance before syncing: {} sats", balance.total());
|
// println!("Wallet balance before syncing: {} sats", balance.total());
|
||||||
|
|
||||||
print!("Syncing...");
|
// print!("Syncing...");
|
||||||
// Scanning the blockchain
|
// // Scanning the blockchain
|
||||||
let esplora_url = "https://mempool.space/testnet/api";
|
// let esplora_url = "https://mempool.space/testnet/api";
|
||||||
let client = esplora_client::Builder::new(esplora_url).build_async()?;
|
// let client = esplora_client::Builder::new(esplora_url).build_async()?;
|
||||||
let checkpoints = wallet.checkpoints();
|
// let checkpoints = wallet.checkpoints();
|
||||||
let spks = wallet
|
// let spks = wallet
|
||||||
.spks_of_all_keychains()
|
// .spks_of_all_keychains()
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
.map(|(k, spks)| {
|
// .map(|(k, spks)| {
|
||||||
let mut first = true;
|
// let mut first = true;
|
||||||
(
|
// (
|
||||||
k,
|
// k,
|
||||||
spks.inspect(move |(spk_i, _)| {
|
// spks.inspect(move |(spk_i, _)| {
|
||||||
if first {
|
// if first {
|
||||||
first = false;
|
// first = false;
|
||||||
print!("\nScanning keychain [{:?}]:", k);
|
// print!("\nScanning keychain [{:?}]:", k);
|
||||||
}
|
// }
|
||||||
print!(" {}", spk_i);
|
// print!(" {}", spk_i);
|
||||||
let _ = std::io::stdout().flush();
|
// let _ = std::io::stdout().flush();
|
||||||
}),
|
// }),
|
||||||
)
|
// )
|
||||||
})
|
// })
|
||||||
.collect();
|
// .collect();
|
||||||
let update = client
|
// let update = client
|
||||||
.scan(
|
// .scan(
|
||||||
checkpoints,
|
// checkpoints,
|
||||||
spks,
|
// spks,
|
||||||
std::iter::empty(),
|
// std::iter::empty(),
|
||||||
std::iter::empty(),
|
// std::iter::empty(),
|
||||||
STOP_GAP,
|
// STOP_GAP,
|
||||||
PARALLEL_REQUESTS,
|
// PARALLEL_REQUESTS,
|
||||||
)
|
// )
|
||||||
.await?;
|
// .await?;
|
||||||
println!();
|
// println!();
|
||||||
wallet.apply_update(update)?;
|
// wallet.apply_update(update)?;
|
||||||
wallet.commit()?;
|
// wallet.commit()?;
|
||||||
|
|
||||||
let balance = wallet.get_balance();
|
// let balance = wallet.get_balance();
|
||||||
println!("Wallet balance after syncing: {} sats", balance.total());
|
// println!("Wallet balance after syncing: {} sats", balance.total());
|
||||||
|
|
||||||
if balance.total() < SEND_AMOUNT {
|
// if balance.total() < SEND_AMOUNT {
|
||||||
println!(
|
// println!(
|
||||||
"Please send at least {} sats to the receiving address",
|
// "Please send at least {} sats to the receiving address",
|
||||||
SEND_AMOUNT
|
// SEND_AMOUNT
|
||||||
);
|
// );
|
||||||
std::process::exit(0);
|
// std::process::exit(0);
|
||||||
}
|
// }
|
||||||
|
|
||||||
let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?;
|
// let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?;
|
||||||
|
|
||||||
let mut tx_builder = wallet.build_tx();
|
// let mut tx_builder = wallet.build_tx();
|
||||||
tx_builder
|
// tx_builder
|
||||||
.add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
|
// .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
|
||||||
.enable_rbf();
|
// .enable_rbf();
|
||||||
|
|
||||||
let (mut psbt, _) = tx_builder.finish()?;
|
// let (mut psbt, _) = tx_builder.finish()?;
|
||||||
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
||||||
assert!(finalized);
|
// assert!(finalized);
|
||||||
|
|
||||||
let tx = psbt.extract_tx();
|
// let tx = psbt.extract_tx();
|
||||||
client.broadcast(&tx).await?;
|
// client.broadcast(&tx).await?;
|
||||||
println!("Tx broadcasted! Txid: {}", tx.txid());
|
// println!("Tx broadcasted! Txid: {}", tx.txid());
|
||||||
|
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user