Compare commits

..

37 Commits

Author SHA1 Message Date
LLFourn
8de422dcfd Add SyncOptions as the second argument to Wallet::sync
The current options are awkware and it would be good if we could
introduce more in the future without breaking changes.
2022-01-27 16:52:53 +11:00
LLFourn
733300623e Remove Blockchain from wallet
Although somewhat convenient to have, coupling the Wallet with
the blockchain trait causes development friction and complexity.
What if sometimes the wallet is "offline" (no access to the blockchain)
but sometimes its online?
The only thing the Wallet needs the blockchain for is to sync.
But not all applications will even use the sync method and the sync
method doesn't require the full blockchain functionality.
So we instead pass the blockchain in when we want to sync.

- To further reduce the coupling with blockchain I removed the get_height call from `new` and just use the height of the
last sync in the database.
- I split up the blockchain trait a bit into subtraits.
2022-01-26 20:11:22 +11:00
Steve Myers
b1346d4ccf Merge bitcoindevkit/bdk#505: Using dust value from rust-bitcoin in `is_dust`
5ac51dfe74 fix and test is_dust (James Taylor)
a0c140bb29 add doc comment for IsDust trait (James Taylor)
bf5994b14a fixed fee in test, removed unnecessary comment (James Taylor)
ca682819b3 using dust value from rust-bitcoin (James Taylor)

Pull request description:

  ### Description

  This PR aims to fix #472 . We can retrieve the dust value for a given ``bitcoin::blockdata::script::Script``, so I adjusted the ``is_dust`` function within the ``IsDust`` trait to receive such a ``&Script``. Thus, the ``is_dust`` function can make the proper comparison.
  Let me know if you think that there could be a better interface than this.

  Furthermore, because this new ``is_dust`` function provides a tighter upper bound on Bitcoin Core's ``GetDustThreshold()``, it actually invalidated a test. In the test, the drain output for a transaction was no longer considered dust and no longer included in the fee. Instead, the drain output was kicked back to the sender, invalidating the asserts in line 3436, 3437 and 3441 in ``src/wallet/mod.rs``. I increased the ``FeeRate`` in the test just enough that the drain output would be small enough to considered dust again and included in the total fee.

  ### 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:
  afilini:
    ACK 5ac51dfe74
  notmandatory:
    re ACK 5ac51dfe74

Tree-SHA512: addf38fe065de581ddfcd3b4e6db92cd35d5bfa8cac78bd08c01f7a01292724a203ef59b09f3f5cd8e0fa0bb6d89efe72afda36efc11ded0424fc8105326af3f
2022-01-12 17:49:16 +01:00
Steve Myers
5107ff80c1 Merge bitcoindevkit/bdk#495: Disable reqwest's default features
380a4f2588 Disable reqwest's default features (Thomas Eizinger)

Pull request description:

  ### Description

  By default, reqwest uses openssl for TLS. Any consumer wanting to use
  rustls will thus pull in unnecessary dependencies.

  ### Checklists

  #### All Submissions:

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

  #### New Features:

  * ~[ ] I've added tests for the new feature~
  * ~[ ] I've added docs for the new feature~
  * [x] I've updated `CHANGELOG.md`

  #### Bugfixes:

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

ACKs for top commit:
  notmandatory:
    ACK 380a4f2588

Tree-SHA512: 17827fdd7656a1e97b4cc302bc3c4907a8493505c798fafd9b15fde12531a32cf60e7d63e878eb2001d6b3e95f7ae3da730e227eb85c73d9de55b56456cfb3a0
2022-01-12 09:16:24 +01:00
James Taylor
5ac51dfe74 fix and test is_dust 2022-01-11 18:21:35 -05:00
Steve Myers
04d58f7903 Merge commit 'refs/pull/508/head' of github.com:bitcoindevkit/bdk 2022-01-11 10:08:33 +01:00
Thomas Eizinger
380a4f2588 Disable reqwest's default features
By default, reqwest uses openssl for TLS. Any consumer wanting to use
rustls will thus pull in unnecessary dependencies. To make getting started
with bdk and reqwest easier, we add a `reqwest-default-tls` feature
that can be used to activate reqwest's `default-tls` feature. TLS is
necessary for the esplora integration. Adding this feature makes it possible
for people to use bdk with esplora without adding a reqwest dependency to
their manifest.
2022-01-10 13:57:22 +11:00
Steve Myers
9e30a79027 Fix CHANGELOG link for v0.15.0 2021-12-29 13:17:11 -08:00
Alekos Filini
fdb272e039 Merge bitcoindevkit/bdk#511: Fix nightly_docs.yml publish_docs 'Commit' step
947a9c29db Fix nightly_docs.yml publish_docs 'Commit' step (Steve Myers)

Pull request description:

  ### Description

  I forgot to fix in #503 the `nightly_docs.yaml` `publish_docs` `Commit` step to add new files for the path `./docs/.vuepress/public/docs-rs`.

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

ACKs for top commit:
  afilini:
    ACK 947a9c2

Tree-SHA512: d2bdbcb6cea46ec1949eba6f334acd5dbbe9b4b1323bb4713dc5d7f749666260ab05c29247c35f08c587b46d6bfb765c6a612c6522fd15211c84f7590f8c4748
2021-12-29 10:59:32 +01:00
Steve Myers
d2b6b5545e Bump version to 0.15.1-dev 2021-12-23 10:40:42 -08:00
Steve Myers
db6ffb90f0 Merge commit 'refs/pull/510/head' of github.com:bitcoindevkit/bdk 2021-12-23 10:34:58 -08:00
Steve Myers
947a9c29db Fix nightly_docs.yml publish_docs 'Commit' step 2021-12-23 10:23:11 -08:00
Alekos Filini
61ee2a9c1c Merge commit 'refs/pull/504/head' of github.com:bitcoindevkit/bdk 2021-12-23 12:24:52 +01:00
Alekos Filini
44e4c5dac5 Merge commit 'refs/pull/509/head' of github.com:bitcoindevkit/bdk 2021-12-23 12:22:09 +01:00
Alekos Filini
e09aaf055a Add a custom logo to our docs
As suggested in #497, add our logo to the docs as well
2021-12-23 11:37:41 +01:00
Alekos Filini
c40898ba08 Merge commit 'refs/pull/503/head' of github.com:bitcoindevkit/bdk 2021-12-23 11:34:34 +01:00
Steve Myers
2f98db8549 Add back old logo to static/bdk.svg
This is required so that old releases of bdk on crates.io won't show a
broken image link. Should be replaced with SVG version of new logo.
2021-12-22 21:38:54 -08:00
Steve Myers
4d7c4bc810 Bump version to 0.15.0 2021-12-22 21:10:27 -08:00
James Taylor
a0c140bb29 add doc comment for IsDust trait 2021-12-22 01:50:17 -05:00
James Taylor
bf5994b14a fixed fee in test, removed unnecessary comment 2021-12-19 18:37:05 -05:00
James Taylor
ca682819b3 using dust value from rust-bitcoin 2021-12-19 02:55:24 -05:00
mcroad
ee41d88f25 Test WIF from BIP39 words has correct network 2021-12-18 15:34:18 -06:00
mcroad
beb1e4114d Add fix to changelog 2021-12-18 15:13:09 -06:00
mcroad
af047f90db Set the correct inner private_key network 2021-12-18 15:10:25 -06:00
mcroad
d01ec6d259 Add test to ensure WIF uses the correct network 2021-12-18 15:08:16 -06:00
Alekos Filini
77bce06caf Update logo 2021-12-18 15:17:45 +01:00
Steve Myers
98c26a1ad9 Bump version to 0.15.0-rc.1 2021-12-17 21:41:06 -08:00
Steve Myers
1a907f8a53 [ci] Fix publish_docs job 2021-12-17 21:28:39 -08:00
Steve Myers
e82edbb7ac Merge bitcoindevkit/bdk#501: Only run clippy for the stable rust version
57a1185aef Only run clippy for the stable rust version (Steve Myers)

Pull request description:

  ### Description

  It was decided during the team call today (2021-12-14)  to only run clippy for the stable rust version.

  ### Notes to the reviewers

  This is required to fix the below build issues when running clippy on rust version 1.46.0.

  ```shell
  cargo clippy --all-targets --features async-interface --no-default-features -- -D warnings
  ```

  ```text
  ...

  Checking bitcoincore-rpc v0.14.0
  error: unknown clippy lint: clippy::no_effect_underscore_binding
    --> src/blockchain/mod.rs:88:1
     |
  88 | #[maybe_async]
     | ^^^^^^^^^^^^^^
     |
     = note: `-D clippy::unknown-clippy-lints` implied by `-D warnings`
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
     = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

  error: unknown clippy lint: clippy::no_effect_underscore_binding
     --> src/blockchain/mod.rs:220:1
      |
  220 | #[maybe_async]
      | ^^^^^^^^^^^^^^
      |
      = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
      = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
  ```

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

Top commit has no ACKs.

Tree-SHA512: 3fe0d2829415c7276d5339e217cefba1255c14d6d73ec0a5eff2b8072d189ffef56088623ef75f84e400d3d05e546f759b8048082b467a3738885796b3338323
2021-12-16 09:30:49 -08:00
Steve Myers
57a1185aef Only run clippy for the stable rust version 2021-12-16 09:12:43 -08:00
Steve Myers
64e88f0e00 Merge bitcoindevkit/bdk#492: bump electrsd dep to 0.13
f7f9bd2409 bump electrsd dep to 0.13 (Riccardo Casatta)

Pull request description:

  <!-- You can erase any parts of this template not applicable to your Pull Request. -->

  ### Description

  bump electrsd dep to 0.13, close #491

ACKs for top commit:
  rajarshimaitra:
    ACK f7f9bd2409
  notmandatory:
    utACK f7f9bd24

Tree-SHA512: 89a8094387896c9296e2f0120d7a2c7419e979049a12fc9a6ae7fe1810b75af43338db235887dd9054a6b52e400491b77f8e006f61451da54aca9635098ab342
2021-12-01 09:34:15 -08:00
Riccardo Casatta
f7f9bd2409 bump electrsd dep to 0.13 2021-12-01 08:45:32 +01:00
Steve Myers
68a3d2b1cc Merge bitcoindevkit/bdk#487: Use "Description" instead of "Descriptive Title" in SoB Issue Template
084ec036a5 Use "Description" instead of "Descriptive Title" (rajarshimaitra)

