Compare commits

...

22 Commits

Author SHA1 Message Date
Steve Myers
50c549b5ac Merge bitcoindevkit/bdk#1347: Bump bdk version to 1.0.0-alpha.6
8379839010 Bump version to 1.0.0-alpha.6 (Steve Myers)

Pull request description:

  ### Description

  Fixes #1343

  Bump bdk version to 1.0.0-alpha.6, also bump:

  bdk_chain to 0.10.0
  bdk_bitcoind_rpc to 0.5.0
  bdk_electrum to 0.8.0
  bdk_esplora to 0.8.0
  bdk_file_store to 0.6.0

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  danielabrozzoni:
    utACK 8379839010

Tree-SHA512: b16a8f88ab66ed78d6f6402d60acfa79b2371a12580a277f9bb3d3df212fb608a8305b6f5082bb280b9a18e209c70be0db38c893fff2f5c1f6085a99e3819479
2024-02-15 11:25:40 -06:00
Steve Myers
8379839010 Bump version to 1.0.0-alpha.6
bdk_chain to 0.10.0
bdk_bitcoind_rpc to 0.5.0
bdk_electrum to 0.8.0
bdk_esplora to 0.8.0
bdk_file_store to 0.6.0
2024-02-15 10:23:05 -06:00
志宇
420e929463 Merge bitcoindevkit/bdk#1335: fix(chain): tx_graph::ChangeSet::is_empty
13ab5a835d chore(chain): Improve TxGraph::ChangeSet docs (LLFourn)
dbbd514242 fix(chain)!: rm duplicate `is_empty` method in tx graph changeset (志宇)
ae00e1ee7b fix(chain): tx_graph::ChangeSet::is_empty (LLFourn)

Pull request description:

  🙈

  ### Changelog notice

  - Fix bug in `tx_graph::ChangeSet::is_empty` where is returns true even when it wasn't empty

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [x] I've added tests to reproduce the issue which are now passing

ACKs for top commit:
  LLFourn:
    Self-ACK: 13ab5a835d
  evanlinjin:
    ACK 13ab5a835d

Tree-SHA512: b9f1f17fd2ed0f8e2337a8033e1cbd3e9f15b1ad4b32da3f0eb73a30913d6798e7a08d6b297d93bd08c2e1c388226e97648650ac636846b2c7aa95c3bcefbcfd
2024-02-11 17:49:21 +08:00
LLFourn
13ab5a835d chore(chain): Improve TxGraph::ChangeSet docs 2024-02-10 09:13:08 +11:00
志宇
728e26f223 Merge bitcoindevkit/bdk#1334: Reorder fields in ConfirmationHeightAnchor fields so Ord DWIM
adc95137ac fix(chain)! Re-order fields in anchors so Ord DWIM (LLFourn)

Pull request description:

  Something that is confirmed more recently should be greater than something that is confirmed earlier regardless of anchor.

ACKs for top commit:
  evanlinjin:
    ACK adc95137ac

Tree-SHA512: 9a588b64afc7e20b35a9abb8c25b8b82858c0f89886320c0fc91f6c61592fccfa7fbaa3020a393b1d5fd79f71302a388255b69cb3726a38a0f2fdab8bb93769c
2024-02-10 04:17:30 +08:00
志宇
dbbd514242 fix(chain)!: rm duplicate is_empty method in tx graph changeset 2024-02-10 03:35:48 +08:00
LLFourn
ae00e1ee7b fix(chain): tx_graph::ChangeSet::is_empty 2024-02-09 20:03:57 +11:00
LLFourn
adc95137ac fix(chain)! Re-order fields in anchors so Ord DWIM 2024-02-09 13:48:19 +11:00
Daniela Brozzoni
7aca88474a Merge bitcoindevkit/bdk#1308: feat(esplora): include previous TxOuts for fee calculation
552f11cb5f feat(esplora): include previous `TxOut`s for fee calculation The previous `TxOut` for transactions received from an external wallet are added as floating `TxOut`s to `TxGraph` to allow for fee calculation. (Wei Chen)

Pull request description:

  ### Description

  Partially implements #1265.

  The previous `TxOut` for transactions received from an external wallet are added as floating `TxOut`s to `TxGraph` to allow for fee calculation.

  ### Notes to the reviewers

  Currently only the `esplora` portion of #1265 has been implemented.
  The `electrum` portion will potentially be done in a new PR, as discussed on the 1/30/24 Lib call.

  ### Checklists

  #### To Do:
  * [ ] Implement `electrum` portion of #1265.

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  evanlinjin:
    re-ACK 552f11cb5f
  danielabrozzoni:
    ACK 552f11cb5f

