Compare commits

...

17 Commits

Author SHA1 Message Date
Steve Myers
ee21ffeee0 Merge bitcoindevkit/bdk#1404: Bump bdk version to 1.0.0-alpha.9
5f238d8e67 Bump bdk version to 1.0.0-alpha.9 (Steve Myers)

Pull request description:

  ### Description

  Bump bdk version to 1.0.0-alpha.9

  bdk_chain to 0.12.0
  bdk_bitcoind_rpc to 0.8.0
  bdk_electrum to 0.11.0
  bdk_esplora to 0.11.0
  bdk_file_store to 0.9.0
  bdk_testenv to 0.2.0

  fixes #1399

  ### Notes to the reviewers

  I also removed unneeded version for bdk_testenv in dev-dependencies, see https://github.com/bitcoindevkit/bdk/pull/1177#discussion_r1545945973.

  ### 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 5f238d8e67

Tree-SHA512: 7ede94a6476a6b8c49a16b0aad147030eab23e26b9c4cadb1dd319fb8562ae325640f506f2d6dab0e85b0ee7f6955ac298ec1f70264704dc7f9a9492f8794f38
2024-04-15 15:10:23 -05:00
Steve Myers
5f238d8e67 Bump bdk version to 1.0.0-alpha.9
bdk_chain to 0.12.0
bdk_bitcoind_rpc to 0.8.0
bdk_electrum to 0.11.0
bdk_esplora to 0.11.0
bdk_file_store to 0.9.0
bdk_testenv to 0.2.0
2024-04-12 11:43:25 -05:00
Steve Myers
358e842dcd Merge bitcoindevkit/bdk#1177: Upgrade bitcoin/miniscript dependencies
984c758f96 Upgrade miniscript/bitcoin dependency (Tobin C. Harding)

Pull request description:

  Upgrade:

  - bitcoin to v0.31.0
  - miniscript to v11.0.0

  Fix: #1196

ACKs for top commit:
  ValuedMammal:
    ACK 984c758f96
  notmandatory:
    ACK 984c758f96
  oleonardolima:
    ACK 984c758f96
  storopoli:
    ACK 984c758f96

Tree-SHA512: d64d530e93cc36688ba07d3677d5c1689b61f246f05d08092bbf86ddbba8a5ec49648e6825b950ef17729dc064da50d50b793475a288862a0461976876807170
2024-04-12 10:58:19 -05:00
志宇
c7f87b50e4 Merge bitcoindevkit/bdk#1397: Introduce proptesting, starting with CheckPoint::range
446b045161 test(chain): introduce proptest for `CheckPoint::range` (志宇)

Pull request description:

  ### Description

  This is based on #1369. It made sense for me to introduce the `CheckPoint::range` test as a proptest. I think we should also start introducing it for other methods too.

  ### Changelog notice

  * Added proptest for `CheckPoint::range`.

  ### 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:

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

Top commit has no ACKs.

Tree-SHA512: 2d8aad5a1ad152413fbc716fed4a47ad621e723928c5331728da2fbade995ebd677acfa05d2e7639d74d0f66e0971c72582d9cd562ea008b012774057b480d62
2024-04-12 13:29:44 +08:00
志宇
446b045161 test(chain): introduce proptest for CheckPoint::range
Ensure that `CheckPoint::range` returns expected values by comparing
against values returned from a primitive approach.

I think it is a good idea to start writing proptests for this crate.
2024-04-11 21:43:21 +08:00
志宇
62619d3a4a Merge bitcoindevkit/bdk#1385: Fix last seen unconfirmed
a2a64ffb6e fix(electrum)!: Remove `seen_at` param from `into_tx_graph` (valued mammal)
37fca35dde feat(tx_graph): Add method update_last_seen_unconfirmed (valued mammal)

Pull request description:

  A method `update_last_seen_unconfirmed` is added for `TxGraph` that allows inserting a `seen_at` time for all unanchored transactions.

  fixes #1336

  ### Changelog notice

  Changed:
  - Methods `into_tx_graph` and `into_confirmation_time_tx_graph`for `RelevantTxids` are changed to no longer accept a `seen_at` parameter.

  Added:
  - Added method `update_last_seen_unconfirmed` for `TxGraph`.

  ### 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 a2a64ffb6e

Tree-SHA512: 9011e63314b0e3ffcd50dbf5024f82b365bab1cc834c0455d7410b682338339ed5284caa721ffc267c65fa26d35ff6ee86cde6052e82a6a79768547fbb7a45eb
2024-04-11 21:33:14 +08:00
Tobin C. Harding
984c758f96 Upgrade miniscript/bitcoin dependency
Upgrade:

- bitcoin to v0.31.0
- miniscript to v11.0.0

Note: The bitcoin upgrade includes improvements to the
`Transaction::weight()` function, it appears those guys did good, we
no longer need to add the 2 additional weight units "just in case".
2024-04-08 15:16:02 +10:00
valued mammal
a2a64ffb6e fix(electrum)!: Remove seen_at param from into_tx_graph
and `into_confirmation_time_tx_graph`, since now it makes
sense to use `TxGraph::update_last_seen_unconfirmed`.

Also, use `update_last_seen_unconfirmed` in examples for
electrum/esplora. We show how to update the last seen
time for transactions by calling `update_last_seen_unconfirmed`
on the graph update returned from a blockchain source, passing
in the current time, before applying it to another `TxGraph`.
2024-04-06 12:04:27 -04:00
valued mammal
37fca35dde feat(tx_graph): Add method update_last_seen_unconfirmed
That accepts a `u64` as param representing the latest timestamp
and internally calls `insert_seen_at` for all transactions in
graph that aren't yet anchored in a confirmed block.
2024-04-06 11:50:37 -04:00
志宇
53791eb6c5 Merge bitcoindevkit/bdk#1369: feat(chain): add get and range methods to CheckPoint
53942cced4 chore(chain)!: rm `From<LocalChain> for BTreeMap<u32, BlockHash>` (志宇)
2d1d95a685 feat(chain): impl `PartialEq` on `CheckPoint` (志宇)
9a62d56900 feat(chain): add `get` and `range` methods to `CheckPoint` (志宇)

Pull request description:

  Partially fixes #1354

  ### Description

  These methods allow us to query for checkpoints contained within the linked list by height and height range. This is useful to determine checkpoints to fetch for chain sources without having to refer back to the `LocalChain`.

  Currently this is not implemented efficiently, but in the future, we will change `CheckPoint` to use a skip list structure.

  ### Notes to the reviewers

  Please refer to the conversation in #1354 for more details. In summary, both `TxGraph::missing_heights` and `tx_graph::ChangeSet::missing_heights_from` are problematic in their own ways. Additionally, it's a better API for chain sources if we only need to lock our receiving structures twice (once to obtain initial state and once for applying the update). Instead of relying on methods such as `EsploraExt::update_local_chain` to get relevant checkpoints, we can use these query methods instead. This allows up to get rid of `::missing_heights`-esc methods and remove the need to lock receiving structures in the middle of the sync.

  ### Changelog notice

  * Added `get` and `range` methods to `CheckPoint` (and in turn, `LocalChain`). This simulates an API where we have implemented a skip list of checkpoints (to implement in the future). This is a better API because we can query for any height or height range with just a checkpoint tip instead of relying on a separate checkpoint index (which needs to live in `LocalChain`).
  * Changed `LocalChain` to have a faster `Eq` implementation. We now maintain an xor value of all checkpoint block hashes. We compare this xor value to determine whether two chains are equal.
  * Added `PartialEq` implementation for `CheckPoint` and `local_chain::Update`.

  ### 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:

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

ACKs for top commit:
  LLFourn:
    ACK 53942cced4

Tree-SHA512: 90ef8047fe1265daa54c9dfe8a8c520685c898a50d18efd6e803707fcb529d0790d20373c9e439b9c7ff301db32b47453020cae7db4da2ea64eba895aa047f30
2024-04-06 10:23:46 +08:00
志宇
53942cced4 chore(chain)!: rm From<LocalChain> for BTreeMap<u32, BlockHash>
I don't think this was ever used. The only possible usecase I can think
of is for tests, but I don't think that is a strong enough incentive for
us to keep this.
2024-04-05 16:36:00 +08:00
志宇
2d1d95a685 feat(chain): impl PartialEq on CheckPoint
We impl `PartialEq` on `CheckPoint` instead of directly on `LocalChain`.
We also made the implementation more efficient.
2024-04-05 16:36:00 +08:00
志宇
9a62d56900 feat(chain): add get and range methods to CheckPoint
These methods allow us to query for checkpoints contained within the
linked list by height and height range. This is useful to determine
checkpoints to fetch for chain sources without having to refer back to
the `LocalChain`.

Currently this is not implemented efficiently, but in the future, we
will change `CheckPoint` to use a skip list structure.
2024-04-05 16:36:00 +08:00
志宇
2bb654077d Merge bitcoindevkit/bdk#1345: fix: remove deprecated max_satisfaction_weight
798ed8ced2 fix: remove deprecated `max_satisfaction_weight (Jose Storopoli)

Pull request description:

  ### Description
  Continuation of #1115.
  Closes #1036.

  * Change deprecated `max_satisfaction_weight` to `max_weight_to_satisfy`
  * Remove `#[allow(deprecated)]` flags

  ### Notes to the reviewers

  I've changed all `max_satisfaction_weight()` to `max_weight_to_satisfy()` in `Wallet.get_available_utxo()` and `Wallet.build_fee_bump()`. Checking the docs on the `miniscript` crate for `max_weight_to_satisfy` has the following note:

  We are testing if the underlying descriptor `is.segwit()` or `.is_taproot`,
  then adding 4WU if true or leaving as it is otherwise.

  Another thing, we are not testing in BDK tests for legacy (pre-segwit) descriptors.
  Should I also add them to this PR?

  ### Changelog notice
  ### Fixed
  Replace the deprecated `max_satisfaction_weight` from `rust-miniscript` to `max_weight_to_satisfy`.

  ### 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
  * [ ]  I've added tests to reproduce the issue which are now passing
  * [x]  I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK 798ed8ced2

Tree-SHA512: 60babecee13c24915348ddb64894127a76a59d9421d52ea37acc714913685d57cc2be1904f9d0508078dd1db1f7d7dad83a734af5ee981801ca87de2e9984429
2024-04-02 19:21:51 +08:00
志宇
19304c13ec Merge bitcoindevkit/bdk#1373: Wrap transactions as Arc<Transaction> in TxGraph
8ab58af093 feat(chain)!: wrap `TxGraph` txs with `Arc` (志宇)

Pull request description:

  ### Description

  This PR makes `TxGraph` store transactions as `Arc<Transaction>`.

  `Arc<Transaction>` can be shared between the chain-source and receiving structures. This allows the chain-source to keep the already-fetched transactions (save bandwith) and have a shared pointer to the transaction (save memory).

  Our current logic to avoid re-fetching transactions is to refer back to the receiving structures. However, this means an additional round of locking our receiving structures.

  ### Notes to the reviewers

  This will make more sense once I update the esplora/electrum chain sources to make use of both #1369 and this PR. The result would be chain sources which would only require locking the receiving structures twice (once for initiating the update, and once for applying the update).

  ### Changelog notice

  * Changed `TxGraph` to store transactions as `Arc<Transaction>`. This allows chain-sources to cheaply keep a copy of already-fetched transactions.

  ### 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
  * [x] I've added docs for the new feature

ACKs for top commit:
  LLFourn:
    ACK 8ab58af093
  notmandatory:
    ACK 8ab58af093

Tree-SHA512: 81d7ad35fed7f253a3b902f09a41aaef2877f6201d4f6e78318741bf00e4b898a8000d878ffcbfe75701094ce3e9021bd718b190a655331a9e11f0ad66bdb02f
2024-03-31 17:50:31 +08:00
Jose Storopoli
798ed8ced2 fix: remove deprecated `max_satisfaction_weight
- Change deprecated `max_satisfaction_weight` to `max_weight_to_satisfy`
- Remove `#[allow(deprecated)]` flags
- updates the calculations in TXIN_BASE_WEIGHT and P2WPKH_SATISFACTION_SIZE

Update crates/bdk/src/wallet/coin_selection.rs

Co-authored-by: ValuedMammal <valuedmammal@protonmail.com>
2024-03-29 06:27:39 -03:00
志宇
8ab58af093 feat(chain)!: wrap TxGraph txs with Arc
Wrapping transactions as `Arc<Transaction>` allows us to share
transactions cheaply between the chain-source and receiving structures.
Therefore the chain-source can keep already-fetched transactions (save
bandwidth) and have a shared pointer to the transactions (save memory).

This is better than the current way we do things, which is to refer back
to the receiving structures mid-sync.

Documentation for `TxGraph` is also updated.
2024-03-25 12:57:26 +08:00
57 changed files with 1107 additions and 812 deletions

View File

@@ -33,6 +33,7 @@ jobs:
cargo update -p zstd-sys --precise "2.0.8+zstd.1.5.5"
cargo update -p time --precise "0.3.20"
cargo update -p home --precise "0.5.5"
cargo update -p proptest --precise "1.2.0"
- name: Build
run: cargo build ${{ matrix.features }}
- name: Test

View File