Pull request description:

  <!-- You can erase any parts of this template not applicable to your Pull Request. -->

  ### Description

  <!-- Describe the purpose of this PR, what's being adding and/or fixed -->

  The `Descriptive Title` part is already in the issue title, and we can use `Description` in the issue body.

  ### Notes to the reviewers

  <!-- In this section you can include notes directed to the reviewers, like explaining why some parts
  of the PR were done in a specific way -->

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

ACKs for top commit:
  thunderbiscuit:
    ACK 084ec03.
  notmandatory:
    ACK 084ec036a5

Tree-SHA512: 2fc365066849033cce056a46bc9e8ab2d931aa45fd2799ebebe3e07bb789c458491ebf2c05e5868bdff63f60dec0657043f3374135dcfbc79a1f89a2562b1883
2021-11-30 16:21:09 -08:00
Steve Myers
aa13186fb0 Merge bitcoindevkit/bdk#478: Fix typos in comments
7f8103dd76 Fix typos in comments (thunderbiscuit)

Pull request description:

  ### Description

  This PR fixes a bunch of small typos in comments. I'm getting acquainted with the codebase and found a few typos just by chance, and ended up going through it with an IDE searching for typos in all files.

  ### Notes to the reviewers

  To be clear, this PR _only addresses typos that are within comments_.

  ### Checklists

  * [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:
  notmandatory:
    ACK 7f8103dd76

Tree-SHA512: eb3f8f21cbd05de06292affd9ef69c21b52022dfdf25c562c8f4d9c9c011f18175dff0c650cb7efcfb2b665f2af80d9a153be3d12327c47796b0d00bfd5d9803
2021-11-30 16:19:53 -08:00
Steve Myers
02980881ac Merge bitcoindevkit/bdk#473: release/0.14.0
fed4a59728 Bump version to 0.14.1-dev (Steve Myers)
c175dd2aae Bump version to 0.14.0 (Steve Myers)
6b1cbcc4b7 Bump version to 0.14.0-rc.1 (Steve Myers)

Pull request description:

  ### Description

  Merge the 0.14.0 bdk release into the master branch.

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

Top commit has no ACKs.

Tree-SHA512: 0a897988c47f56e24b7e831cb45ff348b5637ca1a172555e9456539f5b75f263007421d63820b20737bcddb6f4c8077271471ea830e500ca6f88b902502f8186
2021-11-30 16:16:37 -08:00
rajarshimaitra
084ec036a5 Use "Description" instead of "Descriptive Title" 2021-11-30 12:26:36 +05:30
thunderbiscuit
7f8103dd76 Fix typos in comments 2021-11-23 14:09:54 -05:00
37 changed files with 634 additions and 589 deletions

View File

@@ -32,7 +32,7 @@ fundamentals - achievable over 12-weeks. Below are just a few types of ideas:
create new innovations and new perspectives for your project.
-->
**Descriptive Title**
**Description**
<!-- Description: 3-7 sentences describing the project background and tasks to be done. -->
**Expected Outcomes**

View File

@@ -10,8 +10,9 @@ jobs:
strategy:
matrix:
rust:
- 1.56.0 # STABLE
- 1.46.0 # MSRV
- version: 1.56.0 # STABLE
clippy: true
- version: 1.46.0 # MSRV
features:
- default
- minimal
@@ -31,7 +32,7 @@ jobs:
- name: checkout
uses: actions/checkout@v2
- name: Generate cache key
run: echo "${{ matrix.rust }} ${{ matrix.features }}" | tee .cache_key
run: echo "${{ matrix.rust.version }} ${{ matrix.features }}" | tee .cache_key
- name: cache
uses: actions/cache@v2
with:
@@ -41,16 +42,18 @@ jobs:
target
key: ${{ runner.os }}-cargo-${{ hashFiles('.cache_key') }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Set default toolchain
run: rustup default ${{ matrix.rust }}
run: rustup default ${{ matrix.rust.version }}
- name: Set profile
run: rustup set profile minimal
- name: Add clippy
if: ${{ matrix.rust.clippy }}
run: rustup component add clippy
- name: Update toolchain
run: rustup update
- name: Build
run: cargo build --features ${{ matrix.features }} --no-default-features
- name: Clippy
if: ${{ matrix.rust.clippy }}
run: cargo clippy --all-targets --features ${{ matrix.features }} --no-default-features -- -D warnings
- name: Test
run: cargo test --features ${{ matrix.features }} --no-default-features

View File

@@ -44,18 +44,18 @@ jobs:
repository: bitcoindevkit/bitcoindevkit.org
ref: master
- name: Create directories
run: mkdir -p ./static/docs-rs/bdk/nightly
run: mkdir -p ./docs/.vuepress/public/docs-rs/bdk/nightly
- name: Remove old latest
run: rm -rf ./static/docs-rs/bdk/nightly/latest
run: rm -rf ./docs/.vuepress/public/docs-rs/bdk/nightly/latest
- name: Download built docs
uses: actions/download-artifact@v1
with:
name: built-docs
path: ./static/docs-rs/bdk/nightly/latest
path: ./docs/.vuepress/public/docs-rs/bdk/nightly/latest
- name: Configure git
run: git config user.email "github-actions@github.com" && git config user.name "github-actions"
- name: Commit
continue-on-error: true # If there's nothing to commit this step fails, but it's fine
run: git add ./static && git commit -m "Publish autogenerated nightly docs"
run: git add ./docs/.vuepress/public/docs-rs && git commit -m "Publish autogenerated nightly docs"
- name: Push
run: git push origin master

View File

@@ -6,9 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Sync API change
To decouple the `Wallet` from the `Blockchain` we've made major changes:
- Removed `Blockchain` from Wallet.
- Removed `Wallet::broadcast` (just use `Blockchain::broadcast`)
- Depreciated `Wallet::new_offline` (all wallets are offline now)
- Changed `Wallet::sync` to take a `Blockchain`.
- Stop making a request for the block height when calling `Wallet:new`.
- Added `SyncOptions` to capture extra (future) arguments to `Wallet::sync`.
## [v0.15.0] - [v0.14.0]
- Overhauled sync logic for electrum and esplora.
- Unify ureq and reqwest esplora backends to have the same configuration parameters. This means reqwest now has a timeout parameter and ureq has a concurrency parameter.
- Fixed esplora fee estimation.
- Fixed generating WIF in the correct network format.
- Disable `reqwest` default features.
- Added `reqwest-default-tls` feature: Use this to restore the TLS defaults of reqwest if you don't want to add a dependency to it in your own manifest.
## [v0.14.0] - [v0.13.0]
@@ -401,3 +417,4 @@ final transaction is created by calling `finish` on the builder.
[v0.12.0]: https://github.com/bitcoindevkit/bdk/compare/v0.11.0...v0.12.0
[v0.13.0]: https://github.com/bitcoindevkit/bdk/compare/v0.12.0...v0.13.0
[v0.14.0]: https://github.com/bitcoindevkit/bdk/compare/v0.13.0...v0.14.0
[v0.15.0]: https://github.com/bitcoindevkit/bdk/compare/v0.14.0...v0.15.0

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk"
version = "0.14.1-dev"
version = "0.15.1-dev"
edition = "2018"
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
homepage = "https://bitcoindevkit.org"
@@ -25,7 +25,7 @@ sled = { version = "0.34", optional = true }
electrum-client = { version = "0.8", optional = true }
rusqlite = { version = "0.25.3", optional = true }
ahash = { version = "=0.7.4", optional = true }
reqwest = { version = "0.11", optional = true, features = ["json"] }
reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] }
ureq = { version = "~2.2.0", features = ["json"], optional = true }
futures = { version = "0.3", optional = true }
async-trait = { version = "0.1", optional = true }
@@ -82,6 +82,8 @@ use-esplora-ureq = ["esplora", "ureq", "ureq/socks"]
# Typical configurations will not need to use `esplora` feature directly.
esplora = []
# Use below feature with `use-esplora-reqwest` to enable reqwest default TLS support
reqwest-default-tls = ["reqwest/default-tls"]
# Debug/Test features
test-blockchains = ["bitcoincore-rpc", "electrum-client"]
@@ -94,7 +96,7 @@ test-md-docs = ["electrum"]
lazy_static = "1.4"
env_logger = "0.7"
clap = "2.33"
electrsd = { version= "0.12", features = ["trigger", "bitcoind_22_0"] }
electrsd = { version= "0.13", features = ["trigger", "bitcoind_22_0"] }
[[example]]
name = "address_validator"

View File