Tree-SHA512: 752a24ebd0b9ad7952c1b093ecb251473e346c77b860c1a80c73418130189227405a0f0d7652967cf8c7b89994e8c37df96cd52b52b6daff9cc8c88b5194069a
2024-02-05 12:44:58 +01:00
Daniela Brozzoni
b3278a4c29 Merge bitcoindevkit/bdk#1316: tx_builder: Support setting explicit nSequence for foreign inputs
9bb39a3a3f Avoid a wildcard match in tx construction (Steven Roose)
9e098a5b6d tx_builder: Support setting explicit nSequence for foreign inputs (Steven Roose)

Pull request description:

  Fixes https://github.com/bitcoindevkit/bdk/issues/1315.

ACKs for top commit:
  evanlinjin:
    ACK 9bb39a3a3f
  danielabrozzoni:
    utACK 9bb39a3a3f

Tree-SHA512: 42c96a58a762fa8737402ebd0132ce20ce0359c996cee9feeecb0b84e6fb73305be1aec9429fb97ba932486f0b35ac5caed7b43656456c5fb053e55330a12d47
2024-02-05 12:36:11 +01:00
Wei Chen
552f11cb5f feat(esplora): include previous TxOuts for fee calculation
The previous `TxOut` for transactions received from an external
wallet are added as floating `TxOut`s to `TxGraph` to allow for
fee calculation.
2024-02-05 17:01:11 +08:00
Steve Myers
d8f74dc5e4 Merge bitcoindevkit/bdk#1319: chore: typos
8d93fad778 chore: typos (Jose Storopoli)

Pull request description:

  More caught on by Nix CI in #1257.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [ ] I've added tests for the new feature
  * [ ] I've added docs for the new feature

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [ ] I've added tests to reproduce the issue which are now passing
  * [ ] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK 8d93fad778
  notmandatory:
    ACK 8d93fad778

Tree-SHA512: 28e0316d457658266b2af1de76b114f87ce7485e386ddecd805dda1266a4e8645612c0fa6bc921c58daa4886558b32b538cccbb1644c96c3bab638dd7c42ee2b
2024-02-04 13:28:49 -06:00
Jose Storopoli
8d93fad778 chore: typos
More caught on by Nix CI in #1257.
2024-02-04 06:13:40 -03:00
Steven Roose
9bb39a3a3f Avoid a wildcard match in tx construction 2024-02-02 02:03:55 +00:00
Steven Roose
9e098a5b6d tx_builder: Support setting explicit nSequence for foreign inputs 2024-02-02 02:03:53 +00:00
志宇
c6b9ed3b76 Merge bitcoindevkit/bdk#1186: Clean up clippy allows
1c15cb2f91 ref(example_cli): Add new struct Init (vmammal)
89a7ddca7f ref(esplora): `Box` a large `esplora_client::Error` (vmammal)
097d818d4c ref(wallet): `Wallet::preselect_utxos` now accepts a `&TxParams` (vmammal)
f11d663b7e ref(psbt): refactor body of `get_utxo_for` to address `clippy::manual_map` (vmammal)
4679ca1df7 ref(example_cli): add typedefs to reduce type complexity (vmammal)
64a90192d9 refactor: remove old clippy allow attributes (vmammal)

Pull request description:

  closes #1127

  There are several instances in the code where we allow clippy lints that would otherwise be flagged during regular checks. It would be preferable to minimize the number of "clippy allow" attributes either by fixing the affected areas or setting a specific configuration in `clippy.toml`. In cases where we have to allow a particular lint, it should be documented why the lint doesn't apply.

  For context see https://github.com/bitcoindevkit/bdk/issues/1127#issuecomment-1784256647 as well as the commit message details.

  One area I'm unsure of is whether `Box`ing a large error in 4fc2216 is the right approach. Logically it makes sense to avoid allocating a needlessly heavy `Result`, but I haven't studied the implications or tradeoffs of such a change.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  evanlinjin:
    ACK 1c15cb2f91

