Compare commits
35 Commits
v1.0.0-alp
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad9dbc795d | ||
|
|
c01983d02a | ||
|
|
fef70d5e8f | ||
|
|
c3544c9b8c | ||
|
|
b290b29502 | ||
|
|
d77a7f2ff1 | ||
|
|
3d44ffaef2 | ||
|
|
2efa299d04 | ||
|
|
2647aff4bc | ||
|
|
c151d8fd23 | ||
|
|
2c324d3759 | ||
|
|
50c549b5ac | ||
|
|
8379839010 | ||
|
|
5489f905a4 | ||
|
|
420e929463 | ||
|
|
13ab5a835d | ||
|
|
728e26f223 | ||
|
|
dbbd514242 | ||
|
|
ae00e1ee7b | ||
|
|
adc95137ac | ||
|
|
022d5a21cf | ||
|
|
7aca88474a | ||
|
|
b3278a4c29 | ||
|
|
552f11cb5f | ||
|
|
d8f74dc5e4 | ||
|
|
8d93fad778 | ||
|
|
9bb39a3a3f | ||
|
|
9e098a5b6d | ||
|
|
c6b9ed3b76 | ||
|
|
1c15cb2f91 | ||
|
|
89a7ddca7f | ||
|
|
097d818d4c | ||
|
|
f11d663b7e | ||
|
|
4679ca1df7 | ||
|
|
64a90192d9 |
2
.github/workflows/code_coverage.yml
vendored
2
.github/workflows/code_coverage.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
profile: minimal
|
profile: minimal
|
||||||
components: llvm-tools-preview
|
components: llvm-tools-preview
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.2.1
|
uses: Swatinem/rust-cache@v2.7.3
|
||||||
- name: Install grcov
|
- name: Install grcov
|
||||||
run: if [[ ! -e ~/.cargo/bin/grcov ]]; then cargo install grcov; fi
|
run: if [[ ! -e ~/.cargo/bin/grcov ]]; then cargo install grcov; fi
|
||||||
# TODO: re-enable the hwi tests
|
# TODO: re-enable the hwi tests
|
||||||
|
|||||||
9
.github/workflows/cont_integration.yml
vendored
9
.github/workflows/cont_integration.yml
vendored
@@ -26,13 +26,12 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
profile: minimal
|
profile: minimal
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.2.1
|
uses: Swatinem/rust-cache@v2.7.3
|
||||||
- name: Pin dependencies for MSRV
|
- name: Pin dependencies for MSRV
|
||||||
if: matrix.rust.version == '1.63.0'
|
if: matrix.rust.version == '1.63.0'
|
||||||
run: |
|
run: |
|
||||||
cargo update -p zstd-sys --precise "2.0.8+zstd.1.5.5"
|
cargo update -p zstd-sys --precise "2.0.8+zstd.1.5.5"
|
||||||
cargo update -p time --precise "0.3.20"
|
cargo update -p time --precise "0.3.20"
|
||||||
cargo update -p jobserver --precise "0.1.26"
|
|
||||||
cargo update -p home --precise "0.5.5"
|
cargo update -p home --precise "0.5.5"
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build ${{ matrix.features }}
|
run: cargo build ${{ matrix.features }}
|
||||||
@@ -53,7 +52,7 @@ jobs:
|
|||||||
profile: minimal
|
profile: minimal
|
||||||
# target: "thumbv6m-none-eabi"
|
# target: "thumbv6m-none-eabi"
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.2.1
|
uses: Swatinem/rust-cache@v2.7.3
|
||||||
- name: Check bdk_chain
|
- name: Check bdk_chain
|
||||||
working-directory: ./crates/chain
|
working-directory: ./crates/chain
|
||||||
# TODO "--target thumbv6m-none-eabi" should work but currently does not
|
# TODO "--target thumbv6m-none-eabi" should work but currently does not
|
||||||
@@ -88,7 +87,7 @@ jobs:
|
|||||||
profile: minimal
|
profile: minimal
|
||||||
target: "wasm32-unknown-unknown"
|
target: "wasm32-unknown-unknown"
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.2.1
|
uses: Swatinem/rust-cache@v2.7.3
|
||||||
- name: Check bdk
|
- name: Check bdk
|
||||||
working-directory: ./crates/bdk
|
working-directory: ./crates/bdk
|
||||||
run: cargo check --target wasm32-unknown-unknown --no-default-features --features bitcoin/no-std,miniscript/no-std,bdk_chain/hashbrown,dev-getrandom-wasm
|
run: cargo check --target wasm32-unknown-unknown --no-default-features --features bitcoin/no-std,miniscript/no-std,bdk_chain/hashbrown,dev-getrandom-wasm
|
||||||
@@ -122,7 +121,7 @@ jobs:
|
|||||||
components: clippy
|
components: clippy
|
||||||
override: true
|
override: true
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.2.1
|
uses: Swatinem/rust-cache@v2.7.3
|
||||||
- uses: actions-rs/clippy-check@v1
|
- uses: actions-rs/clippy-check@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/nightly_docs.yml
vendored
2
.github/workflows/nightly_docs.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Update toolchain
|
- name: Update toolchain
|
||||||
run: rustup update
|
run: rustup update
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.2.1
|
uses: Swatinem/rust-cache@v2.7.3
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: cargo doc --no-deps
|
run: cargo doc --no-deps
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk"
|
name = "bdk"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
version = "1.0.0-alpha.5"
|
version = "1.0.0-alpha.7"
|
||||||
repository = "https://github.com/bitcoindevkit/bdk"
|
repository = "https://github.com/bitcoindevkit/bdk"
|
||||||
documentation = "https://docs.rs/bdk"
|
documentation = "https://docs.rs/bdk"
|
||||||
description = "A modern, lightweight, descriptor-based wallet library"
|
description = "A modern, lightweight, descriptor-based wallet library"
|
||||||
@@ -18,7 +18,7 @@ miniscript = { version = "10.0.0", features = ["serde"], default-features = fals
|
|||||||
bitcoin = { version = "0.30.0", features = ["serde", "base64", "rand-std"], default-features = false }
|
bitcoin = { version = "0.30.0", features = ["serde", "base64", "rand-std"], default-features = false }
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
serde_json = { version = "^1.0" }
|
serde_json = { version = "^1.0" }
|
||||||
bdk_chain = { path = "../chain", version = "0.9.0", features = ["miniscript", "serde"], default-features = false }
|
bdk_chain = { path = "../chain", version = "0.11.0", features = ["miniscript", "serde"], default-features = false }
|
||||||
|
|
||||||
# Optional dependencies
|
# Optional dependencies
|
||||||
bip39 = { version = "2.0", optional = true }
|
bip39 = { version = "2.0", optional = true }
|
||||||
|
|||||||
@@ -35,24 +35,16 @@ pub trait PsbtUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PsbtUtils for Psbt {
|
impl PsbtUtils for Psbt {
|
||||||
#[allow(clippy::all)] // We want to allow `manual_map` but it is too new.
|
|
||||||
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
|
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
|
||||||
let tx = &self.unsigned_tx;
|
let tx = &self.unsigned_tx;
|
||||||
|
let input = self.inputs.get(input_index)?;
|
||||||
|
|
||||||
if input_index >= tx.input.len() {
|
match (&input.witness_utxo, &input.non_witness_utxo) {
|
||||||
return None;
|
(Some(_), _) => input.witness_utxo.clone(),
|
||||||
}
|
(_, Some(_)) => input.non_witness_utxo.as_ref().map(|in_tx| {
|
||||||
|
in_tx.output[tx.input[input_index].previous_output.vout as usize].clone()
|
||||||
if let Some(input) = self.inputs.get(input_index) {
|
}),
|
||||||
if let Some(wit_utxo) = &input.witness_utxo {
|
_ => None,
|
||||||
Some(wit_utxo.clone())
|
|
||||||
} else if let Some(in_tx) = &input.non_witness_utxo {
|
|
||||||
Some(in_tx.output[tx.input[input_index].previous_output.vout as usize].clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use core::convert::AsRef;
|
|||||||
use core::ops::Sub;
|
use core::ops::Sub;
|
||||||
|
|
||||||
use bdk_chain::ConfirmationTime;
|
use bdk_chain::ConfirmationTime;
|
||||||
use bitcoin::blockdata::transaction::{OutPoint, TxOut};
|
use bitcoin::blockdata::transaction::{OutPoint, Sequence, TxOut};
|
||||||
use bitcoin::{psbt, Weight};
|
use bitcoin::{psbt, Weight};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -197,6 +197,8 @@ pub enum Utxo {
|
|||||||
Foreign {
|
Foreign {
|
||||||
/// The location of the output.
|
/// The location of the output.
|
||||||
outpoint: OutPoint,
|
outpoint: OutPoint,
|
||||||
|
/// The nSequence value to set for this input.
|
||||||
|
sequence: Option<Sequence>,
|
||||||
/// The information about the input we require to add it to a PSBT.
|
/// The information about the input we require to add it to a PSBT.
|
||||||
// Box it to stop the type being too big.
|
// Box it to stop the type being too big.
|
||||||
psbt_input: Box<psbt::Input>,
|
psbt_input: Box<psbt::Input>,
|
||||||
@@ -219,6 +221,7 @@ impl Utxo {
|
|||||||
Utxo::Foreign {
|
Utxo::Foreign {
|
||||||
outpoint,
|
outpoint,
|
||||||
psbt_input,
|
psbt_input,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
if let Some(prev_tx) = &psbt_input.non_witness_utxo {
|
if let Some(prev_tx) = &psbt_input.non_witness_utxo {
|
||||||
return &prev_tx.output[outpoint.vout as usize];
|
return &prev_tx.output[outpoint.vout as usize];
|
||||||
@@ -232,6 +235,14 @@ impl Utxo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the sequence number if an explicit sequence number has to be set for this input.
|
||||||
|
pub fn sequence(&self) -> Option<Sequence> {
|
||||||
|
match self {
|
||||||
|
Utxo::Local(_) => None,
|
||||||
|
Utxo::Foreign { sequence, .. } => *sequence,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
//! Wallet
|
//! Wallet
|
||||||
//!
|
//!
|
||||||
//! This module defines the [`Wallet`].
|
//! This module defines the [`Wallet`].
|
||||||
use crate::collections::{BTreeMap, HashMap, HashSet};
|
use crate::collections::{BTreeMap, HashMap};
|
||||||
use alloc::{
|
use alloc::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@@ -1347,7 +1347,7 @@ impl<D> Wallet<D> {
|
|||||||
}
|
}
|
||||||
Some(tx_builder::Version(x)) => x,
|
Some(tx_builder::Version(x)) => x,
|
||||||
None if requirements.csv.is_some() => 2,
|
None if requirements.csv.is_some() => 2,
|
||||||
_ => 1,
|
None => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// We use a match here instead of a unwrap_or_else as it's way more readable :)
|
// We use a match here instead of a unwrap_or_else as it's way more readable :)
|
||||||
@@ -1400,6 +1400,7 @@ impl<D> Wallet<D> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The nSequence to be by default for inputs unless an explicit sequence is specified.
|
||||||
let n_sequence = match (params.rbf, requirements.csv) {
|
let n_sequence = match (params.rbf, requirements.csv) {
|
||||||
// No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
|
// No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
|
||||||
(None, None) if lock_time != absolute::LockTime::ZERO => {
|
(None, None) if lock_time != absolute::LockTime::ZERO => {
|
||||||
@@ -1518,15 +1519,8 @@ impl<D> Wallet<D> {
|
|||||||
return Err(CreateTxError::ChangePolicyDescriptor);
|
return Err(CreateTxError::ChangePolicyDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (required_utxos, optional_utxos) = self.preselect_utxos(
|
let (required_utxos, optional_utxos) =
|
||||||
params.change_policy,
|
self.preselect_utxos(¶ms, Some(current_height.to_consensus_u32()));
|
||||||
¶ms.unspendable,
|
|
||||||
params.utxos.clone(),
|
|
||||||
params.drain_wallet,
|
|
||||||
params.manually_selected_only,
|
|
||||||
params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee
|
|
||||||
Some(current_height.to_consensus_u32()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// get drain script
|
// get drain script
|
||||||
let drain_script = match params.drain_to {
|
let drain_script = match params.drain_to {
|
||||||
@@ -1565,7 +1559,7 @@ impl<D> Wallet<D> {
|
|||||||
.map(|u| bitcoin::TxIn {
|
.map(|u| bitcoin::TxIn {
|
||||||
previous_output: u.outpoint(),
|
previous_output: u.outpoint(),
|
||||||
script_sig: ScriptBuf::default(),
|
script_sig: ScriptBuf::default(),
|
||||||
sequence: n_sequence,
|
sequence: u.sequence().unwrap_or(n_sequence),
|
||||||
witness: Witness::new(),
|
witness: Witness::new(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -1745,6 +1739,7 @@ impl<D> Wallet<D> {
|
|||||||
satisfaction_weight,
|
satisfaction_weight,
|
||||||
utxo: Utxo::Foreign {
|
utxo: Utxo::Foreign {
|
||||||
outpoint: txin.previous_output,
|
outpoint: txin.previous_output,
|
||||||
|
sequence: Some(txin.sequence),
|
||||||
psbt_input: Box::new(psbt::Input {
|
psbt_input: Box::new(psbt::Input {
|
||||||
witness_utxo: Some(txout.clone()),
|
witness_utxo: Some(txout.clone()),
|
||||||
non_witness_utxo: Some(prev_tx.clone()),
|
non_witness_utxo: Some(prev_tx.clone()),
|
||||||
@@ -2063,17 +2058,26 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
/// Given the options returns the list of utxos that must be used to form the
|
/// Given the options returns the list of utxos that must be used to form the
|
||||||
/// transaction and any further that may be used if needed.
|
/// transaction and any further that may be used if needed.
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn preselect_utxos(
|
fn preselect_utxos(
|
||||||
&self,
|
&self,
|
||||||
change_policy: tx_builder::ChangeSpendPolicy,
|
params: &TxParams,
|
||||||
unspendable: &HashSet<OutPoint>,
|
|
||||||
manually_selected: Vec<WeightedUtxo>,
|
|
||||||
must_use_all_available: bool,
|
|
||||||
manual_only: bool,
|
|
||||||
must_only_use_confirmed_tx: bool,
|
|
||||||
current_height: Option<u32>,
|
current_height: Option<u32>,
|
||||||
) -> (Vec<WeightedUtxo>, Vec<WeightedUtxo>) {
|
) -> (Vec<WeightedUtxo>, Vec<WeightedUtxo>) {
|
||||||
|
let TxParams {
|
||||||
|
change_policy,
|
||||||
|
unspendable,
|
||||||
|
utxos,
|
||||||
|
drain_wallet,
|
||||||
|
manually_selected_only,
|
||||||
|
bumping_fee,
|
||||||
|
..
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
let manually_selected = utxos.clone();
|
||||||
|
// we mandate confirmed transactions if we're bumping the fee
|
||||||
|
let must_only_use_confirmed_tx = bumping_fee.is_some();
|
||||||
|
let must_use_all_available = *drain_wallet;
|
||||||
|
|
||||||
let chain_tip = self.chain.tip().block_id();
|
let chain_tip = self.chain.tip().block_id();
|
||||||
// must_spend <- manually selected utxos
|
// must_spend <- manually selected utxos
|
||||||
// may_spend <- all other available utxos
|
// may_spend <- all other available utxos
|
||||||
@@ -2088,7 +2092,7 @@ impl<D> Wallet<D> {
|
|||||||
|
|
||||||
// NOTE: we are intentionally ignoring `unspendable` here. i.e manual
|
// NOTE: we are intentionally ignoring `unspendable` here. i.e manual
|
||||||
// selection overrides unspendable.
|
// selection overrides unspendable.
|
||||||
if manual_only {
|
if *manually_selected_only {
|
||||||
return (must_spend, vec![]);
|
return (must_spend, vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2216,8 +2220,9 @@ impl<D> Wallet<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Utxo::Foreign {
|
Utxo::Foreign {
|
||||||
psbt_input: foreign_psbt_input,
|
|
||||||
outpoint,
|
outpoint,
|
||||||
|
psbt_input: foreign_psbt_input,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let is_taproot = foreign_psbt_input
|
let is_taproot = foreign_psbt_input
|
||||||
.witness_utxo
|
.witness_utxo
|
||||||
@@ -2290,9 +2295,6 @@ impl<D> Wallet<D> {
|
|||||||
) -> Result<(), MiniscriptPsbtError> {
|
) -> Result<(), MiniscriptPsbtError> {
|
||||||
// We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all
|
// We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all
|
||||||
// the input utxos and outputs
|
// the input utxos and outputs
|
||||||
//
|
|
||||||
// Clippy complains that the collect is not required, but that's wrong
|
|
||||||
#[allow(clippy::needless_collect)]
|
|
||||||
let utxos = (0..psbt.inputs.len())
|
let utxos = (0..psbt.inputs.len())
|
||||||
.filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo)))
|
.filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo)))
|
||||||
.chain(
|
.chain(
|
||||||
@@ -2474,7 +2476,7 @@ impl<D> Wallet<D> {
|
|||||||
/// This method takes in an iterator of `(tx, last_seen)` where `last_seen` is the timestamp of
|
/// This method takes in an iterator of `(tx, last_seen)` where `last_seen` is the timestamp of
|
||||||
/// when the transaction was last seen in the mempool. This is used for conflict resolution
|
/// when the transaction was last seen in the mempool. This is used for conflict resolution
|
||||||
/// when there is conflicting unconfirmed transactions. The transaction with the later
|
/// when there is conflicting unconfirmed transactions. The transaction with the later
|
||||||
/// `last_seen` is prioritied.
|
/// `last_seen` is prioritized.
|
||||||
pub fn apply_unconfirmed_txs<'t>(
|
pub fn apply_unconfirmed_txs<'t>(
|
||||||
&mut self,
|
&mut self,
|
||||||
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
|
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
|
||||||
|
|||||||
@@ -820,7 +820,6 @@ pub enum TapLeavesOptions {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::derivable_impls)]
|
|
||||||
impl Default for SignOptions {
|
impl Default for SignOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
SignOptions {
|
SignOptions {
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ impl<'a, D, Cs: Clone, Ctx> Clone for TxBuilder<'a, D, Cs, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// methods supported by both contexts, for any CoinSelectionAlgorithm
|
// methods supported by both contexts, for any CoinSelectionAlgorithm
|
||||||
impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D, Cs, Ctx> {
|
impl<'a, D, Cs, Ctx> TxBuilder<'a, D, Cs, Ctx> {
|
||||||
/// Set a custom fee rate
|
/// Set a custom fee rate
|
||||||
/// The fee_rate method sets the mining fee paid by the transaction as a rate on its size.
|
/// The fee_rate method sets the mining fee paid by the transaction as a rate on its size.
|
||||||
/// This means that the total fee paid is equal to this rate * size of the transaction in virtual Bytes (vB) or Weight Unit (wu).
|
/// This means that the total fee paid is equal to this rate * size of the transaction in virtual Bytes (vB) or Weight Unit (wu).
|
||||||
@@ -389,6 +389,22 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
|
|||||||
outpoint: OutPoint,
|
outpoint: OutPoint,
|
||||||
psbt_input: psbt::Input,
|
psbt_input: psbt::Input,
|
||||||
satisfaction_weight: usize,
|
satisfaction_weight: usize,
|
||||||
|
) -> Result<&mut Self, AddForeignUtxoError> {
|
||||||
|
self.add_foreign_utxo_with_sequence(
|
||||||
|
outpoint,
|
||||||
|
psbt_input,
|
||||||
|
satisfaction_weight,
|
||||||
|
Sequence::MAX,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as [add_foreign_utxo](TxBuilder::add_foreign_utxo) but allows to set the nSequence value.
|
||||||
|
pub fn add_foreign_utxo_with_sequence(
|
||||||
|
&mut self,
|
||||||
|
outpoint: OutPoint,
|
||||||
|
psbt_input: psbt::Input,
|
||||||
|
satisfaction_weight: usize,
|
||||||
|
sequence: Sequence,
|
||||||
) -> Result<&mut Self, AddForeignUtxoError> {
|
) -> Result<&mut Self, AddForeignUtxoError> {
|
||||||
if psbt_input.witness_utxo.is_none() {
|
if psbt_input.witness_utxo.is_none() {
|
||||||
match psbt_input.non_witness_utxo.as_ref() {
|
match psbt_input.non_witness_utxo.as_ref() {
|
||||||
@@ -413,6 +429,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
|
|||||||
satisfaction_weight,
|
satisfaction_weight,
|
||||||
utxo: Utxo::Foreign {
|
utxo: Utxo::Foreign {
|
||||||
outpoint,
|
outpoint,
|
||||||
|
sequence: Some(sequence),
|
||||||
psbt_input: Box::new(psbt_input),
|
psbt_input: Box::new(psbt_input),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -557,20 +574,6 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish building the transaction.
|
|
||||||
///
|
|
||||||
/// Returns a new [`Psbt`] per [`BIP174`].
|
|
||||||
///
|
|
||||||
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
|
|
||||||
pub fn finish(self) -> Result<Psbt, CreateTxError<D::WriteError>>
|
|
||||||
where
|
|
||||||
D: PersistBackend<ChangeSet>,
|
|
||||||
{
|
|
||||||
self.wallet
|
|
||||||
.borrow_mut()
|
|
||||||
.create_tx(self.coin_selection, self.params)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enable signaling RBF
|
/// Enable signaling RBF
|
||||||
///
|
///
|
||||||
/// This will use the default nSequence value of `0xFFFFFFFD`.
|
/// This will use the default nSequence value of `0xFFFFFFFD`.
|
||||||
@@ -617,6 +620,22 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx> TxBuilder<'a, D, Cs, Ctx> {
|
||||||
|
/// Finish building the transaction.
|
||||||
|
///
|
||||||
|
/// Returns a new [`Psbt`] per [`BIP174`].
|
||||||
|
///
|
||||||
|
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
|
||||||
|
pub fn finish(self) -> Result<Psbt, CreateTxError<D::WriteError>>
|
||||||
|
where
|
||||||
|
D: PersistBackend<ChangeSet>,
|
||||||
|
{
|
||||||
|
self.wallet
|
||||||
|
.borrow_mut()
|
||||||
|
.create_tx(self.coin_selection, self.params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
/// Error returned from [`TxBuilder::add_utxo`] and [`TxBuilder::add_utxos`]
|
/// Error returned from [`TxBuilder::add_utxo`] and [`TxBuilder::add_utxos`]
|
||||||
pub enum AddUtxoError {
|
pub enum AddUtxoError {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk_bitcoind_rpc"
|
name = "bdk_bitcoind_rpc"
|
||||||
version = "0.4.0"
|
version = "0.6.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
@@ -16,7 +16,7 @@ readme = "README.md"
|
|||||||
# For no-std, remember to enable the bitcoin/no-std feature
|
# For no-std, remember to enable the bitcoin/no-std feature
|
||||||
bitcoin = { version = "0.30", default-features = false }
|
bitcoin = { version = "0.30", default-features = false }
|
||||||
bitcoincore-rpc = { version = "0.17" }
|
bitcoincore-rpc = { version = "0.17" }
|
||||||
bdk_chain = { path = "../chain", version = "0.9", default-features = false }
|
bdk_chain = { path = "../chain", version = "0.11", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bitcoind = { version = "0.33", features = ["25_0"] }
|
bitcoind = { version = "0.33", features = ["25_0"] }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk_chain"
|
name = "bdk_chain"
|
||||||
version = "0.9.0"
|
version = "0.11.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
@@ -18,7 +18,6 @@ bitcoin = { version = "0.30.0", default-features = false }
|
|||||||
serde_crate = { package = "serde", version = "1", optional = true, features = ["derive"] }
|
serde_crate = { package = "serde", version = "1", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
# Use hashbrown as a feature flag to have HashSet and HashMap from it.
|
# Use hashbrown as a feature flag to have HashSet and HashMap from it.
|
||||||
# note versions > 0.9.1 breaks ours 1.57.0 MSRV.
|
|
||||||
hashbrown = { version = "0.9.1", optional = true, features = ["serde"] }
|
hashbrown = { version = "0.9.1", optional = true, features = ["serde"] }
|
||||||
miniscript = { version = "10.0.0", optional = true, default-features = false }
|
miniscript = { version = "10.0.0", optional = true, default-features = false }
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{Anchor, AnchorFromBlockPosition, COINBASE_MATURITY};
|
|||||||
pub enum ChainPosition<A> {
|
pub enum ChainPosition<A> {
|
||||||
/// The chain data is seen as confirmed, and in anchored by `A`.
|
/// The chain data is seen as confirmed, and in anchored by `A`.
|
||||||
Confirmed(A),
|
Confirmed(A),
|
||||||
/// The chain data is seen in mempool at this given timestamp.
|
/// The chain data is not confirmed and last seen in the mempool at this timestamp.
|
||||||
Unconfirmed(u64),
|
Unconfirmed(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,14 +48,14 @@ impl<A: Anchor> ChainPosition<A> {
|
|||||||
serde(crate = "serde_crate")
|
serde(crate = "serde_crate")
|
||||||
)]
|
)]
|
||||||
pub enum ConfirmationTime {
|
pub enum ConfirmationTime {
|
||||||
/// The confirmed variant.
|
/// The transaction is confirmed
|
||||||
Confirmed {
|
Confirmed {
|
||||||
/// Confirmation height.
|
/// Confirmation height.
|
||||||
height: u32,
|
height: u32,
|
||||||
/// Confirmation time in unix seconds.
|
/// Confirmation time in unix seconds.
|
||||||
time: u64,
|
time: u64,
|
||||||
},
|
},
|
||||||
/// The unconfirmed variant.
|
/// The transaction is unconfirmed
|
||||||
Unconfirmed {
|
Unconfirmed {
|
||||||
/// The last-seen timestamp in unix seconds.
|
/// The last-seen timestamp in unix seconds.
|
||||||
last_seen: u64,
|
last_seen: u64,
|
||||||
@@ -157,13 +157,12 @@ impl From<(&u32, &BlockHash)> for BlockId {
|
|||||||
serde(crate = "serde_crate")
|
serde(crate = "serde_crate")
|
||||||
)]
|
)]
|
||||||
pub struct ConfirmationHeightAnchor {
|
pub struct ConfirmationHeightAnchor {
|
||||||
/// The anchor block.
|
|
||||||
pub anchor_block: BlockId,
|
|
||||||
|
|
||||||
/// The exact confirmation height of the transaction.
|
/// The exact confirmation height of the transaction.
|
||||||
///
|
///
|
||||||
/// It is assumed that this value is never larger than the height of the anchor block.
|
/// It is assumed that this value is never larger than the height of the anchor block.
|
||||||
pub confirmation_height: u32,
|
pub confirmation_height: u32,
|
||||||
|
/// The anchor block.
|
||||||
|
pub anchor_block: BlockId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Anchor for ConfirmationHeightAnchor {
|
impl Anchor for ConfirmationHeightAnchor {
|
||||||
@@ -198,12 +197,12 @@ impl AnchorFromBlockPosition for ConfirmationHeightAnchor {
|
|||||||
serde(crate = "serde_crate")
|
serde(crate = "serde_crate")
|
||||||
)]
|
)]
|
||||||
pub struct ConfirmationTimeHeightAnchor {
|
pub struct ConfirmationTimeHeightAnchor {
|
||||||
|
/// The confirmation height of the transaction being anchored.
|
||||||
|
pub confirmation_height: u32,
|
||||||
|
/// The confirmation time of the transaction being anchored.
|
||||||
|
pub confirmation_time: u64,
|
||||||
/// The anchor block.
|
/// The anchor block.
|
||||||
pub anchor_block: BlockId,
|
pub anchor_block: BlockId,
|
||||||
/// The confirmation height of the chain data being anchored.
|
|
||||||
pub confirmation_height: u32,
|
|
||||||
/// The confirmation time of the chain data being anchored.
|
|
||||||
pub confirmation_time: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Anchor for ConfirmationTimeHeightAnchor {
|
impl Anchor for ConfirmationTimeHeightAnchor {
|
||||||
@@ -229,12 +228,12 @@ impl AnchorFromBlockPosition for ConfirmationTimeHeightAnchor {
|
|||||||
/// A `TxOut` with as much data as we can retrieve about it
|
/// A `TxOut` with as much data as we can retrieve about it
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct FullTxOut<A> {
|
pub struct FullTxOut<A> {
|
||||||
|
/// The position of the transaction in `outpoint` in the overall chain.
|
||||||
|
pub chain_position: ChainPosition<A>,
|
||||||
/// The location of the `TxOut`.
|
/// The location of the `TxOut`.
|
||||||
pub outpoint: OutPoint,
|
pub outpoint: OutPoint,
|
||||||
/// The `TxOut`.
|
/// The `TxOut`.
|
||||||
pub txout: TxOut,
|
pub txout: TxOut,
|
||||||
/// The position of the transaction in `outpoint` in the overall chain.
|
|
||||||
pub chain_position: ChainPosition<A>,
|
|
||||||
/// The txid and chain position of the transaction (if any) that has spent this output.
|
/// The txid and chain position of the transaction (if any) that has spent this output.
|
||||||
pub spent_by: Option<(ChainPosition<A>, Txid)>,
|
pub spent_by: Option<(ChainPosition<A>, Txid)>,
|
||||||
/// Whether this output is on a coinbase transaction.
|
/// Whether this output is on a coinbase transaction.
|
||||||
@@ -299,3 +298,35 @@ impl<A: Anchor> FullTxOut<A> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chain_position_ord() {
|
||||||
|
let unconf1 = ChainPosition::<ConfirmationHeightAnchor>::Unconfirmed(10);
|
||||||
|
let unconf2 = ChainPosition::<ConfirmationHeightAnchor>::Unconfirmed(20);
|
||||||
|
let conf1 = ChainPosition::Confirmed(ConfirmationHeightAnchor {
|
||||||
|
confirmation_height: 9,
|
||||||
|
anchor_block: BlockId {
|
||||||
|
height: 20,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let conf2 = ChainPosition::Confirmed(ConfirmationHeightAnchor {
|
||||||
|
confirmation_height: 12,
|
||||||
|
anchor_block: BlockId {
|
||||||
|
height: 15,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(unconf2 > unconf1, "higher last_seen means higher ord");
|
||||||
|
assert!(unconf1 > conf1, "unconfirmed is higher ord than confirmed");
|
||||||
|
assert!(
|
||||||
|
conf2 > conf1,
|
||||||
|
"confirmation_height is higher then it should be higher ord"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -326,12 +326,17 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
|||||||
self.lookahead
|
self.lookahead
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store lookahead scripts until `target_index`.
|
/// Store lookahead scripts until `target_index` (inclusive).
|
||||||
///
|
///
|
||||||
/// This does not change the `lookahead` setting.
|
/// This does not change the global `lookahead` setting.
|
||||||
pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) {
|
pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) {
|
||||||
let next_index = self.next_store_index(keychain);
|
let (next_index, _) = self.next_index(keychain);
|
||||||
if let Some(temp_lookahead) = target_index.checked_sub(next_index).filter(|&v| v > 0) {
|
|
||||||
|
let temp_lookahead = (target_index + 1)
|
||||||
|
.checked_sub(next_index)
|
||||||
|
.filter(|&index| index > 0);
|
||||||
|
|
||||||
|
if let Some(temp_lookahead) = temp_lookahead {
|
||||||
self.replenish_lookahead(keychain, temp_lookahead);
|
self.replenish_lookahead(keychain, temp_lookahead);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ where
|
|||||||
/// Stages a new changeset and commits it (along with any other previously staged changes) to
|
/// Stages a new changeset and commits it (along with any other previously staged changes) to
|
||||||
/// the persistence backend
|
/// the persistence backend
|
||||||
///
|
///
|
||||||
/// Convience method for calling [`stage`] and then [`commit`].
|
/// Convenience method for calling [`stage`] and then [`commit`].
|
||||||
///
|
///
|
||||||
/// [`stage`]: Self::stage
|
/// [`stage`]: Self::stage
|
||||||
/// [`commit`]: Self::commit
|
/// [`commit`]: Self::commit
|
||||||
|
|||||||
@@ -40,20 +40,23 @@
|
|||||||
//! # use bdk_chain::example_utils::*;
|
//! # use bdk_chain::example_utils::*;
|
||||||
//! # use bitcoin::Transaction;
|
//! # use bitcoin::Transaction;
|
||||||
//! # let tx_a = tx_from_hex(RAW_TX_1);
|
//! # let tx_a = tx_from_hex(RAW_TX_1);
|
||||||
//! let mut graph: TxGraph = TxGraph::default();
|
//! let mut tx_graph: TxGraph = TxGraph::default();
|
||||||
//! let mut another_graph: TxGraph = TxGraph::default();
|
|
||||||
//!
|
//!
|
||||||
//! // insert a transaction
|
//! // insert a transaction
|
||||||
//! let changeset = graph.insert_tx(tx_a);
|
//! let changeset = tx_graph.insert_tx(tx_a);
|
||||||
//!
|
//!
|
||||||
//! // the resulting changeset can be applied to another tx graph
|
//! // We can restore the state of the `tx_graph` by applying all
|
||||||
//! another_graph.apply_changeset(changeset);
|
//! // the changesets obtained by mutating the original (the order doesn't matter).
|
||||||
|
//! let mut restored_tx_graph: TxGraph = TxGraph::default();
|
||||||
|
//! restored_tx_graph.apply_changeset(changeset);
|
||||||
|
//!
|
||||||
|
//! assert_eq!(tx_graph, restored_tx_graph);
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! A [`TxGraph`] can also be updated with another [`TxGraph`].
|
//! A [`TxGraph`] can also be updated with another [`TxGraph`] which merges them together.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use bdk_chain::BlockId;
|
//! # use bdk_chain::{Append, BlockId};
|
||||||
//! # use bdk_chain::tx_graph::TxGraph;
|
//! # use bdk_chain::tx_graph::TxGraph;
|
||||||
//! # use bdk_chain::example_utils::*;
|
//! # use bdk_chain::example_utils::*;
|
||||||
//! # use bitcoin::Transaction;
|
//! # use bitcoin::Transaction;
|
||||||
@@ -451,6 +454,21 @@ impl<A> TxGraph<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A: Clone + Ord> TxGraph<A> {
|
||||||
|
/// Transform the [`TxGraph`] to have [`Anchor`]s of another type.
|
||||||
|
///
|
||||||
|
/// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
|
||||||
|
/// transform it.
|
||||||
|
pub fn map_anchors<A2: Clone + Ord, F>(self, f: F) -> TxGraph<A2>
|
||||||
|
where
|
||||||
|
F: FnMut(A) -> A2,
|
||||||
|
{
|
||||||
|
let mut new_graph = TxGraph::<A2>::default();
|
||||||
|
new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
|
||||||
|
new_graph
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<A: Clone + Ord> TxGraph<A> {
|
impl<A: Clone + Ord> TxGraph<A> {
|
||||||
/// Construct a new [`TxGraph`] from a list of transactions.
|
/// Construct a new [`TxGraph`] from a list of transactions.
|
||||||
pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
|
pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
|
||||||
@@ -1212,11 +1230,6 @@ impl<A> Default for ChangeSet<A> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<A> ChangeSet<A> {
|
impl<A> ChangeSet<A> {
|
||||||
/// Returns true if the [`ChangeSet`] is empty (no transactions or txouts).
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.txs.is_empty() && self.txouts.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates over all outpoints contained within [`ChangeSet`].
|
/// Iterates over all outpoints contained within [`ChangeSet`].
|
||||||
pub fn txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
|
pub fn txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
|
||||||
self.txs
|
self.txs
|
||||||
@@ -1296,6 +1309,26 @@ impl<A: Ord> Append for ChangeSet<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A: Ord> ChangeSet<A> {
|
||||||
|
/// Transform the [`ChangeSet`] to have [`Anchor`]s of another type.
|
||||||
|
///
|
||||||
|
/// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
|
||||||
|
/// transform it.
|
||||||
|
pub fn map_anchors<A2: Ord, F>(self, mut f: F) -> ChangeSet<A2>
|
||||||
|
where
|
||||||
|
F: FnMut(A) -> A2,
|
||||||
|
{
|
||||||
|
ChangeSet {
|
||||||
|
txs: self.txs,
|
||||||
|
txouts: self.txouts,
|
||||||
|
anchors: BTreeSet::<(A2, Txid)>::from_iter(
|
||||||
|
self.anchors.into_iter().map(|(a, txid)| (f(a), txid)),
|
||||||
|
),
|
||||||
|
last_seen: self.last_seen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
|
impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
|
||||||
fn as_ref(&self) -> &TxGraph<A> {
|
fn as_ref(&self) -> &TxGraph<A> {
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use rand::distributions::{Alphanumeric, DistString};
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bdk_chain::{tx_graph::TxGraph, BlockId, SpkTxOutIndex};
|
use bdk_chain::{tx_graph::TxGraph, Anchor, SpkTxOutIndex};
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
locktime::absolute::LockTime, secp256k1::Secp256k1, OutPoint, ScriptBuf, Sequence, Transaction,
|
locktime::absolute::LockTime, secp256k1::Secp256k1, OutPoint, ScriptBuf, Sequence, Transaction,
|
||||||
TxIn, TxOut, Txid, Witness,
|
TxIn, TxOut, Txid, Witness,
|
||||||
@@ -49,11 +49,11 @@ impl TxOutTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn init_graph<'a>(
|
pub fn init_graph<'a, A: Anchor + Clone + 'a>(
|
||||||
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, BlockId>>,
|
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, A>>,
|
||||||
) -> (TxGraph<BlockId>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
|
) -> (TxGraph<A>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
|
||||||
let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
|
let (descriptor, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
|
||||||
let mut graph = TxGraph::<BlockId>::default();
|
let mut graph = TxGraph::<A>::default();
|
||||||
let mut spk_index = SpkTxOutIndex::default();
|
let mut spk_index = SpkTxOutIndex::default();
|
||||||
(0..10).for_each(|index| {
|
(0..10).for_each(|index| {
|
||||||
spk_index.insert_spk(
|
spk_index.insert_spk(
|
||||||
@@ -126,7 +126,7 @@ pub fn init_graph<'a>(
|
|||||||
spk_index.scan(&tx);
|
spk_index.scan(&tx);
|
||||||
let _ = graph.insert_tx(tx.clone());
|
let _ = graph.insert_tx(tx.clone());
|
||||||
for anchor in tx_tmp.anchors.iter() {
|
for anchor in tx_tmp.anchors.iter() {
|
||||||
let _ = graph.insert_anchor(tx.txid(), *anchor);
|
let _ = graph.insert_anchor(tx.txid(), anchor.clone());
|
||||||
}
|
}
|
||||||
if let Some(seen_at) = tx_tmp.last_seen {
|
if let Some(seen_at) = tx_tmp.last_seen {
|
||||||
let _ = graph.insert_seen_at(tx.txid(), seen_at);
|
let _ = graph.insert_seen_at(tx.txid(), seen_at);
|
||||||
|
|||||||
@@ -386,3 +386,103 @@ fn test_non_wildcard_derivations() {
|
|||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that calling `lookahead_to_target` stores the expected spks.
|
||||||
|
#[test]
|
||||||
|
fn lookahead_to_target() {
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TestCase {
|
||||||
|
/// Global lookahead value.
|
||||||
|
lookahead: u32,
|
||||||
|
/// Last revealed index for external keychain.
|
||||||
|
external_last_revealed: Option<u32>,
|
||||||
|
/// Last revealed index for internal keychain.
|
||||||
|
internal_last_revealed: Option<u32>,
|
||||||
|
/// Call `lookahead_to_target(External, u32)`.
|
||||||
|
external_target: Option<u32>,
|
||||||
|
/// Call `lookahead_to_target(Internal, u32)`.
|
||||||
|
internal_target: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let test_cases = &[
|
||||||
|
TestCase {
|
||||||
|
lookahead: 0,
|
||||||
|
external_target: Some(100),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TestCase {
|
||||||
|
lookahead: 10,
|
||||||
|
internal_target: Some(99),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TestCase {
|
||||||
|
lookahead: 100,
|
||||||
|
internal_target: Some(9),
|
||||||
|
external_target: Some(10),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TestCase {
|
||||||
|
lookahead: 12,
|
||||||
|
external_last_revealed: Some(2),
|
||||||
|
internal_last_revealed: Some(2),
|
||||||
|
internal_target: Some(15),
|
||||||
|
external_target: Some(13),
|
||||||
|
},
|
||||||
|
TestCase {
|
||||||
|
lookahead: 13,
|
||||||
|
external_last_revealed: Some(100),
|
||||||
|
internal_last_revealed: Some(21),
|
||||||
|
internal_target: Some(120),
|
||||||
|
external_target: Some(130),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for t in test_cases {
|
||||||
|
let (mut index, _, _) = init_txout_index(t.lookahead);
|
||||||
|
|
||||||
|
if let Some(last_revealed) = t.external_last_revealed {
|
||||||
|
let _ = index.reveal_to_target(&TestKeychain::External, last_revealed);
|
||||||
|
}
|
||||||
|
if let Some(last_revealed) = t.internal_last_revealed {
|
||||||
|
let _ = index.reveal_to_target(&TestKeychain::Internal, last_revealed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let keychain_test_cases = [
|
||||||
|
(
|
||||||
|
TestKeychain::External,
|
||||||
|
t.external_last_revealed,
|
||||||
|
t.external_target,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
TestKeychain::Internal,
|
||||||
|
t.internal_last_revealed,
|
||||||
|
t.internal_target,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for (keychain, last_revealed, target) in keychain_test_cases {
|
||||||
|
if let Some(target) = target {
|
||||||
|
let original_last_stored_index = match last_revealed {
|
||||||
|
Some(last_revealed) => Some(last_revealed + t.lookahead),
|
||||||
|
None => t.lookahead.checked_sub(1),
|
||||||
|
};
|
||||||
|
let exp_last_stored_index = match original_last_stored_index {
|
||||||
|
Some(original_last_stored_index) => {
|
||||||
|
Ord::max(target, original_last_stored_index)
|
||||||
|
}
|
||||||
|
None => target,
|
||||||
|
};
|
||||||
|
index.lookahead_to_target(&keychain, target);
|
||||||
|
let keys = index
|
||||||
|
.inner()
|
||||||
|
.all_spks()
|
||||||
|
.range((keychain.clone(), 0)..=(keychain.clone(), u32::MAX))
|
||||||
|
.map(|(k, _)| k.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let exp_keys = core::iter::repeat(keychain)
|
||||||
|
.zip(0_u32..=exp_last_stored_index)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert_eq!(keys, exp_keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ use bdk_chain::{
|
|||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
absolute, hashes::Hash, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid,
|
absolute, hashes::Hash, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid,
|
||||||
};
|
};
|
||||||
|
use common::*;
|
||||||
use core::iter;
|
use core::iter;
|
||||||
|
use rand::RngCore;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -213,7 +215,8 @@ fn insert_tx_graph_doesnt_count_coinbase_as_spent() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut graph = TxGraph::<()>::default();
|
let mut graph = TxGraph::<()>::default();
|
||||||
let _ = graph.insert_tx(tx);
|
let changeset = graph.insert_tx(tx);
|
||||||
|
assert!(!changeset.is_empty());
|
||||||
assert!(graph.outspends(OutPoint::null()).is_empty());
|
assert!(graph.outspends(OutPoint::null()).is_empty());
|
||||||
assert!(graph.tx_spends(Txid::all_zeros()).next().is_none());
|
assert!(graph.tx_spends(Txid::all_zeros()).next().is_none());
|
||||||
}
|
}
|
||||||
@@ -289,7 +292,7 @@ fn insert_tx_displaces_txouts() {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = tx_graph.insert_txout(
|
let changeset = tx_graph.insert_txout(
|
||||||
OutPoint {
|
OutPoint {
|
||||||
txid: tx.txid(),
|
txid: tx.txid(),
|
||||||
vout: 0,
|
vout: 0,
|
||||||
@@ -300,6 +303,8 @@ fn insert_tx_displaces_txouts() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert!(!changeset.is_empty());
|
||||||
|
|
||||||
let _ = tx_graph.insert_txout(
|
let _ = tx_graph.insert_txout(
|
||||||
OutPoint {
|
OutPoint {
|
||||||
txid: tx.txid(),
|
txid: tx.txid(),
|
||||||
@@ -653,7 +658,8 @@ fn test_walk_ancestors() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
[&tx_a0, &tx_b1].iter().for_each(|&tx| {
|
[&tx_a0, &tx_b1].iter().for_each(|&tx| {
|
||||||
let _ = graph.insert_anchor(tx.txid(), tip.block_id());
|
let changeset = graph.insert_anchor(tx.txid(), tip.block_id());
|
||||||
|
assert!(!changeset.is_empty());
|
||||||
});
|
});
|
||||||
|
|
||||||
let ancestors = [
|
let ancestors = [
|
||||||
@@ -1027,10 +1033,12 @@ fn test_changeset_last_seen_append() {
|
|||||||
last_seen: original_ls.map(|ls| (txid, ls)).into_iter().collect(),
|
last_seen: original_ls.map(|ls| (txid, ls)).into_iter().collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
assert!(!original.is_empty() || original_ls.is_none());
|
||||||
let update = ChangeSet::<()> {
|
let update = ChangeSet::<()> {
|
||||||
last_seen: update_ls.map(|ls| (txid, ls)).into_iter().collect(),
|
last_seen: update_ls.map(|ls| (txid, ls)).into_iter().collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
assert!(!update.is_empty() || update_ls.is_none());
|
||||||
|
|
||||||
original.append(update);
|
original.append(update);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1172,3 +1180,86 @@ fn test_missing_blocks() {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// The `map_anchors` allow a caller to pass a function to reconstruct the [`TxGraph`] with any [`Anchor`],
|
||||||
|
/// even though the function is non-deterministic.
|
||||||
|
fn call_map_anchors_with_non_deterministic_anchor() {
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
|
||||||
|
/// A non-deterministic anchor
|
||||||
|
pub struct NonDeterministicAnchor {
|
||||||
|
pub anchor_block: BlockId,
|
||||||
|
pub non_deterministic_field: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let template = [
|
||||||
|
TxTemplate {
|
||||||
|
tx_name: "tx1",
|
||||||
|
inputs: &[TxInTemplate::Bogus],
|
||||||
|
outputs: &[TxOutTemplate::new(10000, Some(1))],
|
||||||
|
anchors: &[block_id!(1, "A")],
|
||||||
|
last_seen: None,
|
||||||
|
},
|
||||||
|
TxTemplate {
|
||||||
|
tx_name: "tx2",
|
||||||
|
inputs: &[TxInTemplate::PrevTx("tx1", 0)],
|
||||||
|
outputs: &[TxOutTemplate::new(20000, Some(2))],
|
||||||
|
anchors: &[block_id!(2, "B")],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TxTemplate {
|
||||||
|
tx_name: "tx3",
|
||||||
|
inputs: &[TxInTemplate::PrevTx("tx2", 0)],
|
||||||
|
outputs: &[TxOutTemplate::new(30000, Some(3))],
|
||||||
|
anchors: &[block_id!(3, "C"), block_id!(4, "D")],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let (graph, _, _) = init_graph(&template);
|
||||||
|
let new_graph = graph.clone().map_anchors(|a| NonDeterministicAnchor {
|
||||||
|
anchor_block: a,
|
||||||
|
// A non-deterministic value
|
||||||
|
non_deterministic_field: rand::thread_rng().next_u32(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check all the details in new_graph reconstruct as well
|
||||||
|
|
||||||
|
let mut full_txs_vec: Vec<_> = graph.full_txs().collect();
|
||||||
|
full_txs_vec.sort();
|
||||||
|
let mut new_txs_vec: Vec<_> = new_graph.full_txs().collect();
|
||||||
|
new_txs_vec.sort();
|
||||||
|
let mut new_txs = new_txs_vec.iter();
|
||||||
|
|
||||||
|
for tx_node in full_txs_vec.iter() {
|
||||||
|
let new_txnode = new_txs.next().unwrap();
|
||||||
|
assert_eq!(new_txnode.txid, tx_node.txid);
|
||||||
|
assert_eq!(new_txnode.tx, tx_node.tx);
|
||||||
|
assert_eq!(
|
||||||
|
new_txnode.last_seen_unconfirmed,
|
||||||
|
tx_node.last_seen_unconfirmed
|
||||||
|
);
|
||||||
|
assert_eq!(new_txnode.anchors.len(), tx_node.anchors.len());
|
||||||
|
|
||||||
|
let mut new_anchors: Vec<_> = new_txnode.anchors.iter().map(|a| a.anchor_block).collect();
|
||||||
|
new_anchors.sort();
|
||||||
|
let mut old_anchors: Vec<_> = tx_node.anchors.iter().copied().collect();
|
||||||
|
old_anchors.sort();
|
||||||
|
assert_eq!(new_anchors, old_anchors);
|
||||||
|
}
|
||||||
|
assert!(new_txs.next().is_none());
|
||||||
|
|
||||||
|
let new_graph_anchors: Vec<_> = new_graph
|
||||||
|
.all_anchors()
|
||||||
|
.iter()
|
||||||
|
.map(|i| i.0.anchor_block)
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
new_graph_anchors,
|
||||||
|
vec![
|
||||||
|
block_id!(1, "A"),
|
||||||
|
block_id!(2, "B"),
|
||||||
|
block_id!(3, "C"),
|
||||||
|
block_id!(4, "D"),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk_electrum"
|
name = "bdk_electrum"
|
||||||
version = "0.7.0"
|
version = "0.9.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
repository = "https://github.com/bitcoindevkit/bdk"
|
repository = "https://github.com/bitcoindevkit/bdk"
|
||||||
@@ -12,6 +12,6 @@ readme = "README.md"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk_chain = { path = "../chain", version = "0.9.0", default-features = false }
|
bdk_chain = { path = "../chain", version = "0.11.0", default-features = false }
|
||||||
electrum-client = { version = "0.18" }
|
electrum-client = { version = "0.18" }
|
||||||
#rustls = { version = "=0.21.1", optional = true, features = ["dangerous_configuration"] }
|
#rustls = { version = "=0.21.1", optional = true, features = ["dangerous_configuration"] }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk_esplora"
|
name = "bdk_esplora"
|
||||||
version = "0.7.0"
|
version = "0.9.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
repository = "https://github.com/bitcoindevkit/bdk"
|
repository = "https://github.com/bitcoindevkit/bdk"
|
||||||
@@ -12,7 +12,7 @@ readme = "README.md"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk_chain = { path = "../chain", version = "0.9.0", default-features = false }
|
bdk_chain = { path = "../chain", version = "0.11.0", default-features = false }
|
||||||
esplora-client = { version = "0.6.0", default-features = false }
|
esplora-client = { version = "0.6.0", default-features = false }
|
||||||
async-trait = { version = "0.1.66", optional = true }
|
async-trait = { version = "0.1.66", optional = true }
|
||||||
futures = { version = "0.3.26", optional = true }
|
futures = { version = "0.3.26", optional = true }
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bdk_chain::collections::btree_map;
|
use bdk_chain::collections::btree_map;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid},
|
bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
local_chain::{self, CheckPoint},
|
local_chain::{self, CheckPoint},
|
||||||
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
|
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
|
||||||
};
|
};
|
||||||
use esplora_client::{Error, TxStatus};
|
use esplora_client::TxStatus;
|
||||||
use futures::{stream::FuturesOrdered, TryStreamExt};
|
use futures::{stream::FuturesOrdered, TryStreamExt};
|
||||||
|
|
||||||
use crate::anchor_from_status;
|
use crate::anchor_from_status;
|
||||||
|
|
||||||
|
/// [`esplora_client::Error`]
|
||||||
|
type Error = Box<esplora_client::Error>;
|
||||||
|
|
||||||
/// Trait to extend the functionality of [`esplora_client::AsyncClient`].
|
/// Trait to extend the functionality of [`esplora_client::AsyncClient`].
|
||||||
///
|
///
|
||||||
/// Refer to [crate-level documentation] for more.
|
/// Refer to [crate-level documentation] for more.
|
||||||
@@ -35,7 +38,6 @@ pub trait EsploraAsyncExt {
|
|||||||
/// [`LocalChain`]: bdk_chain::local_chain::LocalChain
|
/// [`LocalChain`]: bdk_chain::local_chain::LocalChain
|
||||||
/// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
|
/// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
|
||||||
/// [`LocalChain::apply_update`]: bdk_chain::local_chain::LocalChain::apply_update
|
/// [`LocalChain::apply_update`]: bdk_chain::local_chain::LocalChain::apply_update
|
||||||
#[allow(clippy::result_large_err)]
|
|
||||||
async fn update_local_chain(
|
async fn update_local_chain(
|
||||||
&self,
|
&self,
|
||||||
local_tip: CheckPoint,
|
local_tip: CheckPoint,
|
||||||
@@ -50,7 +52,6 @@ pub trait EsploraAsyncExt {
|
|||||||
/// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
|
/// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
|
||||||
/// transactions. `parallel_requests` specifies the max number of HTTP requests to make in
|
/// transactions. `parallel_requests` specifies the max number of HTTP requests to make in
|
||||||
/// parallel.
|
/// parallel.
|
||||||
#[allow(clippy::result_large_err)]
|
|
||||||
async fn full_scan<K: Ord + Clone + Send>(
|
async fn full_scan<K: Ord + Clone + Send>(
|
||||||
&self,
|
&self,
|
||||||
keychain_spks: BTreeMap<
|
keychain_spks: BTreeMap<
|
||||||
@@ -73,7 +74,6 @@ pub trait EsploraAsyncExt {
|
|||||||
/// may include scripts that have been used, use [`full_scan`] with the keychain.
|
/// may include scripts that have been used, use [`full_scan`] with the keychain.
|
||||||
///
|
///
|
||||||
/// [`full_scan`]: EsploraAsyncExt::full_scan
|
/// [`full_scan`]: EsploraAsyncExt::full_scan
|
||||||
#[allow(clippy::result_large_err)]
|
|
||||||
async fn sync(
|
async fn sync(
|
||||||
&self,
|
&self,
|
||||||
misc_spks: impl IntoIterator<IntoIter = impl Iterator<Item = ScriptBuf> + Send> + Send,
|
misc_spks: impl IntoIterator<IntoIter = impl Iterator<Item = ScriptBuf> + Send> + Send,
|
||||||
@@ -204,6 +204,24 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
|
|||||||
if let Some(anchor) = anchor_from_status(&tx.status) {
|
if let Some(anchor) = anchor_from_status(&tx.status) {
|
||||||
let _ = graph.insert_anchor(tx.txid, anchor);
|
let _ = graph.insert_anchor(tx.txid, anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previous_outputs = tx.vin.iter().filter_map(|vin| {
|
||||||
|
let prevout = vin.prevout.as_ref()?;
|
||||||
|
Some((
|
||||||
|
OutPoint {
|
||||||
|
txid: vin.txid,
|
||||||
|
vout: vin.vout,
|
||||||
|
},
|
||||||
|
TxOut {
|
||||||
|
script_pubkey: prevout.scriptpubkey.clone(),
|
||||||
|
value: prevout.value,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
for (outpoint, txout) in previous_outputs {
|
||||||
|
let _ = graph.insert_txout(outpoint, txout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ use std::thread::JoinHandle;
|
|||||||
use bdk_chain::collections::btree_map;
|
use bdk_chain::collections::btree_map;
|
||||||
use bdk_chain::collections::BTreeMap;
|
use bdk_chain::collections::BTreeMap;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid},
|
bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
|
||||||
local_chain::{self, CheckPoint},
|
local_chain::{self, CheckPoint},
|
||||||
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
|
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
|
||||||
};
|
};
|
||||||
use esplora_client::{Error, TxStatus};
|
use esplora_client::TxStatus;
|
||||||
|
|
||||||
use crate::anchor_from_status;
|
use crate::anchor_from_status;
|
||||||
|
|
||||||
|
/// [`esplora_client::Error`]
|
||||||
|
type Error = Box<esplora_client::Error>;
|
||||||
|
|
||||||
/// Trait to extend the functionality of [`esplora_client::BlockingClient`].
|
/// Trait to extend the functionality of [`esplora_client::BlockingClient`].
|
||||||
///
|
///
|
||||||
/// Refer to [crate-level documentation] for more.
|
/// Refer to [crate-level documentation] for more.
|
||||||
@@ -33,7 +36,6 @@ pub trait EsploraExt {
|
|||||||
/// [`LocalChain`]: bdk_chain::local_chain::LocalChain
|
/// [`LocalChain`]: bdk_chain::local_chain::LocalChain
|
||||||
/// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
|
/// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
|
||||||
/// [`LocalChain::apply_update`]: bdk_chain::local_chain::LocalChain::apply_update
|
/// [`LocalChain::apply_update`]: bdk_chain::local_chain::LocalChain::apply_update
|
||||||
#[allow(clippy::result_large_err)]
|
|
||||||
fn update_local_chain(
|
fn update_local_chain(
|
||||||
&self,
|
&self,
|
||||||
local_tip: CheckPoint,
|
local_tip: CheckPoint,
|
||||||
@@ -48,7 +50,6 @@ pub trait EsploraExt {
|
|||||||
/// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
|
/// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
|
||||||
/// transactions. `parallel_requests` specifies the max number of HTTP requests to make in
|
/// transactions. `parallel_requests` specifies the max number of HTTP requests to make in
|
||||||
/// parallel.
|
/// parallel.
|
||||||
#[allow(clippy::result_large_err)]
|
|
||||||
fn full_scan<K: Ord + Clone>(
|
fn full_scan<K: Ord + Clone>(
|
||||||
&self,
|
&self,
|
||||||
keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
|
keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
|
||||||
@@ -68,7 +69,6 @@ pub trait EsploraExt {
|
|||||||
/// may include scripts that have been used, use [`full_scan`] with the keychain.
|
/// may include scripts that have been used, use [`full_scan`] with the keychain.
|
||||||
///
|
///
|
||||||
/// [`full_scan`]: EsploraExt::full_scan
|
/// [`full_scan`]: EsploraExt::full_scan
|
||||||
#[allow(clippy::result_large_err)]
|
|
||||||
fn sync(
|
fn sync(
|
||||||
&self,
|
&self,
|
||||||
misc_spks: impl IntoIterator<Item = ScriptBuf>,
|
misc_spks: impl IntoIterator<Item = ScriptBuf>,
|
||||||
@@ -194,6 +194,24 @@ impl EsploraExt for esplora_client::BlockingClient {
|
|||||||
if let Some(anchor) = anchor_from_status(&tx.status) {
|
if let Some(anchor) = anchor_from_status(&tx.status) {
|
||||||
let _ = graph.insert_anchor(tx.txid, anchor);
|
let _ = graph.insert_anchor(tx.txid, anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previous_outputs = tx.vin.iter().filter_map(|vin| {
|
||||||
|
let prevout = vin.prevout.as_ref()?;
|
||||||
|
Some((
|
||||||
|
OutPoint {
|
||||||
|
txid: vin.txid,
|
||||||
|
vout: vin.vout,
|
||||||
|
},
|
||||||
|
TxOut {
|
||||||
|
script_pubkey: prevout.scriptpubkey.clone(),
|
||||||
|
value: prevout.value,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
for (outpoint, txout) in previous_outputs {
|
||||||
|
let _ = graph.insert_txout(outpoint, txout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +265,12 @@ impl EsploraExt for esplora_client::BlockingClient {
|
|||||||
.map(|txid| {
|
.map(|txid| {
|
||||||
std::thread::spawn({
|
std::thread::spawn({
|
||||||
let client = self.clone();
|
let client = self.clone();
|
||||||
move || client.get_tx_status(&txid).map(|s| (txid, s))
|
move || {
|
||||||
|
client
|
||||||
|
.get_tx_status(&txid)
|
||||||
|
.map_err(Box::new)
|
||||||
|
.map(|s| (txid, s))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<JoinHandle<Result<(Txid, TxStatus), Error>>>>();
|
.collect::<Vec<JoinHandle<Result<(Txid, TxStatus), Error>>>>();
|
||||||
|
|||||||
@@ -109,6 +109,28 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// Check to see if we have the floating txouts available from our two created transactions'
|
||||||
|
// previous outputs in order to calculate transaction fees.
|
||||||
|
for tx in graph_update.full_txs() {
|
||||||
|
// Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the
|
||||||
|
// floating txouts available from the transactions' previous outputs.
|
||||||
|
let fee = graph_update.calculate_fee(tx.tx).expect("Fee must exist");
|
||||||
|
|
||||||
|
// Retrieve the fee in the transaction data from `bitcoind`.
|
||||||
|
let tx_fee = env
|
||||||
|
.bitcoind
|
||||||
|
.client
|
||||||
|
.get_transaction(&tx.txid, None)
|
||||||
|
.expect("Tx must exist")
|
||||||
|
.fee
|
||||||
|
.expect("Fee must exist")
|
||||||
|
.abs()
|
||||||
|
.to_sat() as u64;
|
||||||
|
|
||||||
|
// Check that the calculated fee matches the fee from the transaction data.
|
||||||
|
assert_eq!(fee, tx_fee);
|
||||||
|
}
|
||||||
|
|
||||||
let mut graph_update_txids: Vec<Txid> = graph_update.full_txs().map(|tx| tx.txid).collect();
|
let mut graph_update_txids: Vec<Txid> = graph_update.full_txs().map(|tx| tx.txid).collect();
|
||||||
graph_update_txids.sort();
|
graph_update_txids.sort();
|
||||||
let mut expected_txids = vec![txid1, txid2];
|
let mut expected_txids = vec![txid1, txid2];
|
||||||
|
|||||||
@@ -136,6 +136,28 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
|
|||||||
1,
|
1,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Check to see if we have the floating txouts available from our two created transactions'
|
||||||
|
// previous outputs in order to calculate transaction fees.
|
||||||
|
for tx in graph_update.full_txs() {
|
||||||
|
// Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the
|
||||||
|
// floating txouts available from the transactions' previous outputs.
|
||||||
|
let fee = graph_update.calculate_fee(tx.tx).expect("Fee must exist");
|
||||||
|
|
||||||
|
// Retrieve the fee in the transaction data from `bitcoind`.
|
||||||
|
let tx_fee = env
|
||||||
|
.bitcoind
|
||||||
|
.client
|
||||||
|
.get_transaction(&tx.txid, None)
|
||||||
|
.expect("Tx must exist")
|
||||||
|
.fee
|
||||||
|
.expect("Fee must exist")
|
||||||
|
.abs()
|
||||||
|
.to_sat() as u64;
|
||||||
|
|
||||||
|
// Check that the calculated fee matches the fee from the transaction data.
|
||||||
|
assert_eq!(fee, tx_fee);
|
||||||
|
}
|
||||||
|
|
||||||
let mut graph_update_txids: Vec<Txid> = graph_update.full_txs().map(|tx| tx.txid).collect();
|
let mut graph_update_txids: Vec<Txid> = graph_update.full_txs().map(|tx| tx.txid).collect();
|
||||||
graph_update_txids.sort();
|
graph_update_txids.sort();
|
||||||
let mut expected_txids = vec![txid1, txid2];
|
let mut expected_txids = vec![txid1, txid2];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk_file_store"
|
name = "bdk_file_store"
|
||||||
version = "0.5.0"
|
version = "0.7.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/bitcoindevkit/bdk"
|
repository = "https://github.com/bitcoindevkit/bdk"
|
||||||
@@ -11,7 +11,7 @@ authors = ["Bitcoin Dev Kit Developers"]
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk_chain = { path = "../chain", version = "0.9.0", features = [ "serde", "miniscript" ] }
|
bdk_chain = { path = "../chain", version = "0.11.0", features = [ "serde", "miniscript" ] }
|
||||||
bincode = { version = "1" }
|
bincode = { version = "1" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
|
|||||||
@@ -110,9 +110,13 @@ enum RpcCommands {
|
|||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
let example_cli::Init {
|
||||||
let (args, keymap, index, db, init_changeset) =
|
args,
|
||||||
example_cli::init::<RpcCommands, RpcArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
|
keymap,
|
||||||
|
index,
|
||||||
|
db,
|
||||||
|
init_changeset,
|
||||||
|
} = example_cli::init::<RpcCommands, RpcArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
|
||||||
println!(
|
println!(
|
||||||
"[{:>10}s] loaded initial changeset from db",
|
"[{:>10}s] loaded initial changeset from db",
|
||||||
start.elapsed().as_secs_f32()
|
start.elapsed().as_secs_f32()
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ pub struct Args<CS: clap::Subcommand, S: clap::Args> {
|
|||||||
pub command: Commands<CS, S>,
|
pub command: Commands<CS, S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::almost_swapped)]
|
|
||||||
#[derive(Subcommand, Debug, Clone)]
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
pub enum Commands<CS: clap::Subcommand, S: clap::Args> {
|
pub enum Commands<CS: clap::Subcommand, S: clap::Args> {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
@@ -137,7 +136,6 @@ impl core::fmt::Display for CoinSelectionAlgo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::almost_swapped)]
|
|
||||||
#[derive(Subcommand, Debug, Clone)]
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
pub enum AddressCmd {
|
pub enum AddressCmd {
|
||||||
/// Get the next unused address.
|
/// Get the next unused address.
|
||||||
@@ -190,7 +188,12 @@ impl core::fmt::Display for Keychain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
pub struct CreateTxChange {
|
||||||
|
pub index_changeset: keychain::ChangeSet<Keychain>,
|
||||||
|
pub change_keychain: Keychain,
|
||||||
|
pub index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_tx<A: Anchor, O: ChainOracle>(
|
pub fn create_tx<A: Anchor, O: ChainOracle>(
|
||||||
graph: &mut KeychainTxGraph<A>,
|
graph: &mut KeychainTxGraph<A>,
|
||||||
chain: &O,
|
chain: &O,
|
||||||
@@ -198,10 +201,7 @@ pub fn create_tx<A: Anchor, O: ChainOracle>(
|
|||||||
cs_algorithm: CoinSelectionAlgo,
|
cs_algorithm: CoinSelectionAlgo,
|
||||||
address: Address,
|
address: Address,
|
||||||
value: u64,
|
value: u64,
|
||||||
) -> anyhow::Result<(
|
) -> anyhow::Result<(Transaction, Option<CreateTxChange>)>
|
||||||
Transaction,
|
|
||||||
Option<(keychain::ChangeSet<Keychain>, (Keychain, u32))>,
|
|
||||||
)>
|
|
||||||
where
|
where
|
||||||
O::Error: std::error::Error + Send + Sync + 'static,
|
O::Error: std::error::Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
@@ -393,7 +393,11 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let change_info = if selection_meta.drain_value.is_some() {
|
let change_info = if selection_meta.drain_value.is_some() {
|
||||||
Some((changeset, (internal_keychain, change_index)))
|
Some(CreateTxChange {
|
||||||
|
index_changeset: changeset,
|
||||||
|
change_keychain: internal_keychain,
|
||||||
|
index: change_index,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -401,35 +405,34 @@ where
|
|||||||
Ok((transaction, change_info))
|
Ok((transaction, change_info))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
// Alias the elements of `Result` of `planned_utxos`
|
||||||
|
pub type PlannedUtxo<K, A> = (bdk_tmp_plan::Plan<K>, FullTxOut<A>);
|
||||||
|
|
||||||
pub fn planned_utxos<A: Anchor, O: ChainOracle, K: Clone + bdk_tmp_plan::CanDerive>(
|
pub fn planned_utxos<A: Anchor, O: ChainOracle, K: Clone + bdk_tmp_plan::CanDerive>(
|
||||||
graph: &KeychainTxGraph<A>,
|
graph: &KeychainTxGraph<A>,
|
||||||
chain: &O,
|
chain: &O,
|
||||||
assets: &bdk_tmp_plan::Assets<K>,
|
assets: &bdk_tmp_plan::Assets<K>,
|
||||||
) -> Result<Vec<(bdk_tmp_plan::Plan<K>, FullTxOut<A>)>, O::Error> {
|
) -> Result<Vec<PlannedUtxo<K, A>>, O::Error> {
|
||||||
let chain_tip = chain.get_chain_tip()?;
|
let chain_tip = chain.get_chain_tip()?;
|
||||||
let outpoints = graph.index.outpoints().iter().cloned();
|
let outpoints = graph.index.outpoints().iter().cloned();
|
||||||
graph
|
graph
|
||||||
.graph()
|
.graph()
|
||||||
.try_filter_chain_unspents(chain, chain_tip, outpoints)
|
.try_filter_chain_unspents(chain, chain_tip, outpoints)
|
||||||
.filter_map(
|
.filter_map(|r| -> Option<Result<PlannedUtxo<K, A>, _>> {
|
||||||
#[allow(clippy::type_complexity)]
|
let (k, i, full_txo) = match r {
|
||||||
|r| -> Option<Result<(bdk_tmp_plan::Plan<K>, FullTxOut<A>), _>> {
|
Err(err) => return Some(Err(err)),
|
||||||
let (k, i, full_txo) = match r {
|
Ok(((k, i), full_txo)) => (k, i, full_txo),
|
||||||
Err(err) => return Some(Err(err)),
|
};
|
||||||
Ok(((k, i), full_txo)) => (k, i, full_txo),
|
let desc = graph
|
||||||
};
|
.index
|
||||||
let desc = graph
|
.keychains()
|
||||||
.index
|
.get(&k)
|
||||||
.keychains()
|
.expect("keychain must exist")
|
||||||
.get(&k)
|
.at_derivation_index(i)
|
||||||
.expect("keychain must exist")
|
.expect("i can't be hardened");
|
||||||
.at_derivation_index(i)
|
let plan = bdk_tmp_plan::plan_satisfaction(&desc, assets)?;
|
||||||
.expect("i can't be hardened");
|
Some(Ok((plan, full_txo)))
|
||||||
let plan = bdk_tmp_plan::plan_satisfaction(&desc, assets)?;
|
})
|
||||||
Some(Ok((plan, full_txo)))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,7 +602,12 @@ where
|
|||||||
let (tx, change_info) =
|
let (tx, change_info) =
|
||||||
create_tx(graph, chain, keymap, coin_select, address, value)?;
|
create_tx(graph, chain, keymap, coin_select, address, value)?;
|
||||||
|
|
||||||
if let Some((index_changeset, (change_keychain, index))) = change_info {
|
if let Some(CreateTxChange {
|
||||||
|
index_changeset,
|
||||||
|
change_keychain,
|
||||||
|
index,
|
||||||
|
}) = change_info
|
||||||
|
{
|
||||||
// We must first persist to disk the fact that we've got a new address from the
|
// We must first persist to disk the fact that we've got a new address from the
|
||||||
// change keychain so future scans will find the tx we're about to broadcast.
|
// change keychain so future scans will find the tx we're about to broadcast.
|
||||||
// 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.
|
||||||
@@ -648,17 +656,26 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
/// The initial state returned by [`init`].
|
||||||
|
pub struct Init<CS: clap::Subcommand, S: clap::Args, C> {
|
||||||
|
/// Arguments parsed by the cli.
|
||||||
|
pub args: Args<CS, S>,
|
||||||
|
/// Descriptor keymap.
|
||||||
|
pub keymap: KeyMap,
|
||||||
|
/// Keychain-txout index.
|
||||||
|
pub index: KeychainTxOutIndex<Keychain>,
|
||||||
|
/// Persistence backend.
|
||||||
|
pub db: Mutex<Database<C>>,
|
||||||
|
/// Initial changeset.
|
||||||
|
pub init_changeset: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses command line arguments and initializes all components, creating
|
||||||
|
/// a file store with the given parameters, or loading one if it exists.
|
||||||
pub fn init<CS: clap::Subcommand, S: clap::Args, C>(
|
pub fn init<CS: clap::Subcommand, S: clap::Args, C>(
|
||||||
db_magic: &[u8],
|
db_magic: &[u8],
|
||||||
db_default_path: &str,
|
db_default_path: &str,
|
||||||
) -> anyhow::Result<(
|
) -> anyhow::Result<Init<CS, S, C>>
|
||||||
Args<CS, S>,
|
|
||||||
KeyMap,
|
|
||||||
KeychainTxOutIndex<Keychain>,
|
|
||||||
Mutex<Database<C>>,
|
|
||||||
C,
|
|
||||||
)>
|
|
||||||
where
|
where
|
||||||
C: Default + Append + Serialize + DeserializeOwned,
|
C: Default + Append + Serialize + DeserializeOwned,
|
||||||
{
|
{
|
||||||
@@ -692,11 +709,11 @@ where
|
|||||||
|
|
||||||
let init_changeset = db_backend.load_from_persistence()?.unwrap_or_default();
|
let init_changeset = db_backend.load_from_persistence()?.unwrap_or_default();
|
||||||
|
|
||||||
Ok((
|
Ok(Init {
|
||||||
args,
|
args,
|
||||||
keymap,
|
keymap,
|
||||||
index,
|
index,
|
||||||
Mutex::new(Database::new(db_backend)),
|
db: Mutex::new(Database::new(db_backend)),
|
||||||
init_changeset,
|
init_changeset,
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,8 +103,15 @@ type ChangeSet = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let (args, keymap, index, db, (disk_local_chain, disk_tx_graph)) =
|
let example_cli::Init {
|
||||||
example_cli::init::<ElectrumCommands, ElectrumArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
|
args,
|
||||||
|
keymap,
|
||||||
|
index,
|
||||||
|
db,
|
||||||
|
init_changeset,
|
||||||
|
} = example_cli::init::<ElectrumCommands, ElectrumArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
|
||||||
|
|
||||||
|
let (disk_local_chain, disk_tx_graph) = init_changeset;
|
||||||
|
|
||||||
let graph = Mutex::new({
|
let graph = Mutex::new({
|
||||||
let mut graph = IndexedTxGraph::new(index);
|
let mut graph = IndexedTxGraph::new(index);
|
||||||
|
|||||||
@@ -99,8 +99,13 @@ pub struct ScanOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let (args, keymap, index, db, init_changeset) =
|
let example_cli::Init {
|
||||||
example_cli::init::<EsploraCommands, EsploraArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
|
args,
|
||||||
|
keymap,
|
||||||
|
index,
|
||||||
|
db,
|
||||||
|
init_changeset,
|
||||||
|
} = example_cli::init::<EsploraCommands, EsploraArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
|
||||||
|
|
||||||
let genesis_hash = genesis_block(args.network).block_hash();
|
let genesis_hash = genesis_block(args.network).block_hash();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
$ cargo run --bin wallet_rpc -- --help
|
$ cargo run --bin wallet_rpc -- --help
|
||||||
|
|
||||||
wallet_rpc 0.1.0
|
wallet_rpc 0.1.0
|
||||||
Bitcoind RPC example usign `bdk::Wallet`
|
Bitcoind RPC example using `bdk::Wallet`
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
wallet_rpc [OPTIONS] <DESCRIPTOR> [CHANGE_DESCRIPTOR]
|
wallet_rpc [OPTIONS] <DESCRIPTOR> [CHANGE_DESCRIPTOR]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use std::{path::PathBuf, sync::mpsc::sync_channel, thread::spawn, time::Instant}
|
|||||||
|
|
||||||
const DB_MAGIC: &str = "bdk-rpc-wallet-example";
|
const DB_MAGIC: &str = "bdk-rpc-wallet-example";
|
||||||
|
|
||||||
/// Bitcoind RPC example usign `bdk::Wallet`.
|
/// Bitcoind RPC example using `bdk::Wallet`.
|
||||||
///
|
///
|
||||||
/// This syncs the chain block-by-block and prints the current balance, transaction count and UTXO
|
/// This syncs the chain block-by-block and prints the current balance, transaction count and UTXO
|
||||||
/// count.
|
/// count.
|
||||||
|
|||||||
Reference in New Issue
Block a user