@@ -1,7 +1,7 @@
<div align="center">
<h1>BDK</h1>
<img src="./static/bdk.svg" width="220" />
<img src="./static/bdk.png" width="220" />
<p>
<strong>A modern, lightweight, descriptor-based wallet library written in Rust!</strong>
@@ -41,21 +41,21 @@ The `bdk` library aims to be the core building block for Bitcoin wallets of any
```rust,no_run
use bdk::Wallet;
use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
use bdk::blockchain::ElectrumBlockchain;
use bdk::SyncOptions;
use bdk::electrum_client::Client;
fn main() -> Result<(), bdk::Error> {
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet,
MemoryDatabase::default(),
ElectrumBlockchain::from(client)
)?;
wallet.sync(noop_progress(), None)?;
wallet.sync(&blockchain, SyncOptions::default())?;
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
@@ -70,7 +70,7 @@ use bdk::{Wallet, database::MemoryDatabase};
use bdk::wallet::AddressIndex::New;
fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new_offline(
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet,
@@ -88,9 +88,9 @@ fn main() -> Result<(), bdk::Error> {
### Create a transaction
```rust,no_run
use bdk::{FeeRate, Wallet};
use bdk::{FeeRate, Wallet, SyncOptions};
use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
use bdk::blockchain::ElectrumBlockchain;
use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New;
@@ -98,16 +98,15 @@ use bdk::wallet::AddressIndex::New;
use bitcoin::consensus::serialize;
fn main() -> Result<(), bdk::Error> {
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet,
MemoryDatabase::default(),
ElectrumBlockchain::from(client)
)?;
wallet.sync(noop_progress(), None)?;
wallet.sync(&blockchain, SyncOptions::default())?;
let send_to = wallet.get_address(New)?;
let (psbt, details) = {
@@ -135,7 +134,7 @@ use bdk::{Wallet, SignOptions, database::MemoryDatabase};
use bitcoin::consensus::deserialize;
fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new_offline(
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
bitcoin::Network::Testnet,

View File

@@ -48,8 +48,7 @@ impl AddressValidator for DummyValidator {
fn main() -> Result<(), bdk::Error> {
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
let mut wallet =
Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
let mut wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
wallet.add_address_validator(Arc::new(DummyValidator));

View File

@@ -10,7 +10,6 @@
// licenses.
use bdk::blockchain::compact_filters::*;
use bdk::blockchain::noop_progress;
use bdk::database::MemoryDatabase;
use bdk::*;
use bitcoin::*;
@@ -35,9 +34,8 @@ fn main() -> Result<(), CompactFiltersError> {
let descriptor = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)";
let database = MemoryDatabase::default();
let wallet =
Arc::new(Wallet::new(descriptor, None, Network::Testnet, database, blockchain).unwrap());
wallet.sync(noop_progress(), None).unwrap();
let wallet = Arc::new(Wallet::new(descriptor, None, Network::Testnet, database).unwrap());
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
info!("balance: {}", wallet.get_balance()?);
Ok(())
}

View File

@@ -89,7 +89,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.transpose()
.unwrap()
.unwrap_or(Network::Testnet);
let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?;
let wallet = Wallet::new(&format!("{}", descriptor), None, network, database)?;
info!("... First address: {}", wallet.get_address(New)?);

View File

@@ -16,61 +16,17 @@
//!
//! ## Example
//!
//! In this example both `wallet_electrum` and `wallet_esplora` have the same type of
//! `Wallet<AnyBlockchain, MemoryDatabase>`. This means that they could both, for instance, be
//! assigned to a struct member.
//!
//! ```no_run
//! # use bitcoin::Network;
//! # use bdk::blockchain::*;
//! # use bdk::database::MemoryDatabase;
//! # use bdk::Wallet;
//! # #[cfg(feature = "electrum")]
//! # {
//! let electrum_blockchain = ElectrumBlockchain::from(electrum_client::Client::new("...")?);
//! let wallet_electrum: Wallet<AnyBlockchain, _> = Wallet::new(
//! "...",
//! None,
//! Network::Testnet,
//! MemoryDatabase::default(),
//! electrum_blockchain.into(),
//! )?;
//! # }
//!
//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
//! # {
//! let esplora_blockchain = EsploraBlockchain::new("...", 20);
//! let wallet_esplora: Wallet<AnyBlockchain, _> = Wallet::new(
//! "...",
//! None,
//! Network::Testnet,
//! MemoryDatabase::default(),
//! esplora_blockchain.into(),
//! )?;
//! # }
//!
//! # Ok::<(), bdk::Error>(())
//! ```
//!
//! When paired with the use of [`ConfigurableBlockchain`], it allows creating wallets with any
//! blockchain type supported using a single line of code:
//!
//! ```no_run
//! # use bitcoin::Network;
//! # use bdk::blockchain::*;
//! # use bdk::database::MemoryDatabase;
//! # use bdk::Wallet;
//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
//! # {
//! let config = serde_json::from_str("...")?;
//! let blockchain = AnyBlockchain::from_config(&config)?;
//! let wallet = Wallet::new(
//! "...",
//! None,
//! Network::Testnet,
//! MemoryDatabase::default(),
//! blockchain,
//! )?;
//! let height = blockchain.get_height();
//! # }
//! # Ok::<(), bdk::Error>(())
//! ```
@@ -133,21 +89,6 @@ impl Blockchain for AnyBlockchain {
maybe_await!(impl_inner_method!(self, get_capabilities))
}
fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
database: &mut D,
progress_update: P,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(self, setup, database, progress_update))
}
fn sync<D: BatchDatabase, P: 'static + Progress>(
&self,
database: &mut D,
progress_update: P,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(self, sync, database, progress_update))
}
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
maybe_await!(impl_inner_method!(self, get_tx, txid))
}
@@ -155,11 +96,42 @@ impl Blockchain for AnyBlockchain {
maybe_await!(impl_inner_method!(self, broadcast, tx))
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
maybe_await!(impl_inner_method!(self, estimate_fee, target))
}
}
impl GetHeight for AnyBlockchain {
fn get_height(&self) -> Result<u32, Error> {
maybe_await!(impl_inner_method!(self, get_height))
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
maybe_await!(impl_inner_method!(self, estimate_fee, target))
}
impl WalletSync for AnyBlockchain {
fn wallet_sync<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(
self,
wallet_sync,
database,
progress_update
))
}
fn wallet_setup<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(
self,
wallet_setup,
database,
progress_update
))
}
}

View File

@@ -67,7 +67,7 @@ mod peer;
mod store;
mod sync;
use super::{Blockchain, Capability, ConfigurableBlockchain, Progress};
use super::{Blockchain, Capability, ConfigurableBlockchain, GetHeight, Progress, WalletSync};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::error::Error;
use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
@@ -226,11 +226,36 @@ impl Blockchain for CompactFiltersBlockchain {
vec![Capability::FullHistory].into_iter().collect()
}
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(self.peers[0]
.get_mempool()
.get_tx(&Inventory::Transaction(*txid)))
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
self.peers[0].broadcast_tx(tx.clone())?;
Ok(())
}
fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
// TODO
Ok(FeeRate::default())
}
}
impl GetHeight for CompactFiltersBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Ok(self.headers.get_height()? as u32)
}
}
impl WalletSync for CompactFiltersBlockchain {
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
fn setup<D: BatchDatabase, P: 'static + Progress>(
fn wallet_setup<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: P,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
let first_peer = &self.peers[0];
@@ -431,27 +456,6 @@ impl Blockchain for CompactFiltersBlockchain {
Ok(())
}
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(self.peers[0]
.get_mempool()
.get_tx(&Inventory::Transaction(*txid)))
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
self.peers[0].broadcast_tx(tx.clone())?;
Ok(())
}
fn get_height(&self) -> Result<u32, Error> {
Ok(self.headers.get_height()? as u32)
}
fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
// TODO
Ok(FeeRate::default())
}
}
/// Data to connect to a Bitcoin P2P peer

View File

@@ -398,7 +398,7 @@ impl ChainStore<Full> {
);
}
// Delete full blocks overriden by snapshot
// Delete full blocks overridden by snapshot
let from_key = StoreEntry::Block(Some(snaphost.min_height)).get_key();
let to_key = StoreEntry::Block(Some(usize::MAX)).get_key();
batch.delete_range(&from_key, &to_key);

View File