Tree-SHA512: 5fa3796a33678651414e7aad7ef8309b4cbe2a9ab00dce094964b40784edb2f46a44067785d95ea26f4cd88d593420485be94c9b09ac589f632453fbd8c94d85
2024-02-01 01:19:07 +08:00
vmammal
1c15cb2f91 ref(example_cli): Add new struct Init
for holding the items returned from `example_cli::init`
2024-01-31 11:50:41 -05:00
vmammal
89a7ddca7f ref(esplora): Box a large esplora_client::Error
to address `clippy::result_large_err`. Clippy's default large-error-
threshold is 128. `esplora_client::Error` currently has size 272.
2024-01-31 11:50:41 -05:00
vmammal
097d818d4c ref(wallet): Wallet::preselect_utxos now accepts a &TxParams
to reduce the number of required function args in order to satisfy
`clippy::too_many_arguments`
2024-01-31 11:50:40 -05:00
vmammal
f11d663b7e ref(psbt): refactor body of get_utxo_for to address clippy::manual_map 2024-01-31 11:50:40 -05:00
vmammal
4679ca1df7 ref(example_cli): add typedefs to reduce type complexity
- Add typedefs to model the result of functions `planned_utxos`
and `init`

- Add new struct `CreateTxChange` to hold any change info
resulting from `create_tx`

These changes help resolve clippy::type_complexity
2024-01-31 11:50:40 -05:00
vmammal
64a90192d9 refactor: remove old clippy allow attributes
These lints either resolved themselves, or the code has changed such that
they no longer apply, hence they can be removed with no further changes.

`clippy::derivable_impls`
`clippy::needless_collect`
`clippy::almost_swapped`
2024-01-31 11:11:26 -05:00
25 changed files with 315 additions and 141 deletions

View File

@@ -1,7 +1,7 @@
[package]
name = "bdk"
homepage = "https://bitcoindevkit.org"
version = "1.0.0-alpha.5"
version = "1.0.0-alpha.6"
repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk"
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 }
serde = { version = "^1.0", features = ["derive"] }
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.10.0", features = ["miniscript", "serde"], default-features = false }
# Optional dependencies
bip39 = { version = "2.0", optional = true }

View File