@@ -75,6 +75,8 @@ cargo update -p time --precise "0.3.20"
cargo update -p jobserver --precise "0.1.26"
# home 0.5.9 has MSRV 1.70.0
cargo update -p home --precise "0.5.5"
# proptest 1.4.0 has MSRV 1.65.0
cargo update -p proptest --precise "1.2.0"
```
## License

View File

@@ -1,7 +1,7 @@
[package]
name = "bdk"
homepage = "https://bitcoindevkit.org"
version = "1.0.0-alpha.8"
version = "1.0.0-alpha.9"
repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk"
description = "A modern, lightweight, descriptor-based wallet library"
@@ -14,11 +14,11 @@ rust-version = "1.63"
[dependencies]
rand = "^0.8"
miniscript = { version = "10.0.0", features = ["serde"], default-features = false }
bitcoin = { version = "0.30.0", features = ["serde", "base64", "rand-std"], default-features = false }
miniscript = { version = "11.0.0", features = ["serde"], default-features = false }
bitcoin = { version = "0.31.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.11.0", features = ["miniscript", "serde"], default-features = false }
bdk_chain = { path = "../chain", version = "0.12.0", features = ["miniscript", "serde"], default-features = false }
# Optional dependencies
bip39 = { version = "2.0", optional = true }

View File

@@ -274,14 +274,13 @@ macro_rules! impl_sortedmulti {
#[macro_export]
macro_rules! parse_tap_tree {
( @merge $tree_a:expr, $tree_b:expr) => {{
use $crate::alloc::sync::Arc;
use $crate::miniscript::descriptor::TapTree;
$tree_a
.and_then(|tree_a| Ok((tree_a, $tree_b?)))
.and_then(|((a_tree, mut a_keymap, a_networks), (b_tree, b_keymap, b_networks))| {
a_keymap.extend(b_keymap.into_iter());
Ok((TapTree::Tree(Arc::new(a_tree), Arc::new(b_tree)), a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
Ok((TapTree::combine(a_tree, b_tree), a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
})
}};
@@ -806,7 +805,7 @@ mod test {
use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks};
use bitcoin::bip32;
use bitcoin::network::constants::Network::{Bitcoin, Regtest, Signet, Testnet};
use bitcoin::Network::{Bitcoin, Regtest, Signet, Testnet};
use bitcoin::PrivateKey;
// test the descriptor!() macro
@@ -936,7 +935,7 @@ mod test {
#[test]
fn test_bip32_legacy_descriptors() {
let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap();
let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
@@ -981,7 +980,7 @@ mod test {
#[test]
fn test_bip32_segwitv0_descriptors() {
let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap();
let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
@@ -1038,10 +1037,10 @@ mod test {
#[test]
fn test_dsl_sortedmulti() {
let key_1 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let key_1 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let path_1 = bip32::DerivationPath::from_str("m/0").unwrap();
let key_2 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap();
let key_2 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap();
let path_2 = bip32::DerivationPath::from_str("m/1").unwrap();
let desc_key1 = (key_1, path_1);
@@ -1097,7 +1096,7 @@ mod test {
// - verify the valid_networks returned is correctly computed based on the keys present in the descriptor
#[test]
fn test_valid_networks() {
let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap();
let desc_key = (xprv, path).into_descriptor_key().unwrap();
@@ -1107,7 +1106,7 @@ mod test {
[Testnet, Regtest, Signet].iter().cloned().collect()
);
let xprv = bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap();
let xprv = bip32::Xpriv::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap();
let path = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap();
let desc_key = (xprv, path).into_descriptor_key().unwrap();
@@ -1120,15 +1119,15 @@ mod test {
fn test_key_maps_merged() {
let secp = Secp256k1::new();
let xprv1 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let xprv1 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let path1 = bip32::DerivationPath::from_str("m/0").unwrap();
let desc_key1 = (xprv1, path1.clone()).into_descriptor_key().unwrap();
let xprv2 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap();
let xprv2 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap();
let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap();
let desc_key2 = (xprv2, path2.clone()).into_descriptor_key().unwrap();
let xprv3 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf").unwrap();
let xprv3 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf").unwrap();
let path3 = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap();
let desc_key3 = (xprv3, path3.clone()).into_descriptor_key().unwrap();
@@ -1152,7 +1151,7 @@ mod test {
#[test]
fn test_script_context_validation() {
// this compiles
let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap();
let desc_key: DescriptorKey<Legacy> = (xprv, path).into_descriptor_key().unwrap();

View File

@@ -41,7 +41,7 @@ pub enum Error {
/// Miniscript error
Miniscript(miniscript::Error),
/// Hex decoding error
Hex(bitcoin::hashes::hex::Error),
Hex(bitcoin::hex::HexToBytesError),
}
impl From<crate::keys::KeyError> for Error {
@@ -110,8 +110,8 @@ impl From<miniscript::Error> for Error {
}
}
impl From<bitcoin::hashes::hex::Error> for Error {
fn from(err: bitcoin::hashes::hex::Error) -> Self {
impl From<bitcoin::hex::HexToBytesError> for Error {
fn from(err: bitcoin::hex::HexToBytesError) -> Self {
Error::Hex(err)
}
}

View File

@@ -18,7 +18,7 @@ use crate::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, KeySource, Xpub};
use bitcoin::{key::XOnlyPublicKey, secp256k1, PublicKey};
use bitcoin::{psbt, taproot};
use bitcoin::{Network, TxOut};
@@ -377,7 +377,7 @@ where
pub(crate) trait DescriptorMeta {
fn is_witness(&self) -> bool;
fn is_taproot(&self) -> bool;
fn get_extended_keys(&self) -> Vec<DescriptorXKey<ExtendedPubKey>>;
fn get_extended_keys(&self) -> Vec<DescriptorXKey<Xpub>>;
fn derive_from_hd_keypaths(
&self,
hd_keypaths: &HdKeyPaths,
@@ -418,7 +418,7 @@ impl DescriptorMeta for ExtendedDescriptor {
self.desc_type() == DescriptorType::Tr
}
fn get_extended_keys(&self) -> Vec<DescriptorXKey<ExtendedPubKey>> {
fn get_extended_keys(&self) -> Vec<DescriptorXKey<Xpub>> {
let mut answer = Vec::new();
self.for_each_key(|pk| {
@@ -438,21 +438,20 @@ impl DescriptorMeta for ExtendedDescriptor {
secp: &SecpCtx,
) -> Option<DerivedDescriptor> {
// Ensure that deriving `xpub` with `path` yields `expected`
let verify_key = |xpub: &DescriptorXKey<ExtendedPubKey>,
path: &DerivationPath,
expected: &SinglePubKey| {
let derived = xpub
.xkey
.derive_pub(secp, path)
.expect("The path should never contain hardened derivation steps")
.public_key;
let verify_key =
|xpub: &DescriptorXKey<Xpub>, path: &DerivationPath, expected: &SinglePubKey| {
let derived = xpub
.xkey
.derive_pub(secp, path)
.expect("The path should never contain hardened derivation steps")
.public_key;
match expected {
SinglePubKey::FullKey(pk) if &PublicKey::new(derived) == pk => true,
SinglePubKey::XOnly(pk) if &XOnlyPublicKey::from(derived) == pk => true,
_ => false,
}
};
match expected {
SinglePubKey::FullKey(pk) if &PublicKey::new(derived) == pk => true,
SinglePubKey::XOnly(pk) if &XOnlyPublicKey::from(derived) == pk => true,
_ => false,
}
};
let mut path_found = None;
@@ -605,10 +604,10 @@ mod test {
use core::str::FromStr;
use assert_matches::assert_matches;
use bitcoin::hashes::hex::FromHex;
use bitcoin::hex::FromHex;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::ScriptBuf;
use bitcoin::{bip32, psbt::Psbt};
use bitcoin::{bip32, Psbt};
use super::*;
use crate::psbt::PsbtUtils;
@@ -727,7 +726,7 @@ mod test {
let secp = Secp256k1::new();
let xprv = bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3c3gF1DUWpWNr2SG2XrG8oYPpqYh7hoWsJy9NjabErnzriJPpnGHyKz5NgdXmq1KVbqS1r4NXdCoKitWg5e86zqXHa8kxyB").unwrap();
let xprv = bip32::Xpriv::from_str("xprv9s21ZrQH143K3c3gF1DUWpWNr2SG2XrG8oYPpqYh7hoWsJy9NjabErnzriJPpnGHyKz5NgdXmq1KVbqS1r4NXdCoKitWg5e86zqXHa8kxyB").unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap();
// here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
@@ -746,7 +745,7 @@ mod test {
let mut xprv_testnet = xprv;
xprv_testnet.network = Network::Testnet;
let xpub_testnet = bip32::ExtendedPubKey::from_priv(&secp, &xprv_testnet);
let xpub_testnet = bip32::Xpub::from_priv(&secp, &xprv_testnet);
let desc_pubkey = DescriptorPublicKey::XPub(DescriptorXKey {
xkey: xpub_testnet,
origin: None,
@@ -836,7 +835,7 @@ mod test {
fn test_descriptor_from_str_from_output_of_macro() {
let secp = Secp256k1::new();
let tpub = bip32::ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK").unwrap();
let tpub = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK").unwrap();
let path = bip32::DerivationPath::from_str("m/1/2").unwrap();
let key = (tpub, path).into_descriptor_key().unwrap();
@@ -895,7 +894,7 @@ mod test {
.update_with_descriptor_unchecked(&descriptor)
.unwrap();
assert_eq!(psbt_input.redeem_script, Some(script.to_v0_p2wsh()));
assert_eq!(psbt_input.redeem_script, Some(script.to_p2wsh()));
assert_eq!(psbt_input.witness_script, Some(script));
}
}

View File

@@ -1137,7 +1137,7 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
let key_spend_sig =
miniscript::Tap::make_signature(tr.internal_key(), signers, build_sat, secp);
if tr.taptree().is_none() {
if tr.tap_tree().is_none() {
Ok(Some(key_spend_sig))
} else {
let mut items = vec![key_spend_sig];
@@ -1184,8 +1184,8 @@ mod test {
secp: &SecpCtx,
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
let path = bip32::DerivationPath::from_str(path).unwrap();
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
let tpub = bip32::ExtendedPubKey::from_priv(secp, &tprv);
let tprv = bip32::Xpriv::from_str(tprv).unwrap();
let tpub = bip32::Xpub::from_priv(secp, &tprv);
let fingerprint = tprv.fingerprint(secp);
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
let pubkey = (tpub, path).into_descriptor_key().unwrap();

View File

@@ -195,7 +195,7 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44;
///
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let mut wallet = Wallet::new_no_persist(
/// Bip44(key.clone(), KeychainKind::External),
/// Some(Bip44(key, KeychainKind::Internal)),
@@ -232,7 +232,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44Public;
///
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let mut wallet = Wallet::new_no_persist(
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
@@ -270,7 +270,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49;
///
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let mut wallet = Wallet::new_no_persist(
/// Bip49(key.clone(), KeychainKind::External),
/// Some(Bip49(key, KeychainKind::Internal)),
@@ -307,7 +307,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49Public;
///
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let mut wallet = Wallet::new_no_persist(
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
@@ -345,7 +345,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84;
///
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let mut wallet = Wallet::new_no_persist(
/// Bip84(key.clone(), KeychainKind::External),
/// Some(Bip84(key, KeychainKind::Internal)),
@@ -382,7 +382,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84Public;
///
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let mut wallet = Wallet::new_no_persist(
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
@@ -420,7 +420,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip86;
///
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let mut wallet = Wallet::new_no_persist(
/// Bip86(key.clone(), KeychainKind::External),
/// Some(Bip86(key, KeychainKind::Internal)),
@@ -457,7 +457,7 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip86Public;
///
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let mut wallet = Wallet::new_no_persist(
/// Bip86Public(key.clone(), fingerprint, KeychainKind::External),
@@ -567,7 +567,7 @@ mod test {
fn test_bip44_template_cointype() {
use bitcoin::bip32::ChildNumber::{self, Hardened};
let xprvkey = bitcoin::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap();
let xprvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap();
assert_eq!(Network::Bitcoin, xprvkey.network);
let xdesc = Bip44(xprvkey, KeychainKind::Internal)
.build(Network::Bitcoin)
@@ -581,7 +581,7 @@ mod test {
assert_matches!(coin_type, Hardened { index: 0 });
}
let tprvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let tprvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
assert_eq!(Network::Testnet, tprvkey.network);
let tdesc = Bip44(tprvkey, KeychainKind::Internal)
.build(Network::Testnet)
@@ -740,7 +740,7 @@ mod test {
// BIP44 `pkh(key/44'/0'/0'/{0,1}/*)`
#[test]
fn test_bip44_template() {
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check(
Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin),
false,
@@ -770,7 +770,7 @@ mod test {
// BIP44 public `pkh(key/{0,1}/*)`
#[test]
fn test_bip44_public_template() {
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
let pubkey = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
check(
Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
@@ -801,7 +801,7 @@ mod test {
// BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))`
#[test]
fn test_bip49_template() {
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check(
Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin),
true,
@@ -831,7 +831,7 @@ mod test {
// BIP49 public `sh(wpkh(key/{0,1}/*))`
#[test]
fn test_bip49_public_template() {
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
check(
Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
@@ -862,7 +862,7 @@ mod test {
// BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)`
#[test]
fn test_bip84_template() {
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check(
Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin),
true,
@@ -892,7 +892,7 @@ mod test {
// BIP84 public `wpkh(key/{0,1}/*)`
#[test]
fn test_bip84_public_template() {
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
check(
Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
@@ -924,7 +924,7 @@ mod test {
// Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
#[test]
fn test_bip86_template() {
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap();
let prvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap();
check(
Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin),
false,
@@ -955,7 +955,7 @@ mod test {
// Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
#[test]
fn test_bip86_public_template() {
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap();
let pubkey = bitcoin::bip32::Xpub::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("73c5da0a").unwrap();
check(
Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),

View File

@@ -57,7 +57,7 @@ pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
impl<Ctx: ScriptContext> DerivableKey<Ctx> for Seed {
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
Ok(bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self[..])?.into())
Ok(bip32::Xpriv::new_master(Network::Bitcoin, &self[..])?.into())
}
fn into_descriptor_key(

View File

@@ -110,7 +110,7 @@ impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
Ok((public, KeyMap::default(), valid_networks))
}
DescriptorKey::Secret(secret, valid_networks, _) => {
let mut key_map = KeyMap::with_capacity(1);
let mut key_map = KeyMap::new();
let public = secret
.to_public(secp)
@@ -309,15 +309,15 @@ pub trait IntoDescriptorKey<Ctx: ScriptContext>: Sized {
/// Enum for extended keys that can be either `xprv` or `xpub`
///
/// An instance of [`ExtendedKey`] can be constructed from an [`ExtendedPrivKey`](bip32::ExtendedPrivKey)
/// or an [`ExtendedPubKey`](bip32::ExtendedPubKey) by using the `From` trait.
/// An instance of [`ExtendedKey`] can be constructed from an [`Xpriv`](bip32::Xpriv)
/// or an [`Xpub`](bip32::Xpub) by using the `From` trait.
///
/// Defaults to the [`Legacy`](miniscript::Legacy) context.
pub enum ExtendedKey<Ctx: ScriptContext = miniscript::Legacy> {
/// A private extended key, aka an `xprv`
Private((bip32::ExtendedPrivKey, PhantomData<Ctx>)),
Private((bip32::Xpriv, PhantomData<Ctx>)),
/// A public extended key, aka an `xpub`
Public((bip32::ExtendedPubKey, PhantomData<Ctx>)),
Public((bip32::Xpub, PhantomData<Ctx>)),
}
impl<Ctx: ScriptContext> ExtendedKey<Ctx> {
@@ -329,9 +329,9 @@ impl<Ctx: ScriptContext> ExtendedKey<Ctx> {
}
}
/// Transform the [`ExtendedKey`] into an [`ExtendedPrivKey`](bip32::ExtendedPrivKey) for the
/// Transform the [`ExtendedKey`] into an [`Xpriv`](bip32::Xpriv) for the
/// given [`Network`], if the key contains the private data
pub fn into_xprv(self, network: Network) -> Option<bip32::ExtendedPrivKey> {
pub fn into_xprv(self, network: Network) -> Option<bip32::Xpriv> {
match self {
ExtendedKey::Private((mut xprv, _)) => {
xprv.network = network;
@@ -341,15 +341,15 @@ impl<Ctx: ScriptContext> ExtendedKey<Ctx> {
}
}
/// Transform the [`ExtendedKey`] into an [`ExtendedPubKey`](bip32::ExtendedPubKey) for the
/// Transform the [`ExtendedKey`] into an [`Xpub`](bip32::Xpub) for the
/// given [`Network`]
pub fn into_xpub<C: Signing>(
self,
network: bitcoin::Network,
secp: &Secp256k1<C>,
) -> bip32::ExtendedPubKey {
) -> bip32::Xpub {
let mut xpub = match self {
ExtendedKey::Private((xprv, _)) => bip32::ExtendedPubKey::from_priv(secp, &xprv),
ExtendedKey::Private((xprv, _)) => bip32::Xpub::from_priv(secp, &xprv),
ExtendedKey::Public((xpub, _)) => xpub,
};
@@ -358,14 +358,14 @@ impl<Ctx: ScriptContext> ExtendedKey<Ctx> {
}
}
impl<Ctx: ScriptContext> From<bip32::ExtendedPubKey> for ExtendedKey<Ctx> {
fn from(xpub: bip32::ExtendedPubKey) -> Self {
impl<Ctx: ScriptContext> From<bip32::Xpub> for ExtendedKey<Ctx> {
fn from(xpub: bip32::Xpub) -> Self {
ExtendedKey::Public((xpub, PhantomData))
}
}
impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
fn from(xprv: bip32::ExtendedPrivKey) -> Self {
impl<Ctx: ScriptContext> From<bip32::Xpriv> for ExtendedKey<Ctx> {
fn from(xprv: bip32::Xpriv) -> Self {
ExtendedKey::Private((xprv, PhantomData))
}
}
@@ -383,8 +383,8 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
///
/// ## Examples
///
/// Key types that can be directly converted into an [`ExtendedPrivKey`] or
/// an [`ExtendedPubKey`] can implement only the required `into_extended_key()` method.
/// Key types that can be directly converted into an [`Xpriv`] or
/// an [`Xpub`] can implement only the required `into_extended_key()` method.
///
/// ```
/// use bdk::bitcoin;
@@ -399,7 +399,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
///
/// impl<Ctx: ScriptContext> DerivableKey<Ctx> for MyCustomKeyType {
/// fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
/// let xprv = bip32::ExtendedPrivKey {
/// let xprv = bip32::Xpriv {
/// network: self.network,
/// depth: 0,
/// parent_fingerprint: bip32::Fingerprint::default(),
@@ -415,7 +415,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
///
/// Types that don't internally encode the [`Network`] in which they are valid need some extra
/// steps to override the set of valid networks, otherwise only the network specified in the
/// [`ExtendedPrivKey`] or [`ExtendedPubKey`] will be considered valid.
/// [`Xpriv`] or [`Xpub`] will be considered valid.
///
/// ```
/// use bdk::bitcoin;
@@ -431,7 +431,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
///
/// impl<Ctx: ScriptContext> DerivableKey<Ctx> for MyCustomKeyType {
/// fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
/// let xprv = bip32::ExtendedPrivKey {
/// let xprv = bip32::Xpriv {
/// network: bitcoin::Network::Bitcoin, // pick an arbitrary network here
/// depth: 0,
/// parent_fingerprint: bip32::Fingerprint::default(),
@@ -459,8 +459,8 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
/// ```
///
/// [`DerivationPath`]: (bip32::DerivationPath)
/// [`ExtendedPrivKey`]: (bip32::ExtendedPrivKey)
/// [`ExtendedPubKey`]: (bip32::ExtendedPubKey)
/// [`Xpriv`]: (bip32::Xpriv)
/// [`Xpub`]: (bip32::Xpub)
pub trait DerivableKey<Ctx: ScriptContext = miniscript::Legacy>: Sized {
/// Consume `self` and turn it into an [`ExtendedKey`]
#[cfg_attr(
@@ -520,13 +520,13 @@ impl<Ctx: ScriptContext> DerivableKey<Ctx> for ExtendedKey<Ctx> {
}
}
impl<Ctx: ScriptContext> DerivableKey<Ctx> for bip32::ExtendedPubKey {
impl<Ctx: ScriptContext> DerivableKey<Ctx> for bip32::Xpub {
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
Ok(self.into())
}
}
impl<Ctx: ScriptContext> DerivableKey<Ctx> for bip32::ExtendedPrivKey {
impl<Ctx: ScriptContext> DerivableKey<Ctx> for bip32::Xpriv {
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
Ok(self.into())
}
@@ -670,7 +670,7 @@ where
{
}
impl<Ctx: ScriptContext> GeneratableKey<Ctx> for bip32::ExtendedPrivKey {
impl<Ctx: ScriptContext> GeneratableKey<Ctx> for bip32::Xpriv {
type Entropy = [u8; 32];
type Options = ();
@@ -681,7 +681,7 @@ impl<Ctx: ScriptContext> GeneratableKey<Ctx> for bip32::ExtendedPrivKey {
entropy: Self::Entropy,
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
// pick a arbitrary network here, but say that we support all of them
let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, entropy.as_ref())?;
let xprv = bip32::Xpriv::new_master(Network::Bitcoin, entropy.as_ref())?;
Ok(GeneratedKey::new(xprv, any_network()))
}
}
@@ -971,7 +971,7 @@ pub mod test {
#[test]
fn test_keys_generate_xprv() {
let generated_xprv: GeneratedKey<_, miniscript::Segwitv0> =
bip32::ExtendedPrivKey::generate_with_entropy_default(TEST_ENTROPY).unwrap();
bip32::Xpriv::generate_with_entropy_default(TEST_ENTROPY).unwrap();
assert_eq!(generated_xprv.valid_networks, any_network());
assert_eq!(generated_xprv.to_string(), "xprv9s21ZrQH143K4Xr1cJyqTvuL2FWR8eicgY9boWqMBv8MDVUZ65AXHnzBrK1nyomu6wdcabRgmGTaAKawvhAno1V5FowGpTLVx3jxzE5uk3Q");

View File

@@ -9,12 +9,12 @@
// You may not use this file except in accordance with one or both of these
// licenses.
//! Additional functions on the `rust-bitcoin` `PartiallySignedTransaction` structure.
//! Additional functions on the `rust-bitcoin` `Psbt` structure.
use alloc::vec::Vec;
use bitcoin::psbt::PartiallySignedTransaction as Psbt;
use bitcoin::Amount;
use bitcoin::FeeRate;
use bitcoin::Psbt;
use bitcoin::TxOut;
// TODO upstream the functions here to `rust-bitcoin`?
@@ -29,7 +29,7 @@ pub trait PsbtUtils {
fn fee_amount(&self) -> Option<u64>;
/// The transaction's fee rate. This value will only be accurate if calculated AFTER the
/// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
/// `Psbt` is finalized and all witness/signature data is added to the
/// transaction.
/// If the PSBT is missing a TxOut for an input returns None.
fn fee_rate(&self) -> Option<FeeRate>;
@@ -54,8 +54,13 @@ impl PsbtUtils for Psbt {
let utxos: Option<Vec<TxOut>> = (0..tx.input.len()).map(|i| self.get_utxo_for(i)).collect();
utxos.map(|inputs| {
let input_amount: u64 = inputs.iter().map(|i| i.value).sum();
let output_amount: u64 = self.unsigned_tx.output.iter().map(|o| o.value).sum();
let input_amount: u64 = inputs.iter().map(|i| i.value.to_sat()).sum();
let output_amount: u64 = self
.unsigned_tx
.output
.iter()
.map(|o| o.value.to_sat())
.sum();
input_amount
.checked_sub(output_amount)
.expect("input amount must be greater than output amount")
@@ -64,9 +69,7 @@ impl PsbtUtils for Psbt {
fn fee_rate(&self) -> Option<FeeRate> {
let fee_amount = self.fee_amount();
fee_amount.map(|fee| {
let weight = self.clone().extract_tx().weight();
Amount::from_sat(fee) / weight
})
let weight = self.clone().extract_tx().ok()?.weight();
fee_amount.map(|fee| Amount::from_sat(fee) / weight)
}
}

View File

@@ -32,7 +32,6 @@
//! # use bdk::*;
//! # use bdk::wallet::coin_selection::decide_change;
//! # use anyhow::Error;
//! # const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4) * 4;
//! #[derive(Debug)]
//! struct AlwaysSpendEverything;
//!
@@ -53,9 +52,11 @@
//! .scan(
//! (&mut selected_amount, &mut additional_weight),
//! |(selected_amount, additional_weight), weighted_utxo| {
//! **selected_amount += weighted_utxo.utxo.txout().value;
//! **selected_amount += weighted_utxo.utxo.txout().value.to_sat();
//! **additional_weight += Weight::from_wu(
//! (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64,
//! (TxIn::default().segwit_weight().to_wu()
//! + weighted_utxo.satisfaction_weight as u64)
//! as u64,
//! );
//! Some(weighted_utxo.utxo)
//! },
@@ -109,6 +110,7 @@ use bitcoin::FeeRate;
use alloc::vec::Vec;
use bitcoin::consensus::encode::serialize;
use bitcoin::OutPoint;
use bitcoin::TxIn;
use bitcoin::{Script, Weight};
use core::convert::TryInto;
@@ -119,10 +121,6 @@ use rand::seq::SliceRandom;
/// overridden
pub type DefaultCoinSelectionAlgorithm = BranchAndBoundCoinSelection;
// Base weight of a Txin, not counting the weight needed for satisfying it.
// prev_txid (32 bytes) + prev_vout (4 bytes) + sequence (4 bytes)
pub(crate) const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4) * 4;
/// Errors that can be thrown by the [`coin_selection`](crate::wallet::coin_selection) module
#[derive(Debug)]
pub enum Error {
@@ -195,7 +193,7 @@ pub struct CoinSelectionResult {
impl CoinSelectionResult {
/// The total value of the inputs selected.
pub fn selected_amount(&self) -> u64 {
self.selected.iter().map(|u| u.txout().value).sum()
self.selected.iter().map(|u| u.txout().value.to_sat()).sum()
}
/// The total value of the inputs selected from the local wallet.
@@ -203,7 +201,7 @@ impl CoinSelectionResult {
self.selected
.iter()
.filter_map(|u| match u {
Utxo::Local(_) => Some(u.txout().value),
Utxo::Local(_) => Some(u.txout().value.to_sat()),
_ => None,
})
.sum()
@@ -347,11 +345,11 @@ fn select_sorted_utxos(
if must_use || **selected_amount < target_amount + **fee_amount {
**fee_amount += (fee_rate
* Weight::from_wu(
(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64,
TxIn::default().segwit_weight().to_wu()
+ weighted_utxo.satisfaction_weight as u64,
))
.to_sat();
**selected_amount += weighted_utxo.utxo.txout().value;
**selected_amount += weighted_utxo.utxo.txout().value.to_sat();
Some(weighted_utxo.utxo)
} else {
None
@@ -392,10 +390,11 @@ struct OutputGroup {
impl OutputGroup {
fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self {
let fee = (fee_rate
* Weight::from_wu((TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64))
* Weight::from_wu(
TxIn::default().segwit_weight().to_wu() + weighted_utxo.satisfaction_weight as u64,
))
.to_sat();
let effective_value = weighted_utxo.utxo.txout().value as i64 - fee as i64;
let effective_value = weighted_utxo.utxo.txout().value.to_sat() as i64 - fee as i64;
OutputGroup {
weighted_utxo,
fee,
@@ -486,7 +485,7 @@ impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection {
.chain(optional_utxos.iter())
.fold((0, 0), |(mut fees, mut value), utxo| {
fees += utxo.fee;
value += utxo.weighted_utxo.utxo.txout().value;
value += utxo.weighted_utxo.utxo.txout().value.to_sat();
(fees, value)
});
@@ -590,7 +589,7 @@ impl BranchAndBoundCoinSelection {
// If we found a solution better than the previous one, or if there wasn't previous
// solution, update the best solution
if best_selection_value.is_none() || curr_value < best_selection_value.unwrap() {
best_selection = current_selection.clone();
best_selection.clone_from(&current_selection);
best_selection_value = Some(curr_value);
}
@@ -744,7 +743,7 @@ mod test {
use core::str::FromStr;
use bdk_chain::ConfirmationTime;
use bitcoin::{Amount, OutPoint, ScriptBuf, TxOut};
use bitcoin::{Amount, ScriptBuf, TxIn, TxOut};
use super::*;
use crate::types::*;
@@ -754,9 +753,9 @@ mod test {
use rand::seq::SliceRandom;
use rand::{Rng, RngCore, SeedableRng};
// n. of items on witness (1WU) + signature len (1WU) + signature and sighash (72WU)
// + pubkey len (1WU) + pubkey (33WU) + script sig len (1 byte, 4WU)
const P2WPKH_SATISFACTION_SIZE: usize = 1 + 1 + 72 + 1 + 33 + 4;
// signature len (1WU) + signature and sighash (72WU)
// + pubkey len (1WU) + pubkey (33WU)
const P2WPKH_SATISFACTION_SIZE: usize = 1 + 72 + 1 + 33;
const FEE_AMOUNT: u64 = 50;
@@ -772,7 +771,7 @@ mod test {
utxo: Utxo::Local(LocalOutput {
outpoint,
txout: TxOut {
value,
value: Amount::from_sat(value),
script_pubkey: ScriptBuf::new(),
},
keychain: KeychainKind::External,
@@ -836,7 +835,7 @@ mod test {
))
.unwrap(),
txout: TxOut {
value: rng.gen_range(0..200000000),
value: Amount::from_sat(rng.gen_range(0..200000000)),
script_pubkey: ScriptBuf::new(),
},
keychain: KeychainKind::External,
@@ -867,7 +866,7 @@ mod test {
))
.unwrap(),
txout: TxOut {
value: utxos_value,
value: Amount::from_sat(utxos_value),
script_pubkey: ScriptBuf::new(),
},
keychain: KeychainKind::External,
@@ -884,7 +883,7 @@ mod test {
utxos.shuffle(&mut rng);
utxos[..utxos_picked_len]
.iter()
.map(|u| u.utxo.txout().value)
.map(|u| u.utxo.txout().value.to_sat())
.sum()
}
@@ -1073,7 +1072,11 @@ mod test {
fn test_oldest_first_coin_selection_insufficient_funds_high_fees() {
let utxos = get_oldest_first_test_utxos();
let target_amount: u64 = utxos.iter().map(|wu| wu.utxo.txout().value).sum::<u64>() - 50;
let target_amount: u64 = utxos
.iter()
.map(|wu| wu.utxo.txout().value.to_sat())
.sum::<u64>()
- 50;
let drain_script = ScriptBuf::default();
OldestFirstCoinSelection
@@ -1168,9 +1171,9 @@ mod test {
));
// Defensive assertions, for sanity and in case someone changes the test utxos vector.
let amount: u64 = required.iter().map(|u| u.utxo.txout().value).sum();
let amount: u64 = required.iter().map(|u| u.utxo.txout().value.to_sat()).sum();
assert_eq!(amount, 100_000);
let amount: u64 = optional.iter().map(|u| u.utxo.txout().value).sum();
let amount: u64 = optional.iter().map(|u| u.utxo.txout().value.to_sat()).sum();
assert!(amount > 150_000);
let drain_script = ScriptBuf::default();
@@ -1240,7 +1243,8 @@ mod test {
assert_eq!(result.selected.len(), 1);
assert_eq!(result.selected_amount(), 100_000);
let input_weight = (TXIN_BASE_WEIGHT + P2WPKH_SATISFACTION_SIZE) as u64;
let input_weight =
TxIn::default().segwit_weight().to_wu() + P2WPKH_SATISFACTION_SIZE as u64;
// the final fee rate should be exactly the same as the fee rate given
let result_feerate = Amount::from_sat(result.fee_amount) / Weight::from_wu(input_weight);
assert_eq!(result_feerate, feerate);
@@ -1462,9 +1466,9 @@ mod test {
let utxos = get_test_utxos();
let drain_script = ScriptBuf::default();
let (required, optional) = utxos
.into_iter()
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
let (required, optional) = utxos.into_iter().partition(
|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value.to_sat() < 1000),
);
let selection = BranchAndBoundCoinSelection::default().coin_select(
required,
@@ -1513,7 +1517,7 @@ mod test {
utxo: Utxo::Local(LocalOutput {
outpoint: OutPoint::new(bitcoin::hashes::Hash::hash(txid.as_bytes()), 0),
txout: TxOut {
value,
value: Amount::from_sat(value),
script_pubkey: ScriptBuf::new(),
},
keychain: KeychainKind::External,

View File

@@ -216,7 +216,7 @@ mod test {
use bdk_chain::{BlockId, ConfirmationTime};
use bitcoin::hashes::Hash;
use bitcoin::{BlockHash, Network, Transaction};
use bitcoin::{transaction, BlockHash, Network, Transaction};
use super::*;
use crate::wallet::Wallet;
@@ -230,7 +230,7 @@ mod test {
let transaction = Transaction {
input: vec![],
output: vec![],
version: 0,
version: transaction::Version::non_standard(0),
lock_time: bitcoin::absolute::LockTime::ZERO,
};
wallet

View File

@@ -48,8 +48,8 @@
//! ```
use bitcoin::bip32::Fingerprint;
use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::Psbt;
use hwi::error::Error;
use hwi::types::{HWIChain, HWIDevice};
@@ -87,7 +87,7 @@ impl SignerCommon for HWISigner {
impl TransactionSigner for HWISigner {
fn sign_transaction(
&self,
psbt: &mut PartiallySignedTransaction,
psbt: &mut Psbt,
_sign_options: &crate::SignOptions,
_secp: &crate::wallet::utils::SecpCtx,
) -> Result<(), SignerError> {

View File

@@ -30,14 +30,14 @@ use bdk_chain::{
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
IndexedTxGraph, Persist, PersistBackend,
};
use bitcoin::constants::genesis_block;
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
use bitcoin::{
absolute, Address, Block, FeeRate, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction,
TxOut, Txid, Weight, Witness,
absolute, psbt, Address, Block, FeeRate, Network, OutPoint, Script, ScriptBuf, Sequence,
Transaction, TxOut, Txid, Witness,
};
use bitcoin::{consensus::encode::serialize, BlockHash};
use bitcoin::{constants::genesis_block, psbt};
use bitcoin::{consensus::encode::serialize, transaction, Amount, BlockHash, Psbt};
use core::fmt;
use core::ops::Deref;
use descriptor::error::Error as DescriptorError;
@@ -54,7 +54,6 @@ pub(crate) mod utils;
pub mod error;
pub use utils::IsDust;
#[allow(deprecated)]
use coin_selection::DefaultCoinSelectionAlgorithm;
use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner};
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
@@ -942,15 +941,15 @@ impl<D> Wallet<D> {
/// # let mut wallet: Wallet<()> = todo!();
/// # let txid:Txid = todo!();
/// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
/// let fee = wallet.calculate_fee(tx).expect("fee");
/// let fee = wallet.calculate_fee(&tx).expect("fee");
/// ```
///
/// ```rust, no_run
/// # use bitcoin::psbt::PartiallySignedTransaction;
/// # use bitcoin::Psbt;
/// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!();
/// # let mut psbt: PartiallySignedTransaction = todo!();
/// let tx = &psbt.clone().extract_tx();
/// # let mut psbt: Psbt = todo!();
/// let tx = &psbt.clone().extract_tx().expect("tx");
/// let fee = wallet.calculate_fee(tx).expect("fee");
/// ```
/// [`insert_txout`]: Self::insert_txout
@@ -973,15 +972,15 @@ impl<D> Wallet<D> {
/// # let mut wallet: Wallet<()> = todo!();
/// # let txid:Txid = todo!();
/// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
/// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate");
/// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate");
/// ```
///
/// ```rust, no_run
/// # use bitcoin::psbt::PartiallySignedTransaction;
/// # use bitcoin::Psbt;
/// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!();
/// # let mut psbt: PartiallySignedTransaction = todo!();
/// let tx = &psbt.clone().extract_tx();
/// # let mut psbt: Psbt = todo!();
/// let tx = &psbt.clone().extract_tx().expect("tx");
/// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate");
/// ```
/// [`insert_txout`]: Self::insert_txout
@@ -1003,16 +1002,16 @@ impl<D> Wallet<D> {
/// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!();
/// # let txid:Txid = todo!();
/// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
/// let (sent, received) = wallet.sent_and_received(tx);
/// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx;
/// let (sent, received) = wallet.sent_and_received(&tx);
/// ```
///
/// ```rust, no_run
/// # use bitcoin::psbt::PartiallySignedTransaction;
/// # use bitcoin::Psbt;
/// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!();
/// # let mut psbt: PartiallySignedTransaction = todo!();
/// let tx = &psbt.clone().extract_tx();
/// # let mut psbt: Psbt = todo!();
/// let tx = &psbt.clone().extract_tx().expect("tx");
/// let (sent, received) = wallet.sent_and_received(tx);
/// ```
pub fn sent_and_received(&self, tx: &Transaction) -> (u64, u64) {
@@ -1065,7 +1064,7 @@ impl<D> Wallet<D> {
pub fn get_tx(
&self,
txid: Txid,
) -> Option<CanonicalTx<'_, Transaction, ConfirmationTimeHeightAnchor>> {
) -> Option<CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> {
let graph = self.indexed_graph.graph();
Some(CanonicalTx {
@@ -1128,18 +1127,14 @@ impl<D> Wallet<D> {
// anchor tx to checkpoint with lowest height that is >= position's height
let anchor = self
.chain
.blocks()
.range(height..)
.next()
.last()
.ok_or(InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
tip_height: self.chain.tip().height(),
tx_height: height,
})
.map(|(&anchor_height, &hash)| ConfirmationTimeHeightAnchor {
anchor_block: BlockId {
height: anchor_height,
hash,
},
.map(|anchor_cp| ConfirmationTimeHeightAnchor {
anchor_block: anchor_cp.block_id(),
confirmation_height: height,
confirmation_time: time,
})?;
@@ -1167,7 +1162,8 @@ impl<D> Wallet<D> {
/// Iterate over the transactions in the wallet.
pub fn transactions(
&self,
) -> impl Iterator<Item = CanonicalTx<'_, Transaction, ConfirmationTimeHeightAnchor>> + '_ {
) -> impl Iterator<Item = CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> + '_
{
self.indexed_graph
.graph()
.list_chain_txs(&self.chain, self.chain.tip().block_id())
@@ -1265,7 +1261,7 @@ impl<D> Wallet<D> {
&mut self,
coin_selection: Cs,
params: TxParams,
) -> Result<psbt::PartiallySignedTransaction, CreateTxError<D::WriteError>>
) -> Result<Psbt, CreateTxError<D::WriteError>>
where
D: PersistBackend<ChangeSet>,
{
@@ -1459,7 +1455,7 @@ impl<D> Wallet<D> {
};
let mut tx = Transaction {
version,
version: transaction::Version::non_standard(version),
lock_time,
input: vec![],
output: vec![],
@@ -1489,7 +1485,7 @@ impl<D> Wallet<D> {
let new_out = TxOut {
script_pubkey: script_pubkey.clone(),
value,
value: Amount::from_sat(value),
};
tx.output.push(new_out);
@@ -1499,17 +1495,6 @@ impl<D> Wallet<D> {
fee_amount += (fee_rate * tx.weight()).to_sat();
// Segwit transactions' header is 2WU larger than legacy txs' header,
// as they contain a witness marker (1WU) and a witness flag (1WU) (see BIP144).
// At this point we really don't know if the resulting transaction will be segwit
// or legacy, so we just add this 2WU to the fee_amount - overshooting the fee amount
// is better than undershooting it.
// If we pass a fee_amount that is slightly higher than the final fee_amount, we
// end up with a transaction with a slightly higher fee rate than the requested one.
// If, instead, we undershoot, we may end up with a feerate lower than the requested one
// - we might come up with non broadcastable txs!
fee_amount += (fee_rate * Weight::from_wu(2)).to_sat();
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
&& internal_descriptor.is_none()
{
@@ -1598,7 +1583,7 @@ impl<D> Wallet<D> {
// create drain output
let drain_output = TxOut {
value: *amount,
value: Amount::from_sat(*amount),
script_pubkey: drain_script,
};
@@ -1644,7 +1629,7 @@ impl<D> Wallet<D> {
/// builder.finish()?
/// };
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
/// let tx = psbt.extract_tx();
/// let tx = psbt.clone().extract_tx().expect("tx");
/// // broadcast tx but it's taking too long to confirm so we want to bump the fee
/// let mut psbt = {
/// let mut builder = wallet.build_fee_bump(tx.txid())?;
@@ -1670,6 +1655,7 @@ impl<D> Wallet<D> {
let mut tx = graph
.get_tx(txid)
.ok_or(BuildFeeBumpError::TransactionNotFound(txid))?
.as_ref()
.clone();
let pos = graph
@@ -1712,10 +1698,9 @@ impl<D> Wallet<D> {
let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
Some((keychain, derivation_index)) => {
#[allow(deprecated)]
let satisfaction_weight = self
.get_descriptor_for_keychain(keychain)
.max_satisfaction_weight()
.max_weight_to_satisfy()
.unwrap();
WeightedUtxo {
utxo: Utxo::Local(LocalOutput {
@@ -1733,16 +1718,16 @@ impl<D> Wallet<D> {
let satisfaction_weight =
serialize(&txin.script_sig).len() * 4 + serialize(&txin.witness).len();
WeightedUtxo {
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()),
non_witness_utxo: Some(prev_tx.as_ref().clone()),
..Default::default()
}),
},
satisfaction_weight,
}
}
};
@@ -1768,11 +1753,11 @@ impl<D> Wallet<D> {
let params = TxParams {
// TODO: figure out what rbf option should be?
version: Some(tx_builder::Version(tx.version)),
version: Some(tx_builder::Version(tx.version.0)),
recipients: tx
.output
.into_iter()
.map(|txout| (txout.script_pubkey, txout.value))
.map(|txout| (txout.script_pubkey, txout.value.to_sat()))
.collect(),
utxos: original_utxos,
bumping_fee: Some(tx_builder::PreviousFee {
@@ -1818,11 +1803,7 @@ impl<D> Wallet<D> {
/// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
/// assert!(finalized, "we should have signed all the inputs");
/// # Ok::<(),anyhow::Error>(())
pub fn sign(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
sign_options: SignOptions,
) -> Result<bool, SignerError> {
pub fn sign(&self, psbt: &mut Psbt, sign_options: SignOptions) -> Result<bool, SignerError> {
// This adds all the PSBT metadata for the inputs, which will help us later figure out how
// to derive our keys
self.update_psbt_with_descriptor(psbt)
@@ -1902,7 +1883,7 @@ impl<D> Wallet<D> {
/// The [`SignOptions`] can be used to tweak the behavior of the finalizer.
pub fn finalize_psbt(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
psbt: &mut Psbt,
sign_options: SignOptions,
) -> Result<bool, SignerError> {
let chain_tip = self.chain.tip().block_id();
@@ -2057,13 +2038,11 @@ impl<D> Wallet<D> {
self.list_unspent()
.map(|utxo| {
let keychain = utxo.keychain;
#[allow(deprecated)]
(
utxo,
(utxo, {
self.get_descriptor_for_keychain(keychain)
.max_satisfaction_weight()
.unwrap(),
)
.max_weight_to_satisfy()
.unwrap()
})
})
.collect()
}
@@ -2130,7 +2109,7 @@ impl<D> Wallet<D> {
if must_only_use_confirmed_tx && !confirmation_time.is_confirmed() {
return false;
}
if tx.is_coin_base() {
if tx.is_coinbase() {
debug_assert!(
confirmation_time.is_confirmed(),
"coinbase must always be confirmed"
@@ -2179,11 +2158,11 @@ impl<D> Wallet<D> {
tx: Transaction,
selected: Vec<Utxo>,
params: TxParams,
) -> Result<psbt::PartiallySignedTransaction, CreateTxError<D::WriteError>>
) -> Result<Psbt, CreateTxError<D::WriteError>>
where
D: PersistBackend<ChangeSet>,
{
let mut psbt = psbt::PartiallySignedTransaction::from_unsigned_tx(tx)?;
let mut psbt = Psbt::from_unsigned_tx(tx)?;
if params.add_global_xpubs {
let all_xpubs = self
@@ -2239,7 +2218,7 @@ impl<D> Wallet<D> {
let is_taproot = foreign_psbt_input
.witness_utxo
.as_ref()
.map(|txout| txout.script_pubkey.is_v1_p2tr())
.map(|txout| txout.script_pubkey.is_p2tr())
.unwrap_or(false);
if !is_taproot
&& !params.only_witness_utxo
@@ -2295,16 +2274,13 @@ impl<D> Wallet<D> {
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
}
if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
psbt_input.non_witness_utxo = Some(prev_tx.clone());
psbt_input.non_witness_utxo = Some(prev_tx.as_ref().clone());
}
}
Ok(psbt_input)
}
fn update_psbt_with_descriptor(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
) -> Result<(), MiniscriptPsbtError> {
fn update_psbt_with_descriptor(&self, psbt: &mut Psbt) -> 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
let utxos = (0..psbt.inputs.len())
@@ -2608,11 +2584,11 @@ macro_rules! doctest_wallet {
.unwrap();
let address = wallet.get_address(AddressIndex::New).address;
let tx = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: 500_000,
value: Amount::from_sat(500_000),
script_pubkey: address.script_pubkey(),
}],
};

View File

@@ -19,13 +19,12 @@
//! # use core::str::FromStr;
//! # use bitcoin::secp256k1::{Secp256k1, All};
//! # use bitcoin::*;
//! # use bitcoin::psbt;
//! # use bdk::signer::*;
//! # use bdk::*;
//! # #[derive(Debug)]
//! # struct CustomHSM;
//! # impl CustomHSM {
//! # fn hsm_sign_input(&self, _psbt: &mut psbt::PartiallySignedTransaction, _input: usize) -> Result<(), SignerError> {
//! # fn hsm_sign_input(&self, _psbt: &mut Psbt, _input: usize) -> Result<(), SignerError> {
//! # Ok(())
//! # }
//! # fn connect() -> Self {
@@ -55,7 +54,7 @@
//! impl InputSigner for CustomSigner {
//! fn sign_input(
//! &self,
//! psbt: &mut psbt::PartiallySignedTransaction,
//! psbt: &mut Psbt,
//! input_index: usize,
//! _sign_options: &SignOptions,
//! _secp: &Secp256k1<All>,
@@ -87,13 +86,13 @@ use core::cmp::Ordering;
use core::fmt;
use core::ops::{Bound::Included, Deref};
use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint};
use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpriv};
use bitcoin::hashes::hash160;
use bitcoin::secp256k1::Message;
use bitcoin::sighash::{EcdsaSighashType, TapSighash, TapSighashType};
use bitcoin::{ecdsa, psbt, sighash, taproot};
use bitcoin::{key::TapTweak, key::XOnlyPublicKey, secp256k1};
use bitcoin::{PrivateKey, PublicKey};
use bitcoin::{PrivateKey, Psbt, PublicKey};
use miniscript::descriptor::{
Descriptor, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey,
@@ -264,7 +263,7 @@ pub trait InputSigner: SignerCommon {
/// Sign a single psbt input
fn sign_input(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
psbt: &mut Psbt,
input_index: usize,
sign_options: &SignOptions,
secp: &SecpCtx,
@@ -279,7 +278,7 @@ pub trait TransactionSigner: SignerCommon {
/// Sign all the inputs of the psbt
fn sign_transaction(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
psbt: &mut Psbt,
sign_options: &SignOptions,
secp: &SecpCtx,
) -> Result<(), SignerError>;
@@ -288,7 +287,7 @@ pub trait TransactionSigner: SignerCommon {
impl<T: InputSigner> TransactionSigner for T {
fn sign_transaction(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
psbt: &mut Psbt,
sign_options: &SignOptions,
secp: &SecpCtx,
) -> Result<(), SignerError> {
@@ -300,7 +299,7 @@ impl<T: InputSigner> TransactionSigner for T {
}
}
impl SignerCommon for SignerWrapper<DescriptorXKey<ExtendedPrivKey>> {
impl SignerCommon for SignerWrapper<DescriptorXKey<Xpriv>> {
fn id(&self, secp: &SecpCtx) -> SignerId {
SignerId::from(self.root_fingerprint(secp))
}
@@ -310,10 +309,10 @@ impl SignerCommon for SignerWrapper<DescriptorXKey<ExtendedPrivKey>> {
}
}
impl InputSigner for SignerWrapper<DescriptorXKey<ExtendedPrivKey>> {
impl InputSigner for SignerWrapper<DescriptorXKey<Xpriv>> {
fn sign_input(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
psbt: &mut Psbt,
input_index: usize,
sign_options: &SignOptions,
secp: &SecpCtx,
@@ -396,7 +395,7 @@ fn multikey_to_xkeys<K: InnerXKey + Clone>(
.collect()
}
impl SignerCommon for SignerWrapper<DescriptorMultiXKey<ExtendedPrivKey>> {
impl SignerCommon for SignerWrapper<DescriptorMultiXKey<Xpriv>> {
fn id(&self, secp: &SecpCtx) -> SignerId {
SignerId::from(self.root_fingerprint(secp))
}
@@ -406,10 +405,10 @@ impl SignerCommon for SignerWrapper<DescriptorMultiXKey<ExtendedPrivKey>> {
}
}
impl InputSigner for SignerWrapper<DescriptorMultiXKey<ExtendedPrivKey>> {
impl InputSigner for SignerWrapper<DescriptorMultiXKey<Xpriv>> {
fn sign_input(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
psbt: &mut Psbt,
input_index: usize,
sign_options: &SignOptions,
secp: &SecpCtx,
@@ -438,7 +437,7 @@ impl SignerCommon for SignerWrapper<PrivateKey> {
impl InputSigner for SignerWrapper<PrivateKey> {
fn sign_input(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
psbt: &mut Psbt,
input_index: usize,
sign_options: &SignOptions,
secp: &SecpCtx,
@@ -577,7 +576,7 @@ fn sign_psbt_schnorr(
hash_ty: TapSighashType,
secp: &SecpCtx,
) {
let keypair = secp256k1::KeyPair::from_seckey_slice(secp, secret_key.as_ref()).unwrap();
let keypair = secp256k1::Keypair::from_seckey_slice(secp, secret_key.as_ref()).unwrap();
let keypair = match leaf_hash {
None => keypair
.tap_tweak(secp, psbt_input.tap_merkle_root)
@@ -852,7 +851,7 @@ pub(crate) trait ComputeSighash {
type SighashType;
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
psbt: &Psbt,
input_index: usize,
extra: Self::Extra,
) -> Result<(Self::Sighash, Self::SighashType), SignerError>;
@@ -864,7 +863,7 @@ impl ComputeSighash for Legacy {
type SighashType = EcdsaSighashType;
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
psbt: &Psbt,
input_index: usize,
_extra: (),
) -> Result<(Self::Sighash, Self::SighashType), SignerError> {
@@ -913,7 +912,7 @@ impl ComputeSighash for Segwitv0 {
type SighashType = EcdsaSighashType;
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
psbt: &Psbt,
input_index: usize,
_extra: (),
) -> Result<(Self::Sighash, Self::SighashType), SignerError> {
@@ -924,7 +923,7 @@ impl ComputeSighash for Segwitv0 {
let psbt_input = &psbt.inputs[input_index];
let tx_input = &psbt.unsigned_tx.input[input_index];
let sighash = psbt_input
let sighash_type = psbt_input
.sighash_type
.unwrap_or_else(|| EcdsaSighashType::All.into())
.ecdsa_hash_ty()
@@ -952,40 +951,39 @@ impl ComputeSighash for Segwitv0 {
};
let value = utxo.value;
let script = match psbt_input.witness_script {
Some(ref witness_script) => witness_script.clone(),
let mut sighasher = sighash::SighashCache::new(&psbt.unsigned_tx);
let sighash = match psbt_input.witness_script {
Some(ref witness_script) => {
sighasher.p2wsh_signature_hash(input_index, witness_script, value, sighash_type)?
}
None => {
if utxo.script_pubkey.is_v0_p2wpkh() {
utxo.script_pubkey
.p2wpkh_script_code()
.expect("We check above that the spk is a p2wpkh")
if utxo.script_pubkey.is_p2wpkh() {
sighasher.p2wpkh_signature_hash(
input_index,
&utxo.script_pubkey,
value,
sighash_type,
)?
} else if psbt_input
.redeem_script
.as_ref()
.map(|s| s.is_v0_p2wpkh())
.map(|s| s.is_p2wpkh())
.unwrap_or(false)
{
psbt_input
.redeem_script
.as_ref()
.unwrap()
.p2wpkh_script_code()
.expect("We check above that the spk is a p2wpkh")
let script_pubkey = psbt_input.redeem_script.as_ref().unwrap();
sighasher.p2wpkh_signature_hash(
input_index,
script_pubkey,
value,
sighash_type,
)?
} else {
return Err(SignerError::MissingWitnessScript);
}
}
};
Ok((
sighash::SighashCache::new(&psbt.unsigned_tx).segwit_signature_hash(
input_index,
&script,
value,
sighash,
)?,
sighash,
))
Ok((sighash, sighash_type))
}
}
@@ -995,7 +993,7 @@ impl ComputeSighash for Tap {
type SighashType = TapSighashType;
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
psbt: &Psbt,
input_index: usize,
extra: Self::Extra,
) -> Result<(Self::Sighash, TapSighashType), SignerError> {
@@ -1166,7 +1164,7 @@ mod signers_container_tests {
impl TransactionSigner for DummySigner {
fn sign_transaction(
&self,
_psbt: &mut psbt::PartiallySignedTransaction,
_psbt: &mut Psbt,
_sign_options: &SignOptions,
_secp: &SecpCtx,
) -> Result<(), SignerError> {
@@ -1184,8 +1182,8 @@ mod signers_container_tests {
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
let secp: Secp256k1<All> = Secp256k1::new();
let path = bip32::DerivationPath::from_str(PATH).unwrap();
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
let tpub = bip32::ExtendedPubKey::from_priv(&secp, &tprv);
let tprv = bip32::Xpriv::from_str(tprv).unwrap();
let tpub = bip32::Xpub::from_priv(&secp, &tprv);
let fingerprint = tprv.fingerprint(&secp);
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
let pubkey = (tpub, path).into_descriptor_key().unwrap();

View File

@@ -46,7 +46,7 @@ use core::fmt;
use core::marker::PhantomData;
use bdk_chain::PersistBackend;
use bitcoin::psbt::{self, PartiallySignedTransaction as Psbt};
use bitcoin::psbt::{self, Psbt};
use bitcoin::script::PushBytes;
use bitcoin::{absolute, FeeRate, OutPoint, ScriptBuf, Sequence, Transaction, Txid};
@@ -314,8 +314,7 @@ impl<'a, D, Cs, Ctx> TxBuilder<'a, D, Cs, Ctx> {
for utxo in utxos {
let descriptor = wallet.get_descriptor_for_keychain(utxo.keychain);
#[allow(deprecated)]
let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap();
let satisfaction_weight = descriptor.max_weight_to_satisfy().unwrap();
self.params.utxos.push(WeightedUtxo {
satisfaction_weight,
utxo: Utxo::Local(utxo),
@@ -356,9 +355,9 @@ impl<'a, D, Cs, Ctx> TxBuilder<'a, D, Cs, Ctx> {
/// causing you to pay a fee that is too high. The party who is broadcasting the transaction can
/// of course check the real input weight matches the expected weight prior to broadcasting.
///
/// To guarantee the `satisfaction_weight` is correct, you can require the party providing the
/// To guarantee the `max_weight_to_satisfy` is correct, you can require the party providing the
/// `psbt_input` provide a miniscript descriptor for the input so you can check it against the
/// `script_pubkey` and then ask it for the [`max_satisfaction_weight`].
/// `script_pubkey` and then ask it for the [`max_weight_to_satisfy`].
///
/// This is an **EXPERIMENTAL** feature, API and other major changes are expected.
///
@@ -379,7 +378,7 @@ impl<'a, D, Cs, Ctx> TxBuilder<'a, D, Cs, Ctx> {
///
/// [`only_witness_utxo`]: Self::only_witness_utxo
/// [`finish`]: Self::finish
/// [`max_satisfaction_weight`]: miniscript::Descriptor::max_satisfaction_weight
/// [`max_weight_to_satisfy`]: miniscript::Descriptor::max_weight_to_satisfy
pub fn add_foreign_utxo(
&mut self,
outpoint: OutPoint,
@@ -928,7 +927,8 @@ mod test {
use bdk_chain::ConfirmationTime;
use bitcoin::consensus::deserialize;
use bitcoin::hashes::hex::FromHex;
use bitcoin::hex::FromHex;
use bitcoin::TxOut;
use super::*;
@@ -999,7 +999,7 @@ mod test {
.unwrap()
);
assert_eq!(tx.output[0].value, 800);
assert_eq!(tx.output[0].value.to_sat(), 800);
assert_eq!(tx.output[1].script_pubkey, ScriptBuf::from(vec![0xAA]));
assert_eq!(
tx.output[2].script_pubkey,
@@ -1016,7 +1016,7 @@ mod test {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
vout: 0,
},
txout: Default::default(),
txout: TxOut::NULL,
keychain: KeychainKind::External,
is_spent: false,
confirmation_time: ConfirmationTime::Unconfirmed { last_seen: 0 },
@@ -1027,7 +1027,7 @@ mod test {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
vout: 1,
},
txout: Default::default(),
txout: TxOut::NULL,
keychain: KeychainKind::Internal,
is_spent: false,
confirmation_time: ConfirmationTime::Confirmed {

View File

@@ -138,7 +138,7 @@ mod test {
.require_network(Network::Bitcoin)
.unwrap()
.script_pubkey();
assert!(script_p2wpkh.is_v0_p2wpkh());
assert!(script_p2wpkh.is_p2wpkh());
assert!(293.is_dust(&script_p2wpkh));
assert!(!294.is_dust(&script_p2wpkh));
}

View File

@@ -4,7 +4,10 @@ use bdk::{wallet::AddressIndex, KeychainKind, LocalOutput, Wallet};
use bdk_chain::indexed_tx_graph::Indexer;
use bdk_chain::{BlockId, ConfirmationTime};
use bitcoin::hashes::Hash;
use bitcoin::{Address, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut, Txid};
use bitcoin::{
transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut,
Txid,
};
use std::str::FromStr;
// Return a fake wallet that appears to be funded for testing.
@@ -24,7 +27,7 @@ pub fn get_funded_wallet_with_change(
.unwrap();
let tx0 = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
@@ -36,13 +39,13 @@ pub fn get_funded_wallet_with_change(
witness: Default::default(),
}],
output: vec![TxOut {
value: 76_000,
value: Amount::from_sat(76_000),
script_pubkey: change_address.script_pubkey(),
}],
};
let tx1 = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
@@ -55,11 +58,11 @@ pub fn get_funded_wallet_with_change(
}],
output: vec![
TxOut {
value: 50_000,
value: Amount::from_sat(50_000),
script_pubkey: change_address.script_pubkey(),
},
TxOut {
value: 25_000,
value: Amount::from_sat(25_000),
script_pubkey: sendto_address.script_pubkey(),
},
],

View File

@@ -1,9 +1,7 @@
use bdk::bitcoin::FeeRate;
use bdk::bitcoin::TxIn;
use bdk::bitcoin::{Amount, FeeRate, Psbt, TxIn};
use bdk::wallet::AddressIndex;
use bdk::wallet::AddressIndex::New;
use bdk::{psbt, SignOptions};
use bitcoin::psbt::PartiallySignedTransaction as Psbt;
use core::str::FromStr;
mod common;
use common::*;
@@ -163,7 +161,7 @@ fn test_psbt_multiple_internalkey_signers() {
use bdk::signer::{SignerContext, SignerOrdering, SignerWrapper};
use bdk::KeychainKind;
use bitcoin::key::TapTweak;
use bitcoin::secp256k1::{schnorr, KeyPair, Message, Secp256k1, XOnlyPublicKey};
use bitcoin::secp256k1::{schnorr, Keypair, Message, Secp256k1, XOnlyPublicKey};
use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType};
use bitcoin::{PrivateKey, TxOut};
use std::sync::Arc;
@@ -172,7 +170,7 @@ fn test_psbt_multiple_internalkey_signers() {
let wif = "cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG";
let desc = format!("tr({})", wif);
let prv = PrivateKey::from_wif(wif).unwrap();
let keypair = KeyPair::from_secret_key(&secp, &prv.inner);
let keypair = Keypair::from_secret_key(&secp, &prv.inner);
let (mut wallet, _) = get_funded_wallet(&desc);
let to_spend = wallet.get_balance().total();
@@ -205,7 +203,7 @@ fn test_psbt_multiple_internalkey_signers() {
// the prevout we're spending
let prevouts = &[TxOut {
script_pubkey: send_to.script_pubkey(),
value: to_spend,
value: Amount::from_sat(to_spend),
}];
let prevouts = Prevouts::All(prevouts);
let input_index = 0;

View File

@@ -18,8 +18,8 @@ use bitcoin::script::PushBytesBuf;
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
use bitcoin::taproot::TapNodeHash;
use bitcoin::{
absolute, Address, Amount, BlockHash, FeeRate, Network, OutPoint, ScriptBuf, Sequence,
Transaction, TxIn, TxOut, Txid, Weight,
absolute, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, ScriptBuf,
Sequence, Transaction, TxIn, TxOut, Txid, Weight,
};
mod common;
@@ -27,12 +27,12 @@ use common::*;
fn receive_output(wallet: &mut Wallet, value: u64, height: ConfirmationTime) -> OutPoint {
let tx = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
script_pubkey: wallet.get_address(LastUnused).script_pubkey(),
value,
value: Amount::from_sat(value),
}],
};
@@ -208,12 +208,12 @@ fn test_get_funded_wallet_sent_and_received() {
let mut tx_amounts: Vec<(Txid, (u64, u64))> = wallet
.transactions()
.map(|ct| (ct.tx_node.txid, wallet.sent_and_received(ct.tx_node.tx)))
.map(|ct| (ct.tx_node.txid, wallet.sent_and_received(&ct.tx_node)))
.collect();
tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0));
let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
let (sent, received) = wallet.sent_and_received(tx);
let (sent, received) = wallet.sent_and_received(&tx);
// The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
@@ -227,7 +227,7 @@ fn test_get_funded_wallet_tx_fees() {
let (wallet, txid) = get_funded_wallet(get_test_wpkh());
let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
let tx_fee = wallet.calculate_fee(tx).expect("transaction fee");
let tx_fee = wallet.calculate_fee(&tx).expect("transaction fee");
// The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
@@ -240,7 +240,9 @@ fn test_get_funded_wallet_tx_fee_rate() {
let (wallet, txid) = get_funded_wallet(get_test_wpkh());
let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
let tx_fee_rate = wallet.calculate_fee_rate(tx).expect("transaction fee rate");
let tx_fee_rate = wallet
.calculate_fee_rate(&tx)
.expect("transaction fee rate");
// The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
@@ -263,10 +265,10 @@ fn test_list_output() {
assert_eq!(txos.len(), 2);
for (op, txo) in txos {
if op.txid == txid {
assert_eq!(txo.txout.value, 50_000);
assert_eq!(txo.txout.value.to_sat(), 50_000);
assert!(!txo.is_spent);
} else {
assert_eq!(txo.txout.value, 76_000);
assert_eq!(txo.txout.value.to_sat(), 76_000);
assert!(txo.is_spent);
}
}
@@ -276,7 +278,7 @@ macro_rules! assert_fee_rate {
($psbt:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({
let psbt = $psbt.clone();
#[allow(unused_mut)]
let mut tx = $psbt.clone().extract_tx();
let mut tx = $psbt.clone().extract_tx().expect("failed to extract tx");
$(
$( $add_signature )*
for txin in &mut tx.input {
@@ -295,12 +297,12 @@ macro_rules! assert_fee_rate {
let fee_amount = psbt
.inputs
.iter()
.fold(0, |acc, i| acc + i.witness_utxo.as_ref().unwrap().value)
.fold(0, |acc, i| acc + i.witness_utxo.as_ref().unwrap().value.to_sat())
- psbt
.unsigned_tx
.output
.iter()
.fold(0, |acc, o| acc + o.value);
.fold(0, |acc, o| acc + o.value.to_sat());
assert_eq!(fee_amount, $fees);
@@ -381,7 +383,7 @@ fn test_create_tx_custom_version() {
.version(42);
let psbt = builder.finish().unwrap();
assert_eq!(psbt.unsigned_tx.version, 42);
assert_eq!(psbt.unsigned_tx.version.0, 42);
}
#[test]
@@ -568,7 +570,7 @@ fn test_create_tx_change_policy_no_internal() {
macro_rules! check_fee {
($wallet:expr, $psbt: expr) => {{
let tx = $psbt.clone().extract_tx();
let tx = $psbt.clone().extract_tx().expect("failed to extract tx");
let tx_fee = $wallet.calculate_fee(&tx).ok();
assert_eq!(tx_fee, $psbt.fee_amount());
tx_fee
@@ -585,7 +587,10 @@ fn test_create_tx_drain_wallet_and_drain_to() {
let fee = check_fee!(wallet, psbt);
assert_eq!(psbt.unsigned_tx.output.len(), 1);
assert_eq!(psbt.unsigned_tx.output[0].value, 50_000 - fee.unwrap_or(0));
assert_eq!(
psbt.unsigned_tx.output[0].value.to_sat(),
50_000 - fee.unwrap_or(0)
);
}
#[test]
@@ -613,8 +618,8 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
.iter()
.find(|x| x.script_pubkey == drain_addr.script_pubkey())
.unwrap();
assert_eq!(main_output.value, 20_000,);
assert_eq!(drain_output.value, 30_000 - fee.unwrap_or(0));
assert_eq!(main_output.value.to_sat(), 20_000,);
assert_eq!(drain_output.value.to_sat(), 30_000 - fee.unwrap_or(0));
}
#[test]
@@ -631,7 +636,10 @@ fn test_create_tx_drain_to_and_utxos() {
let fee = check_fee!(wallet, psbt);
assert_eq!(psbt.unsigned_tx.output.len(), 1);
assert_eq!(psbt.unsigned_tx.output[0].value, 50_000 - fee.unwrap_or(0));
assert_eq!(
psbt.unsigned_tx.output[0].value.to_sat(),
50_000 - fee.unwrap_or(0)
);
}
#[test]
@@ -684,7 +692,10 @@ fn test_create_tx_absolute_fee() {
assert_eq!(fee.unwrap_or(0), 100);
assert_eq!(psbt.unsigned_tx.output.len(), 1);
assert_eq!(psbt.unsigned_tx.output[0].value, 50_000 - fee.unwrap_or(0));
assert_eq!(
psbt.unsigned_tx.output[0].value.to_sat(),
50_000 - fee.unwrap_or(0)
);
}
#[test]
@@ -701,7 +712,10 @@ fn test_create_tx_absolute_zero_fee() {
assert_eq!(fee.unwrap_or(0), 0);
assert_eq!(psbt.unsigned_tx.output.len(), 1);
assert_eq!(psbt.unsigned_tx.output[0].value, 50_000 - fee.unwrap_or(0));
assert_eq!(
psbt.unsigned_tx.output[0].value.to_sat(),
50_000 - fee.unwrap_or(0)
);
}
#[test]
@@ -731,8 +745,11 @@ fn test_create_tx_add_change() {
let fee = check_fee!(wallet, psbt);
assert_eq!(psbt.unsigned_tx.output.len(), 2);
assert_eq!(psbt.unsigned_tx.output[0].value, 25_000);
assert_eq!(psbt.unsigned_tx.output[1].value, 25_000 - fee.unwrap_or(0));
assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 25_000);
assert_eq!(
psbt.unsigned_tx.output[1].value.to_sat(),
25_000 - fee.unwrap_or(0)
);
}
#[test]
@@ -745,7 +762,7 @@ fn test_create_tx_skip_change_dust() {
let fee = check_fee!(wallet, psbt);
assert_eq!(psbt.unsigned_tx.output.len(), 1);
assert_eq!(psbt.unsigned_tx.output[0].value, 49_800);
assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 49_800);
assert_eq!(fee.unwrap_or(0), 200);
}
@@ -776,9 +793,12 @@ fn test_create_tx_ordering_respected() {
let fee = check_fee!(wallet, psbt);
assert_eq!(psbt.unsigned_tx.output.len(), 3);
assert_eq!(psbt.unsigned_tx.output[0].value, 10_000 - fee.unwrap_or(0));
assert_eq!(psbt.unsigned_tx.output[1].value, 10_000);
assert_eq!(psbt.unsigned_tx.output[2].value, 30_000);
assert_eq!(
psbt.unsigned_tx.output[0].value.to_sat(),
10_000 - fee.unwrap_or(0)
);
assert_eq!(psbt.unsigned_tx.output[1].value.to_sat(), 10_000);
assert_eq!(psbt.unsigned_tx.output[2].value.to_sat(), 30_000);
}
#[test]
@@ -854,7 +874,7 @@ fn test_create_tx_output_hd_keypaths() {
#[test]
fn test_create_tx_set_redeem_script_p2sh() {
use bitcoin::hashes::hex::FromHex;
use bitcoin::hex::FromHex;
let (mut wallet, _) =
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
@@ -877,7 +897,7 @@ fn test_create_tx_set_redeem_script_p2sh() {
#[test]
fn test_create_tx_set_witness_script_p2wsh() {
use bitcoin::hashes::hex::FromHex;
use bitcoin::hex::FromHex;
let (mut wallet, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
@@ -912,7 +932,7 @@ fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() {
)
.unwrap();
assert_eq!(psbt.inputs[0].redeem_script, Some(script.to_v0_p2wsh()));
assert_eq!(psbt.inputs[0].redeem_script, Some(script.to_p2wsh()));
assert_eq!(psbt.inputs[0].witness_script, Some(script));
}
@@ -976,10 +996,10 @@ fn test_create_tx_add_utxo() {
let small_output_tx = Transaction {
input: vec![],
output: vec![TxOut {
value: 25_000,
value: Amount::from_sat(25_000),
script_pubkey: wallet.get_address(New).address.script_pubkey(),
}],
version: 0,
version: transaction::Version::non_standard(0),
lock_time: absolute::LockTime::ZERO,
};
wallet
@@ -1001,7 +1021,8 @@ fn test_create_tx_add_utxo() {
})
.unwrap();
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
assert_eq!(
psbt.unsigned_tx.input.len(),
@@ -1021,10 +1042,10 @@ fn test_create_tx_manually_selected_insufficient() {
let small_output_tx = Transaction {
input: vec![],
output: vec![TxOut {
value: 25_000,
value: Amount::from_sat(25_000),
script_pubkey: wallet.get_address(New).address.script_pubkey(),
}],
version: 0,
version: transaction::Version::non_standard(0),
lock_time: absolute::LockTime::ZERO,
};
@@ -1069,11 +1090,11 @@ fn test_create_tx_policy_path_no_csv() {
let mut wallet = Wallet::new_no_persist(descriptors, None, Network::Regtest).unwrap();
let tx = Transaction {
version: 0,
version: transaction::Version::non_standard(0),
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: 50_000,
value: Amount::from_sat(50_000),
script_pubkey: wallet.get_address(New).script_pubkey(),
}],
};
@@ -1143,7 +1164,7 @@ fn test_create_tx_policy_path_ignored_subtree_with_csv() {
#[test]
fn test_create_tx_global_xpubs_with_origin() {
use bitcoin::bip32;
use bitcoin::hashes::hex::FromHex;
use bitcoin::hex::FromHex;
let (mut wallet, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
let addr = wallet.get_address(New);
@@ -1153,7 +1174,7 @@ fn test_create_tx_global_xpubs_with_origin() {
.add_global_xpubs();
let psbt = builder.finish().unwrap();
let key = bip32::ExtendedPubKey::from_str("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap();
let key = bip32::Xpub::from_str("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap();
let fingerprint = bip32::Fingerprint::from_hex("73756c7f").unwrap();
let path = bip32::DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
@@ -1171,10 +1192,9 @@ fn test_add_foreign_utxo() {
.unwrap()
.assume_checked();
let utxo = wallet2.list_unspent().next().expect("must take!");
#[allow(deprecated)]
let foreign_utxo_satisfaction = wallet2
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
.max_weight_to_satisfy()
.unwrap();
let psbt_input = psbt::Input {
@@ -1191,7 +1211,8 @@ fn test_add_foreign_utxo() {
let mut psbt = builder.finish().unwrap();
wallet1.insert_txout(utxo.outpoint, utxo.txout);
let fee = check_fee!(wallet1, psbt);
let sent_received = wallet1.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
assert_eq!(
sent_received.0 - sent_received.1,
@@ -1247,10 +1268,9 @@ fn test_calculate_fee_with_missing_foreign_utxo() {
.unwrap()
.assume_checked();
let utxo = wallet2.list_unspent().next().expect("must take!");
#[allow(deprecated)]
let foreign_utxo_satisfaction = wallet2
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
.max_weight_to_satisfy()
.unwrap();
let psbt_input = psbt::Input {
@@ -1265,7 +1285,7 @@ fn test_calculate_fee_with_missing_foreign_utxo() {
.add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
.unwrap();
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
wallet1.calculate_fee(&tx).unwrap();
}
@@ -1273,10 +1293,9 @@ fn test_calculate_fee_with_missing_foreign_utxo() {
fn test_add_foreign_utxo_invalid_psbt_input() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let outpoint = wallet.list_unspent().next().expect("must exist").outpoint;
#[allow(deprecated)]
let foreign_utxo_satisfaction = wallet
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
.max_weight_to_satisfy()
.unwrap();
let mut builder = wallet.build_tx();
@@ -1295,10 +1314,9 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone();
let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx.clone();
#[allow(deprecated)]
let satisfaction_weight = wallet2
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
.max_weight_to_satisfy()
.unwrap();
let mut builder = wallet1.build_tx();
@@ -1307,7 +1325,7 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
.add_foreign_utxo(
utxo2.outpoint,
psbt::Input {
non_witness_utxo: Some(tx1),
non_witness_utxo: Some(tx1.as_ref().clone()),
..Default::default()
},
satisfaction_weight
@@ -1320,7 +1338,7 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
.add_foreign_utxo(
utxo2.outpoint,
psbt::Input {
non_witness_utxo: Some(tx2),
non_witness_utxo: Some(tx2.as_ref().clone()),
..Default::default()
},
satisfaction_weight
@@ -1340,10 +1358,9 @@ fn test_add_foreign_utxo_only_witness_utxo() {
.assume_checked();
let utxo2 = wallet2.list_unspent().next().unwrap();
#[allow(deprecated)]
let satisfaction_weight = wallet2
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
.max_weight_to_satisfy()
.unwrap();
let mut builder = wallet1.build_tx();
@@ -1384,7 +1401,7 @@ fn test_add_foreign_utxo_only_witness_utxo() {
let mut builder = builder.clone();
let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx;
let psbt_input = psbt::Input {
non_witness_utxo: Some(tx2.clone()),
non_witness_utxo: Some(tx2.as_ref().clone()),
..Default::default()
};
builder
@@ -1424,7 +1441,7 @@ fn test_create_tx_global_xpubs_origin_missing() {
#[test]
fn test_create_tx_global_xpubs_master_without_origin() {
use bitcoin::bip32;
use bitcoin::hashes::hex::FromHex;
use bitcoin::hex::FromHex;
let (mut wallet, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
let addr = wallet.get_address(New);
@@ -1434,7 +1451,7 @@ fn test_create_tx_global_xpubs_master_without_origin() {
.add_global_xpubs();
let psbt = builder.finish().unwrap();
let key = bip32::ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
let key = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap();
assert_eq!(psbt.xpub.len(), 1);
@@ -1453,7 +1470,7 @@ fn test_bump_fee_irreplaceable_tx() {
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.txid();
wallet
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
@@ -1470,7 +1487,7 @@ fn test_bump_fee_confirmed_tx() {
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.txid();
wallet
@@ -1497,7 +1514,7 @@ fn test_bump_fee_low_fee_rate() {
let psbt = builder.finish().unwrap();
let feerate = psbt.fee_rate().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.txid();
wallet
@@ -1530,7 +1547,7 @@ fn test_bump_fee_low_abs() {
.enable_rbf();
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.txid();
wallet
@@ -1553,7 +1570,7 @@ fn test_bump_fee_zero_abs() {
.enable_rbf();
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.txid();
wallet
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
@@ -1575,10 +1592,11 @@ fn test_bump_fee_reduce_change() {
.add_recipient(addr.script_pubkey(), 25_000)
.enable_rbf();
let psbt = builder.finish().unwrap();
let original_sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let original_sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let original_fee = check_fee!(wallet, psbt);
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.txid();
wallet
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
@@ -1588,7 +1606,8 @@ fn test_bump_fee_reduce_change() {
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_rate(feerate).enable_rbf();
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let fee = check_fee!(wallet, psbt);
assert_eq!(sent_received.0, original_sent_received.0);
@@ -1606,14 +1625,15 @@ fn test_bump_fee_reduce_change() {
.find(|txout| txout.script_pubkey == addr.script_pubkey())
.unwrap()
.value,
25_000
Amount::from_sat(25_000)
);
assert_eq!(
tx.output
.iter()
.find(|txout| txout.script_pubkey != addr.script_pubkey())
.unwrap()
.value,
.value
.to_sat(),
sent_received.1
);
@@ -1623,7 +1643,8 @@ fn test_bump_fee_reduce_change() {
builder.fee_absolute(200);
builder.enable_rbf();
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let fee = check_fee!(wallet, psbt);
assert_eq!(sent_received.0, original_sent_received.0);
@@ -1646,14 +1667,15 @@ fn test_bump_fee_reduce_change() {
.find(|txout| txout.script_pubkey == addr.script_pubkey())
.unwrap()
.value,
25_000
Amount::from_sat(25_000)
);
assert_eq!(
tx.output
.iter()
.find(|txout| txout.script_pubkey != addr.script_pubkey())
.unwrap()
.value,
.value
.to_sat(),
sent_received.1
);
@@ -1672,7 +1694,7 @@ fn test_bump_fee_reduce_single_recipient() {
.drain_wallet()
.enable_rbf();
let psbt = builder.finish().unwrap();
let tx = psbt.clone().extract_tx();
let tx = psbt.clone().extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
let original_fee = check_fee!(wallet, psbt);
let txid = tx.txid();
@@ -1687,7 +1709,8 @@ fn test_bump_fee_reduce_single_recipient() {
.allow_shrinking(addr.script_pubkey())
.unwrap();
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let fee = check_fee!(wallet, psbt);
assert_eq!(sent_received.0, original_sent_received.0);
@@ -1695,7 +1718,10 @@ fn test_bump_fee_reduce_single_recipient() {
let tx = &psbt.unsigned_tx;
assert_eq!(tx.output.len(), 1);
assert_eq!(tx.output[0].value + fee.unwrap_or(0), sent_received.0);
assert_eq!(
tx.output[0].value.to_sat() + fee.unwrap_or(0),
sent_received.0
);
assert_fee_rate!(psbt, fee.unwrap_or(0), feerate, @add_signature);
}
@@ -1713,7 +1739,7 @@ fn test_bump_fee_absolute_reduce_single_recipient() {
.enable_rbf();
let psbt = builder.finish().unwrap();
let original_fee = check_fee!(wallet, psbt);
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
let txid = tx.txid();
wallet
@@ -1734,7 +1760,10 @@ fn test_bump_fee_absolute_reduce_single_recipient() {
assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0));
assert_eq!(tx.output.len(), 1);
assert_eq!(tx.output[0].value + fee.unwrap_or(0), sent_received.0);
assert_eq!(
tx.output[0].value.to_sat() + fee.unwrap_or(0),
sent_received.0
);
assert_eq!(fee.unwrap_or(0), 300);
}
@@ -1744,11 +1773,11 @@ fn test_bump_fee_drain_wallet() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
// receive an extra tx so that our wallet has two utxos.
let tx = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: 25_000,
value: Amount::from_sat(25_000),
script_pubkey: wallet.get_address(New).script_pubkey(),
}],
};
@@ -1776,7 +1805,7 @@ fn test_bump_fee_drain_wallet() {
.manually_selected_only()
.enable_rbf();
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
let txid = tx.txid();
@@ -1794,7 +1823,7 @@ fn test_bump_fee_drain_wallet() {
.unwrap()
.fee_rate(FeeRate::from_sat_per_vb_unchecked(5));
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.extract_tx());
let sent_received = wallet.sent_and_received(&psbt.extract_tx().expect("failed to extract tx"));
assert_eq!(sent_received.0, 75_000);
}
@@ -1809,12 +1838,12 @@ fn test_bump_fee_remove_output_manually_selected_only() {
// existing output. In other words, bump_fee + manually_selected_only is always an error
// unless you've also set "allow_shrinking" OR there is a change output.
let init_tx = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
script_pubkey: wallet.get_address(New).script_pubkey(),
value: 25_000,
value: Amount::from_sat(25_000),
}],
};
wallet
@@ -1844,7 +1873,7 @@ fn test_bump_fee_remove_output_manually_selected_only() {
.manually_selected_only()
.enable_rbf();
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
let txid = tx.txid();
wallet
@@ -1863,12 +1892,12 @@ fn test_bump_fee_remove_output_manually_selected_only() {
fn test_bump_fee_add_input() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let init_tx = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
script_pubkey: wallet.get_address(New).script_pubkey(),
value: 25_000,
value: Amount::from_sat(25_000),
}],
};
let pos = wallet
@@ -1888,7 +1917,7 @@ fn test_bump_fee_add_input() {
.add_recipient(addr.script_pubkey(), 45_000)
.enable_rbf();
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let original_details = wallet.sent_and_received(&tx);
let txid = tx.txid();
wallet
@@ -1898,7 +1927,8 @@ fn test_bump_fee_add_input() {
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50));
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let fee = check_fee!(wallet, psbt);
assert_eq!(sent_received.0, original_details.0 + 25_000);
assert_eq!(fee.unwrap_or(0) + sent_received.1, 30_000);
@@ -1912,14 +1942,15 @@ fn test_bump_fee_add_input() {
.find(|txout| txout.script_pubkey == addr.script_pubkey())
.unwrap()
.value,
45_000
Amount::from_sat(45_000)
);
assert_eq!(
tx.output
.iter()
.find(|txout| txout.script_pubkey != addr.script_pubkey())
.unwrap()
.value,
.value
.to_sat(),
sent_received.1
);
@@ -1938,7 +1969,7 @@ fn test_bump_fee_absolute_add_input() {
.add_recipient(addr.script_pubkey(), 45_000)
.enable_rbf();
let psbt = builder.finish().unwrap();
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
let txid = tx.txid();
wallet
@@ -1948,7 +1979,8 @@ fn test_bump_fee_absolute_add_input() {
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_absolute(6_000);
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let fee = check_fee!(wallet, psbt);
assert_eq!(sent_received.0, original_sent_received.0 + 25_000);
@@ -1963,14 +1995,15 @@ fn test_bump_fee_absolute_add_input() {
.find(|txout| txout.script_pubkey == addr.script_pubkey())
.unwrap()
.value,
45_000
Amount::from_sat(45_000)
);
assert_eq!(
tx.output
.iter()
.find(|txout| txout.script_pubkey != addr.script_pubkey())
.unwrap()
.value,
.value
.to_sat(),
sent_received.1
);
@@ -1994,10 +2027,11 @@ fn test_bump_fee_no_change_add_input_and_change() {
.manually_selected_only()
.enable_rbf();
let psbt = builder.finish().unwrap();
let original_sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let original_sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let original_fee = check_fee!(wallet, psbt);
let tx = psbt.extract_tx();
let tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.txid();
wallet
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
@@ -2008,7 +2042,8 @@ fn test_bump_fee_no_change_add_input_and_change() {
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50));
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let fee = check_fee!(wallet, psbt);
let original_send_all_amount = original_sent_received.0 - original_fee.unwrap_or(0);
@@ -2027,14 +2062,15 @@ fn test_bump_fee_no_change_add_input_and_change() {
.find(|txout| txout.script_pubkey == addr.script_pubkey())
.unwrap()
.value,
original_send_all_amount
Amount::from_sat(original_send_all_amount)
);
assert_eq!(
tx.output
.iter()
.find(|txout| txout.script_pubkey != addr.script_pubkey())
.unwrap()
.value,
.value
.to_sat(),
75_000 - original_send_all_amount - fee.unwrap_or(0)
);
@@ -2053,10 +2089,11 @@ fn test_bump_fee_add_input_change_dust() {
.add_recipient(addr.script_pubkey(), 45_000)
.enable_rbf();
let psbt = builder.finish().unwrap();
let original_sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let original_sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let original_fee = check_fee!(wallet, psbt);
let mut tx = psbt.extract_tx();
let mut tx = psbt.extract_tx().expect("failed to extract tx");
for txin in &mut tx.input {
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // to get realistic weight
}
@@ -2085,7 +2122,8 @@ fn test_bump_fee_add_input_change_dust() {
let fee_abs = 50_000 + 25_000 - 45_000 - 10;
builder.fee_rate(Amount::from_sat(fee_abs) / new_tx_weight);
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let fee = check_fee!(wallet, psbt);
assert_eq!(original_sent_received.1, 5_000 - original_fee.unwrap_or(0));
@@ -2103,7 +2141,7 @@ fn test_bump_fee_add_input_change_dust() {
.find(|txout| txout.script_pubkey == addr.script_pubkey())
.unwrap()
.value,
45_000
Amount::from_sat(45_000)
);
assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(140), @dust_change, @add_signature);
@@ -2122,7 +2160,7 @@ fn test_bump_fee_force_add_input() {
.add_recipient(addr.script_pubkey(), 45_000)
.enable_rbf();
let psbt = builder.finish().unwrap();
let mut tx = psbt.extract_tx();
let mut tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
let txid = tx.txid();
for txin in &mut tx.input {
@@ -2139,7 +2177,8 @@ fn test_bump_fee_force_add_input() {
.unwrap()
.fee_rate(FeeRate::from_sat_per_vb_unchecked(5));
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let fee = check_fee!(wallet, psbt);
assert_eq!(sent_received.0, original_sent_received.0 + 25_000);
@@ -2154,14 +2193,15 @@ fn test_bump_fee_force_add_input() {
.find(|txout| txout.script_pubkey == addr.script_pubkey())
.unwrap()
.value,
45_000
Amount::from_sat(45_000)
);
assert_eq!(
tx.output
.iter()
.find(|txout| txout.script_pubkey != addr.script_pubkey())
.unwrap()
.value,
.value
.to_sat(),
sent_received.1
);
@@ -2181,7 +2221,7 @@ fn test_bump_fee_absolute_force_add_input() {
.add_recipient(addr.script_pubkey(), 45_000)
.enable_rbf();
let psbt = builder.finish().unwrap();
let mut tx = psbt.extract_tx();
let mut tx = psbt.extract_tx().expect("failed to extract tx");
let original_sent_received = wallet.sent_and_received(&tx);
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
@@ -2197,7 +2237,8 @@ fn test_bump_fee_absolute_force_add_input() {
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.add_utxo(incoming_op).unwrap().fee_absolute(250);
let psbt = builder.finish().unwrap();
let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
let fee = check_fee!(wallet, psbt);
assert_eq!(sent_received.0, original_sent_received.0 + 25_000);
@@ -2212,14 +2253,15 @@ fn test_bump_fee_absolute_force_add_input() {
.find(|txout| txout.script_pubkey == addr.script_pubkey())
.unwrap()
.value,
45_000
Amount::from_sat(45_000)
);
assert_eq!(
tx.output
.iter()
.find(|txout| txout.script_pubkey != addr.script_pubkey())
.unwrap()
.value,
.value
.to_sat(),
sent_received.1
);
@@ -2252,7 +2294,7 @@ fn test_bump_fee_unconfirmed_inputs_only() {
25_000,
ConfirmationTime::Unconfirmed { last_seen: 0 },
);
let mut tx = psbt.extract_tx();
let mut tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.txid();
for txin in &mut tx.input {
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
@@ -2285,7 +2327,7 @@ fn test_bump_fee_unconfirmed_input() {
.drain_to(addr.script_pubkey())
.enable_rbf();
let psbt = builder.finish().unwrap();
let mut tx = psbt.extract_tx();
let mut tx = psbt.extract_tx().expect("failed to extract tx");
let txid = tx.txid();
for txin in &mut tx.input {
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
@@ -2344,7 +2386,7 @@ fn test_sign_single_xprv() {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized);
let extracted = psbt.extract_tx();
let extracted = psbt.extract_tx().expect("failed to extract tx");
assert_eq!(extracted.input[0].witness.len(), 2);
}
@@ -2359,7 +2401,7 @@ fn test_sign_single_xprv_with_master_fingerprint_and_path() {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized);
let extracted = psbt.extract_tx();
let extracted = psbt.extract_tx().expect("failed to extract tx");
assert_eq!(extracted.input[0].witness.len(), 2);
}
@@ -2374,7 +2416,7 @@ fn test_sign_single_xprv_bip44_path() {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized);
let extracted = psbt.extract_tx();
let extracted = psbt.extract_tx().expect("failed to extract tx");
assert_eq!(extracted.input[0].witness.len(), 2);
}
@@ -2389,7 +2431,7 @@ fn test_sign_single_xprv_sh_wpkh() {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized);
let extracted = psbt.extract_tx();
let extracted = psbt.extract_tx().expect("failed to extract tx");
assert_eq!(extracted.input[0].witness.len(), 2);
}
@@ -2405,7 +2447,7 @@ fn test_sign_single_wif() {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized);
let extracted = psbt.extract_tx();
let extracted = psbt.extract_tx().expect("failed to extract tx");
assert_eq!(extracted.input[0].witness.len(), 2);
}
@@ -2423,7 +2465,7 @@ fn test_sign_single_xprv_no_hd_keypaths() {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized);
let extracted = psbt.extract_tx();
let extracted = psbt.extract_tx().expect("failed to extract tx");
assert_eq!(extracted.input[0].witness.len(), 2);
}
@@ -2461,7 +2503,7 @@ fn test_signing_only_one_of_multiple_inputs() {
// add another input to the psbt that is at least passable.
let dud_input = bitcoin::psbt::Input {
witness_utxo: Some(TxOut {
value: 100_000,
value: Amount::from_sat(100_000),
script_pubkey: miniscript::Descriptor::<bitcoin::PublicKey>::from_str(
"wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)",
)
@@ -2594,7 +2636,7 @@ fn test_sign_nonstandard_sighash() {
"Should finalize the input since we can produce signatures"
);
let extracted = psbt.extract_tx();
let extracted = psbt.extract_tx().expect("failed to extract tx");
assert_eq!(
*extracted.input[0].witness.to_vec()[0].last().unwrap(),
sighash.to_u32() as u8,
@@ -2765,7 +2807,7 @@ fn test_sending_to_bip350_bech32m_address() {
#[test]
fn test_get_address() {
use bdk::descriptor::template::Bip84;
let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let mut wallet = Wallet::new_no_persist(
Bip84(key, KeychainKind::External),
Some(Bip84(key, KeychainKind::Internal)),
@@ -2816,7 +2858,7 @@ fn test_get_address_no_reuse_single_descriptor() {
use bdk::descriptor::template::Bip84;
use std::collections::HashSet;
let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let mut wallet =
Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
@@ -2957,7 +2999,7 @@ fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
#[test]
fn test_taproot_psbt_input_tap_tree() {
use bitcoin::hashes::hex::FromHex;
use bitcoin::hex::FromHex;
use bitcoin::taproot;
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
@@ -3050,7 +3092,8 @@ fn test_taproot_sign_using_non_witness_utxo() {
let mut psbt = builder.finish().unwrap();
psbt.inputs[0].witness_utxo = None;
psbt.inputs[0].non_witness_utxo = Some(wallet.get_tx(prev_txid).unwrap().tx_node.tx.clone());
psbt.inputs[0].non_witness_utxo =
Some(wallet.get_tx(prev_txid).unwrap().tx_node.as_ref().clone());
assert!(
psbt.inputs[0].non_witness_utxo.is_some(),
"Previous tx should be present in the database"
@@ -3074,10 +3117,9 @@ fn test_taproot_foreign_utxo() {
.assume_checked();
let utxo = wallet2.list_unspent().next().unwrap();
let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
#[allow(deprecated)]
let foreign_utxo_satisfaction = wallet2
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
.max_weight_to_satisfy()
.unwrap();
assert!(
@@ -3091,7 +3133,8 @@ fn test_taproot_foreign_utxo() {
.add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
.unwrap();
let psbt = builder.finish().unwrap();
let sent_received = wallet1.sent_and_received(&psbt.clone().extract_tx());
let sent_received =
wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
wallet1.insert_txout(utxo.outpoint, utxo.txout);
let fee = check_fee!(wallet1, psbt);
@@ -3399,7 +3442,7 @@ fn test_taproot_sign_non_default_sighash() {
"Should finalize the input since we can produce signatures"
);
let extracted = psbt.extract_tx();
let extracted = psbt.extract_tx().expect("failed to extract tx");
assert_eq!(
*extracted.input[0].witness.to_vec()[0].last().unwrap(),
sighash as u8,
@@ -3420,14 +3463,14 @@ fn test_spend_coinbase() {
})
.unwrap();
let coinbase_tx = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
..Default::default()
}],
output: vec![TxOut {
value: 25_000,
value: Amount::from_sat(25_000),
script_pubkey: wallet.get_address(New).address.script_pubkey(),
}],
};
@@ -3689,7 +3732,7 @@ fn test_tx_cancellation() {
.unwrap();
assert_eq!(change_derivation_2, (KeychainKind::Internal, 1));
wallet.cancel_tx(&psbt1.extract_tx());
wallet.cancel_tx(&psbt1.extract_tx().expect("failed to extract tx"));
let psbt3 = new_tx!(wallet);
let change_derivation_3 = psbt3
@@ -3709,7 +3752,7 @@ fn test_tx_cancellation() {
.unwrap();
assert_eq!(change_derivation_3, (KeychainKind::Internal, 2));
wallet.cancel_tx(&psbt3.extract_tx());
wallet.cancel_tx(&psbt3.extract_tx().expect("failed to extract tx"));
let psbt3 = new_tx!(wallet);
let change_derivation_4 = psbt3

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_bitcoind_rpc"
version = "0.7.0"
version = "0.8.0"
edition = "2021"
rust-version = "1.63"
homepage = "https://bitcoindevkit.org"
@@ -14,12 +14,12 @@ readme = "README.md"
[dependencies]
# 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.11", default-features = false }
bitcoin = { version = "0.31", default-features = false }
bitcoincore-rpc = { version = "0.18" }
bdk_chain = { path = "../chain", version = "0.12", default-features = false }
[dev-dependencies]
bdk_testenv = { path = "../testenv", version = "0.1.0", default_features = false }
bdk_testenv = { path = "../testenv", default_features = false }
anyhow = { version = "1" }
[features]

View File

@@ -57,12 +57,15 @@ pub fn test_sync_local_chain() -> anyhow::Result<()> {
}
assert_eq!(
local_chain.blocks(),
&exp_hashes
local_chain
.iter_checkpoints()
.map(|cp| (cp.height(), cp.hash()))
.collect::<BTreeSet<_>>(),
exp_hashes
.iter()
.enumerate()
.map(|(i, hash)| (i as u32, *hash))
.collect(),
.collect::<BTreeSet<_>>(),
"final local_chain state is unexpected",
);
@@ -110,12 +113,15 @@ pub fn test_sync_local_chain() -> anyhow::Result<()> {
}
assert_eq!(
local_chain.blocks(),
&exp_hashes
local_chain
.iter_checkpoints()
.map(|cp| (cp.height(), cp.hash()))
.collect::<BTreeSet<_>>(),
exp_hashes
.iter()
.enumerate()
.map(|(i, hash)| (i as u32, *hash))
.collect(),
.collect::<BTreeSet<_>>(),
"final local_chain state is unexpected after reorg",
);
@@ -344,7 +350,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
.rpc_client()
.get_new_address(None, None)?
.assume_checked();
let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros());
let spk_to_track = ScriptBuf::new_p2wsh(&WScriptHash::all_zeros());
let addr_to_track = Address::from_script(&spk_to_track, bitcoin::Network::Regtest)?;
// setup receiver

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_chain"
version = "0.11.0"
version = "0.12.0"
edition = "2021"
rust-version = "1.63"
homepage = "https://bitcoindevkit.org"
@@ -14,15 +14,16 @@ readme = "README.md"
[dependencies]
# For no-std, remember to enable the bitcoin/no-std feature
bitcoin = { version = "0.30.0", default-features = false }
serde_crate = { package = "serde", version = "1", optional = true, features = ["derive"] }
bitcoin = { version = "0.31.0", default-features = false }
serde_crate = { package = "serde", version = "1", optional = true, features = ["derive", "rc"] }
# Use hashbrown as a feature flag to have HashSet and HashMap from it.
hashbrown = { version = "0.9.1", optional = true, features = ["serde"] }
miniscript = { version = "10.0.0", optional = true, default-features = false }
miniscript = { version = "11.0.0", optional = true, default-features = false }
[dev-dependencies]
rand = "0.8"
proptest = "1.2.0"
[features]
default = ["std"]

View File

@@ -1,6 +1,7 @@
//! The [`LocalChain`] is a local implementation of [`ChainOracle`].
use core::convert::Infallible;
use core::ops::RangeBounds;
use crate::collections::BTreeMap;
use crate::{BlockId, ChainOracle};
@@ -34,6 +35,14 @@ struct CPInner {
prev: Option<Arc<CPInner>>,
}
impl PartialEq for CheckPoint {
fn eq(&self, other: &Self) -> bool {
let self_cps = self.iter().map(|cp| cp.block_id());
let other_cps = other.iter().map(|cp| cp.block_id());
self_cps.eq(other_cps)
}
}
impl CheckPoint {
/// Construct a new base block at the front of a linked list.
pub fn new(block: BlockId) -> Self {
@@ -148,6 +157,36 @@ impl CheckPoint {
pub fn iter(&self) -> CheckPointIter {
self.clone().into_iter()
}
/// Get checkpoint at `height`.
///
/// Returns `None` if checkpoint at `height` does not exist`.
pub fn get(&self, height: u32) -> Option<Self> {
self.range(height..=height).next()
}
/// Iterate checkpoints over a height range.
///
/// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
/// height).
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
where
R: RangeBounds<u32>,
{
let start_bound = range.start_bound().cloned();
let end_bound = range.end_bound().cloned();
self.iter()
.skip_while(move |cp| match end_bound {
core::ops::Bound::Included(inc_bound) => cp.height() > inc_bound,
core::ops::Bound::Excluded(exc_bound) => cp.height() >= exc_bound,
core::ops::Bound::Unbounded => false,
})
.take_while(move |cp| match start_bound {
core::ops::Bound::Included(inc_bound) => cp.height() >= inc_bound,
core::ops::Bound::Excluded(exc_bound) => cp.height() > exc_bound,
core::ops::Bound::Unbounded => true,
})
}
}
/// Iterates over checkpoints backwards.
@@ -188,7 +227,7 @@ impl IntoIterator for CheckPoint {
/// Script-pubkey based syncing mechanisms may not introduce transactions in a chronological order
/// so some updates require introducing older blocks (to anchor older transactions). For
/// script-pubkey based syncing, `introduce_older_blocks` would typically be `true`.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct Update {
/// The update chain's new tip.
pub tip: CheckPoint,
@@ -202,22 +241,9 @@ pub struct Update {
}
/// This is a local implementation of [`ChainOracle`].
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct LocalChain {
tip: CheckPoint,
index: BTreeMap<u32, BlockHash>,
}
impl PartialEq for LocalChain {
fn eq(&self, other: &Self) -> bool {
self.index == other.index
}
}
impl From<LocalChain> for BTreeMap<u32, BlockHash> {
fn from(value: LocalChain) -> Self {
value.index
}
}
impl ChainOracle for LocalChain {
@@ -228,18 +254,16 @@ impl ChainOracle for LocalChain {
block: BlockId,
chain_tip: BlockId,
) -> Result<Option<bool>, Self::Error> {
if block.height > chain_tip.height {
return Ok(None);
let chain_tip_cp = match self.tip.get(chain_tip.height) {
// we can only determine whether `block` is in chain of `chain_tip` if `chain_tip` can
// be identified in chain
Some(cp) if cp.hash() == chain_tip.hash => cp,
_ => return Ok(None),
};
match chain_tip_cp.get(block.height) {
Some(cp) => Ok(Some(cp.hash() == block.hash)),
None => Ok(None),
}
Ok(
match (
self.index.get(&block.height),
self.index.get(&chain_tip.height),
) {
(Some(cp), Some(tip_cp)) => Some(*cp == block.hash && *tip_cp == chain_tip.hash),
_ => None,
},
)
}
fn get_chain_tip(&self) -> Result<BlockId, Self::Error> {
@@ -250,7 +274,7 @@ impl ChainOracle for LocalChain {
impl LocalChain {
/// Get the genesis hash.
pub fn genesis_hash(&self) -> BlockHash {
self.index.get(&0).copied().expect("must have genesis hash")
self.tip.get(0).expect("genesis must exist").hash()
}
/// Construct [`LocalChain`] from genesis `hash`.
@@ -259,7 +283,6 @@ impl LocalChain {
let height = 0;
let chain = Self {
tip: CheckPoint::new(BlockId { height, hash }),
index: core::iter::once((height, hash)).collect(),
};
let changeset = chain.initial_changeset();
(chain, changeset)
@@ -276,7 +299,6 @@ impl LocalChain {
let (mut chain, _) = Self::from_genesis_hash(genesis_hash);
chain.apply_changeset(&changeset)?;
debug_assert!(chain._check_index_is_consistent_with_tip());
debug_assert!(chain._check_changeset_is_applied(&changeset));
Ok(chain)
@@ -284,18 +306,11 @@ impl LocalChain {
/// Construct a [`LocalChain`] from a given `checkpoint` tip.
pub fn from_tip(tip: CheckPoint) -> Result<Self, MissingGenesisError> {
let mut chain = Self {
tip,
index: BTreeMap::new(),
};
chain.reindex(0);
if chain.index.get(&0).copied().is_none() {
let genesis_cp = tip.iter().last().expect("must have at least one element");
if genesis_cp.height() != 0 {
return Err(MissingGenesisError);
}
debug_assert!(chain._check_index_is_consistent_with_tip());
Ok(chain)
Ok(Self { tip })
}
/// Constructs a [`LocalChain`] from a [`BTreeMap`] of height to [`BlockHash`].
@@ -303,12 +318,11 @@ impl LocalChain {
/// The [`BTreeMap`] enforces the height order. However, the caller must ensure the blocks are
/// all of the same chain.
pub fn from_blocks(blocks: BTreeMap<u32, BlockHash>) -> Result<Self, MissingGenesisError> {
if !blocks.contains_key(&0) {
if blocks.get(&0).is_none() {
return Err(MissingGenesisError);
}
let mut tip: Option<CheckPoint> = None;
for block in &blocks {
match tip {
Some(curr) => {
@@ -321,13 +335,9 @@ impl LocalChain {
}
}
let chain = Self {
index: blocks,
Ok(Self {
tip: tip.expect("already checked to have genesis"),
};
debug_assert!(chain._check_index_is_consistent_with_tip());
Ok(chain)
})
}
/// Get the highest checkpoint.
@@ -494,9 +504,7 @@ impl LocalChain {
None => LocalChain::from_blocks(extension)?.tip(),
};
self.tip = new_tip;
self.reindex(start_height);
debug_assert!(self._check_index_is_consistent_with_tip());
debug_assert!(self._check_changeset_is_applied(changeset));
}
@@ -509,16 +517,16 @@ impl LocalChain {
///
/// Replacing the block hash of an existing checkpoint will result in an error.
pub fn insert_block(&mut self, block_id: BlockId) -> Result<ChangeSet, AlterCheckPointError> {
if let Some(&original_hash) = self.index.get(&block_id.height) {
if let Some(original_cp) = self.tip.get(block_id.height) {
let original_hash = original_cp.hash();
if original_hash != block_id.hash {
return Err(AlterCheckPointError {
height: block_id.height,
original_hash,
update_hash: Some(block_id.hash),
});
} else {
return Ok(ChangeSet::default());
}
return Ok(ChangeSet::default());
}
let mut changeset = ChangeSet::default();
@@ -542,33 +550,41 @@ impl LocalChain {
/// This will fail with [`MissingGenesisError`] if the caller attempts to disconnect from the
/// genesis block.
pub fn disconnect_from(&mut self, block_id: BlockId) -> Result<ChangeSet, MissingGenesisError> {
if self.index.get(&block_id.height) != Some(&block_id.hash) {
return Ok(ChangeSet::default());
}
let changeset = self
.index
.range(block_id.height..)
.map(|(&height, _)| (height, None))
.collect::<ChangeSet>();
self.apply_changeset(&changeset).map(|_| changeset)
}
/// Reindex the heights in the chain from (and including) `from` height
fn reindex(&mut self, from: u32) {
let _ = self.index.split_off(&from);
for cp in self.iter_checkpoints() {
if cp.height() < from {
let mut remove_from = Option::<CheckPoint>::None;
let mut changeset = ChangeSet::default();
for cp in self.tip().iter() {
let cp_id = cp.block_id();
if cp_id.height < block_id.height {
break;
}
self.index.insert(cp.height(), cp.hash());
changeset.insert(cp_id.height, None);
if cp_id == block_id {
remove_from = Some(cp);
}
}
self.tip = match remove_from.map(|cp| cp.prev()) {
// The checkpoint below the earliest checkpoint to remove will be the new tip.
Some(Some(new_tip)) => new_tip,
// If there is no checkpoint below the earliest checkpoint to remove, it means the
// "earliest checkpoint to remove" is the genesis block. We disallow removing the
// genesis block.
Some(None) => return Err(MissingGenesisError),
// If there is nothing to remove, we return an empty changeset.
None => return Ok(ChangeSet::default()),
};
Ok(changeset)
}
/// Derives an initial [`ChangeSet`], meaning that it can be applied to an empty chain to
/// recover the current chain.
pub fn initial_changeset(&self) -> ChangeSet {
self.index.iter().map(|(k, v)| (*k, Some(*v))).collect()
self.tip
.iter()
.map(|cp| {
let block_id = cp.block_id();
(block_id.height, Some(block_id.hash))
})
.collect()
}
/// Iterate over checkpoints in descending height order.
@@ -578,28 +594,49 @@ impl LocalChain {
}
}
/// Get a reference to the internal index mapping the height to block hash.
pub fn blocks(&self) -> &BTreeMap<u32, BlockHash> {
&self.index
}
fn _check_index_is_consistent_with_tip(&self) -> bool {
let tip_history = self
.tip
.iter()
.map(|cp| (cp.height(), cp.hash()))
.collect::<BTreeMap<_, _>>();
self.index == tip_history
}
fn _check_changeset_is_applied(&self, changeset: &ChangeSet) -> bool {
for (height, exp_hash) in changeset {
if self.index.get(height) != exp_hash.as_ref() {
return false;
let mut curr_cp = self.tip.clone();
for (height, exp_hash) in changeset.iter().rev() {
match curr_cp.get(*height) {
Some(query_cp) => {
if query_cp.height() != *height || Some(query_cp.hash()) != *exp_hash {
return false;
}
curr_cp = query_cp;
}
None => {
if exp_hash.is_some() {
return false;
}
}
}
}
true
}
/// Get checkpoint at given `height` (if it exists).
///
/// This is a shorthand for calling [`CheckPoint::get`] on the [`tip`].
///
/// [`tip`]: LocalChain::tip
pub fn get(&self, height: u32) -> Option<CheckPoint> {
self.tip.get(height)
}
/// Iterate checkpoints over a height range.
///
/// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
/// height).
///
/// This is a shorthand for calling [`CheckPoint::range`] on the [`tip`].
///
/// [`tip`]: LocalChain::tip
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
where
R: RangeBounds<u32>,
{
self.tip.range(range)
}
}
/// An error which occurs when a [`LocalChain`] is constructed without a genesis checkpoint.

View File

@@ -4,7 +4,7 @@ use crate::{
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
indexed_tx_graph::Indexer,
};
use bitcoin::{self, OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid};
use bitcoin::{OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid};
/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
///
@@ -281,12 +281,12 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
for txin in &tx.input {
if let Some((_, txout)) = self.txout(txin.previous_output) {
sent += txout.value;
sent += txout.value.to_sat();
}
}
for txout in &tx.output {
if self.index_of_spk(&txout.script_pubkey).is_some() {
received += txout.value;
received += txout.value.to_sat();
}
}

View File

@@ -1,26 +1,27 @@
//! Module for structures that store and traverse transactions.
//!
//! [`TxGraph`] contains transactions and indexes them so you can easily traverse the graph of those transactions.
//! `TxGraph` is *monotone* in that you can always insert a transaction -- it doesn't care whether that
//! transaction is in the current best chain or whether it conflicts with any of the
//! existing transactions or what order you insert the transactions. This means that you can always
//! combine two [`TxGraph`]s together, without resulting in inconsistencies.
//! Furthermore, there is currently no way to delete a transaction.
//! [`TxGraph`] contains transactions and indexes them so you can easily traverse the graph of
//! those transactions. `TxGraph` is *monotone* in that you can always insert a transaction -- it
//! does not care whether that transaction is in the current best chain or whether it conflicts with
//! any of the existing transactions or what order you insert the transactions. This means that you
//! can always combine two [`TxGraph`]s together, without resulting in inconsistencies. Furthermore,
//! there is currently no way to delete a transaction.
//!
//! Transactions can be either whole or partial (i.e., transactions for which we only
//! know some outputs, which we usually call "floating outputs"; these are usually inserted
//! using the [`insert_txout`] method.).
//! Transactions can be either whole or partial (i.e., transactions for which we only know some
//! outputs, which we usually call "floating outputs"; these are usually inserted using the
//! [`insert_txout`] method.).
//!
//! The graph contains transactions in the form of [`TxNode`]s. Each node contains the
//! txid, the transaction (whole or partial), the blocks it's anchored in (see the [`Anchor`]
//! documentation for more details), and the timestamp of the last time we saw
//! the transaction as unconfirmed.
//! The graph contains transactions in the form of [`TxNode`]s. Each node contains the txid, the
//! transaction (whole or partial), the blocks that it is anchored to (see the [`Anchor`]
//! documentation for more details), and the timestamp of the last time we saw the transaction as
//! unconfirmed.
//!
//! Conflicting transactions are allowed to coexist within a [`TxGraph`]. This is useful for
//! identifying and traversing conflicts and descendants of a given transaction. Some [`TxGraph`]
//! methods only consider "canonical" (i.e., in the best chain or in mempool) transactions,
//! we decide which transactions are canonical based on anchors `last_seen_unconfirmed`;
//! see the [`try_get_chain_position`] documentation for more details.
//! methods only consider transactions that are "canonical" (i.e., in the best chain or in mempool).
//! We decide which transactions are canonical based on the transaction's anchors and the
//! `last_seen` (as unconfirmed) timestamp; see the [`try_get_chain_position`] documentation for
//! more details.
//!
//! The [`ChangeSet`] reports changes made to a [`TxGraph`]; it can be used to either save to
//! persistent storage, or to be applied to another [`TxGraph`].
@@ -30,10 +31,22 @@
//!
//! # Applying changes
//!
//! Methods that apply changes to [`TxGraph`] will return [`ChangeSet`].
//! [`ChangeSet`] can be applied back to a [`TxGraph`] or be used to inform persistent storage
//! Methods that change the state of [`TxGraph`] will return [`ChangeSet`]s.
//! [`ChangeSet`]s can be applied back to a [`TxGraph`] or be used to inform persistent storage
//! of the changes to [`TxGraph`].
//!
//! # Generics
//!
//! Anchors are represented as generics within `TxGraph<A>`. To make use of all functionality of the
//! `TxGraph`, anchors (`A`) should implement [`Anchor`].
//!
//! Anchors are made generic so that different types of data can be stored with how a transaction is
//! *anchored* to a given block. An example of this is storing a merkle proof of the transaction to
//! the confirmation block - this can be done with a custom [`Anchor`] type. The minimal [`Anchor`]
//! type would just be a [`BlockId`] which just represents the height and hash of the block which
//! the transaction is contained in. Note that a transaction can be contained in multiple
//! conflicting blocks (by nature of the Bitcoin network).
//!
//! ```
//! # use bdk_chain::BlockId;
//! # use bdk_chain::tx_graph::TxGraph;
@@ -80,6 +93,7 @@ use crate::{
ChainOracle, ChainPosition, FullTxOut,
};
use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
use alloc::vec::Vec;
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
use core::fmt::{self, Formatter};
@@ -122,7 +136,7 @@ pub struct TxNode<'a, T, A> {
/// Txid of the transaction.
pub txid: Txid,
/// A partial or full representation of the transaction.
pub tx: &'a T,
pub tx: T,
/// The blocks that the transaction is "anchored" in.
pub anchors: &'a BTreeSet<A>,
/// The last-seen unix timestamp of the transaction as unconfirmed.
@@ -133,7 +147,7 @@ impl<'a, T, A> Deref for TxNode<'a, T, A> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.tx
&self.tx
}
}
@@ -143,7 +157,7 @@ impl<'a, T, A> Deref for TxNode<'a, T, A> {
/// outputs).
#[derive(Clone, Debug, PartialEq)]
enum TxNodeInternal {
Whole(Transaction),
Whole(Arc<Transaction>),
Partial(BTreeMap<u32, TxOut>),
}
@@ -198,6 +212,7 @@ impl<A> TxGraph<A> {
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
TxNodeInternal::Whole(tx) => tx
.as_ref()
.output
.iter()
.enumerate()
@@ -229,13 +244,13 @@ impl<A> TxGraph<A> {
}
/// Iterate over all full transactions in the graph.
pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Transaction, A>> {
pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, A>> {
self.txs
.iter()
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
TxNodeInternal::Whole(tx) => Some(TxNode {
txid,
tx,
tx: tx.clone(),
anchors,
last_seen_unconfirmed: *last_seen,
}),
@@ -248,16 +263,16 @@ impl<A> TxGraph<A> {
/// Refer to [`get_txout`] for getting a specific [`TxOut`].
///
/// [`get_txout`]: Self::get_txout
pub fn get_tx(&self, txid: Txid) -> Option<&Transaction> {
pub fn get_tx(&self, txid: Txid) -> Option<Arc<Transaction>> {
self.get_tx_node(txid).map(|n| n.tx)
}
/// Get a transaction node by txid. This only returns `Some` for full transactions.
pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Transaction, A>> {
pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Arc<Transaction>, A>> {
match &self.txs.get(&txid)? {
(TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode {
txid,
tx,
tx: tx.clone(),
anchors,
last_seen_unconfirmed: *last_seen,
}),
@@ -268,7 +283,7 @@ impl<A> TxGraph<A> {
/// Obtains a single tx output (if any) at the specified outpoint.
pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
match &self.txs.get(&outpoint.txid)?.0 {
TxNodeInternal::Whole(tx) => tx.output.get(outpoint.vout as usize),
TxNodeInternal::Whole(tx) => tx.as_ref().output.get(outpoint.vout as usize),
TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout),
}
}
@@ -279,6 +294,7 @@ impl<A> TxGraph<A> {
pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
Some(match &self.txs.get(&txid)?.0 {
TxNodeInternal::Whole(tx) => tx
.as_ref()
.output
.iter()
.enumerate()
@@ -303,7 +319,7 @@ impl<A> TxGraph<A> {
///
/// [`insert_txout`]: Self::insert_txout
pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
if tx.is_coin_base() {
if tx.is_coinbase() {
return Ok(0);
}
@@ -315,7 +331,7 @@ impl<A> TxGraph<A> {
(sum, missing_outpoints)
}
Some(txout) => {
sum += txout.value as i64;
sum += txout.value.to_sat() as i64;
(sum, missing_outpoints)
}
},
@@ -327,7 +343,7 @@ impl<A> TxGraph<A> {
let outputs_sum = tx
.output
.iter()
.map(|txout| txout.value as i64)
.map(|txout| txout.value.to_sat() as i64)
.sum::<i64>();
let fee = inputs_sum - outputs_sum;
@@ -356,16 +372,15 @@ impl<A> TxGraph<A> {
&self,
txid: Txid,
) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
let start = OutPoint { txid, vout: 0 };
let end = OutPoint {
txid,
vout: u32::MAX,
};
let start = OutPoint::new(txid, 0);
let end = OutPoint::new(txid, u32::MAX);
self.spends
.range(start..=end)
.map(|(outpoint, spends)| (outpoint.vout, spends))
}
}
impl<A: Clone + Ord> TxGraph<A> {
/// Creates an iterator that filters and maps ancestor transactions.
///
/// The iterator starts with the ancestors of the supplied `tx` (ancestor transactions of `tx`
@@ -379,13 +394,10 @@ impl<A> TxGraph<A> {
///
/// The supplied closure returns an `Option<T>`, allowing the caller to map each `Transaction`
/// it visits and decide whether to visit ancestors.
pub fn walk_ancestors<'g, F, O>(
&'g self,
tx: &'g Transaction,
walk_map: F,
) -> TxAncestors<'g, A, F>
pub fn walk_ancestors<'g, T, F, O>(&'g self, tx: T, walk_map: F) -> TxAncestors<'g, A, F>
where
F: FnMut(usize, &'g Transaction) -> Option<O> + 'g,
T: Into<Arc<Transaction>>,
F: FnMut(usize, Arc<Transaction>) -> Option<O> + 'g,
{
TxAncestors::new_exclude_root(self, tx, walk_map)
}
@@ -406,7 +418,9 @@ impl<A> TxGraph<A> {
{
TxDescendants::new_exclude_root(self, txid, walk_map)
}
}
impl<A> TxGraph<A> {
/// Creates an iterator that both filters and maps conflicting transactions (this includes
/// descendants of directly-conflicting transactions, which are also considered conflicts).
///
@@ -419,7 +433,7 @@ impl<A> TxGraph<A> {
where
F: FnMut(usize, Txid) -> Option<O> + 'g,
{
let txids = self.direct_conflitcs(tx).map(|(_, txid)| txid);
let txids = self.direct_conflicts(tx).map(|(_, txid)| txid);
TxDescendants::from_multiple_include_root(self, txids, walk_map)
}
@@ -430,7 +444,7 @@ impl<A> TxGraph<A> {
/// Note that this only returns directly conflicting txids and won't include:
/// - descendants of conflicting transactions (which are technically also conflicting)
/// - transactions conflicting with the given transaction's ancestors
pub fn direct_conflitcs<'g>(
pub fn direct_conflicts<'g>(
&'g self,
tx: &'g Transaction,
) -> impl Iterator<Item = (usize, Txid)> + '_ {
@@ -467,9 +481,7 @@ impl<A: Clone + Ord> TxGraph<A> {
new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
new_graph
}
}
impl<A: Clone + Ord> TxGraph<A> {
/// Construct a new [`TxGraph`] from a list of transactions.
pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
let mut new = Self::default();
@@ -506,9 +518,10 @@ impl<A: Clone + Ord> TxGraph<A> {
/// The [`ChangeSet`] returned will be empty if `tx` already exists.
pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet<A> {
let mut update = Self::default();
update
.txs
.insert(tx.txid(), (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
update.txs.insert(
tx.txid(),
(TxNodeInternal::Whole(tx.into()), BTreeSet::new(), 0),
);
self.apply_update(update)
}
@@ -541,7 +554,11 @@ impl<A: Clone + Ord> TxGraph<A> {
/// Inserts the given `seen_at` for `txid` into [`TxGraph`].
///
/// Note that [`TxGraph`] only keeps track of the latest `seen_at`.
/// Note that [`TxGraph`] only keeps track of the latest `seen_at`. To batch
/// update all unconfirmed transactions with the latest `seen_at`, see
/// [`update_last_seen_unconfirmed`].
///
/// [`update_last_seen_unconfirmed`]: Self::update_last_seen_unconfirmed
pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
let mut update = Self::default();
let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
@@ -549,6 +566,65 @@ impl<A: Clone + Ord> TxGraph<A> {
self.apply_update(update)
}
/// Update the last seen time for all unconfirmed transactions.
///
/// This method updates the last seen unconfirmed time for this [`TxGraph`] by inserting
/// the given `seen_at` for every transaction not yet anchored to a confirmed block,
/// and returns the [`ChangeSet`] after applying all updates to `self`.
///
/// This is useful for keeping track of the latest time a transaction was seen
/// unconfirmed, which is important for evaluating transaction conflicts in the same
/// [`TxGraph`]. For details of how [`TxGraph`] resolves conflicts, see the docs for
/// [`try_get_chain_position`].
///
/// A normal use of this method is to call it with the current system time. Although
/// block headers contain a timestamp, using the header time would be less effective
/// at tracking mempool transactions, because it can drift from actual clock time, plus
/// we may want to update a transaction's last seen time repeatedly between blocks.
///
/// # Example
///
/// ```rust
/// # use bdk_chain::example_utils::*;
/// # use std::time::UNIX_EPOCH;
/// # let tx = tx_from_hex(RAW_TX_1);
/// # let mut tx_graph = bdk_chain::TxGraph::<()>::new([tx]);
/// let now = std::time::SystemTime::now()
/// .duration_since(UNIX_EPOCH)
/// .expect("valid duration")
/// .as_secs();
/// let changeset = tx_graph.update_last_seen_unconfirmed(now);
/// assert!(!changeset.last_seen.is_empty());
/// ```
///
/// Note that [`TxGraph`] only keeps track of the latest `seen_at`, so the given time must
/// by strictly greater than what is currently stored for a transaction to have an effect.
/// To insert a last seen time for a single txid, see [`insert_seen_at`].
///
/// [`insert_seen_at`]: Self::insert_seen_at
/// [`try_get_chain_position`]: Self::try_get_chain_position
pub fn update_last_seen_unconfirmed(&mut self, seen_at: u64) -> ChangeSet<A> {
let mut changeset = ChangeSet::default();
let unanchored_txs: Vec<Txid> = self
.txs
.iter()
.filter_map(
|(&txid, (_, anchors, _))| {
if anchors.is_empty() {
Some(txid)
} else {
None
}
},
)
.collect();
for txid in unanchored_txs {
changeset.append(self.insert_seen_at(txid, seen_at));
}
changeset
}
/// Extends this graph with another so that `self` becomes the union of the two sets of
/// transactions.
///
@@ -567,7 +643,8 @@ impl<A: Clone + Ord> TxGraph<A> {
/// Applies [`ChangeSet`] to [`TxGraph`].
pub fn apply_changeset(&mut self, changeset: ChangeSet<A>) {
for tx in changeset.txs {
for wrapped_tx in changeset.txs {
let tx = wrapped_tx.as_ref();
let txid = tx.txid();
tx.input
@@ -582,18 +659,20 @@ impl<A: Clone + Ord> TxGraph<A> {
match self.txs.get_mut(&txid) {
Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => {
*tx_node = TxNodeInternal::Whole(tx);
*tx_node = TxNodeInternal::Whole(wrapped_tx.clone());
}
Some((TxNodeInternal::Whole(tx), _, _)) => {
debug_assert_eq!(
tx.txid(),
tx.as_ref().txid(),
txid,
"tx should produce txid that is same as key"
);
}
None => {
self.txs
.insert(txid, (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
self.txs.insert(
txid,
(TxNodeInternal::Whole(wrapped_tx), BTreeSet::new(), 0),
);
}
}
}
@@ -630,7 +709,7 @@ impl<A: Clone + Ord> TxGraph<A> {
/// The [`ChangeSet`] would be the set difference between `update` and `self` (transactions that
/// exist in `update` but not in `self`).
pub(crate) fn determine_changeset(&self, update: TxGraph<A>) -> ChangeSet<A> {
let mut changeset = ChangeSet::default();
let mut changeset = ChangeSet::<A>::default();
for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs {
let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) {
@@ -709,13 +788,13 @@ impl<A: Anchor> TxGraph<A> {
};
let mut has_missing_height = false;
for anchor_block in tx_anchors.iter().map(Anchor::anchor_block) {
match chain.blocks().get(&anchor_block.height) {
match chain.get(anchor_block.height) {
None => {
has_missing_height = true;
continue;
}
Some(chain_hash) => {
if chain_hash == &anchor_block.hash {
Some(chain_cp) => {
if chain_cp.hash() == anchor_block.hash {
return true;
}
}
@@ -733,7 +812,7 @@ impl<A: Anchor> TxGraph<A> {
.filter_map(move |(a, _)| {
let anchor_block = a.anchor_block();
if Some(anchor_block.height) != last_height_emitted
&& !chain.blocks().contains_key(&anchor_block.height)
&& chain.get(anchor_block.height).is_none()
{
last_height_emitted = Some(anchor_block.height);
Some(anchor_block.height)
@@ -791,10 +870,10 @@ impl<A: Anchor> TxGraph<A> {
TxNodeInternal::Whole(tx) => {
// A coinbase tx that is not anchored in the best chain cannot be unconfirmed and
// should always be filtered out.
if tx.is_coin_base() {
if tx.is_coinbase() {
return Ok(None);
}
tx
tx.clone()
}
TxNodeInternal::Partial(_) => {
// Partial transactions (outputs only) cannot have conflicts.
@@ -811,8 +890,8 @@ impl<A: Anchor> TxGraph<A> {
// First of all, we retrieve all our ancestors. Since we're using `new_include_root`, the
// resulting array will also include `tx`
let unconfirmed_ancestor_txs =
TxAncestors::new_include_root(self, tx, |_, ancestor_tx: &Transaction| {
let tx_node = self.get_tx_node(ancestor_tx.txid())?;
TxAncestors::new_include_root(self, tx.clone(), |_, ancestor_tx: Arc<Transaction>| {
let tx_node = self.get_tx_node(ancestor_tx.as_ref().txid())?;
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
// the best chain)
for block in tx_node.anchors {
@@ -828,8 +907,10 @@ impl<A: Anchor> TxGraph<A> {
// We determine our tx's last seen, which is the max between our last seen,
// and our unconf descendants' last seen.
let unconfirmed_descendants_txs =
TxDescendants::new_include_root(self, tx.txid(), |_, descendant_txid: Txid| {
let unconfirmed_descendants_txs = TxDescendants::new_include_root(
self,
tx.as_ref().txid(),
|_, descendant_txid: Txid| {
let tx_node = self.get_tx_node(descendant_txid)?;
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
// the best chain)
@@ -841,8 +922,9 @@ impl<A: Anchor> TxGraph<A> {
}
}
Some(Ok(tx_node))
})
.collect::<Result<Vec<_>, C::Error>>()?;
},
)
.collect::<Result<Vec<_>, C::Error>>()?;
let tx_last_seen = unconfirmed_descendants_txs
.iter()
@@ -853,7 +935,8 @@ impl<A: Anchor> TxGraph<A> {
// Now we traverse our ancestors and consider all their conflicts
for tx_node in unconfirmed_ancestor_txs {
// We retrieve all the transactions conflicting with this specific ancestor
let conflicting_txs = self.walk_conflicts(tx_node.tx, |_, txid| self.get_tx_node(txid));
let conflicting_txs =
self.walk_conflicts(tx_node.tx.as_ref(), |_, txid| self.get_tx_node(txid));
// If a conflicting tx is in the best chain, or has `last_seen` higher than this ancestor, then
// this tx cannot exist in the best chain
@@ -867,7 +950,7 @@ impl<A: Anchor> TxGraph<A> {
return Ok(None);
}
if conflicting_tx.last_seen_unconfirmed == *last_seen
&& conflicting_tx.txid() > tx.txid()
&& conflicting_tx.as_ref().txid() > tx.as_ref().txid()
{
// Conflicting tx has priority if txid of conflicting tx > txid of original tx
return Ok(None);
@@ -960,7 +1043,7 @@ impl<A: Anchor> TxGraph<A> {
&'a self,
chain: &'a C,
chain_tip: BlockId,
) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>> {
) -> impl Iterator<Item = Result<CanonicalTx<'a, Arc<Transaction>, A>, C::Error>> {
self.full_txs().filter_map(move |tx| {
self.try_get_chain_position(chain, chain_tip, tx.txid)
.map(|v| {
@@ -982,7 +1065,7 @@ impl<A: Anchor> TxGraph<A> {
&'a self,
chain: &'a C,
chain_tip: BlockId,
) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>> {
) -> impl Iterator<Item = CanonicalTx<'a, Arc<Transaction>, A>> {
self.try_list_chain_txs(chain, chain_tip)
.map(|r| r.expect("oracle is infallible"))
}
@@ -1021,7 +1104,7 @@ impl<A: Anchor> TxGraph<A> {
None => return Ok(None),
};
let txout = match tx_node.tx.output.get(op.vout as usize) {
let txout = match tx_node.tx.as_ref().output.get(op.vout as usize) {
Some(txout) => txout.clone(),
None => return Ok(None),
};
@@ -1043,7 +1126,7 @@ impl<A: Anchor> TxGraph<A> {
txout,
chain_position,
spent_by,
is_on_coinbase: tx_node.tx.is_coin_base(),
is_on_coinbase: tx_node.tx.is_coinbase(),
},
)))
},
@@ -1146,16 +1229,16 @@ impl<A: Anchor> TxGraph<A> {
match &txout.chain_position {
ChainPosition::Confirmed(_) => {
if txout.is_confirmed_and_spendable(chain_tip.height) {
confirmed += txout.txout.value;
confirmed += txout.txout.value.to_sat();
} else if !txout.is_mature(chain_tip.height) {
immature += txout.txout.value;
immature += txout.txout.value.to_sat();
}
}
ChainPosition::Unconfirmed(_) => {
if trust_predicate(&spk_i, &txout.txout.script_pubkey) {
trusted_pending += txout.txout.value;
trusted_pending += txout.txout.value.to_sat();
} else {
untrusted_pending += txout.txout.value;
untrusted_pending += txout.txout.value.to_sat();
}
}
}
@@ -1209,7 +1292,7 @@ impl<A: Anchor> TxGraph<A> {
#[must_use]
pub struct ChangeSet<A = ()> {
/// Added transactions.
pub txs: BTreeSet<Transaction>,
pub txs: BTreeSet<Arc<Transaction>>,
/// Added txouts.
pub txouts: BTreeMap<OutPoint, TxOut>,
/// Added anchors.
@@ -1279,7 +1362,7 @@ impl<A> ChangeSet<A> {
A: Anchor,
{
self.anchor_heights()
.filter(move |height| !local_chain.blocks().contains_key(height))
.filter(move |&height| local_chain.get(height).is_none())
}
}
@@ -1345,7 +1428,7 @@ impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
pub struct TxAncestors<'g, A, F> {
graph: &'g TxGraph<A>,
visited: HashSet<Txid>,
queue: VecDeque<(usize, &'g Transaction)>,
queue: VecDeque<(usize, Arc<Transaction>)>,
filter_map: F,
}
@@ -1353,13 +1436,13 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
/// Creates a `TxAncestors` that includes the starting `Transaction` when iterating.
pub(crate) fn new_include_root(
graph: &'g TxGraph<A>,
tx: &'g Transaction,
tx: impl Into<Arc<Transaction>>,
filter_map: F,
) -> Self {
Self {
graph,
visited: Default::default(),
queue: [(0, tx)].into(),
queue: [(0, tx.into())].into(),
filter_map,
}
}
@@ -1367,7 +1450,7 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
/// Creates a `TxAncestors` that excludes the starting `Transaction` when iterating.
pub(crate) fn new_exclude_root(
graph: &'g TxGraph<A>,
tx: &'g Transaction,
tx: impl Into<Arc<Transaction>>,
filter_map: F,
) -> Self {
let mut ancestors = Self {
@@ -1376,7 +1459,7 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
queue: Default::default(),
filter_map,
};
ancestors.populate_queue(1, tx);
ancestors.populate_queue(1, tx.into());
ancestors
}
@@ -1389,12 +1472,13 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
filter_map: F,
) -> Self
where
I: IntoIterator<Item = &'g Transaction>,
I: IntoIterator,
I::Item: Into<Arc<Transaction>>,
{
Self {
graph,
visited: Default::default(),
queue: txs.into_iter().map(|tx| (0, tx)).collect(),
queue: txs.into_iter().map(|tx| (0, tx.into())).collect(),
filter_map,
}
}
@@ -1408,7 +1492,8 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
filter_map: F,
) -> Self
where
I: IntoIterator<Item = &'g Transaction>,
I: IntoIterator,
I::Item: Into<Arc<Transaction>>,
{
let mut ancestors = Self {
graph,
@@ -1417,12 +1502,12 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
filter_map,
};
for tx in txs {
ancestors.populate_queue(1, tx);
ancestors.populate_queue(1, tx.into());
}
ancestors
}
fn populate_queue(&mut self, depth: usize, tx: &'g Transaction) {
fn populate_queue(&mut self, depth: usize, tx: Arc<Transaction>) {
let ancestors = tx
.input
.iter()
@@ -1436,7 +1521,7 @@ impl<'g, A, F> TxAncestors<'g, A, F> {
impl<'g, A, F, O> Iterator for TxAncestors<'g, A, F>
where
F: FnMut(usize, &'g Transaction) -> Option<O>,
F: FnMut(usize, Arc<Transaction>) -> Option<O>,
{
type Item = O;
@@ -1445,7 +1530,7 @@ where
// we have exhausted all paths when queue is empty
let (ancestor_depth, tx) = self.queue.pop_front()?;
// ignore paths when user filters them out
let item = match (self.filter_map)(ancestor_depth, tx) {
let item = match (self.filter_map)(ancestor_depth, tx.clone()) {
Some(item) => item,
None => continue,
};

View File

@@ -70,7 +70,7 @@ macro_rules! changeset {
#[allow(unused)]
pub fn new_tx(lt: u32) -> bitcoin::Transaction {
bitcoin::Transaction {
version: 0x00,
version: bitcoin::transaction::Version::non_standard(0x00),
lock_time: bitcoin::absolute::LockTime::from_consensus(lt),
input: vec![],
output: vec![],

View File

@@ -3,8 +3,8 @@ use std::collections::HashMap;
use bdk_chain::{tx_graph::TxGraph, Anchor, SpkTxOutIndex};
use bitcoin::{
locktime::absolute::LockTime, secp256k1::Secp256k1, OutPoint, ScriptBuf, Sequence, Transaction,
TxIn, TxOut, Txid, Witness,
locktime::absolute::LockTime, secp256k1::Secp256k1, transaction, Amount, OutPoint, ScriptBuf,
Sequence, Transaction, TxIn, TxOut, Txid, Witness,
};
use miniscript::Descriptor;
@@ -68,7 +68,7 @@ pub fn init_graph<'a, A: Anchor + Clone + 'a>(
for (bogus_txin_vout, tx_tmp) in tx_templates.into_iter().enumerate() {
let tx = Transaction {
version: 0,
version: transaction::Version::non_standard(0),
lock_time: LockTime::ZERO,
input: tx_tmp
.inputs
@@ -111,11 +111,11 @@ pub fn init_graph<'a, A: Anchor + Clone + 'a>(
.iter()
.map(|output| match &output.spk_index {
None => TxOut {
value: output.value,
value: Amount::from_sat(output.value),
script_pubkey: ScriptBuf::new(),
},
Some(index) => TxOut {
value: output.value,
value: Amount::from_sat(output.value),
script_pubkey: spk_index.spk_at_index(index).unwrap().to_owned(),
},
})

View File

@@ -1,15 +1,17 @@
#[macro_use]
mod common;
use std::collections::BTreeSet;
use std::{collections::BTreeSet, sync::Arc};
use bdk_chain::{
indexed_tx_graph::{self, IndexedTxGraph},
keychain::{self, Balance, KeychainTxOutIndex},
local_chain::LocalChain,
tx_graph, BlockId, ChainPosition, ConfirmationHeightAnchor,
tx_graph, ChainPosition, ConfirmationHeightAnchor,
};
use bitcoin::{
secp256k1::Secp256k1, Amount, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut,
};
use bitcoin::{secp256k1::Secp256k1, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut};
use miniscript::Descriptor;
/// Ensure [`IndexedTxGraph::insert_relevant_txs`] can successfully index transactions NOT presented
@@ -35,11 +37,11 @@ fn insert_relevant_txs() {
let tx_a = Transaction {
output: vec![
TxOut {
value: 10_000,
value: Amount::from_sat(10_000),
script_pubkey: spk_0,
},
TxOut {
value: 20_000,
value: Amount::from_sat(20_000),
script_pubkey: spk_1,
},
],
@@ -66,7 +68,7 @@ fn insert_relevant_txs() {
let changeset = indexed_tx_graph::ChangeSet {
graph: tx_graph::ChangeSet {
txs: txs.clone().into(),
txs: txs.iter().cloned().map(Arc::new).collect(),
..Default::default()
},
indexer: keychain::ChangeSet([((), 9_u32)].into()),
@@ -80,7 +82,6 @@ fn insert_relevant_txs() {
assert_eq!(graph.initial_changeset(), changeset,);
}
#[test]
/// Ensure consistency IndexedTxGraph list_* and balance methods. These methods lists
/// relevant txouts and utxos from the information fetched from a ChainOracle (here a LocalChain).
///
@@ -108,7 +109,7 @@ fn insert_relevant_txs() {
///
/// Finally Add more blocks to local chain until tx1 coinbase maturity hits.
/// Assert maturity at coinbase maturity inflection height. Block height 98 and 99.
#[test]
fn test_list_owned_txouts() {
// Create Local chains
let local_chain = LocalChain::from_blocks((0..150).map(|i| (i as u32, h!("random"))).collect())
@@ -155,7 +156,7 @@ fn test_list_owned_txouts() {
..Default::default()
}],
output: vec![TxOut {
value: 70000,
value: Amount::from_sat(70000),
script_pubkey: trusted_spks[0].to_owned(),
}],
..common::new_tx(0)
@@ -164,7 +165,7 @@ fn test_list_owned_txouts() {
// tx2 is an incoming transaction received at untrusted keychain at block 1.
let tx2 = Transaction {
output: vec![TxOut {
value: 30000,
value: Amount::from_sat(30000),
script_pubkey: untrusted_spks[0].to_owned(),
}],
..common::new_tx(0)
@@ -177,7 +178,7 @@ fn test_list_owned_txouts() {
..Default::default()
}],
output: vec![TxOut {
value: 10000,
value: Amount::from_sat(10000),
script_pubkey: trusted_spks[1].to_owned(),
}],
..common::new_tx(0)
@@ -186,7 +187,7 @@ fn test_list_owned_txouts() {
// tx4 is an external transaction receiving at untrusted keychain, unconfirmed.
let tx4 = Transaction {
output: vec![TxOut {
value: 20000,
value: Amount::from_sat(20000),
script_pubkey: untrusted_spks[1].to_owned(),
}],
..common::new_tx(0)
@@ -195,7 +196,7 @@ fn test_list_owned_txouts() {
// tx5 is spending tx3 and receiving change at trusted keychain, unconfirmed.
let tx5 = Transaction {
output: vec![TxOut {
value: 15000,
value: Amount::from_sat(15000),
script_pubkey: trusted_spks[2].to_owned(),
}],
..common::new_tx(0)
@@ -213,10 +214,8 @@ fn test_list_owned_txouts() {
(
*tx,
local_chain
.blocks()
.get(&height)
.cloned()
.map(|hash| BlockId { height, hash })
.get(height)
.map(|cp| cp.block_id())
.map(|anchor_block| ConfirmationHeightAnchor {
anchor_block,
confirmation_height: anchor_block.height,
@@ -231,9 +230,8 @@ fn test_list_owned_txouts() {
|height: u32,
graph: &IndexedTxGraph<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>| {
let chain_tip = local_chain
.blocks()
.get(&height)
.map(|&hash| BlockId { height, hash })
.get(height)
.map(|cp| cp.block_id())
.unwrap_or_else(|| panic!("block must exist at {}", height));
let txouts = graph
.graph()

View File

@@ -9,7 +9,7 @@ use bdk_chain::{
Append,
};
use bitcoin::{secp256k1::Secp256k1, OutPoint, ScriptBuf, Transaction, TxOut};
use bitcoin::{secp256k1::Secp256k1, Amount, OutPoint, ScriptBuf, Transaction, TxOut};
use miniscript::{Descriptor, DescriptorPublicKey};
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
@@ -176,14 +176,14 @@ fn test_lookahead() {
.at_derivation_index(external_index)
.unwrap()
.script_pubkey(),
value: 10_000,
value: Amount::from_sat(10_000),
},
TxOut {
script_pubkey: internal_desc
.at_derivation_index(internal_index)
.unwrap()
.script_pubkey(),
value: 10_000,
value: Amount::from_sat(10_000),
},
],
..common::new_tx(external_index)
@@ -238,7 +238,7 @@ fn test_scan_with_lookahead() {
let op = OutPoint::new(h!("fake tx"), spk_i);
let txout = TxOut {
script_pubkey: spk.clone(),
value: 0,
value: Amount::ZERO,
};
let changeset = txout_index.index_txout(op, &txout);
@@ -264,7 +264,7 @@ fn test_scan_with_lookahead() {
let op = OutPoint::new(h!("fake tx"), 41);
let txout = TxOut {
script_pubkey: spk_41,
value: 0,
value: Amount::ZERO,
};
let changeset = txout_index.index_txout(op, &txout);
assert!(changeset.is_empty());

View File

@@ -1,3 +1,5 @@
use std::ops::{Bound, RangeBounds};
use bdk_chain::{
local_chain::{
AlterCheckPointError, ApplyHeaderError, CannotConnectError, ChangeSet, CheckPoint,
@@ -6,6 +8,7 @@ use bdk_chain::{
BlockId,
};
use bitcoin::{block::Header, hashes::Hash, BlockHash};
use proptest::prelude::*;
#[macro_use]
mod common;
@@ -528,6 +531,52 @@ fn checkpoint_from_block_ids() {
}
}
#[test]
fn checkpoint_query() {
struct TestCase {
chain: LocalChain,
/// The heights we want to call [`CheckPoint::query`] with, represented as an inclusive
/// range.
///
/// If a [`CheckPoint`] exists at that height, we expect [`CheckPoint::query`] to return
/// it. If not, [`CheckPoint::query`] should return `None`.
query_range: (u32, u32),
}
let test_cases = [
TestCase {
chain: local_chain![(0, h!("_")), (1, h!("A"))],
query_range: (0, 2),
},
TestCase {
chain: local_chain![(0, h!("_")), (2, h!("B")), (3, h!("C"))],
query_range: (0, 3),
},
];
for t in test_cases.into_iter() {
let tip = t.chain.tip();
for h in t.query_range.0..=t.query_range.1 {
let query_result = tip.get(h);
// perform an exhausitive search for the checkpoint at height `h`
let exp_hash = t
.chain
.iter_checkpoints()
.find(|cp| cp.height() == h)
.map(|cp| cp.hash());
match query_result {
Some(cp) => {
assert_eq!(Some(cp.hash()), exp_hash);
assert_eq!(cp.height(), h);
}
None => assert!(exp_hash.is_none()),
}
}
}
}
#[test]
fn local_chain_apply_header_connected_to() {
fn header_from_prev_blockhash(prev_blockhash: BlockHash) -> Header {
@@ -679,3 +728,48 @@ fn local_chain_apply_header_connected_to() {
assert_eq!(result, exp_result, "[{}:{}] unexpected result", i, t.name);
}
}
fn generate_height_range_bounds(
height_upper_bound: u32,
) -> impl Strategy<Value = (Bound<u32>, Bound<u32>)> {
fn generate_height_bound(height_upper_bound: u32) -> impl Strategy<Value = Bound<u32>> {
prop_oneof![
(0..height_upper_bound).prop_map(Bound::Included),
(0..height_upper_bound).prop_map(Bound::Excluded),
Just(Bound::Unbounded),
]
}
(
generate_height_bound(height_upper_bound),
generate_height_bound(height_upper_bound),
)
}
fn generate_checkpoints(max_height: u32, max_count: usize) -> impl Strategy<Value = CheckPoint> {
proptest::collection::btree_set(1..max_height, 0..max_count).prop_map(|mut heights| {
heights.insert(0); // must have genesis
CheckPoint::from_block_ids(heights.into_iter().map(|height| {
let hash = bitcoin::hashes::Hash::hash(height.to_le_bytes().as_slice());
BlockId { height, hash }
}))
.expect("blocks must be in order as it comes from btreeset")
})
}
proptest! {
#![proptest_config(ProptestConfig {
..Default::default()
})]
/// Ensure that [`CheckPoint::range`] returns the expected checkpoint heights by comparing it
/// against a more primitive approach.
#[test]
fn checkpoint_range(
range in generate_height_range_bounds(21_000),
cp in generate_checkpoints(21_000, 2100)
) {
let exp_heights = cp.iter().map(|cp| cp.height()).filter(|h| range.contains(h)).collect::<Vec<u32>>();
let heights = cp.range(range).map(|cp| cp.height()).collect::<Vec<u32>>();
prop_assert_eq!(heights, exp_heights);
}
}

View File

@@ -1,5 +1,5 @@
use bdk_chain::{indexed_tx_graph::Indexer, SpkTxOutIndex};
use bitcoin::{absolute, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
use bitcoin::{absolute, transaction, Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
#[test]
fn spk_txout_sent_and_received() {
@@ -11,11 +11,11 @@ fn spk_txout_sent_and_received() {
index.insert_spk(1, spk2.clone());
let tx1 = Transaction {
version: 0x02,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: 42_000,
value: Amount::from_sat(42_000),
script_pubkey: spk1.clone(),
}],
};
@@ -30,7 +30,7 @@ fn spk_txout_sent_and_received() {
);
let tx2 = Transaction {
version: 0x1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
@@ -41,12 +41,12 @@ fn spk_txout_sent_and_received() {
}],
output: vec![
TxOut {
value: 20_000,
value: Amount::from_sat(20_000),
script_pubkey: spk2,
},
TxOut {
script_pubkey: spk1,
value: 30_000,
value: Amount::from_sat(30_000),
},
],
};
@@ -73,11 +73,11 @@ fn mark_used() {
assert!(spk_index.is_used(&1));
let tx1 = Transaction {
version: 0x02,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: 42_000,
value: Amount::from_sat(42_000),
script_pubkey: spk1,
}],
};

View File

@@ -8,11 +8,13 @@ use bdk_chain::{
Anchor, Append, BlockId, ChainOracle, ChainPosition, ConfirmationHeightAnchor,
};
use bitcoin::{
absolute, hashes::Hash, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid,
absolute, hashes::Hash, transaction, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn,
TxOut, Txid,
};
use common::*;
use core::iter;
use rand::RngCore;
use std::sync::Arc;
use std::vec;
#[test]
@@ -22,14 +24,14 @@ fn insert_txouts() {
(
OutPoint::new(h!("tx1"), 1),
TxOut {
value: 10_000,
value: Amount::from_sat(10_000),
script_pubkey: ScriptBuf::new(),
},
),
(
OutPoint::new(h!("tx1"), 2),
TxOut {
value: 20_000,
value: Amount::from_sat(20_000),
script_pubkey: ScriptBuf::new(),
},
),
@@ -39,21 +41,21 @@ fn insert_txouts() {
let update_ops = [(
OutPoint::new(h!("tx2"), 0),
TxOut {
value: 20_000,
value: Amount::from_sat(20_000),
script_pubkey: ScriptBuf::new(),
},
)];
// One full transaction to be included in the update
let update_txs = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
..Default::default()
}],
output: vec![TxOut {
value: 30_000,
value: Amount::from_sat(30_000),
script_pubkey: ScriptBuf::new(),
}],
};
@@ -119,7 +121,7 @@ fn insert_txouts() {
assert_eq!(
graph.insert_tx(update_txs.clone()),
ChangeSet {
txs: [update_txs.clone()].into(),
txs: [Arc::new(update_txs.clone())].into(),
..Default::default()
}
);
@@ -143,7 +145,7 @@ fn insert_txouts() {
assert_eq!(
changeset,
ChangeSet {
txs: [update_txs.clone()].into(),
txs: [Arc::new(update_txs.clone())].into(),
txouts: update_ops.clone().into(),
anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(),
last_seen: [(h!("tx2"), 1000000)].into()
@@ -163,14 +165,14 @@ fn insert_txouts() {
(
1u32,
&TxOut {
value: 10_000,
value: Amount::from_sat(10_000),
script_pubkey: ScriptBuf::new(),
}
),
(
2u32,
&TxOut {
value: 20_000,
value: Amount::from_sat(20_000),
script_pubkey: ScriptBuf::new(),
}
)
@@ -183,7 +185,7 @@ fn insert_txouts() {
[(
0u32,
&TxOut {
value: 30_000,
value: Amount::from_sat(30_000),
script_pubkey: ScriptBuf::new()
}
)]
@@ -194,7 +196,7 @@ fn insert_txouts() {
assert_eq!(
graph.initial_changeset(),
ChangeSet {
txs: [update_txs.clone()].into(),
txs: [Arc::new(update_txs.clone())].into(),
txouts: update_ops.into_iter().chain(original_ops).collect(),
anchors: [(conf_anchor, update_txs.txid()), (unconf_anchor, h!("tx2"))].into(),
last_seen: [(h!("tx2"), 1000000)].into()
@@ -205,7 +207,7 @@ fn insert_txouts() {
#[test]
fn insert_tx_graph_doesnt_count_coinbase_as_spent() {
let tx = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
@@ -224,10 +226,10 @@ fn insert_tx_graph_doesnt_count_coinbase_as_spent() {
#[test]
fn insert_tx_graph_keeps_track_of_spend() {
let tx1 = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
};
let op = OutPoint {
@@ -236,7 +238,7 @@ fn insert_tx_graph_keeps_track_of_spend() {
};
let tx2 = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: op,
@@ -265,30 +267,33 @@ fn insert_tx_graph_keeps_track_of_spend() {
#[test]
fn insert_tx_can_retrieve_full_tx_from_graph() {
let tx = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
..Default::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
};
let mut graph = TxGraph::<()>::default();
let _ = graph.insert_tx(tx.clone());
assert_eq!(graph.get_tx(tx.txid()), Some(&tx));
assert_eq!(
graph.get_tx(tx.txid()).map(|tx| tx.as_ref().clone()),
Some(tx)
);
}
#[test]
fn insert_tx_displaces_txouts() {
let mut tx_graph = TxGraph::<()>::default();
let tx = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: 42_000,
script_pubkey: ScriptBuf::default(),
value: Amount::from_sat(42_000),
script_pubkey: ScriptBuf::new(),
}],
};
@@ -298,7 +303,7 @@ fn insert_tx_displaces_txouts() {
vout: 0,
},
TxOut {
value: 1_337_000,
value: Amount::from_sat(1_337_000),
script_pubkey: ScriptBuf::default(),
},
);
@@ -311,8 +316,8 @@ fn insert_tx_displaces_txouts() {
vout: 0,
},
TxOut {
value: 1_000_000_000,
script_pubkey: ScriptBuf::default(),
value: Amount::from_sat(1_000_000_000),
script_pubkey: ScriptBuf::new(),
},
);
@@ -326,7 +331,7 @@ fn insert_tx_displaces_txouts() {
})
.unwrap()
.value,
42_000
Amount::from_sat(42_000)
);
assert_eq!(
tx_graph.get_txout(OutPoint {
@@ -341,12 +346,12 @@ fn insert_tx_displaces_txouts() {
fn insert_txout_does_not_displace_tx() {
let mut tx_graph = TxGraph::<()>::default();
let tx = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: 42_000,
script_pubkey: ScriptBuf::default(),
value: Amount::from_sat(42_000),
script_pubkey: ScriptBuf::new(),
}],
};
@@ -358,8 +363,8 @@ fn insert_txout_does_not_displace_tx() {
vout: 0,
},
TxOut {
value: 1_337_000,
script_pubkey: ScriptBuf::default(),
value: Amount::from_sat(1_337_000),
script_pubkey: ScriptBuf::new(),
},
);
@@ -369,8 +374,8 @@ fn insert_txout_does_not_displace_tx() {
vout: 0,
},
TxOut {
value: 1_000_000_000,
script_pubkey: ScriptBuf::default(),
value: Amount::from_sat(1_000_000_000),
script_pubkey: ScriptBuf::new(),
},
);
@@ -382,7 +387,7 @@ fn insert_txout_does_not_displace_tx() {
})
.unwrap()
.value,
42_000
Amount::from_sat(42_000)
);
assert_eq!(
tx_graph.get_txout(OutPoint {
@@ -397,21 +402,21 @@ fn insert_txout_does_not_displace_tx() {
fn test_calculate_fee() {
let mut graph = TxGraph::<()>::default();
let intx1 = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: 100,
..Default::default()
value: Amount::from_sat(100),
script_pubkey: ScriptBuf::new(),
}],
};
let intx2 = Transaction {
version: 0x02,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
value: 200,
..Default::default()
value: Amount::from_sat(200),
script_pubkey: ScriptBuf::new(),
}],
};
@@ -421,8 +426,8 @@ fn test_calculate_fee() {
vout: 0,
},
TxOut {
value: 300,
..Default::default()
value: Amount::from_sat(300),
script_pubkey: ScriptBuf::new(),
},
);
@@ -431,7 +436,7 @@ fn test_calculate_fee() {
let _ = graph.insert_txout(intxout1.0, intxout1.1);
let mut tx = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![
TxIn {
@@ -454,8 +459,8 @@ fn test_calculate_fee() {
},
],
output: vec![TxOut {
value: 500,
..Default::default()
value: Amount::from_sat(500),
script_pubkey: ScriptBuf::new(),
}],
};
@@ -487,13 +492,13 @@ fn test_calculate_fee() {
#[test]
fn test_calculate_fee_on_coinbase() {
let tx = Transaction {
version: 0x01,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
..Default::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
};
let graph = TxGraph::<()>::default();
@@ -529,7 +534,7 @@ fn test_walk_ancestors() {
previous_output: OutPoint::new(h!("op0"), 0),
..TxIn::default()
}],
output: vec![TxOut::default(), TxOut::default()],
output: vec![TxOut::NULL, TxOut::NULL],
..common::new_tx(0)
};
@@ -539,7 +544,7 @@ fn test_walk_ancestors() {
previous_output: OutPoint::new(tx_a0.txid(), 0),
..TxIn::default()
}],
output: vec![TxOut::default(), TxOut::default()],
output: vec![TxOut::NULL, TxOut::NULL],
..common::new_tx(0)
};
@@ -549,7 +554,7 @@ fn test_walk_ancestors() {
previous_output: OutPoint::new(tx_a0.txid(), 1),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
@@ -558,7 +563,7 @@ fn test_walk_ancestors() {
previous_output: OutPoint::new(h!("op1"), 0),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
@@ -568,7 +573,7 @@ fn test_walk_ancestors() {
previous_output: OutPoint::new(tx_b0.txid(), 0),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
@@ -578,7 +583,7 @@ fn test_walk_ancestors() {
previous_output: OutPoint::new(tx_b0.txid(), 1),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
@@ -594,7 +599,7 @@ fn test_walk_ancestors() {
..TxIn::default()
},
],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
@@ -603,7 +608,7 @@ fn test_walk_ancestors() {
previous_output: OutPoint::new(h!("op2"), 0),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
@@ -613,7 +618,7 @@ fn test_walk_ancestors() {
previous_output: OutPoint::new(tx_c1.txid(), 0),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
@@ -629,7 +634,7 @@ fn test_walk_ancestors() {
..TxIn::default()
},
],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
@@ -639,11 +644,11 @@ fn test_walk_ancestors() {
previous_output: OutPoint::new(tx_d1.txid(), 0),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
let mut graph = TxGraph::<BlockId>::new(vec![
let mut graph = TxGraph::<BlockId>::new([
tx_a0.clone(),
tx_b0.clone(),
tx_b1.clone(),
@@ -664,17 +669,17 @@ fn test_walk_ancestors() {
let ancestors = [
graph
.walk_ancestors(&tx_c0, |depth, tx| Some((depth, tx)))
.walk_ancestors(tx_c0.clone(), |depth, tx| Some((depth, tx)))
.collect::<Vec<_>>(),
graph
.walk_ancestors(&tx_d0, |depth, tx| Some((depth, tx)))
.walk_ancestors(tx_d0.clone(), |depth, tx| Some((depth, tx)))
.collect::<Vec<_>>(),
graph
.walk_ancestors(&tx_e0, |depth, tx| Some((depth, tx)))
.walk_ancestors(tx_e0.clone(), |depth, tx| Some((depth, tx)))
.collect::<Vec<_>>(),
// Only traverse unconfirmed ancestors of tx_e0 this time
graph
.walk_ancestors(&tx_e0, |depth, tx| {
.walk_ancestors(tx_e0.clone(), |depth, tx| {
let tx_node = graph.get_tx_node(tx.txid())?;
for block in tx_node.anchors {
match local_chain.is_block_in_chain(block.anchor_block(), tip.block_id()) {
@@ -701,8 +706,14 @@ fn test_walk_ancestors() {
vec![(1, &tx_d1), (2, &tx_c2), (2, &tx_c3), (3, &tx_b2)],
];
for (txids, expected_txids) in ancestors.iter().zip(expected_ancestors.iter()) {
assert_eq!(txids, expected_txids);
for (txids, expected_txids) in ancestors.into_iter().zip(expected_ancestors) {
assert_eq!(
txids,
expected_txids
.into_iter()
.map(|(i, tx)| (i, Arc::new(tx.clone())))
.collect::<Vec<_>>()
);
}
}
@@ -716,7 +727,7 @@ fn test_conflicting_descendants() {
previous_output,
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(0)
};
@@ -726,7 +737,7 @@ fn test_conflicting_descendants() {
previous_output,
..TxIn::default()
}],
output: vec![TxOut::default(), TxOut::default()],
output: vec![TxOut::NULL, TxOut::NULL],
..common::new_tx(1)
};
@@ -736,7 +747,7 @@ fn test_conflicting_descendants() {
previous_output: OutPoint::new(tx_a.txid(), 0),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(2)
};
@@ -758,7 +769,7 @@ fn test_conflicting_descendants() {
#[test]
fn test_descendants_no_repeat() {
let tx_a = Transaction {
output: vec![TxOut::default(), TxOut::default(), TxOut::default()],
output: vec![TxOut::NULL, TxOut::NULL, TxOut::NULL],
..common::new_tx(0)
};
@@ -768,7 +779,7 @@ fn test_descendants_no_repeat() {
previous_output: OutPoint::new(tx_a.txid(), vout),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(1)
})
.collect::<Vec<_>>();
@@ -779,7 +790,7 @@ fn test_descendants_no_repeat() {
previous_output: OutPoint::new(txs_b[vout as usize].txid(), vout),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(2)
})
.collect::<Vec<_>>();
@@ -795,7 +806,7 @@ fn test_descendants_no_repeat() {
..TxIn::default()
},
],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(3)
};
@@ -804,7 +815,7 @@ fn test_descendants_no_repeat() {
previous_output: OutPoint::new(tx_d.txid(), 0),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(4)
};
@@ -814,7 +825,7 @@ fn test_descendants_no_repeat() {
previous_output: OutPoint::new(h!("tx_does_not_exist"), v),
..TxIn::default()
}],
output: vec![TxOut::default()],
output: vec![TxOut::NULL],
..common::new_tx(v)
})
.collect::<Vec<_>>();
@@ -861,11 +872,11 @@ fn test_chain_spends() {
input: vec![],
output: vec![
TxOut {
value: 10_000,
value: Amount::from_sat(10_000),
script_pubkey: ScriptBuf::new(),
},
TxOut {
value: 20_000,
value: Amount::from_sat(20_000),
script_pubkey: ScriptBuf::new(),
},
],
@@ -880,11 +891,11 @@ fn test_chain_spends() {
}],
output: vec![
TxOut {
value: 5_000,
value: Amount::from_sat(5_000),
script_pubkey: ScriptBuf::new(),
},
TxOut {
value: 5_000,
value: Amount::from_sat(5_000),
script_pubkey: ScriptBuf::new(),
},
],
@@ -899,11 +910,11 @@ fn test_chain_spends() {
}],
output: vec![
TxOut {
value: 10_000,
value: Amount::from_sat(10_000),
script_pubkey: ScriptBuf::new(),
},
TxOut {
value: 10_000,
value: Amount::from_sat(10_000),
script_pubkey: ScriptBuf::new(),
},
],
@@ -1048,6 +1059,34 @@ fn test_changeset_last_seen_append() {
}
}
#[test]
fn update_last_seen_unconfirmed() {
let mut graph = TxGraph::<()>::default();
let tx = new_tx(0);
let txid = tx.txid();
// insert a new tx
// initially we have a last_seen of 0, and no anchors
let _ = graph.insert_tx(tx);
let tx = graph.full_txs().next().unwrap();
assert_eq!(tx.last_seen_unconfirmed, 0);
assert!(tx.anchors.is_empty());
// higher timestamp should update last seen
let changeset = graph.update_last_seen_unconfirmed(2);
assert_eq!(changeset.last_seen.get(&txid).unwrap(), &2);
// lower timestamp has no effect
let changeset = graph.update_last_seen_unconfirmed(1);
assert!(changeset.last_seen.is_empty());
// once anchored, last seen is not updated
let _ = graph.insert_anchor(txid, ());
let changeset = graph.update_last_seen_unconfirmed(4);
assert!(changeset.is_empty());
assert_eq!(graph.full_txs().next().unwrap().last_seen_unconfirmed, 2);
}
#[test]
fn test_missing_blocks() {
/// An anchor implementation for testing, made up of `(the_anchor_block, random_data)`.

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_electrum"
version = "0.10.0"
version = "0.11.0"
edition = "2021"
homepage = "https://bitcoindevkit.org"
repository = "https://github.com/bitcoindevkit/bdk"
@@ -12,11 +12,11 @@ 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.11.0", default-features = false }
electrum-client = { version = "0.18" }
bdk_chain = { path = "../chain", version = "0.12.0", default-features = false }
electrum-client = { version = "0.19" }
#rustls = { version = "=0.21.1", optional = true, features = ["dangerous_configuration"] }
[dev-dependencies]
bdk_testenv = { path = "../testenv", version = "0.1.0", default-features = false }
electrsd = { version= "0.25.0", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] }
bdk_testenv = { path = "../testenv", default-features = false }
electrsd = { version= "0.27.1", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] }
anyhow = "1"

View File

@@ -40,15 +40,11 @@ impl RelevantTxids {
pub fn into_tx_graph(
self,
client: &Client,
seen_at: Option<u64>,
missing: Vec<Txid>,
) -> Result<TxGraph<ConfirmationHeightAnchor>, Error> {
let new_txs = client.batch_transaction_get(&missing)?;
let mut graph = TxGraph::<ConfirmationHeightAnchor>::new(new_txs);
for (txid, anchors) in self.0 {
if let Some(seen_at) = seen_at {
let _ = graph.insert_seen_at(txid, seen_at);
}
for anchor in anchors {
let _ = graph.insert_anchor(txid, anchor);
}
@@ -67,10 +63,9 @@ impl RelevantTxids {
pub fn into_confirmation_time_tx_graph(
self,
client: &Client,
seen_at: Option<u64>,
missing: Vec<Txid>,
) -> Result<TxGraph<ConfirmationTimeHeightAnchor>, Error> {
let graph = self.into_tx_graph(client, seen_at, missing)?;
let graph = self.into_tx_graph(client, missing)?;
let relevant_heights = {
let mut visited_heights = HashSet::new();

View File

@@ -40,7 +40,7 @@ fn scan_detects_confirmed_tx() -> Result<()> {
.client
.get_new_address(None, None)?
.assume_checked();
let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros());
let spk_to_track = ScriptBuf::new_p2wsh(&WScriptHash::all_zeros());
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
// Setup receiver.
@@ -68,7 +68,7 @@ fn scan_detects_confirmed_tx() -> Result<()> {
} = client.sync(recv_chain.tip(), [spk_to_track], None, None, 5)?;
let missing = relevant_txids.missing_full_txs(recv_graph.graph());
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?;
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, missing)?;
let _ = recv_chain
.apply_update(chain_update)
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
@@ -106,7 +106,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
.client
.get_new_address(None, None)?
.assume_checked();
let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros());
let spk_to_track = ScriptBuf::new_p2wsh(&WScriptHash::all_zeros());
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
// Setup receiver.
@@ -134,7 +134,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
} = client.sync(recv_chain.tip(), [spk_to_track.clone()], None, None, 5)?;
let missing = relevant_txids.missing_full_txs(recv_graph.graph());
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?;
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, missing)?;
let _ = recv_chain
.apply_update(chain_update)
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
@@ -164,8 +164,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
} = client.sync(recv_chain.tip(), [spk_to_track.clone()], None, None, 5)?;
let missing = relevant_txids.missing_full_txs(recv_graph.graph());
let graph_update =
relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?;
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, missing)?;
let _ = recv_chain
.apply_update(chain_update)
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_esplora"
version = "0.10.0"
version = "0.11.0"
edition = "2021"
homepage = "https://bitcoindevkit.org"
repository = "https://github.com/bitcoindevkit/bdk"
@@ -12,18 +12,18 @@ 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.11.0", default-features = false }
esplora-client = { version = "0.6.0", default-features = false }
bdk_chain = { path = "../chain", version = "0.12.0", default-features = false }
esplora-client = { version = "0.7.0", default-features = false }
async-trait = { version = "0.1.66", optional = true }
futures = { version = "0.3.26", optional = true }
# use these dependencies if you need to enable their /no-std features
bitcoin = { version = "0.30.0", optional = true, default-features = false }
miniscript = { version = "10.0.0", optional = true, default-features = false }
bitcoin = { version = "0.31.0", optional = true, default-features = false }
miniscript = { version = "11.0.0", optional = true, default-features = false }
[dev-dependencies]
bdk_testenv = { path = "../testenv", version = "0.1.0", default_features = false }
electrsd = { version= "0.25.0", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] }
bdk_testenv = { path = "../testenv", default_features = false }
electrsd = { version= "0.27.1", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
[features]

View File

@@ -1,7 +1,7 @@
use async_trait::async_trait;
use bdk_chain::collections::btree_map;
use bdk_chain::{
bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
collections::BTreeMap,
local_chain::{self, CheckPoint},
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
@@ -228,7 +228,7 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
},
TxOut {
script_pubkey: prevout.scriptpubkey.clone(),
value: prevout.value,
value: Amount::from_sat(prevout.value),
},
))
});

View File

@@ -3,7 +3,7 @@ use std::thread::JoinHandle;
use bdk_chain::collections::btree_map;
use bdk_chain::collections::BTreeMap;
use bdk_chain::{
bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
local_chain::{self, CheckPoint},
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
};
@@ -218,7 +218,7 @@ impl EsploraExt for esplora_client::BlockingClient {
},
TxOut {
script_pubkey: prevout.scriptpubkey.clone(),
value: prevout.value,
value: Amount::from_sat(prevout.value),
},
))
});

View File

@@ -66,7 +66,7 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
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");
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

View File

@@ -30,7 +30,7 @@ macro_rules! local_chain {
pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
let env = TestEnv::new()?;
let base_url = format!("http://{}", &env.electrsd.esplora_url.clone().unwrap());
let client = Builder::new(base_url.as_str()).build_blocking()?;
let client = Builder::new(base_url.as_str()).build_blocking();
let receive_address0 =
Address::from_str("bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm")?.assume_checked();
@@ -80,7 +80,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
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");
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
@@ -111,7 +111,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
let env = TestEnv::new()?;
let base_url = format!("http://{}", &env.electrsd.esplora_url.clone().unwrap());
let client = Builder::new(base_url.as_str()).build_blocking()?;
let client = Builder::new(base_url.as_str()).build_blocking();
let _block_hashes = env.mine_blocks(101, None)?;
// Now let's test the gap limit. First of all get a chain of 10 addresses.
@@ -215,7 +215,7 @@ fn update_local_chain() -> anyhow::Result<()> {
// so new blocks can be seen by Electrs
let env = env.reset_electrsd()?;
let base_url = format!("http://{}", &env.electrsd.esplora_url.clone().unwrap());
let client = Builder::new(base_url.as_str()).build_blocking()?;
let client = Builder::new(base_url.as_str()).build_blocking();
struct TestCase {
name: &'static str,
@@ -360,8 +360,8 @@ fn update_local_chain() -> anyhow::Result<()> {
for height in t.request_heights {
let exp_blockhash = blocks.get(height).expect("block must exist in bitcoind");
assert_eq!(
chain.blocks().get(height),
Some(exp_blockhash),
chain.get(*height).map(|cp| cp.hash()),
Some(*exp_blockhash),
"[{}:{}] block {}:{} must exist in final chain",
i,
t.name,

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_file_store"
version = "0.8.0"
version = "0.9.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.11.0", features = [ "serde", "miniscript" ] }
bdk_chain = { path = "../chain", version = "0.12.0", features = [ "serde", "miniscript" ] }
bincode = { version = "1" }
serde = { version = "1", features = ["derive"] }

View File

@@ -10,4 +10,4 @@ readme = "README.md"
[dependencies]
bdk = { path = "../bdk" }
hwi = { version = "0.7.0", features = [ "miniscript"] }
hwi = { version = "0.8.0", features = [ "miniscript"] }

View File

@@ -1,6 +1,6 @@
use bdk::bitcoin::bip32::Fingerprint;
use bdk::bitcoin::psbt::PartiallySignedTransaction;
use bdk::bitcoin::secp256k1::{All, Secp256k1};
use bdk::bitcoin::Psbt;
use hwi::error::Error;
use hwi::types::{HWIChain, HWIDevice};
@@ -37,7 +37,7 @@ impl SignerCommon for HWISigner {
impl TransactionSigner for HWISigner {
fn sign_transaction(
&self,
psbt: &mut PartiallySignedTransaction,
psbt: &mut Psbt,
_sign_options: &bdk::SignOptions,
_secp: &Secp256k1<All>,
) -> Result<(), SignerError> {

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk_testenv"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
rust-version = "1.63"
homepage = "https://bitcoindevkit.org"
@@ -13,9 +13,9 @@ readme = "README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bitcoincore-rpc = { version = "0.17" }
bdk_chain = { path = "../chain", version = "0.11", default-features = false }
electrsd = { version= "0.25.0", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] }
bitcoincore-rpc = { version = "0.18" }
bdk_chain = { path = "../chain", version = "0.12", default-features = false }
electrsd = { version= "0.27.1", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] }
anyhow = { version = "1" }
[features]

View File

@@ -1,7 +1,7 @@
use bdk_chain::bitcoin::{
address::NetworkChecked, block::Header, hash_types::TxMerkleNode, hashes::Hash,
secp256k1::rand::random, Address, Amount, Block, BlockHash, CompactTarget, ScriptBuf,
ScriptHash, Transaction, TxIn, TxOut, Txid,
secp256k1::rand::random, transaction, Address, Amount, Block, BlockHash, CompactTarget,
ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid,
};
use bitcoincore_rpc::{
bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules},
@@ -109,7 +109,7 @@ impl TestEnv {
)?;
let txdata = vec![Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: bdk_chain::bitcoin::absolute::LockTime::from_height(0)?,
input: vec![TxIn {
previous_output: bdk_chain::bitcoin::OutPoint::default(),
@@ -122,7 +122,7 @@ impl TestEnv {
witness: bdk_chain::bitcoin::Witness::new(),
}],
output: vec![TxOut {
value: 0,
value: Amount::ZERO,
script_pubkey: ScriptBuf::new_p2sh(&ScriptHash::all_zeros()),
}],
}];

View File

@@ -3,12 +3,14 @@ use anyhow::Context;
use bdk_coin_select::{coin_select_bnb, CoinSelector, CoinSelectorOpt, WeightedValue};
use bdk_file_store::Store;
use serde::{de::DeserializeOwned, Serialize};
use std::{cmp::Reverse, collections::HashMap, path::PathBuf, sync::Mutex, time::Duration};
use std::{cmp::Reverse, collections::BTreeMap, path::PathBuf, sync::Mutex, time::Duration};
use bdk_chain::{
bitcoin::{
absolute, address, psbt::Prevouts, secp256k1::Secp256k1, sighash::SighashCache, Address,
Network, Sequence, Transaction, TxIn, TxOut,
absolute, address,
secp256k1::Secp256k1,
sighash::{Prevouts, SighashCache},
transaction, Address, Amount, Network, Sequence, Transaction, TxIn, TxOut,
},
indexed_tx_graph::{self, IndexedTxGraph},
keychain::{self, KeychainTxOutIndex},
@@ -197,7 +199,7 @@ pub struct CreateTxChange {
pub fn create_tx<A: Anchor, O: ChainOracle>(
graph: &mut KeychainTxGraph<A>,
chain: &O,
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
keymap: &BTreeMap<DescriptorPublicKey, DescriptorSecretKey>,
cs_algorithm: CoinSelectionAlgo,
address: Address,
value: u64,
@@ -235,7 +237,7 @@ where
.iter()
.map(|(plan, utxo)| {
WeightedValue::new(
utxo.txout.value,
utxo.txout.value.to_sat(),
plan.expected_weight() as _,
plan.witness_version().is_some(),
)
@@ -243,7 +245,7 @@ where
.collect();
let mut outputs = vec![TxOut {
value,
value: Amount::from_sat(value),
script_pubkey: address.script_pubkey(),
}];
@@ -273,7 +275,7 @@ where
.expect("failed to obtain change plan");
let mut change_output = TxOut {
value: 0,
value: Amount::ZERO,
script_pubkey: change_script,
};
@@ -311,13 +313,13 @@ where
let selected_txos = selection.apply_selection(&candidates).collect::<Vec<_>>();
if let Some(drain_value) = selection_meta.drain_value {
change_output.value = drain_value;
change_output.value = Amount::from_sat(drain_value);
// if the selection tells us to use change and the change value is sufficient, we add it as an output
outputs.push(change_output)
}
let mut transaction = Transaction {
version: 0x02,
version: transaction::Version::TWO,
// because the temporary planning module does not support timelocks, we can use the chain
// tip as the `lock_time` for anti-fee-sniping purposes
lock_time: absolute::LockTime::from_height(chain.get_chain_tip()?.height)
@@ -440,7 +442,7 @@ pub fn handle_commands<CS: clap::Subcommand, S: clap::Args, A: Anchor, O: ChainO
graph: &Mutex<KeychainTxGraph<A>>,
db: &Mutex<Database<C>>,
chain: &Mutex<O>,
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
keymap: &BTreeMap<DescriptorPublicKey, DescriptorSecretKey>,
network: Network,
broadcast: impl FnOnce(S, &Transaction) -> anyhow::Result<()>,
cmd: Commands<CS, S>,

View File

@@ -299,12 +299,12 @@ fn main() -> anyhow::Result<()> {
relevant_txids.missing_full_txs(graph.graph())
};
let mut graph_update = relevant_txids.into_tx_graph(&client, missing_txids)?;
let now = std::time::UNIX_EPOCH
.elapsed()
.expect("must get time")
.as_secs();
let graph_update = relevant_txids.into_tx_graph(&client, Some(now), missing_txids)?;
let _ = graph_update.update_last_seen_unconfirmed(now);
let db_changeset = {
let mut chain = chain.lock().unwrap();

View File

@@ -86,7 +86,7 @@ impl EsploraArgs {
_ => panic!("unsupported network"),
});
let client = esplora_client::Builder::new(esplora_url).build_blocking()?;
let client = esplora_client::Builder::new(esplora_url).build_blocking();
Ok(client)
}
}
@@ -189,10 +189,14 @@ fn main() -> anyhow::Result<()> {
// is reached. It returns a `TxGraph` update (`graph_update`) and a structure that
// represents the last active spk derivation indices of keychains
// (`keychain_indices_update`).
let (graph_update, last_active_indices) = client
let (mut graph_update, last_active_indices) = client
.full_scan(keychain_spks, *stop_gap, scan_options.parallel_requests)
.context("scanning for transactions")?;
// We want to keep track of the latest time a transaction was seen unconfirmed.
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
let _ = graph_update.update_last_seen_unconfirmed(now);
let mut graph = graph.lock().expect("mutex must not be poisoned");
// Because we did a stop gap based scan we are likely to have some updates to our
// deriviation indices. Usually before a scan you are on a fresh wallet with no
@@ -307,9 +311,13 @@ fn main() -> anyhow::Result<()> {
}
}
let graph_update =
let mut graph_update =
client.sync(spks, txids, outpoints, scan_options.parallel_requests)?;
// Update last seen unconfirmed
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
let _ = graph_update.update_last_seen_unconfirmed(now);
graph.lock().unwrap().apply_update(graph_update)
}
};

View File

@@ -66,7 +66,9 @@ fn main() -> Result<(), anyhow::Error> {
println!();
let missing = relevant_txids.missing_full_txs(wallet.as_ref());
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?;
let mut graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, missing)?;
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
let _ = graph_update.update_last_seen_unconfirmed(now);
let wallet_update = Update {
last_active_indices: keychain_update,
@@ -99,7 +101,7 @@ fn main() -> Result<(), anyhow::Error> {
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
assert!(finalized);
let tx = psbt.extract_tx();
let tx = psbt.extract_tx()?;
client.transaction_broadcast(&tx)?;
println!("Tx broadcasted! Txid: {}", tx.txid());

View File

@@ -53,9 +53,12 @@ async fn main() -> Result<(), anyhow::Error> {
(k, k_spks)
})
.collect();
let (update_graph, last_active_indices) = client
let (mut update_graph, last_active_indices) = client
.full_scan(keychain_spks, STOP_GAP, PARALLEL_REQUESTS)
.await?;
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
let _ = update_graph.update_last_seen_unconfirmed(now);
let missing_heights = update_graph.missing_heights(wallet.local_chain());
let chain_update = client.update_local_chain(prev_tip, missing_heights).await?;
let update = Update {
@@ -90,7 +93,7 @@ async fn main() -> Result<(), anyhow::Error> {
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
assert!(finalized);
let tx = psbt.extract_tx();
let tx = psbt.extract_tx()?;
client.broadcast(&tx).await?;
println!("Tx broadcasted! Txid: {}", tx.txid());

View File

@@ -34,7 +34,7 @@ fn main() -> Result<(), anyhow::Error> {
print!("Syncing...");
let client =
esplora_client::Builder::new("https://blockstream.info/testnet/api").build_blocking()?;
esplora_client::Builder::new("https://blockstream.info/testnet/api").build_blocking();
let prev_tip = wallet.latest_checkpoint();
let keychain_spks = wallet
@@ -53,8 +53,11 @@ fn main() -> Result<(), anyhow::Error> {
})
.collect();
let (update_graph, last_active_indices) =
let (mut update_graph, last_active_indices) =
client.full_scan(keychain_spks, STOP_GAP, PARALLEL_REQUESTS)?;
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
let _ = update_graph.update_last_seen_unconfirmed(now);
let missing_heights = update_graph.missing_heights(wallet.local_chain());
let chain_update = client.update_local_chain(prev_tip, missing_heights)?;
let update = Update {
@@ -90,7 +93,7 @@ fn main() -> Result<(), anyhow::Error> {
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
assert!(finalized);
let tx = psbt.extract_tx();
let tx = psbt.extract_tx()?;
client.broadcast(&tx)?;
println!("Tx broadcasted! Txid: {}", tx.txid());

View File

@@ -96,7 +96,7 @@ impl CoinSelectorOpt {
) -> Self {
let mut tx = Transaction {
input: vec![],
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
output: txouts.to_vec(),
};
@@ -112,7 +112,7 @@ impl CoinSelectorOpt {
target_value: if txouts.is_empty() {
None
} else {
Some(txouts.iter().map(|txout| txout.value).sum())
Some(txouts.iter().map(|txout| txout.value.to_sat()).sum())
},
..Self::from_weights(
base_weight.to_wu() as u32,

View File

@@ -12,7 +12,7 @@ use bdk_chain::{
bitcoin,
collections::{BTreeSet, HashMap},
};
use bitcoin::{absolute, Transaction, TxOut};
use bitcoin::{absolute, transaction, Transaction, TxOut};
use core::fmt::{Debug, Display};
mod coin_selector;
@@ -29,5 +29,5 @@ pub const TXIN_BASE_WEIGHT: u32 = (32 + 4 + 4) * 4;
// Shamelessly copied from
// https://github.com/rust-bitcoin/rust-miniscript/blob/d5615acda1a7fdc4041a11c1736af139b8c7ebe8/src/util.rs#L8
pub(crate) fn varint_size(v: usize) -> u32 {
bitcoin::VarInt(v as u64).len() as u32
bitcoin::VarInt(v as u64).size() as u32
}

View File

@@ -17,14 +17,13 @@
use bdk_chain::{bitcoin, collections::*, miniscript};
use bitcoin::{
absolute,
address::WitnessVersion,
bip32::{DerivationPath, Fingerprint, KeySource},
blockdata::transaction::Sequence,
ecdsa,
hashes::{hash160, ripemd160, sha256},
secp256k1::Secp256k1,
taproot::{self, LeafVersion, TapLeafHash},
ScriptBuf, TxIn, Witness,
ScriptBuf, TxIn, Witness, WitnessVersion,
};
use miniscript::{
descriptor::{InnerXKey, Tr},
@@ -32,7 +31,7 @@ use miniscript::{
};
pub(crate) fn varint_len(v: usize) -> usize {
bitcoin::VarInt(v as u64).len() as usize
bitcoin::VarInt(v as u64).size() as usize
}
mod plan_impls;

View File

@@ -3,12 +3,11 @@ use core::ops::Deref;
use bitcoin::{
bip32,
hashes::{hash160, ripemd160, sha256},
hashes::{hash160, ripemd160, sha256, Hash},
key::XOnlyPublicKey,
psbt::Prevouts,
secp256k1::{KeyPair, Message, PublicKey, Signing, Verification},
secp256k1::{Keypair, Message, PublicKey, Signing, Verification},
sighash,
sighash::{EcdsaSighashType, SighashCache, TapSighashType},
sighash::{EcdsaSighashType, Prevouts, SighashCache, TapSighashType},
taproot, Transaction, TxOut,
};
@@ -163,11 +162,11 @@ impl RequiredSignatures<DescriptorPublicKey> {
let tweak =
taproot::TapTweakHash::from_key_and_tweak(x_only_pubkey, merkle_root.clone());
let keypair = KeyPair::from_secret_key(&secp, &secret_key.clone())
let keypair = Keypair::from_secret_key(&secp, &secret_key.clone())
.add_xonly_tweak(&secp, &tweak.to_scalar())
.unwrap();
let msg = Message::from_slice(sighash.as_ref()).expect("Sighashes are 32 bytes");
let msg = Message::from_digest(sighash.to_byte_array());
let sig = secp.sign_schnorr_no_aux_rand(&msg, &keypair);
let bitcoin_sig = taproot::Signature {
@@ -209,9 +208,8 @@ impl RequiredSignatures<DescriptorPublicKey> {
todo!();
}
};
let keypair = KeyPair::from_secret_key(&secp, &secret_key.clone());
let msg =
Message::from_slice(sighash.as_ref()).expect("Sighashes are 32 bytes");
let keypair = Keypair::from_secret_key(&secp, &secret_key.clone());
let msg = Message::from_digest(sighash.to_byte_array());
let sig = secp.sign_schnorr_no_aux_rand(&msg, &keypair);
let bitcoin_sig = taproot::Signature {
sig,