@@ -68,10 +68,37 @@ impl Blockchain for ElectrumBlockchain {
.collect()
}
fn setup<D: BatchDatabase, P: Progress>(
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(self.client.transaction_get(txid).map(Option::Some)?)
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
Ok(self.client.transaction_broadcast(tx).map(|_| ())?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
Ok(FeeRate::from_btc_per_kvb(
self.client.estimate_fee(target)? as f32
))
}
}
impl GetHeight for ElectrumBlockchain {
fn get_height(&self) -> Result<u32, Error> {
// TODO: unsubscribe when added to the client, or is there a better call to use here?
Ok(self
.client
.block_headers_subscribe()
.map(|data| data.height as u32)?)
}
}
impl WalletSync for ElectrumBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &mut D,
_progress_update: P,
_progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
let mut request = script_sync::start(database, self.stop_gap)?;
let mut block_times = HashMap::<u32, u32>::new();
@@ -205,29 +232,6 @@ impl Blockchain for ElectrumBlockchain {
database.commit_batch(batch_update)?;
Ok(())
}
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(self.client.transaction_get(txid).map(Option::Some)?)
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
Ok(self.client.transaction_broadcast(tx).map(|_| ())?)
}
fn get_height(&self) -> Result<u32, Error> {
// TODO: unsubscribe when added to the client, or is there a better call to use here?
Ok(self
.client
.block_headers_subscribe()
.map(|data| data.height as u32)?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
Ok(FeeRate::from_btc_per_kvb(
self.client.estimate_fee(target)? as f32
))
}
}
struct TxCache<'a, 'b, D> {

View File

@@ -91,7 +91,30 @@ impl Blockchain for EsploraBlockchain {
.collect()
}
fn setup<D: BatchDatabase, P: Progress>(
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(await_or_block!(self.url_client._get_tx(txid))?)
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
Ok(await_or_block!(self.url_client._broadcast(tx))?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let estimates = await_or_block!(self.url_client._get_fee_estimates())?;
super::into_fee_rate(target, estimates)
}
}
#[maybe_async]
impl GetHeight for EsploraBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Ok(await_or_block!(self.url_client._get_height())?)
}
}
#[maybe_async]
impl WalletSync for EsploraBlockchain {
fn wallet_setup<D: BatchDatabase, P: Progress>(
&self,
database: &mut D,
_progress_update: P,
@@ -180,23 +203,6 @@ impl Blockchain for EsploraBlockchain {
Ok(())
}
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(await_or_block!(self.url_client._get_tx(txid))?)
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
Ok(await_or_block!(self.url_client._broadcast(tx))?)
}
fn get_height(&self) -> Result<u32, Error> {
Ok(await_or_block!(self.url_client._get_height())?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let estimates = await_or_block!(self.url_client._get_fee_estimates())?;
super::into_fee_rate(target, estimates)
}
}
impl UrlClient {

View File

@@ -87,10 +87,32 @@ impl Blockchain for EsploraBlockchain {
.collect()
}
fn setup<D: BatchDatabase, P: Progress>(
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(self.url_client._get_tx(txid)?)
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
let _txid = self.url_client._broadcast(tx)?;
Ok(())
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let estimates = self.url_client._get_fee_estimates()?;
super::into_fee_rate(target, estimates)
}
}
impl GetHeight for EsploraBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Ok(self.url_client._get_height()?)
}
}
impl WalletSync for EsploraBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &mut D,
_progress_update: P,
_progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
use crate::blockchain::script_sync::Request;
let mut request = script_sync::start(database, self.stop_gap)?;
@@ -179,24 +201,6 @@ impl Blockchain for EsploraBlockchain {
Ok(())
}
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(self.url_client._get_tx(txid)?)
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
let _txid = self.url_client._broadcast(tx)?;
Ok(())
}
fn get_height(&self) -> Result<u32, Error> {
Ok(self.url_client._get_height()?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let estimates = self.url_client._get_fee_estimates()?;
super::into_fee_rate(target, estimates)
}
}
impl UrlClient {

View File

@@ -86,28 +86,45 @@ pub enum Capability {
/// Trait that defines the actions that must be supported by a blockchain backend
#[maybe_async]
pub trait Blockchain {
pub trait Blockchain: WalletSync + GetHeight {
/// Return the set of [`Capability`] supported by this backend
fn get_capabilities(&self) -> HashSet<Capability>;
/// Fetch a transaction from the blockchain given its txid
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
/// Broadcast a transaction
fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
/// Estimate the fee rate required to confirm a transaction in a given `target` of blocks
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
}
/// Trait for getting the current height of the blockchain.
#[maybe_async]
pub trait GetHeight {
/// Return the current height
fn get_height(&self) -> Result<u32, Error>;
}
/// Trait for blockchains that can sync by updating the database directly.
#[maybe_async]
pub trait WalletSync {
/// Setup the backend and populate the internal database for the first time
///
/// This method is the equivalent of [`Blockchain::sync`], but it's guaranteed to only be
/// This method is the equivalent of [`Self::wallet_sync`], but it's guaranteed to only be
/// called once, at the first [`Wallet::sync`](crate::wallet::Wallet::sync).
///
/// The rationale behind the distinction between `sync` and `setup` is that some custom backends
/// might need to perform specific actions only the first time they are synced.
///
/// For types that do not have that distinction, only this method can be implemented, since
/// [`Blockchain::sync`] defaults to calling this internally if not overridden.
fn setup<D: BatchDatabase, P: 'static + Progress>(
/// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden.
/// Populate the internal database with transactions and UTXOs
fn wallet_setup<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: P,
progress_update: Box<dyn Progress>,
) -> Result<(), Error>;
/// Populate the internal database with transactions and UTXOs
///
/// If not overridden, it defaults to calling [`Blockchain::setup`] internally.
/// If not overridden, it defaults to calling [`Self::wallet_setup`] internally.
///
/// This method should implement the logic required to iterate over the list of the wallet's
/// script_pubkeys using [`Database::iter_script_pubkeys`] and look for relevant transactions
@@ -124,23 +141,13 @@ pub trait Blockchain {
/// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx
/// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
fn sync<D: BatchDatabase, P: 'static + Progress>(
fn wallet_sync<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: P,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.setup(database, progress_update))
maybe_await!(self.wallet_setup(database, progress_update))
}
/// Fetch a transaction from the blockchain given its txid
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
/// Broadcast a transaction
fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
/// Return the current height
fn get_height(&self) -> Result<u32, Error>;
/// Estimate the fee rate required to confirm a transaction in a given `target` of blocks
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
}
/// Trait for [`Blockchain`] types that can be created given a configuration
@@ -155,9 +162,9 @@ pub trait ConfigurableBlockchain: Blockchain + Sized {
/// Data sent with a progress update over a [`channel`]
pub type ProgressData = (f32, Option<String>);
/// Trait for types that can receive and process progress updates during [`Blockchain::sync`] and
/// [`Blockchain::setup`]
pub trait Progress: Send {
/// Trait for types that can receive and process progress updates during [`WalletSync::wallet_sync`] and
/// [`WalletSync::wallet_setup`]
pub trait Progress: Send + 'static + core::fmt::Debug {
/// Send a new progress update
///
/// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an
@@ -182,7 +189,7 @@ impl Progress for Sender<ProgressData> {
}
/// Type that implements [`Progress`] and drops every update received
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Default, Debug)]
pub struct NoopProgress;
/// Create a new instance of [`NoopProgress`]
@@ -197,7 +204,7 @@ impl Progress for NoopProgress {
}
/// Type that implements [`Progress`] and logs at level `INFO` every update received
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Default, Debug)]
pub struct LogProgress;
/// Create a new instance of [`LogProgress`]
@@ -223,22 +230,6 @@ impl<T: Blockchain> Blockchain for Arc<T> {
maybe_await!(self.deref().get_capabilities())
}
fn setup<D: BatchDatabase, P: 'static + Progress>(
&self,
database: &mut D,
progress_update: P,
) -> Result<(), Error> {
maybe_await!(self.deref().setup(database, progress_update))
}
fn sync<D: BatchDatabase, P: 'static + Progress>(
&self,
database: &mut D,
progress_update: P,
) -> Result<(), Error> {
maybe_await!(self.deref().sync(database, progress_update))
}
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
maybe_await!(self.deref().get_tx(txid))
}
@@ -246,10 +237,33 @@ impl<T: Blockchain> Blockchain for Arc<T> {
maybe_await!(self.deref().broadcast(tx))
}
fn get_height(&self) -> Result<u32, Error> {
maybe_await!(self.deref().get_height())
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
maybe_await!(self.deref().estimate_fee(target))
}
}
#[maybe_async]
impl<T: GetHeight> GetHeight for Arc<T> {
fn get_height(&self) -> Result<u32, Error> {
maybe_await!(self.deref().get_height())
}
}
#[maybe_async]
impl<T: WalletSync> WalletSync for Arc<T> {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.deref().wallet_setup(database, progress_update))
}
fn wallet_sync<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.deref().wallet_sync(database, progress_update))
}
}

View File