@@ -35,24 +35,16 @@ pub trait PsbtUtils {
}
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> {
let tx = &self.unsigned_tx;
let input = self.inputs.get(input_index)?;
if input_index >= tx.input.len() {
return None;
}
if let Some(input) = self.inputs.get(input_index) {
if let Some(wit_utxo) = &input.witness_utxo {
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
match (&input.witness_utxo, &input.non_witness_utxo) {
(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()
}),
_ => None,
}
}

View File

@@ -14,7 +14,7 @@ use core::convert::AsRef;
use core::ops::Sub;
use bdk_chain::ConfirmationTime;
use bitcoin::blockdata::transaction::{OutPoint, TxOut};
use bitcoin::blockdata::transaction::{OutPoint, Sequence, TxOut};
use bitcoin::{psbt, Weight};
use serde::{Deserialize, Serialize};
@@ -197,6 +197,8 @@ pub enum Utxo {
Foreign {
/// The location of the output.
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.
// Box it to stop the type being too big.
psbt_input: Box<psbt::Input>,
@@ -219,6 +221,7 @@ impl Utxo {
Utxo::Foreign {
outpoint,
psbt_input,
..
} => {
if let Some(prev_tx) = &psbt_input.non_witness_utxo {
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)]

View File

@@ -12,7 +12,7 @@
//! Wallet
//!
//! This module defines the [`Wallet`].
use crate::collections::{BTreeMap, HashMap, HashSet};
use crate::collections::{BTreeMap, HashMap};
use alloc::{
boxed::Box,
string::{String, ToString},
@@ -1347,7 +1347,7 @@ impl<D> Wallet<D> {
}
Some(tx_builder::Version(x)) => x,
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 :)
@@ -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) {
// No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
(None, None) if lock_time != absolute::LockTime::ZERO => {
@@ -1518,15 +1519,8 @@ impl<D> Wallet<D> {
return Err(CreateTxError::ChangePolicyDescriptor);
}
let (required_utxos, optional_utxos) = self.preselect_utxos(
params.change_policy,
&params.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()),
);
let (required_utxos, optional_utxos) =
self.preselect_utxos(&params, Some(current_height.to_consensus_u32()));
// get drain script
let drain_script = match params.drain_to {
@@ -1565,7 +1559,7 @@ impl<D> Wallet<D> {
.map(|u| bitcoin::TxIn {
previous_output: u.outpoint(),
script_sig: ScriptBuf::default(),
sequence: n_sequence,
sequence: u.sequence().unwrap_or(n_sequence),
witness: Witness::new(),
})
.collect();
@@ -1745,6 +1739,7 @@ impl<D> Wallet<D> {
satisfaction_weight,
utxo: Utxo::Foreign {
outpoint: txin.previous_output,
sequence: Some(txin.sequence),
psbt_input: Box::new(psbt::Input {
witness_utxo: Some(txout.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
/// transaction and any further that may be used if needed.
#[allow(clippy::too_many_arguments)]
fn preselect_utxos(
&self,
change_policy: tx_builder::ChangeSpendPolicy,
unspendable: &HashSet<OutPoint>,
manually_selected: Vec<WeightedUtxo>,
must_use_all_available: bool,
manual_only: bool,
must_only_use_confirmed_tx: bool,
params: &TxParams,
current_height: Option<u32>,
) -> (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();
// must_spend <- manually selected 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
// selection overrides unspendable.
if manual_only {
if *manually_selected_only {
return (must_spend, vec![]);
}
@@ -2216,8 +2220,9 @@ impl<D> Wallet<D> {
}
}
Utxo::Foreign {
psbt_input: foreign_psbt_input,
outpoint,
psbt_input: foreign_psbt_input,
..
} => {
let is_taproot = foreign_psbt_input
.witness_utxo
@@ -2290,9 +2295,6 @@ impl<D> Wallet<D> {
) -> Result<(), MiniscriptPsbtError> {
// We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all
// 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())
.filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo)))
.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
/// 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
/// `last_seen` is prioritied.
/// `last_seen` is prioritized.
pub fn apply_unconfirmed_txs<'t>(
&mut self,
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,

View File

@@ -820,7 +820,6 @@ pub enum TapLeavesOptions {
None,
}
#[allow(clippy::derivable_impls)]
impl Default for SignOptions {
fn default() -> Self {
SignOptions {

View File

@@ -389,6 +389,22 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
outpoint: OutPoint,
psbt_input: psbt::Input,
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> {
if psbt_input.witness_utxo.is_none() {
match psbt_input.non_witness_utxo.as_ref() {
@@ -413,6 +429,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
satisfaction_weight,
utxo: Utxo::Foreign {
outpoint,
sequence: Some(sequence),
psbt_input: Box::new(psbt_input),
},
});

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_bitcoind_rpc"
version = "0.4.0"
version = "0.5.0"
edition = "2021"
rust-version = "1.63"
homepage = "https://bitcoindevkit.org"
@@ -16,7 +16,7 @@ readme = "README.md"
# For no-std, remember to enable the bitcoin/no-std feature
bitcoin = { version = "0.30", default-features = false }
bitcoincore-rpc = { version = "0.17" }
bdk_chain = { path = "../chain", version = "0.9", default-features = false }
bdk_chain = { path = "../chain", version = "0.10", default-features = false }
[dev-dependencies]
bitcoind = { version = "0.33", features = ["25_0"] }

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_chain"
version = "0.9.0"
version = "0.10.0"
edition = "2021"
rust-version = "1.63"
homepage = "https://bitcoindevkit.org"

View File

@@ -9,7 +9,7 @@ use crate::{Anchor, AnchorFromBlockPosition, COINBASE_MATURITY};
pub enum ChainPosition<A> {
/// The chain data is seen as confirmed, and in anchored by `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),
}
@@ -48,14 +48,14 @@ impl<A: Anchor> ChainPosition<A> {
serde(crate = "serde_crate")
)]
pub enum ConfirmationTime {
/// The confirmed variant.
/// The transaction is confirmed
Confirmed {
/// Confirmation height.
height: u32,
/// Confirmation time in unix seconds.
time: u64,
},
/// The unconfirmed variant.
/// The transaction is unconfirmed
Unconfirmed {
/// The last-seen timestamp in unix seconds.
last_seen: u64,
@@ -157,13 +157,12 @@ impl From<(&u32, &BlockHash)> for BlockId {
serde(crate = "serde_crate")
)]
pub struct ConfirmationHeightAnchor {
/// The anchor block.
pub anchor_block: BlockId,
/// The exact confirmation height of the transaction.
///
/// It is assumed that this value is never larger than the height of the anchor block.
pub confirmation_height: u32,
/// The anchor block.
pub anchor_block: BlockId,
}
impl Anchor for ConfirmationHeightAnchor {
@@ -198,12 +197,12 @@ impl AnchorFromBlockPosition for ConfirmationHeightAnchor {
serde(crate = "serde_crate")
)]
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.
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 {
@@ -229,12 +228,12 @@ impl AnchorFromBlockPosition for ConfirmationTimeHeightAnchor {
/// A `TxOut` with as much data as we can retrieve about it
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
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`.
pub outpoint: OutPoint,
/// The `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.
pub spent_by: Option<(ChainPosition<A>, Txid)>,
/// Whether this output is on a coinbase transaction.
@@ -299,3 +298,35 @@ impl<A: Anchor> FullTxOut<A> {
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"
);
}
}

View File

@@ -59,7 +59,7 @@ where
/// Stages a new changeset and commits it (along with any other previously staged changes) to
/// the persistence backend
///
/// Convience method for calling [`stage`] and then [`commit`].
/// Convenience method for calling [`stage`] and then [`commit`].
///
/// [`stage`]: Self::stage
/// [`commit`]: Self::commit

View File

@@ -40,20 +40,23 @@
//! # use bdk_chain::example_utils::*;
//! # use bitcoin::Transaction;
//! # let tx_a = tx_from_hex(RAW_TX_1);
//! let mut graph: TxGraph = TxGraph::default();
//! let mut another_graph: TxGraph = TxGraph::default();
//! let mut tx_graph: TxGraph = TxGraph::default();
//!
//! // 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
//! another_graph.apply_changeset(changeset);
//! // We can restore the state of the `tx_graph` by applying all
//! // 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::example_utils::*;
//! # use bitcoin::Transaction;
@@ -1212,11 +1215,6 @@ impl<A> Default for 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`].
pub fn txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
self.txs

View File

@@ -213,7 +213,8 @@ fn insert_tx_graph_doesnt_count_coinbase_as_spent() {
};
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.tx_spends(Txid::all_zeros()).next().is_none());
}
@@ -289,7 +290,7 @@ fn insert_tx_displaces_txouts() {
}],
};
let _ = tx_graph.insert_txout(
let changeset = tx_graph.insert_txout(
OutPoint {
txid: tx.txid(),
vout: 0,
@@ -300,6 +301,8 @@ fn insert_tx_displaces_txouts() {
},
);
assert!(!changeset.is_empty());
let _ = tx_graph.insert_txout(
OutPoint {
txid: tx.txid(),
@@ -653,7 +656,8 @@ fn test_walk_ancestors() {
]);
[&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 = [
@@ -1027,10 +1031,12 @@ fn test_changeset_last_seen_append() {
last_seen: original_ls.map(|ls| (txid, ls)).into_iter().collect(),
..Default::default()
};
assert!(!original.is_empty() || original_ls.is_none());
let update = ChangeSet::<()> {
last_seen: update_ls.map(|ls| (txid, ls)).into_iter().collect(),
..Default::default()
};
assert!(!update.is_empty() || update_ls.is_none());
original.append(update);
assert_eq!(

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_electrum"
version = "0.7.0"
version = "0.8.0"
edition = "2021"
homepage = "https://bitcoindevkit.org"
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
[dependencies]
bdk_chain = { path = "../chain", version = "0.9.0", default-features = false }
bdk_chain = { path = "../chain", version = "0.10.0", default-features = false }
electrum-client = { version = "0.18" }
#rustls = { version = "=0.21.1", optional = true, features = ["dangerous_configuration"] }

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_esplora"
version = "0.7.0"
version = "0.8.0"
edition = "2021"
homepage = "https://bitcoindevkit.org"
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
[dependencies]
bdk_chain = { path = "../chain", version = "0.9.0", default-features = false }
bdk_chain = { path = "../chain", version = "0.10.0", default-features = false }
esplora-client = { version = "0.6.0", default-features = false }
async-trait = { version = "0.1.66", optional = true }
futures = { version = "0.3.26", optional = true }

View File

@@ -1,16 +1,19 @@
use async_trait::async_trait;
use bdk_chain::collections::btree_map;
use bdk_chain::{
bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid},
bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
collections::BTreeMap,
local_chain::{self, CheckPoint},
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
};
use esplora_client::{Error, TxStatus};
use esplora_client::TxStatus;
use futures::{stream::FuturesOrdered, TryStreamExt};
use crate::anchor_from_status;
/// [`esplora_client::Error`]
type Error = Box<esplora_client::Error>;
/// Trait to extend the functionality of [`esplora_client::AsyncClient`].
///
/// Refer to [crate-level documentation] for more.
@@ -35,7 +38,6 @@ pub trait EsploraAsyncExt {
/// [`LocalChain`]: bdk_chain::local_chain::LocalChain
/// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
/// [`LocalChain::apply_update`]: bdk_chain::local_chain::LocalChain::apply_update
#[allow(clippy::result_large_err)]
async fn update_local_chain(
&self,
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
/// transactions. `parallel_requests` specifies the max number of HTTP requests to make in
/// parallel.
#[allow(clippy::result_large_err)]
async fn full_scan<K: Ord + Clone + Send>(
&self,
keychain_spks: BTreeMap<
@@ -73,7 +74,6 @@ pub trait EsploraAsyncExt {
/// may include scripts that have been used, use [`full_scan`] with the keychain.
///
/// [`full_scan`]: EsploraAsyncExt::full_scan
#[allow(clippy::result_large_err)]
async fn sync(
&self,
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) {
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);
}
}
}

View File

@@ -3,14 +3,17 @@ use std::thread::JoinHandle;
use bdk_chain::collections::btree_map;
use bdk_chain::collections::BTreeMap;
use bdk_chain::{
bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid},
bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
local_chain::{self, CheckPoint},
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
};
use esplora_client::{Error, TxStatus};
use esplora_client::TxStatus;
use crate::anchor_from_status;
/// [`esplora_client::Error`]
type Error = Box<esplora_client::Error>;
/// Trait to extend the functionality of [`esplora_client::BlockingClient`].
///
/// Refer to [crate-level documentation] for more.
@@ -33,7 +36,6 @@ pub trait EsploraExt {
/// [`LocalChain`]: bdk_chain::local_chain::LocalChain
/// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
/// [`LocalChain::apply_update`]: bdk_chain::local_chain::LocalChain::apply_update
#[allow(clippy::result_large_err)]
fn update_local_chain(
&self,
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
/// transactions. `parallel_requests` specifies the max number of HTTP requests to make in
/// parallel.
#[allow(clippy::result_large_err)]
fn full_scan<K: Ord + Clone>(
&self,
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.
///
/// [`full_scan`]: EsploraExt::full_scan
#[allow(clippy::result_large_err)]
fn sync(
&self,
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) {
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| {
std::thread::spawn({
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>>>>();

View File

@@ -109,6 +109,28 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
)
.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();
graph_update_txids.sort();
let mut expected_txids = vec![txid1, txid2];

View File

@@ -136,6 +136,28 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
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();
graph_update_txids.sort();
let mut expected_txids = vec![txid1, txid2];

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_file_store"
version = "0.5.0"
version = "0.6.0"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/bitcoindevkit/bdk"
@@ -11,7 +11,7 @@ authors = ["Bitcoin Dev Kit Developers"]
readme = "README.md"
[dependencies]
bdk_chain = { path = "../chain", version = "0.9.0", features = [ "serde", "miniscript" ] }
bdk_chain = { path = "../chain", version = "0.10.0", features = [ "serde", "miniscript" ] }
bincode = { version = "1" }
serde = { version = "1", features = ["derive"] }

View File

@@ -110,9 +110,13 @@ enum RpcCommands {
fn main() -> anyhow::Result<()> {
let start = Instant::now();
let (args, keymap, index, db, init_changeset) =
example_cli::init::<RpcCommands, RpcArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
let example_cli::Init {
args,
keymap,
index,
db,
init_changeset,
} = example_cli::init::<RpcCommands, RpcArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
println!(
"[{:>10}s] loaded initial changeset from db",
start.elapsed().as_secs_f32()

View File

@@ -53,7 +53,6 @@ pub struct Args<CS: clap::Subcommand, S: clap::Args> {
pub command: Commands<CS, S>,
}
#[allow(clippy::almost_swapped)]
#[derive(Subcommand, Debug, Clone)]
pub enum Commands<CS: clap::Subcommand, S: clap::Args> {
#[clap(flatten)]
@@ -137,7 +136,6 @@ impl core::fmt::Display for CoinSelectionAlgo {
}
}
#[allow(clippy::almost_swapped)]
#[derive(Subcommand, Debug, Clone)]
pub enum AddressCmd {
/// 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>(
graph: &mut KeychainTxGraph<A>,
chain: &O,
@@ -198,10 +201,7 @@ pub fn create_tx<A: Anchor, O: ChainOracle>(
cs_algorithm: CoinSelectionAlgo,
address: Address,
value: u64,
) -> anyhow::Result<(
Transaction,
Option<(keychain::ChangeSet<Keychain>, (Keychain, u32))>,
)>
) -> anyhow::Result<(Transaction, Option<CreateTxChange>)>
where
O::Error: std::error::Error + Send + Sync + 'static,
{
@@ -393,7 +393,11 @@ where
}
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 {
None
};
@@ -401,35 +405,34 @@ where
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>(
graph: &KeychainTxGraph<A>,
chain: &O,
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 outpoints = graph.index.outpoints().iter().cloned();
graph
.graph()
.try_filter_chain_unspents(chain, chain_tip, outpoints)
.filter_map(
#[allow(clippy::type_complexity)]
|r| -> Option<Result<(bdk_tmp_plan::Plan<K>, FullTxOut<A>), _>> {
let (k, i, full_txo) = match r {
Err(err) => return Some(Err(err)),
Ok(((k, i), full_txo)) => (k, i, full_txo),
};
let desc = graph
.index
.keychains()
.get(&k)
.expect("keychain must exist")
.at_derivation_index(i)
.expect("i can't be hardened");
let plan = bdk_tmp_plan::plan_satisfaction(&desc, assets)?;
Some(Ok((plan, full_txo)))
},
)
.filter_map(|r| -> Option<Result<PlannedUtxo<K, A>, _>> {
let (k, i, full_txo) = match r {
Err(err) => return Some(Err(err)),
Ok(((k, i), full_txo)) => (k, i, full_txo),
};
let desc = graph
.index
.keychains()
.get(&k)
.expect("keychain must exist")
.at_derivation_index(i)
.expect("i can't be hardened");
let plan = bdk_tmp_plan::plan_satisfaction(&desc, assets)?;
Some(Ok((plan, full_txo)))
})
.collect()
}
@@ -599,7 +602,12 @@ where
let (tx, change_info) =
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
// 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.
@@ -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>(
db_magic: &[u8],
db_default_path: &str,
) -> anyhow::Result<(
Args<CS, S>,
KeyMap,
KeychainTxOutIndex<Keychain>,
Mutex<Database<C>>,
C,
)>
) -> anyhow::Result<Init<CS, S, C>>
where
C: Default + Append + Serialize + DeserializeOwned,
{
@@ -692,11 +709,11 @@ where
let init_changeset = db_backend.load_from_persistence()?.unwrap_or_default();
Ok((
Ok(Init {
args,
keymap,
index,
Mutex::new(Database::new(db_backend)),
db: Mutex::new(Database::new(db_backend)),
init_changeset,
))
})
}

View File

@@ -103,8 +103,15 @@ type ChangeSet = (
);
fn main() -> anyhow::Result<()> {
let (args, keymap, index, db, (disk_local_chain, disk_tx_graph)) =
example_cli::init::<ElectrumCommands, ElectrumArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
let example_cli::Init {
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 mut graph = IndexedTxGraph::new(index);

View File

@@ -99,8 +99,13 @@ pub struct ScanOptions {
}
fn main() -> anyhow::Result<()> {
let (args, keymap, index, db, init_changeset) =
example_cli::init::<EsploraCommands, EsploraArgs, ChangeSet>(DB_MAGIC, DB_PATH)?;
let example_cli::Init {
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();

View File

@@ -4,7 +4,7 @@
$ cargo run --bin wallet_rpc -- --help
wallet_rpc 0.1.0
Bitcoind RPC example usign `bdk::Wallet`
Bitcoind RPC example using `bdk::Wallet`
USAGE:
wallet_rpc [OPTIONS] <DESCRIPTOR> [CHANGE_DESCRIPTOR]

View File

@@ -12,7 +12,7 @@ use std::{path::PathBuf, sync::mpsc::sync_channel, thread::spawn, time::Instant}
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
/// count.