@@ -33,7 +33,9 @@
use crate::bitcoin::consensus::deserialize;
use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid};
use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress};
use crate::blockchain::{
Blockchain, Capability, ConfigurableBlockchain, GetHeight, Progress, WalletSync,
};
use crate::database::{BatchDatabase, DatabaseUtils};
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
use bitcoincore_rpc::json::{
@@ -139,10 +141,37 @@ impl Blockchain for RpcBlockchain {
self.capabilities.clone()
}
fn setup<D: BatchDatabase, P: 'static + Progress>(
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(Some(self.client.get_raw_transaction(txid, None)?))
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let sat_per_kb = self
.client
.estimate_smart_fee(target as u16, None)?
.fee_rate
.ok_or(Error::FeeRateUnavailable)?
.as_sat() as f64;
Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
}
}
impl GetHeight for RpcBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
}
}
impl WalletSync for RpcBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &mut D,
progress_update: P,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
let mut scripts_pubkeys = database.iter_script_pubkeys(Some(KeychainKind::External))?;
scripts_pubkeys.extend(database.iter_script_pubkeys(Some(KeychainKind::Internal))?);
@@ -187,13 +216,13 @@ impl Blockchain for RpcBlockchain {
}
}
self.sync(database, progress_update)
self.wallet_sync(database, progress_update)
}
fn sync<D: BatchDatabase, P: 'static + Progress>(
fn wallet_sync<D: BatchDatabase>(
&self,
db: &mut D,
_progress_update: P,
_progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
let mut indexes = HashMap::new();
for keykind in &[KeychainKind::External, KeychainKind::Internal] {
@@ -323,29 +352,6 @@ impl Blockchain for RpcBlockchain {
Ok(())
}
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
Ok(Some(self.client.get_raw_transaction(txid, None)?))
}
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
}
fn get_height(&self) -> Result<u32, Error> {
Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
}
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
let sat_per_kb = self
.client
.estimate_smart_fee(target as u16, None)?
.fee_rate
.ok_or(Error::FeeRateUnavailable)?
.as_sat() as f64;
Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
}
}
impl ConfigurableBlockchain for RpcBlockchain {

View File

@@ -23,12 +23,12 @@
//! # use bdk::database::{AnyDatabase, MemoryDatabase};
//! # use bdk::{Wallet};
//! let memory = MemoryDatabase::default();
//! let wallet_memory = Wallet::new_offline("...", None, Network::Testnet, memory)?;
//! let wallet_memory = Wallet::new("...", None, Network::Testnet, memory)?;
//!
//! # #[cfg(feature = "key-value-db")]
//! # {
//! let sled = sled::open("my-database")?.open_tree("default_tree")?;
//! let wallet_sled = Wallet::new_offline("...", None, Network::Testnet, sled)?;
//! let wallet_sled = Wallet::new("...", None, Network::Testnet, sled)?;
//! # }
//! # Ok::<(), bdk::Error>(())
//! ```
@@ -42,7 +42,7 @@
//! # use bdk::{Wallet};
//! let config = serde_json::from_str("...")?;
//! let database = AnyDatabase::from_config(&config)?;
//! let wallet = Wallet::new_offline("...", None, Network::Testnet, database)?;
//! let wallet = Wallet::new("...", None, Network::Testnet, database)?;
//! # Ok::<(), bdk::Error>(())
//! ```

View File

@@ -109,7 +109,7 @@ fn after(key: &[u8]) -> Vec<u8> {
/// Once it's dropped its content will be lost.
///
/// If you are looking for a permanent storage solution, you can try with the default key-value
/// database called [`sled`]. See the [`database`] module documentation for more defailts.
/// database called [`sled`]. See the [`database`] module documentation for more details.
///
/// [`database`]: crate::database
#[derive(Debug, Default)]
@@ -555,7 +555,7 @@ macro_rules! doctest_wallet {
Some(100),
);
$crate::Wallet::new_offline(
$crate::Wallet::new(
&descriptors.0,
descriptors.1.as_ref(),
Network::Regtest,

View File

@@ -149,7 +149,7 @@ pub trait Database: BatchOperations {
fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
/// Fetch the transaction metadata and optionally also the raw transaction
fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error>;
/// Return the last defivation index for a keychain.
/// Return the last derivation index for a keychain.
fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error>;
/// Return the sync time, if present
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error>;

View File

@@ -521,7 +521,7 @@ macro_rules! fragment_internal {
// three operands it's (X, (X, (X, ()))), etc.
//
// To check that the right number of arguments has been passed we can "cast" those tuples to
// more convenient structures like `TupleTwo`. If the conversion succedes, the right number of
// more convenient structures like `TupleTwo`. If the conversion succeeds, the right number of
// args was passed. Otherwise the compilation fails entirely.
( @t $op:ident ( $( $args:tt )* ) $( $tail:tt )* ) => ({
($crate::fragment!( $op ( $( $args )* ) ), $crate::fragment_internal!( @t $( $tail )* ))

View File

@@ -667,7 +667,7 @@ mod test {
// make a descriptor out of it
let desc = crate::descriptor!(wpkh(key)).unwrap();
// this should conver the key that supports "any_network" to the right network (testnet)
// this should convert the key that supports "any_network" to the right network (testnet)
let (wallet_desc, _) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();

View File

@@ -354,9 +354,9 @@ impl Satisfaction {
indexes
.into_iter()
// .inspect(|x| println!("--- orig --- {:?}", x))
// we map each of the combinations of elements into a tuple of ([choosen items], [conditions]). unfortunately, those items have potentially more than one
// we map each of the combinations of elements into a tuple of ([chosen items], [conditions]). unfortunately, those items have potentially more than one
// condition (think about ORs), so we also use `mix` to expand those, i.e. [[0], [1, 2]] becomes [[0, 1], [0, 2]]. This is necessary to make sure that we
// consider every possibile options and check whether or not they are compatible.
// consider every possible options and check whether or not they are compatible.
.map(|i_vec| {
mix(i_vec
.iter()

View File

@@ -79,7 +79,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline(
/// let wallet = Wallet::new(
/// P2Pkh(key),
/// None,
/// Network::Testnet,
@@ -113,7 +113,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline(
/// let wallet = Wallet::new(
/// P2Wpkh_P2Sh(key),
/// None,
/// Network::Testnet,
@@ -148,7 +148,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline(
/// let wallet = Wallet::new(
/// P2Wpkh(key),
/// None,
/// Network::Testnet,
@@ -186,7 +186,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
/// use bdk::template::Bip44;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline(
/// let wallet = Wallet::new(
/// Bip44(key.clone(), KeychainKind::External),
/// Some(Bip44(key, KeychainKind::Internal)),
/// Network::Testnet,
@@ -226,7 +226,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline(
/// let wallet = Wallet::new(
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,
@@ -262,7 +262,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
/// use bdk::template::Bip49;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline(
/// let wallet = Wallet::new(
/// Bip49(key.clone(), KeychainKind::External),
/// Some(Bip49(key, KeychainKind::Internal)),
/// Network::Testnet,
@@ -302,7 +302,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline(
/// let wallet = Wallet::new(
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,
@@ -338,7 +338,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
/// use bdk::template::Bip84;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline(
/// let wallet = Wallet::new(
/// Bip84(key.clone(), KeychainKind::External),
/// Some(Bip84(key, KeychainKind::Internal)),
/// Network::Testnet,
@@ -378,7 +378,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline(
/// let wallet = Wallet::new(
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,

View File

@@ -115,7 +115,7 @@ pub enum Error {
Hex(bitcoin::hashes::hex::Error),
/// Partially signed bitcoin transaction error
Psbt(bitcoin::util::psbt::Error),
/// Partially signed bitcoin transaction parseerror
/// Partially signed bitcoin transaction parse error
PsbtParse(bitcoin::util::psbt::PsbtParseError),
//KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),

View File

@@ -319,6 +319,7 @@ impl<Ctx: ScriptContext> ExtendedKey<Ctx> {
match self {
ExtendedKey::Private((mut xprv, _)) => {
xprv.network = network;
xprv.private_key.network = network;
Some(xprv)
}
ExtendedKey::Public(_) => None,
@@ -356,7 +357,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
/// Trait for keys that can be derived.
///
/// When extra metadata are provided, a [`DerivableKey`] can be transofrmed into a
/// When extra metadata are provided, a [`DerivableKey`] can be transformed into a
/// [`DescriptorKey`]: the trait [`IntoDescriptorKey`] is automatically implemented
/// for `(DerivableKey, DerivationPath)` and
/// `(DerivableKey, KeySource, DerivationPath)` tuples.
@@ -931,4 +932,43 @@ pub mod test {
"L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch"
);
}
#[test]
fn test_keys_wif_network() {
// test mainnet wif
let generated_xprv: GeneratedKey<_, miniscript::Segwitv0> =
bip32::ExtendedPrivKey::generate_with_entropy_default(TEST_ENTROPY).unwrap();
let xkey = generated_xprv.into_extended_key().unwrap();
let network = Network::Bitcoin;
let xprv = xkey.into_xprv(network).unwrap();
let wif = PrivateKey::from_wif(&xprv.private_key.to_wif()).unwrap();
assert_eq!(wif.network, network);
// test testnet wif
let generated_xprv: GeneratedKey<_, miniscript::Segwitv0> =
bip32::ExtendedPrivKey::generate_with_entropy_default(TEST_ENTROPY).unwrap();
let xkey = generated_xprv.into_extended_key().unwrap();
let network = Network::Testnet;
let xprv = xkey.into_xprv(network).unwrap();
let wif = PrivateKey::from_wif(&xprv.private_key.to_wif()).unwrap();
assert_eq!(wif.network, network);
}
#[cfg(feature = "keys-bip39")]
#[test]
fn test_keys_wif_network_bip39() {
let xkey: ExtendedKey = bip39::Mnemonic::parse_in(
bip39::Language::English,
"jelly crash boy whisper mouse ecology tuna soccer memory million news short",
)
.unwrap()
.into_extended_key()
.unwrap();
let xprv = xkey.into_xprv(Network::Testnet).unwrap();
let wif = PrivateKey::from_wif(&xprv.private_key.to_wif()).unwrap();
assert_eq!(wif.network, Network::Testnet);
}
}

View File

@@ -14,6 +14,10 @@
// only enables the `doc_cfg` feature when
// the `docsrs` configuration attribute is defined
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(
docsrs,
doc(html_logo_url = "https://github.com/bitcoindevkit/bdk/raw/master/static/bdk.png")
)]
//! A modern, lightweight, descriptor-based wallet library written in Rust.
//!
@@ -40,7 +44,7 @@
//! interact with the bitcoin P2P network.
//!
//! ```toml
//! bdk = "0.14.0"
//! bdk = "0.15.0"
//! ```
#![cfg_attr(
feature = "electrum",
@@ -49,22 +53,22 @@
### Example
```no_run
use bdk::Wallet;
use bdk::{Wallet, SyncOptions};
use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
use bdk::blockchain::ElectrumBlockchain;
use bdk::electrum_client::Client;
fn main() -> Result<(), bdk::Error> {
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
let blockchain = ElectrumBlockchain::from(client);
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet,
MemoryDatabase::default(),
ElectrumBlockchain::from(client)
)?;
wallet.sync(noop_progress(), None)?;
wallet.sync(&blockchain, SyncOptions::default())?;
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
@@ -83,7 +87,7 @@ fn main() -> Result<(), bdk::Error> {
//! use bdk::wallet::AddressIndex::New;
//!
//! fn main() -> Result<(), bdk::Error> {
//! let wallet = Wallet::new_offline(
//! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
//! bitcoin::Network::Testnet,
@@ -104,9 +108,9 @@ fn main() -> Result<(), bdk::Error> {
### Example
```no_run
use bdk::{FeeRate, Wallet};
use bdk::{FeeRate, Wallet, SyncOptions};
use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
use bdk::blockchain::ElectrumBlockchain;
use bdk::electrum_client::Client;
use bitcoin::consensus::serialize;
@@ -119,10 +123,10 @@ fn main() -> Result<(), bdk::Error> {
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet,
MemoryDatabase::default(),
ElectrumBlockchain::from(client)
)?;
let blockchain = ElectrumBlockchain::from(client);
wallet.sync(noop_progress(), None)?;
wallet.sync(&blockchain, SyncOptions::default())?;
let send_to = wallet.get_address(New)?;
let (psbt, details) = {
@@ -156,7 +160,7 @@ fn main() -> Result<(), bdk::Error> {
//! use bdk::database::MemoryDatabase;
//!
//! fn main() -> Result<(), bdk::Error> {
//! let wallet = Wallet::new_offline(
//! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
//! bitcoin::Network::Testnet,
@@ -268,6 +272,7 @@ pub use wallet::address_validator;
pub use wallet::signer;
pub use wallet::signer::SignOptions;
pub use wallet::tx_builder::TxBuilder;
pub use wallet::SyncOptions;
pub use wallet::Wallet;
/// Get the version of BDK at runtime
@@ -275,7 +280,7 @@ pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION", "unknown")
}
// We should consider putting this under a feature flag but we need the macro in doctets so we need
// We should consider putting this under a feature flag but we need the macro in doctests so we need
// to wait until https://github.com/rust-lang/rust/issues/67295 is fixed.
//
// Stuff in here is too rough to document atm

View File

@@ -355,10 +355,10 @@ macro_rules! bdk_blockchain_tests {
mod bdk_blockchain_tests {
use $crate::bitcoin::Network;
use $crate::testutils::blockchain_tests::TestClient;
use $crate::blockchain::noop_progress;
use $crate::blockchain::Blockchain;
use $crate::database::MemoryDatabase;
use $crate::types::KeychainKind;
use $crate::{Wallet, FeeRate};
use $crate::{Wallet, FeeRate, SyncOptions};
use $crate::testutils;
use super::*;
@@ -369,11 +369,11 @@ macro_rules! bdk_blockchain_tests {
$block
}
fn get_wallet_from_descriptors(descriptors: &(String, Option<String>), test_client: &TestClient) -> Wallet<$blockchain, MemoryDatabase> {
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new(), get_blockchain(test_client)).unwrap()
fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<MemoryDatabase> {
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new()).unwrap()
}
fn init_single_sig() -> (Wallet<$blockchain, MemoryDatabase>, (String, Option<String>), TestClient) {
fn init_single_sig() -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
let _ = env_logger::try_init();
let descriptors = testutils! {
@@ -381,13 +381,14 @@ macro_rules! bdk_blockchain_tests {
};
let test_client = TestClient::default();
let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
let blockchain = get_blockchain(&test_client);
let wallet = get_wallet_from_descriptors(&descriptors);
// rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool
#[cfg(feature = "test-rpc")]
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
(wallet, descriptors, test_client)
(wallet, blockchain, descriptors, test_client)
}
#[test]
@@ -395,7 +396,7 @@ macro_rules! bdk_blockchain_tests {
use std::ops::Deref;
use crate::database::Database;
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let tx = testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
@@ -408,7 +409,7 @@ macro_rules! bdk_blockchain_tests {
#[cfg(not(feature = "test-rpc"))]
assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none");
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated");
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
@@ -423,7 +424,7 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_stop_gap_20() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
test_client.receive(testutils! {
@tx ( (@external descriptors, 5) => 50_000 )
@@ -432,7 +433,7 @@ macro_rules! bdk_blockchain_tests {
@tx ( (@external descriptors, 25) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
@@ -440,16 +441,16 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_before_and_after_receive() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
@@ -457,13 +458,13 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_multiple_outputs_same_tx() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
@@ -478,7 +479,7 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_receive_multi() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
@@ -487,7 +488,7 @@ macro_rules! bdk_blockchain_tests {
@tx ( (@external descriptors, 5) => 25_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
@@ -496,32 +497,32 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_address_reuse() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 25_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
}
#[test]
fn test_sync_receive_rbf_replaced() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
@@ -535,7 +536,7 @@ macro_rules! bdk_blockchain_tests {
let new_txid = test_client.bump_fee(&txid);
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump");
@@ -553,13 +554,13 @@ macro_rules! bdk_blockchain_tests {
#[cfg(not(feature = "esplora"))]
#[test]
fn test_sync_reorg_block() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
@@ -572,7 +573,7 @@ macro_rules! bdk_blockchain_tests {
// Invalidate 1 block
test_client.invalidate(1);
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate");
@@ -583,15 +584,15 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_after_send() {
let (wallet, descriptors, mut test_client) = init_single_sig();
println!("{}", descriptors.0);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
println!("{}", descriptors.0);
let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
@@ -601,8 +602,8 @@ macro_rules! bdk_blockchain_tests {
assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx();
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
wallet.broadcast(&tx).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&tx).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
@@ -613,16 +614,16 @@ macro_rules! bdk_blockchain_tests {
/// The coins should only be received once!
#[test]
fn test_sync_double_receive() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None), &test_client);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None));
// need to sync so rpc can start watching
receiver_wallet.sync(noop_progress(), None).unwrap();
receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap();
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
@@ -644,16 +645,16 @@ macro_rules! bdk_blockchain_tests {
psbt.extract_tx()
};
wallet.broadcast(&tx1).unwrap();
wallet.broadcast(&tx2).unwrap();
blockchain.broadcast(&tx1).unwrap();
blockchain.broadcast(&tx2).unwrap();
receiver_wallet.sync(noop_progress(), None).unwrap();
receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(receiver_wallet.get_balance().unwrap(), 49_000, "should have received coins once and only once");
}
#[test]
fn test_sync_many_sends_to_a_single_address() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
for _ in 0..4 {
// split this up into multiple blocks so rpc doesn't get angry
@@ -672,22 +673,22 @@ macro_rules! bdk_blockchain_tests {
});
}
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 100_000);
}
#[test]
fn test_update_confirmation_time_after_generate() {
let (wallet, descriptors, mut test_client) = init_single_sig();
println!("{}", descriptors.0);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
println!("{}", descriptors.0);
let node_addr = test_client.get_node_address(None);
let received_txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
@@ -695,7 +696,7 @@ macro_rules! bdk_blockchain_tests {
assert!(details.confirmation_time.is_none());
test_client.generate(1, Some(node_addr));
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
let details = tx_map.get(&received_txid).unwrap();
@@ -705,13 +706,13 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_outgoing_from_scratch() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let received_txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
@@ -720,25 +721,26 @@ macro_rules! bdk_blockchain_tests {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(&psbt.extract_tx()).unwrap();
let sent_tx = psbt.extract_tx();
blockchain.broadcast(&sent_tx).unwrap();
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
// empty wallet
let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
let wallet = get_wallet_from_descriptors(&descriptors);
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
test_client.generate(1, Some(node_addr));
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
let received = tx_map.get(&received_txid).unwrap();
assert_eq!(received.received, 50_000, "incorrect received from receiver");
assert_eq!(received.sent, 0, "incorrect sent from receiver");
let sent = tx_map.get(&sent_txid).unwrap();
let sent = tx_map.get(&sent_tx.txid()).unwrap();
assert_eq!(sent.received, details.received, "incorrect received from sender");
assert_eq!(sent.sent, details.sent, "incorrect sent from sender");
assert_eq!(sent.fee.unwrap_or(0), details.fee.unwrap_or(0), "incorrect fees from sender");
@@ -746,14 +748,14 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_long_change_chain() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut total_sent = 0;
@@ -763,38 +765,38 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap();
blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
total_sent += 5_000 + details.fee.unwrap_or(0);
}
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain");
// empty wallet
let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
let wallet = get_wallet_from_descriptors(&descriptors);
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
test_client.generate(1, Some(node_addr));
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet");
}
#[test]
fn test_sync_bump_fee_basic() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
@@ -802,8 +804,8 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received");
@@ -812,8 +814,8 @@ macro_rules! bdk_blockchain_tests {
let (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance from received after bump");
@@ -822,14 +824,14 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_bump_fee_remove_change() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
@@ -837,18 +839,18 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect received after send");
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(5.0));
builder.fee_rate(FeeRate::from_sat_per_vb(5.1));
let (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal");
assert_eq!(new_details.received, 0, "incorrect received after change removal");
@@ -857,14 +859,14 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_bump_fee_add_input_simple() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
let mut builder = wallet.build_tx();
@@ -872,8 +874,8 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
@@ -882,22 +884,22 @@ macro_rules! bdk_blockchain_tests {
let (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(new_details.sent, 75_000, "incorrect sent");
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input");
}
#[test]
fn test_sync_bump_fee_add_input_no_change() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
let mut builder = wallet.build_tx();
@@ -905,8 +907,8 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
@@ -917,8 +919,8 @@ macro_rules! bdk_blockchain_tests {
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(new_details.sent, 75_000, "incorrect sent");
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input");
assert_eq!(new_details.received, 0, "incorrect received after add input");
@@ -927,13 +929,13 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_add_data() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let _ = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
@@ -946,22 +948,22 @@ macro_rules! bdk_blockchain_tests {
let tx = psbt.extract_tx();
let serialized_tx = bitcoin::consensus::encode::serialize(&tx);
assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction");
let sent_txid = wallet.broadcast(&tx).unwrap();
blockchain.broadcast(&tx).unwrap();
test_client.generate(1, Some(node_addr));
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send");
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
let _ = tx_map.get(&sent_txid).unwrap();
let _ = tx_map.get(&tx.txid()).unwrap();
}
#[test]
fn test_sync_receive_coinbase() {
let (wallet, _, mut test_client) = init_single_sig();
let (wallet, blockchain, _, mut test_client) = init_single_sig();
let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance");
test_client.generate(1, Some(wallet_addr));
@@ -974,7 +976,7 @@ macro_rules! bdk_blockchain_tests {
}
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase");
}
@@ -987,7 +989,7 @@ macro_rules! bdk_blockchain_tests {
use bitcoincore_rpc::jsonrpc::serde_json::Value;
use bitcoincore_rpc::{Auth, Client, RpcApi};
let (wallet, descriptors, mut test_client) = init_single_sig();
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
// TODO remove once rust-bitcoincore-rpc with PR 199 released
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199
@@ -1050,7 +1052,7 @@ macro_rules! bdk_blockchain_tests {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance");
// 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet
@@ -1061,8 +1063,8 @@ macro_rules! bdk_blockchain_tests {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "wallet cannot finalize transaction");
let tx = psbt.extract_tx();
wallet.broadcast(&tx).unwrap();
wallet.sync(noop_progress(), None).unwrap();
blockchain.broadcast(&tx).unwrap();
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "wallet has incorrect balance after send");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "wallet has incorrect number of txs");
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents");

View File

@@ -217,7 +217,7 @@ pub struct TransactionDetails {
/// ensure an unstrusted [`Blockchain`](crate::blockchain::Blockchain) backend can't trick the
/// wallet into using an invalid tx as an RBF template.
///
/// The check is only perfomed when the `verify` feature is enabled.
/// The check is only performed when the `verify` feature is enabled.
#[serde(default = "bool::default")] // default to `false` if not specified
pub verified: bool,
}

View File

@@ -55,7 +55,7 @@
//! }
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
//!
//! let address = wallet.get_address(New)?;

View File

@@ -372,7 +372,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
impl BranchAndBoundCoinSelection {
// TODO: make this more Rust-onic :)
// (And perhpaps refactor with less arguments?)
// (And perhaps refactor with less arguments?)
#[allow(clippy::too_many_arguments)]
fn bnb(
&self,

View File

@@ -30,7 +30,7 @@
//! }"#;
//!
//! let import = WalletExport::from_str(import)?;
//! let wallet = Wallet::new_offline(
//! let wallet = Wallet::new(
//! &import.descriptor(),
//! import.change_descriptor().as_ref(),
//! Network::Testnet,
@@ -45,7 +45,7 @@
//! # use bdk::database::*;
//! # use bdk::wallet::export::*;
//! # use bdk::*;
//! let wallet = Wallet::new_offline(
//! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
//! Network::Testnet,
@@ -111,8 +111,8 @@ impl WalletExport {
///
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
/// returned will be `0`.
pub fn export_wallet<B, D: BatchDatabase>(
wallet: &Wallet<B, D>,
pub fn export_wallet<D: BatchDatabase>(
wallet: &Wallet<D>,
label: &str,
include_blockheight: bool,
) -> Result<Self, &'static str> {
@@ -242,7 +242,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
let wallet = Wallet::new_offline(
let wallet = Wallet::new(
descriptor,
Some(change_descriptor),
Network::Bitcoin,
@@ -266,8 +266,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let wallet =
Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
let wallet = Wallet::new(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
}
@@ -280,7 +279,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
let wallet = Wallet::new_offline(
let wallet = Wallet::new(
descriptor,
Some(change_descriptor),
Network::Bitcoin,
@@ -303,7 +302,7 @@ mod test {
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
))";
let wallet = Wallet::new_offline(
let wallet = Wallet::new(
descriptor,
Some(change_descriptor),
Network::Testnet,
@@ -323,7 +322,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
let wallet = Wallet::new_offline(
let wallet = Wallet::new(
descriptor,
Some(change_descriptor),
Network::Bitcoin,

View File

@@ -53,9 +53,9 @@ use address_validator::AddressValidator;
use coin_selection::DefaultCoinSelectionAlgorithm;
use signer::{SignOptions, Signer, SignerOrdering, SignersContainer};
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LIMIT_SATOSHI};
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx};
use crate::blockchain::{Blockchain, Progress};
use crate::blockchain::{GetHeight, NoopProgress, Progress, WalletSync};
use crate::database::memory::MemoryDatabase;
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
use crate::descriptor::derived::AsDerived;
@@ -75,16 +75,17 @@ const CACHE_ADDR_BATCH_SIZE: u32 = 100;
/// A Bitcoin wallet
///
/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
/// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets
/// needs to operate, like [generating addresses](Wallet::get_address), [returning the balance](Wallet::get_balance),
/// [creating transactions](Wallet::build_tx), etc.
/// The `Wallet` struct acts as a way of coherently interfacing with output descriptors and related transactions.
/// Its main components are:
///
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose
/// methods that don't need any interaction with the blockchain to work.
/// 1. output *descriptors* from which it can derive addresses.
/// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors.
/// 3. [`Signer`]s that can contribute signatures to addresses instantiated from the descriptors.
///
/// [`Database`]: crate::database::Database
/// [`Signer`]: crate::signer::Signer
#[derive(Debug)]
pub struct Wallet<B, D> {
pub struct Wallet<D> {
descriptor: ExtendedDescriptor,
change_descriptor: Option<ExtendedDescriptor>,
@@ -95,88 +96,11 @@ pub struct Wallet<B, D> {
network: Network,
current_height: Option<u32>,
client: B,
database: RefCell<D>,
secp: SecpCtx,
}
impl<D> Wallet<(), D>
where
D: BatchDatabase,
{
/// Create a new "offline" wallet
pub fn new_offline<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
database: D,
) -> Result<Self, Error> {
Self::_new(descriptor, change_descriptor, network, database, (), None)
}
}
impl<B, D> Wallet<B, D>
where
D: BatchDatabase,
{
fn _new<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
mut database: D,
client: B,
current_height: Option<u32>,
) -> Result<Self, Error> {
let secp = Secp256k1::new();
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
database.check_descriptor_checksum(
KeychainKind::External,
get_checksum(&descriptor.to_string())?.as_bytes(),
)?;
let signers = Arc::new(SignersContainer::from(keymap));
let (change_descriptor, change_signers) = match change_descriptor {
Some(desc) => {
let (change_descriptor, change_keymap) =
into_wallet_descriptor_checked(desc, &secp, network)?;
database.check_descriptor_checksum(
KeychainKind::Internal,
get_checksum(&change_descriptor.to_string())?.as_bytes(),
)?;
let change_signers = Arc::new(SignersContainer::from(change_keymap));
// if !parsed.same_structure(descriptor.as_ref()) {
// return Err(Error::DifferentDescriptorStructure);
// }
(Some(change_descriptor), change_signers)
}
None => (None, Arc::new(SignersContainer::new())),
};
Ok(Wallet {
descriptor,
change_descriptor,
signers,
change_signers,
address_validators: Vec::new(),
network,
current_height,
client,
database: RefCell::new(database),
secp,
})
}
/// Get the Bitcoin network the wallet is using.
pub fn network(&self) -> Network {
self.network
}
}
/// The address index selection strategy to use to derived an address from the wallet's external
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
#[derive(Debug)]
@@ -232,11 +156,72 @@ impl fmt::Display for AddressInfo {
}
}
// offline actions, always available
impl<B, D> Wallet<B, D>
#[derive(Debug, Default)]
/// Options to a [`Wallet::sync`]
pub struct SyncOptions {
/// The progress tracker which may be informated when progress is made.
pub progress: Option<Box<dyn Progress>>,
/// The maximum number of addresses sync on.
pub max_addresses: Option<u32>,
}
impl<D> Wallet<D>
where
D: BatchDatabase,
{
/// Create a wallet.
///
/// The only way this can fail is if the descriptors passed in do not match the checksums in `database`.
pub fn new<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
mut database: D,
) -> Result<Self, Error> {
let secp = Secp256k1::new();
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
database.check_descriptor_checksum(
KeychainKind::External,
get_checksum(&descriptor.to_string())?.as_bytes(),
)?;
let signers = Arc::new(SignersContainer::from(keymap));
let (change_descriptor, change_signers) = match change_descriptor {
Some(desc) => {
let (change_descriptor, change_keymap) =
into_wallet_descriptor_checked(desc, &secp, network)?;
database.check_descriptor_checksum(
KeychainKind::Internal,
get_checksum(&change_descriptor.to_string())?.as_bytes(),
)?;
let change_signers = Arc::new(SignersContainer::from(change_keymap));
// if !parsed.same_structure(descriptor.as_ref()) {
// return Err(Error::DifferentDescriptorStructure);
// }
(Some(change_descriptor), change_signers)
}
None => (None, Arc::new(SignersContainer::new())),
};
Ok(Wallet {
descriptor,
change_descriptor,
signers,
change_signers,
address_validators: Vec::new(),
network,
database: RefCell::new(database),
secp,
})
}
/// Get the Bitcoin network the wallet is using.
pub fn network(&self) -> Network {
self.network
}
// Return a newly derived address using the external descriptor
fn get_new_address(&self) -> Result<AddressInfo, Error> {
let incremented_index = self.fetch_and_increment_index(KeychainKind::External)?;
@@ -422,7 +407,7 @@ where
/// ```
///
/// [`TxBuilder`]: crate::TxBuilder
pub fn build_tx(&self) -> TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, CreateTx> {
pub fn build_tx(&self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> {
TxBuilder {
wallet: self,
params: TxParams::default(),
@@ -601,7 +586,7 @@ where
let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
for (index, (script_pubkey, value)) in recipients.enumerate() {
if value.is_dust() && !script_pubkey.is_provably_unspendable() {
if value.is_dust(script_pubkey) && !script_pubkey.is_provably_unspendable() {
return Err(Error::OutputBelowDustLimit(index));
}
@@ -677,9 +662,9 @@ where
if tx.output.is_empty() {
if params.drain_to.is_some() {
if drain_val.is_dust() {
if drain_val.is_dust(&drain_output.script_pubkey) {
return Err(Error::InsufficientFunds {
needed: DUST_LIMIT_SATOSHI,
needed: drain_output.script_pubkey.dust_value().as_sat(),
available: drain_val,
});
}
@@ -688,7 +673,7 @@ where
}
}
if drain_val.is_dust() {
if drain_val.is_dust(&drain_output.script_pubkey) {
fee_amount += drain_val;
} else {
drain_output.value = drain_val;
@@ -721,7 +706,7 @@ where
/// Bump the fee of a transaction previously created with this wallet.
///
/// Returns an error if the transaction is already confirmed or doesn't explicitly signal
/// *repalce by fee* (RBF). If the transaction can be fee bumped then it returns a [`TxBuilder`]
/// *replace by fee* (RBF). If the transaction can be fee bumped then it returns a [`TxBuilder`]
/// pre-populated with the inputs and outputs of the original transaction.
///
/// ## Example
@@ -763,7 +748,7 @@ where
pub fn build_fee_bump(
&self,
txid: Txid,
) -> Result<TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
let mut details = match self.database.borrow().get_tx(&txid, true)? {
None => return Err(Error::TransactionNotFound),
Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
@@ -994,7 +979,11 @@ where
.borrow()
.get_tx(&input.previous_output.txid, false)?
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX));
let current_height = sign_options.assume_height.or(self.current_height);
let last_sync_height = self
.database()
.get_sync_time()?
.map(|sync_time| sync_time.block_time.height);
let current_height = sign_options.assume_height.or(last_sync_height);
debug!(
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
@@ -1059,7 +1048,7 @@ where
&self.secp
}
/// Returns the descriptor used to create adddresses for a particular `keychain`.
/// Returns the descriptor used to create addresses for a particular `keychain`.
pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor {
let (descriptor, _) = self._get_descriptor_for_keychain(keychain);
descriptor
@@ -1452,47 +1441,26 @@ where
pub fn database(&self) -> impl std::ops::Deref<Target = D> + '_ {
self.database.borrow()
}
}
impl<B, D> Wallet<B, D>
where
B: Blockchain,
D: BatchDatabase,
{
/// Create a new "online" wallet
#[maybe_async]
pub fn new<E: IntoWalletDescriptor>(
descriptor: E,
change_descriptor: Option<E>,
network: Network,
database: D,
client: B,
) -> Result<Self, Error> {
let current_height = Some(maybe_await!(client.get_height())? as u32);
Self::_new(
descriptor,
change_descriptor,
network,
database,
client,
current_height,
)
}
/// Sync the internal database with the blockchain
#[maybe_async]
pub fn sync<P: 'static + Progress>(
pub fn sync<B: WalletSync + GetHeight>(
&self,
progress_update: P,
max_address_param: Option<u32>,
blockchain: &B,
sync_opts: SyncOptions,
) -> Result<(), Error> {
debug!("Begin sync...");
let mut run_setup = false;
let SyncOptions {
max_addresses,
progress,
} = sync_opts;
let progress = progress.unwrap_or_else(|| Box::new(NoopProgress));
let max_address = match self.descriptor.is_deriveable() {
false => 0,
true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
true => max_addresses.unwrap_or(CACHE_ADDR_BATCH_SIZE),
};
debug!("max_address {}", max_address);
if self
@@ -1509,7 +1477,7 @@ where
if let Some(change_descriptor) = &self.change_descriptor {
let max_address = match change_descriptor.is_deriveable() {
false => 0,
true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
true => max_addresses.unwrap_or(CACHE_ADDR_BATCH_SIZE),
};
if self
@@ -1528,13 +1496,11 @@ where
// TODO: what if i generate an address first and cache some addresses?
// TODO: we should sync if generating an address triggers a new batch to be stored
if run_setup {
maybe_await!(self
.client
.setup(self.database.borrow_mut().deref_mut(), progress_update,))?;
maybe_await!(
blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress,)
)?;
} else {
maybe_await!(self
.client
.sync(self.database.borrow_mut().deref_mut(), progress_update,))?;
maybe_await!(blockchain.wallet_sync(self.database.borrow_mut().deref_mut(), progress,))?;
}
#[cfg(feature = "verify")]
@@ -1545,7 +1511,7 @@ where
verify::verify_tx(
tx.transaction.as_ref().ok_or(Error::TransactionNotFound)?,
self.database.borrow().deref(),
&self.client,
blockchain,
)?;
tx.verified = true;
@@ -1556,7 +1522,7 @@ where
let sync_time = SyncTime {
block_time: BlockTime {
height: maybe_await!(self.client.get_height())?,
height: maybe_await!(blockchain.get_height())?,
timestamp: time::get_timestamp(),
},
};
@@ -1565,31 +1531,18 @@ where
Ok(())
}
/// Return a reference to the internal blockchain client
pub fn client(&self) -> &B {
&self.client
}
/// Broadcast a transaction to the network
#[maybe_async]
pub fn broadcast(&self, tx: &Transaction) -> Result<Txid, Error> {
maybe_await!(self.client.broadcast(tx))?;
Ok(tx.txid())
}
}
/// Return a fake wallet that appears to be funded for testing.
pub fn get_funded_wallet(
descriptor: &str,
) -> (
Wallet<(), MemoryDatabase>,
Wallet<MemoryDatabase>,
(String, Option<String>),
bitcoin::Txid,
) {
let descriptors = testutils!(@descriptors (descriptor));
let wallet = Wallet::new_offline(
let wallet = Wallet::new(
&descriptors.0,
None,
Network::Regtest,
@@ -1639,7 +1592,7 @@ pub(crate) mod test {
#[test]
fn test_cache_addresses_fixed() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline(
let wallet = Wallet::new(
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
None,
Network::Testnet,
@@ -1673,7 +1626,7 @@ pub(crate) mod test {
#[test]
fn test_cache_addresses() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
@@ -1701,7 +1654,7 @@ pub(crate) mod test {
#[test]
fn test_cache_addresses_refill() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
@@ -3424,7 +3377,7 @@ pub(crate) mod test {
.unwrap();
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(140.0));
builder.fee_rate(FeeRate::from_sat_per_vb(141.0));
let (psbt, details) = builder.finish().unwrap();
assert_eq!(
@@ -3799,7 +3752,7 @@ pub(crate) mod test {
#[test]
fn test_unused_address() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
assert_eq!(
@@ -3816,7 +3769,7 @@ pub(crate) mod test {
fn test_next_unused_address() {
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
let descriptors = testutils!(@descriptors (descriptor));
let wallet = Wallet::new_offline(
let wallet = Wallet::new(
&descriptors.0,
None,
Network::Testnet,
@@ -3845,7 +3798,7 @@ pub(crate) mod test {
#[test]
fn test_peek_address_at_index() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
assert_eq!(
@@ -3878,7 +3831,7 @@ pub(crate) mod test {
#[test]
fn test_peek_address_at_index_not_derivable() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
None, Network::Testnet, db).unwrap();
assert_eq!(
@@ -3900,7 +3853,7 @@ pub(crate) mod test {
#[test]
fn test_reset_address_index() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
// new index 0
@@ -3937,7 +3890,7 @@ pub(crate) mod test {
#[test]
fn test_returns_index_and_address() {
let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
// new index 0

View File

@@ -72,7 +72,7 @@
//! let custom_signer = CustomSigner::connect();
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_signer(
//! KeychainKind::External,
//! SignerOrdering(200),
@@ -143,7 +143,7 @@ pub enum SignerError {
InvalidNonWitnessUtxo,
/// The `witness_utxo` field of the transaction is required to sign this input
MissingWitnessUtxo,
/// The `witness_script` field of the transaction is requied to sign this input
/// The `witness_script` field of the transaction is required to sign this input
MissingWitnessScript,
/// The fingerprint and derivation path are missing from the psbt input
MissingHdKeypath,
@@ -289,7 +289,7 @@ impl Signer for PrivateKey {
}
// FIXME: use the presence of `witness_utxo` as an indication that we should make a bip143
// sig. Does this make sense? Should we add an extra argument to explicitly swith between
// sig. Does this make sense? Should we add an extra argument to explicitly switch between
// these? The original idea was to declare sign() as sign<Ctx: ScriptContex>() and use Ctx,
// but that violates the rules for trait-objects, so we can't do it.
let (hash, sighash) = match psbt.inputs[input_index].witness_utxo {

View File

@@ -120,8 +120,8 @@ impl TxBuilderContext for BumpFee {}
/// [`finish`]: Self::finish
/// [`coin_selection`]: Self::coin_selection
#[derive(Debug)]
pub struct TxBuilder<'a, B, D, Cs, Ctx> {
pub(crate) wallet: &'a Wallet<B, D>,
pub struct TxBuilder<'a, D, Cs, Ctx> {
pub(crate) wallet: &'a Wallet<D>,
pub(crate) params: TxParams,
pub(crate) coin_selection: Cs,
pub(crate) phantom: PhantomData<Ctx>,
@@ -170,7 +170,7 @@ impl std::default::Default for FeePolicy {
}
}
impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> {
impl<'a, Cs: Clone, Ctx, D> Clone for TxBuilder<'a, D, Cs, Ctx> {
fn clone(&self) -> Self {
TxBuilder {
wallet: self.wallet,
@@ -182,8 +182,8 @@ impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> {
}
// methods supported by both contexts, for any CoinSelectionAlgorithm
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
TxBuilder<'a, B, D, Cs, Ctx>
impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
TxBuilder<'a, D, Cs, Ctx>
{
/// Set a custom fee rate
pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
@@ -310,7 +310,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// 2. `psbt_input`: To know the value.
/// 3. `satisfaction_weight`: To know how much weight/vbytes the input will add to the transaction for fee calculation.
///
/// There are several security concerns about adding foregin UTXOs that application
/// There are several security concerns about adding foreign UTXOs that application
/// developers should consider. First, how do you know the value of the input is correct? If a
/// `non_witness_utxo` is provided in the `psbt_input` then this method implicitly verifies the
/// value by checking it against the transaction. If only a `witness_utxo` is provided then this
@@ -508,7 +508,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
pub fn coin_selection<P: CoinSelectionAlgorithm<D>>(
self,
coin_selection: P,
) -> TxBuilder<'a, B, D, P, Ctx> {
) -> TxBuilder<'a, D, P, Ctx> {
TxBuilder {
wallet: self.wallet,
params: self.params,
@@ -547,7 +547,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
}
}
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> {
impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, CreateTx> {
/// Replace the recipients already added with a new list
pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
self.params.recipients = recipients;
@@ -614,7 +614,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
}
// methods supported only by bump_fee
impl<'a, B, D: BatchDatabase> TxBuilder<'a, B, D, DefaultCoinSelectionAlgorithm, BumpFee> {
impl<'a, D: BatchDatabase> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> {
/// Explicitly tells the wallet that it is allowed to reduce the fee of the output matching this
/// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
/// will attempt to find a change output to shrink instead.

View File

@@ -9,13 +9,11 @@
// You may not use this file except in accordance with one or both of these
// licenses.
use bitcoin::blockdata::script::Script;
use bitcoin::secp256k1::{All, Secp256k1};
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
// De-facto standard "dust limit" (even though it should change based on the output type)
pub const DUST_LIMIT_SATOSHI: u64 = 546;
// MSB of the nSequence. If set there's no consensus-constraint, so it must be disabled when
// spending using CSV in order to enforce CSV rules
pub(crate) const SEQUENCE_LOCKTIME_DISABLE_FLAG: u32 = 1 << 31;
@@ -28,18 +26,19 @@ pub(crate) const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000FFFF;
// Threshold for nLockTime to be considered a block-height-based timelock rather than time-based
pub(crate) const BLOCKS_TIMELOCK_THRESHOLD: u32 = 500000000;
/// Trait to check if a value is below the dust limit
/// Trait to check if a value is below the dust limit.
/// We are performing dust value calculation for a given script public key using rust-bitcoin to
/// keep it compatible with network dust rate
// we implement this trait to make sure we don't mess up the comparison with off-by-one like a <
// instead of a <= etc. The constant value for the dust limit is not public on purpose, to
// encourage the usage of this trait.
// instead of a <= etc.
pub trait IsDust {
/// Check whether or not a value is below dust limit
fn is_dust(&self) -> bool;
fn is_dust(&self, script: &Script) -> bool;
}
impl IsDust for u64 {
fn is_dust(&self) -> bool {
*self <= DUST_LIMIT_SATOSHI
fn is_dust(&self, script: &Script) -> bool {
*self < script.dust_value().as_sat()
}
}
@@ -141,10 +140,29 @@ pub(crate) type SecpCtx = Secp256k1<All>;
#[cfg(test)]
mod test {
use super::{
check_nlocktime, check_nsequence_rbf, BLOCKS_TIMELOCK_THRESHOLD,
check_nlocktime, check_nsequence_rbf, IsDust, BLOCKS_TIMELOCK_THRESHOLD,
SEQUENCE_LOCKTIME_TYPE_FLAG,
};
use crate::bitcoin::Address;
use crate::types::FeeRate;
use std::str::FromStr;
#[test]
fn test_is_dust() {
let script_p2pkh = Address::from_str("1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe")
.unwrap()
.script_pubkey();
assert!(script_p2pkh.is_p2pkh());
assert!(545.is_dust(&script_p2pkh));
assert!(!546.is_dust(&script_p2pkh));
let script_p2wpkh = Address::from_str("bc1qxlh2mnc0yqwas76gqq665qkggee5m98t8yskd8")
.unwrap()
.script_pubkey();
assert!(script_p2wpkh.is_v0_p2wpkh());
assert!(293.is_dust(&script_p2wpkh));
assert!(!294.is_dust(&script_p2wpkh));
}
#[test]
fn test_fee_from_btc_per_kb() {

BIN
static/bdk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB