Compare commits

...

134 Commits

Author SHA1 Message Date
Alekos Filini
3897e29740 Fix changelog 2021-05-12 15:11:20 +02:00
Alekos Filini
8f06e45872 Bump version to 0.7.1-dev 2021-05-12 15:10:28 +02:00
Alekos Filini
766570abfd Bump version to 0.7.0 2021-05-12 14:20:58 +02:00
Alekos Filini
934ec366d9 Use the released testutils-macros 2021-05-12 14:20:23 +02:00
Alekos Filini
d0733e9496 Bump version in src/lib.rs 2021-05-12 14:19:58 +02:00
Alekos Filini
3c7a1f5918 Bump testutils-macros to v0.6.0 2021-05-12 14:19:00 +02:00
Alekos Filini
85aadaccd2 Update changelog in preparation of v0.7.0 2021-05-12 14:17:46 +02:00
Tobin Harding
fad0fe9f30 Update create transaction example code
The transaction builder changed a while ago, looks like some of the
example code did not get updated.

Update the transaction creation code to use a mutable builder.
2021-05-12 14:13:23 +02:00
Riccardo Casatta
47f26447da continue signing when finding already finalized inputs 2021-05-07 16:32:24 +02:00
Alekos Filini
3608ff9f14 Merge commit 'refs/pull/341/head' of github.com:bitcoindevkit/bdk into release/0.7.0 2021-05-07 11:00:00 +02:00
Riccardo Casatta
898dfe6cf1 get psbt inputs with bounds check 2021-05-06 16:10:11 +02:00
Riccardo Casatta
7961ae7f8e Check index out of bound also for tx inputs not only for psbt inputs 2021-05-06 15:13:25 +02:00
Alekos Filini
8bf77c8f07 Bump version to 0.7.0-rc.1 2021-05-06 13:56:38 +02:00
Alekos Filini
3c7bae9ce9 Rewrite the non_witness_utxo check 2021-05-06 11:39:01 +02:00
Alekos Filini
17bcd8ed7d [signer] Replace force_non_witness_utxo with only_witness_utxo
Instead of providing an opt-in option to force the addition of the
`non_witness_utxo`, we will now add them by default and provide the
option to disable them when they aren't considered necessary.
2021-05-06 08:58:39 +02:00
Alekos Filini
b5e9589803 [signer] Adjust signing behavior with SignOptions 2021-05-06 08:58:38 +02:00
Alekos Filini
1d628d84b5 [signer] Fix error variant 2021-05-05 16:59:59 +02:00
Alekos Filini
b84fd6ea5c Fix import for FromStr 2021-05-05 16:59:57 +02:00
Alekos Filini
8fe4222c33 Merge commit 'refs/pull/336/head' of github.com:bitcoindevkit/bdk 2021-05-05 14:51:36 +02:00
codeShark149
e626f2e255 Added grcov based code coverage reporting in github action 2021-04-30 17:20:20 +05:30
LLFourn
5a0c150ff9 Make wallet methods take &mut psbt
Rather than consuming it because that is unergonomic.
2021-04-28 15:34:25 +10:00
Alekos Filini
00f07818f9 Merge commit 'refs/pull/321/head' of github.com:bitcoindevkit/bdk 2021-04-16 14:08:26 +02:00
Riccardo Casatta
136a4bddb2 Verify PSBT input satisfaction 2021-04-16 12:22:49 +02:00
Riccardo Casatta
ff7b74ec27 destructure tuple to improve clarity 2021-04-16 12:22:47 +02:00
Steve Myers
8c00326990 [ci] Revert fixed nightly-2021-03-23, use actual nightly 2021-04-15 10:15:13 -07:00
Riccardo Casatta
afcd26032d comment out println in tests, fix doc 2021-04-15 16:48:42 +02:00
Riccardo Casatta
8f422a1bf9 Add timelocks to policy satisfaction results
Also for signature the logic has been refactored to handle appropriately nested cases.
2021-04-15 15:57:35 +02:00
Alekos Filini
45983d2166 Merge commit 'refs/pull/322/head' of github.com:bitcoindevkit/bdk 2021-04-15 11:40:34 +02:00
Steve Myers
89cb4de7f6 [ci] Update 'test-readme-examples' job to use nightly-2021-03-23 2021-04-14 20:34:32 -07:00
Steve Myers
7ca0e0e2bd [ci] Update 'Tarpaulin to codecov.io' job to use nightly-2021-03-23 2021-04-14 20:33:42 -07:00
Alekos Filini
2bddd9baed Update CHANGELOG for v0.6.0 2021-04-14 18:49:52 +02:00
Alekos Filini
0135ba29c5 Bump version to 0.6.1-dev 2021-04-14 18:47:31 +02:00
Alekos Filini
549cd24812 Bump version to 0.6.0 2021-04-14 17:27:28 +02:00
Alekos Filini
a841b5d635 Use published bdk-testutils-macros 2021-04-14 17:26:40 +02:00
Alekos Filini
16ceb6cb30 Bump version of bdk-testutils-macros 2021-04-14 17:25:11 +02:00
Alekos Filini
edfd7d454c Merge commit 'refs/pull/325/head' of github.com:bitcoindevkit/bdk into release/0.6.0 2021-04-13 09:25:47 +02:00
Alekos Filini
1d874e50c2 Merge commit 'refs/pull/326/head' of github.com:bitcoindevkit/bdk into release/0.6.0 2021-04-13 09:25:27 +02:00
Richard Ulrich
98127cc5da Allow setting RBF when bumping the fee of a transaction. This enables to further bump the fee. 2021-04-13 09:18:46 +02:00
Richard Ulrich
e243107bb6 Adding tests to demonstrate that we can't keep RBF when bumping the fee of a transaction. 2021-04-13 09:18:43 +02:00
Steve Myers
237a8d4e69 [ci] Update 'Build docs' job to use nightly-2021-03-23 2021-04-12 10:33:54 -07:00
Steve Myers
7f4042ba1b Bump version to 0.6.0-rc.1 2021-04-09 15:30:34 -07:00
Steve Myers
3ed44ce8cf Remove unneeded script 2021-04-09 09:19:19 -07:00
Steve Myers
8e7d8312a9 [ci] Update 'build-test' job to clippy check all-targets 2021-04-08 14:44:35 -07:00
Steve Myers
4da7488dc4 Update 'cargo-check.sh' to not check +nightly 2021-04-08 14:36:07 -07:00
Steve Myers
e37680af96 Use .flatten() instead of .filter_map(|x| x), clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity
2021-04-08 14:18:07 -07:00
Steve Myers
5f873ae500 Use .any() instead of .find().is_some(), clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
2021-04-08 14:18:07 -07:00
Steve Myers
2380634496 Use .get(0) instead of .iter().next(), clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
2021-04-08 14:18:07 -07:00
Steve Myers
af98b8da06 Compare float equality using error margin EPSILON, clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
2021-04-08 14:17:59 -07:00
Steve Myers
b68ec050e2 Remove redundant clone, clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
2021-04-08 11:41:58 -07:00
Steve Myers
ac7df09200 Remove needlessly taken reference of both operands, clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
2021-04-08 11:39:38 -07:00
Riccardo Casatta
192965413c Convert upper-case acronyms as suggested by CamelCase convention
see https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms
2021-04-07 22:14:54 +02:00
Riccardo Casatta
745be7bea8 remove format! from assert! (will be an error in rust edition 2021) 2021-04-07 22:09:08 +02:00
Riccardo Casatta
b6007e05c1 upgrade CI rust version to 1.51.0 2021-04-07 22:08:56 +02:00
Steve Myers
f53654d9f4 Merge commit 'refs/pull/314/head' of github.com:bitcoindevkit/bdk 2021-04-06 10:21:07 -07:00
Daniel Karzel
e5ecc7f541 Avoid over-/underflow error in coin_select
Adds fix for edge-cases involving small UTXOs (where value < fee) where the coin_select calculation would panic with overflow/underflow errors.
Bitcoin is limited to 21*(10^6), so any Bitcoin amount fits into i64.
2021-04-06 10:21:55 +10:00
LLFourn
882a9c27cc Use tagged serialization for blockchain config
also make the config types Clone and PartialEq
2021-04-03 15:30:49 +11:00
Steve Myers
1e6b8e12b2 Merge commit 'refs/pull/310/head' of github.com:bitcoindevkit/bdk 2021-03-31 16:06:53 -07:00
Steve Myers
b226658977 [ci] update MSRV to 1.46.0 2021-03-29 11:17:50 -07:00
Alekos Filini
6d6776eb58 Merge branch 'release/0.5.1' 2021-03-29 19:48:00 +02:00
Alekos Filini
f1f844a5b6 Bump version to 0.5.2-dev 2021-03-29 19:10:47 +02:00
Alekos Filini
a3e45358de Bump version to 0.5.1 2021-03-29 18:28:06 +02:00
Alekos Filini
07e79f6e8a Update CHANGELOG.md 2021-03-29 18:28:04 +02:00
Steve Myers
d94b8f87a3 Pin hyper version to =0.14.4 2021-03-29 10:12:56 +02:00
Steve Myers
fdb895d26c Update DEVELOPMENT_CYCLE for unreleased dev-dependencies 2021-03-22 10:48:39 -07:00
Steve Myers
7041e96737 Fix new test to use new get_address() fn 2021-03-22 10:26:56 -07:00
Steve Myers
199f716ebb Fix bdk-testutils-macros version 2021-03-22 10:24:21 -07:00
Steve Myers
b12e358c1d Fix 0.5.1-dev CHANGELOG.md 2021-03-20 11:42:00 -07:00
Alekos Filini
f786f0e624 Merge branch 'release/0.5.0' of github.com:bitcoindevkit/bdk 2021-03-17 22:27:44 +01:00
Alekos Filini
71e0472dc9 Bump version to 0.5.1-dev 2021-03-17 20:58:23 +01:00
Alekos Filini
f7944e871b Bump version to 0.5.0 2021-03-17 15:21:37 +01:00
Alekos Filini
2fea1761c1 Bump deps version 2021-03-17 15:21:07 +01:00
Alekos Filini
fa27ae210f Update version in lib.rs 2021-03-17 15:14:35 +01:00
Alekos Filini
46fa41470e Update CHANGELOG with the new release tag 2021-03-17 15:13:46 +01:00
Alekos Filini
c456a252f8 Merge commit 'refs/pull/296/head' of github.com:bitcoindevkit/bdk 2021-03-17 11:30:31 +01:00
Riccardo Casatta
d837a762fc update changelog and fix docs 2021-03-17 11:24:48 +01:00
davemo88
e82dfa971e brevity 2021-03-16 10:20:07 -04:00
davemo88
cc17ac8859 update changelog 2021-03-15 21:58:03 -04:00
davemo88
3798b4d115 add get_psbt_input 2021-03-15 21:50:51 -04:00
Steve Myers
2d0f6c4ec5 [wallet] Add get_address(AddressIndex::Reset(u32)), update CHANGELOG 2021-03-15 09:13:23 -07:00
Steve Myers
f3b475ff0e [wallet] Refactor get_*_address() into get_address(AddressIndex), update CHANGELOG 2021-03-15 08:58:11 -07:00
Steve Myers
41ae202d02 [wallet] Add get_unused_address() function, update CHANGELOG 2021-03-15 08:58:09 -07:00
Steve Myers
fef6176275 [wallet] Add fetch_index() helper function 2021-03-15 08:58:07 -07:00
Alekos Filini
8ebe7f0ea5 Merge commit 'refs/pull/308/head' of github.com:bitcoindevkit/bdk into release/0.5.0 2021-03-15 10:53:49 +01:00
Alekos Filini
eb85390846 Merge commit 'refs/pull/309/head' of github.com:bitcoindevkit/bdk into release/0.5.0 2021-03-15 10:53:29 +01:00
davemo88
dc83db273a better derivation path building 2021-03-11 21:54:00 -05:00
davemo88
201bd6ee02 better derivation path building 2021-03-11 21:35:16 -05:00
davemo88
396ffb42f9 handle descriptor xkey origin 2021-03-11 17:39:02 -05:00
Steve Myers
9cf62ce874 [ci] Manually install libclang-common-10-dev to 'check-wasm' job 2021-03-11 11:10:10 -08:00
Alekos Filini
9c6b98d98b Bump version to 0.5.0-rc.1 2021-03-11 10:07:26 +01:00
Riccardo Casatta
14ae64e09d [policy] Populate satisfaction with singatures already present in a PSBT 2021-03-08 16:58:56 +01:00
Riccardo Casatta
48215675b0 [policy] uncomment and update 4 tests: 2 ignored and 2 restored 2021-03-08 16:51:43 +01:00
Riccardo Casatta
37fa35b24a [policy] pass existing context instead of new one 2021-03-08 16:51:42 +01:00
Riccardo Casatta
23ec9c3ba0 [policy] pass secp context to setup_keys 2021-03-08 16:51:40 +01:00
Steve Myers
e33a6a12c1 Update README license badge 2021-03-05 16:48:57 -08:00
Steve Myers
12ae1c3479 Update license to Apache 2.0 or MIT, copyright to Bitcoin Dev Kit Developers 2021-03-03 13:23:25 -08:00
Thomas Eizinger
fdde0e691e Make constructor functions on FeeRate const
This allows `FeeRate`s to be stored inside `const`s.

For example:

const MY_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb(10.0);

Unfortunately, floating point maths inside const expressions is
still unstable, hence we cannot make `from_btc_per_kvb` const.
2021-03-01 11:04:39 +11:00
Alekos Filini
1cbd47b988 Merge commit 'refs/pull/285/head' of github.com:bitcoindevkit/bdk 2021-02-26 10:14:01 +01:00
Alekos Filini
e0183ed5c7 Merge commit 'refs/pull/279/head' of github.com:bitcoindevkit/bdk 2021-02-26 10:09:24 +01:00
Alekos Filini
dae900cc59 Merge commit 'refs/pull/297/head' of github.com:bitcoindevkit/bdk 2021-02-26 10:00:01 +01:00
Alekos Filini
4c2042ab01 [descriptor] Ensure that there are no duplicated keys 2021-02-26 09:46:38 +01:00
Thomas Eizinger
2f0ca206f3 Update electrum-client to 0.7 2021-02-26 14:09:46 +11:00
LLFourn
ac7c1bd97b Clean up add_foreign_utxo tests a bit
Noticed some suboptimal things while reviewing myself.
2021-02-26 13:33:52 +11:00
LLFourn
d9a102afa9 Improve docs of satisfaction_weight 2021-02-26 13:33:52 +11:00
Lloyd Fournier
7c1dcd8a72 Apply typo fixes from @tcharding
Co-authored-by: Tobin C. Harding <me@tobin.cc>
2021-02-26 13:33:52 +11:00
LLFourn
1fbfeabd77 Added add_foreign_utxo
To allow adding UTXOs external to the current wallet.
The caller must provide the psbt::Input so we can create a coherent PSBT
at the end and so this is compatible with existing PSBT workflows.

Main changes:

- There are now two types of UTXOs, local and foreign reflected in a
`Utxo` enum.
- `WeightedUtxo` now captures floating `(Utxo, usize)` tuples
- `CoinSelectionResult` now has methods on it for distinguishing between
local amount included vs total.
2021-02-26 13:33:52 +11:00
LLFourn
9a918f285d Make TxBuilder actually Clone
it derived Clone but in practice it was never clone because some of the
parameters were not Clone.
2021-02-26 13:33:52 +11:00
LLFourn
a7183f34ef s/UTXO/LocalUtxo/g
Since this struct has a "keychain" it is not a general "UTXO" but a
local wallet UTXO.
2021-02-26 13:33:52 +11:00
Tobin Harding
bda416df0a Use mixed order insertions
Currently we have a unit test to test that signers are sorted by
ordering. We call `add_external` to add them but currently we add them
in the same order we expect them to be in. This means if the
implementation happens to insert them simply in the order they are
added (i.e. insert to end of list) then this test will still pass.

Insert in a mixed order, including one lower followed by one higher -
this ensures we are not inserting at the front or at the back but are
actually sorting based on the `SignerOrdering`.
2021-02-24 13:39:36 +11:00
Tobin Harding
a838c2bacc Use id() for DummySigner comparison
If we give the `DummySigner` a valid identifier then we can use this to
do comparison.

Half the time we do comparison we only have a `dyn Signer` so we cannot
use `PartialEq`, add a helper function to check equality (this is in
test code so its not toooo ugly).

Thanks @afilini for the suggestion.
2021-02-24 13:37:41 +11:00
Tobin Harding
d2a094aa4c Align multi-line string
Recently we shortened the first line of a multi-line string and failed
to re-align the rest of the lines.

Thanks @afilini
2021-02-24 13:30:49 +11:00
Tobin Harding
bdb2a53597 Add cargo check script
We build against various targets on CI, in order to not abuse CI its
nice to see if code is good before pushing.

Add a script that runs `cargo check` with various combinations of
features and targets in order to enable thorough checking of the project
source code during development.
2021-02-24 13:30:49 +11:00
Tobin Harding
97ad0f1b4f Remove unused macro_use
Found by Clippy, we don't need this `macro_use` statement.
2021-02-24 13:30:48 +11:00
Tobin Harding
2b5e177ab2 Use lazy_static
`lazy_static` is not imported, this error is hidden behind the
`compact_filters` feature flag.
2021-02-24 13:30:48 +11:00
Tobin Harding
bfe29c4ef6 Use map instead of and_then
As suggested by Clippy us `map` instead of `and_then` with an inner
option.
2021-02-24 13:30:48 +11:00
Tobin Harding
e35601bb19 Use vec! instead of mut and push
As suggested by Clippy, use the `vec!` macro directly instead of
declaring a mutable vector and pushing elements onto it.
2021-02-24 13:30:48 +11:00
Tobin Harding
24df438607 Remove useless question mark operator
Clippy emits:

  warning: Question mark operator is useless here

No need to use the `?` operator inside an `Ok()` statement when
returning, just return directly.
2021-02-24 13:30:48 +11:00
Tobin Harding
cb3b8cf21b Do not compare vtable
Clippy emits error:

 comparing trait object pointers compares a non-unique vtable address

The vtable is an implementation detail, it may change in future. we
should not be comparing vtable addresses for equality. Instead we can
get a pointer to the data field of a fat pointer and compare on that.
2021-02-24 13:30:48 +11:00
Tobin Harding
0e6add0cfb Refactor db/batch matching
Remove the TODO; refactor matching to correctly handle conditionally
built `Sled` variants. Use `unreachable` instead of `unimplemented` with
a comment hinting that this is a bug, this makes it explicit, both at
runtime and when reading the code, that this match arm should not be hit.
2021-02-24 13:30:47 +11:00
Tobin Harding
343e97da0e Conditionally compile constructor
The `ChunksIterator` constructor is only used when either `electrum` or
`esplora` features are enabled. Conditionally build it so that we do not
get a clippy warning when building without these features.
2021-02-24 13:30:47 +11:00
Tobin Harding
ba8ce7233d Allow mutex_atomic
Clippy complains about use of a mutex, suggesting we use an
 `AtomicUsize`. While the same functionality _could_ be achieved using an
 `AtomicUsize` and a CAS loop it makes the code harder to reason about
 for little gain. Lets just quieten clippy with an allow attribute and
 document why we did so.
2021-02-24 13:30:47 +11:00
Tobin Harding
35184e6908 Use default pattern
Clippy emits warning:

  warning: field assignment outside of initializer for an instance
  created with Default::default()

Do as suggested by clippy and use the default init pattern.

```
    let foo = Foo {
    	bar: ...,
        Default::default()
    }
```
2021-02-24 13:30:47 +11:00
Tobin Harding
824b00c9e0 Use next instead of nth(0)
As suggested by clippy we can use `.next()` on an iterator instead of
`nth(0)`. Although arguably no clearer, and certainly no worse, it keeps
clippy quiet and a clean lint is a good thing.
2021-02-24 13:30:47 +11:00
Tobin Harding
79cab93d49 Use count instead of collect and len
Clippy emits warning:

	warning: avoid using `collect()` when not needed

As suggested by clippy just use `count` directly on the iterator instead
of `collect` followed by `len`.
2021-02-24 13:30:47 +11:00
Tobin Harding
2afc9faa08 Remove needles explicit reference
Clippy emits warning:

	warning: needlessly taken reference of both operands

Remove the explicit reference's as suggested.
2021-02-24 13:30:46 +11:00
Tobin Harding
0e99d02fbe Remove redundant calls to clone
No need to clone copy types, found by clippy.
2021-02-24 13:30:46 +11:00
Tobin Harding
3a0a1e6d4a Remove static lifetime
const str types do not need an explicit lifetime, remove it. Found by
clippy.
2021-02-24 13:30:46 +11:00
Tobin Harding
2057c35468 Use ! is_empty instead of len > 0
As directed by clippy use `!a.is_empty()` instead of `a.len() > 0`.
2021-02-24 13:30:46 +11:00
Tobin Harding
5eaa3b0916 Use unwrap_or_else
As directed by clippy use `unwrap_or_else` in order to take advantage of
lazy evaluation.
2021-02-24 13:30:46 +11:00
Steve Myers
4ad0f54c30 [ci] Rename MAGICAL_ env vars to BDK_, for tests use wallet name in RPC calls 2021-02-21 19:47:06 -08:00
Steve Myers
eeff3b5049 [ci] Update start-core.sh to create default wallet for bitcoind 0.21.0 2021-02-21 19:04:52 -08:00
Steve Myers
5e352489a0 Merge branch 'release/0.4.0' 2021-02-17 18:33:11 -08:00
Alekos Filini
fa5a5c8c05 Merge commit 'refs/pull/290/head' of github.com:bitcoindevkit/bdk 2021-02-16 11:54:52 -05:00
Lloyd Fournier
7fe5a30424 Don't fix tokio minor version
This is also what they give as an example in their docs: https://docs.rs/tokio/1.2.0/tokio/
2021-02-16 16:31:55 +11:00
Steve Myers
a82b2155e9 [ci] Manually set rust stable version in CI pipeline 2021-02-15 14:33:10 -08:00
57 changed files with 3015 additions and 1855 deletions

View File

@@ -3,25 +3,35 @@ on: [push]
name: Code Coverage name: Code Coverage
jobs: jobs:
tarpaulin-codecov:
name: Tarpaulin to codecov.io Codecov:
name: Code Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CARGO_INCREMENTAL: '0'
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off'
RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install rustup
run: curl https://sh.rustup.rs -sSf | sh -s -- -y
- name: Set default toolchain - name: Set default toolchain
run: rustup default nightly run: rustup default nightly
- name: Set profile - name: Set profile
run: rustup set profile minimal run: rustup set profile minimal
- name: Update toolchain
run: rustup update
- name: Test
run: cargo test --features all-keys,compiler,esplora,compact_filters --no-default-features
- id: coverage
name: Generate coverage
uses: actions-rs/grcov@v0.1.5
- name: Install tarpaulin - name: Upload coverage to Codecov
run: cargo install cargo-tarpaulin uses: codecov/codecov-action@v1
- name: Tarpaulin
run: cargo tarpaulin --features all-keys,compiler,esplora,compact_filters --run-types Tests,Doctests --exclude-files "testutils/*" --out Xml
- name: Publish to codecov.io
uses: codecov/codecov-action@v1.0.15
with: with:
fail_ci_if_error: true file: ${{ steps.coverage.outputs.report }}
file: ./cobertura.xml directory: ./coverage/reports/

View File

@@ -10,8 +10,8 @@ jobs:
strategy: strategy:
matrix: matrix:
rust: rust:
- stable - 1.51.0 # STABLE
- 1.45.0 # MSRV - 1.46.0 # MSRV
features: features:
- default - default
- minimal - minimal
@@ -46,7 +46,7 @@ jobs:
- name: Build - name: Build
run: cargo build --features ${{ matrix.features }} --no-default-features run: cargo build --features ${{ matrix.features }} --no-default-features
- name: Clippy - name: Clippy
run: cargo clippy --features ${{ matrix.features }} --no-default-features -- -D warnings run: cargo clippy --all-targets --features ${{ matrix.features }} --no-default-features -- -D warnings
- name: Test - name: Test
run: cargo test --features ${{ matrix.features }} --no-default-features run: cargo test --features ${{ matrix.features }} --no-default-features
@@ -76,13 +76,14 @@ jobs:
test-electrum: test-electrum:
name: Test electrum name: Test electrum
runs-on: ubuntu-16.04 runs-on: ubuntu-16.04
container: bitcoindevkit/electrs container: bitcoindevkit/electrs:0.2.0
env: env:
MAGICAL_RPC_AUTH: USER_PASS BDK_RPC_AUTH: USER_PASS
MAGICAL_RPC_USER: admin BDK_RPC_USER: admin
MAGICAL_RPC_PASS: passw BDK_RPC_PASS: passw
MAGICAL_RPC_URL: 127.0.0.1:18443 BDK_RPC_URL: 127.0.0.1:18443
MAGICAL_ELECTRUM_URL: tcp://127.0.0.1:60401 BDK_RPC_WALLET: bdk-test
BDK_ELECTRUM_URL: tcp://127.0.0.1:60401
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@@ -97,7 +98,7 @@ jobs:
- name: Install rustup - name: Install rustup
run: curl https://sh.rustup.rs -sSf | sh -s -- -y run: curl https://sh.rustup.rs -sSf | sh -s -- -y
- name: Set default toolchain - name: Set default toolchain
run: $HOME/.cargo/bin/rustup default stable run: $HOME/.cargo/bin/rustup default 1.51.0 # STABLE
- name: Set profile - name: Set profile
run: $HOME/.cargo/bin/rustup set profile minimal run: $HOME/.cargo/bin/rustup set profile minimal
- name: Update toolchain - name: Update toolchain
@@ -128,9 +129,9 @@ jobs:
- run: wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - || exit 1 - run: wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - || exit 1
- run: sudo apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-10 main" || exit 1 - run: sudo apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-10 main" || exit 1
- run: sudo apt-get update || exit 1 - run: sudo apt-get update || exit 1
- run: sudo apt-get install -y clang-10 libc6-dev-i386 || exit 1 - run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1
- name: Set default toolchain - name: Set default toolchain
run: rustup default stable run: rustup default 1.51.0 # STABLE
- name: Set profile - name: Set profile
run: rustup set profile minimal run: rustup set profile minimal
- name: Add target wasm32 - name: Add target wasm32
@@ -147,10 +148,10 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set default toolchain - name: Set default toolchain
run: rustup default stable run: rustup default 1.51.0 # STABLE
- name: Set profile - name: Set profile
run: rustup set profile minimal run: rustup set profile minimal
- name: Add clippy - name: Add rustfmt
run: rustup component add rustfmt run: rustup component add rustfmt
- name: Update toolchain - name: Update toolchain
run: rustup update run: rustup update

View File

@@ -17,17 +17,14 @@ jobs:
~/.cargo/git ~/.cargo/git
target target
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Install nightly toolchain - name: Set default toolchain
uses: actions-rs/toolchain@v1 run: rustup default nightly
with: - name: Set profile
profile: minimal run: rustup set profile minimal
toolchain: nightly - name: Update toolchain
override: true run: rustup update
- name: Build docs - name: Build docs
uses: actions-rs/cargo@v1 run: cargo rustdoc --verbose --features=compiler,electrum,esplora,compact_filters,key-value-db,all-keys -- --cfg docsrs -Dwarnings
with:
command: rustdoc
args: --verbose --features=compiler,electrum,esplora,compact_filters,key-value-db,all-keys -- --cfg docsrs -Dwarnings
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:

View File

@@ -6,6 +6,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [v0.7.0] - [v0.6.0]
### Policy
#### Changed
Removed `fill_satisfaction` method in favor of enum parameter in `extract_policy` method
#### Added
Timelocks are considered (optionally) in building the `satisfaction` field
### Wallet
- Changed `Wallet::{sign, finalize_psbt}` now take a `&mut psbt` rather than consuming it.
- Require and validate `non_witness_utxo` for SegWit signatures by default, can be adjusted with `SignOptions`
- Replace the opt-in builder option `force_non_witness_utxo` with the opposite `only_witness_utxo`. From now on we will provide the `non_witness_utxo`, unless explicitly asked not to.
## [v0.6.0] - [v0.5.1]
### Misc
#### Changed
- New minimum supported rust version is 1.46.0
- Changed `AnyBlockchainConfig` to use serde tagged representation.
### Descriptor
#### Added
- Added ability to analyze a `PSBT` to check which and how many signatures are already available
### Wallet
#### Changed
- `get_new_address()` refactored to `get_address(AddressIndex::New)` to support different `get_address()` index selection strategies
#### Added
- Added `get_address(AddressIndex::LastUnused)` which returns the last derived address if it has not been used or if used in a received transaction returns a new address
- Added `get_address(AddressIndex::Peek(u32))` which returns a derived address for a specified descriptor index but does not change the current index
- Added `get_address(AddressIndex::Reset(u32))` which returns a derived address for a specified descriptor index and resets current index to the given value
- Added `get_psbt_input` to create the corresponding psbt input for a local utxo.
#### Fixed
- Fixed `coin_select` calculation for UTXOs where `value < fee` that caused over-/underflow errors.
## [v0.5.1] - [v0.5.0]
### Misc
#### Changed
- Pin `hyper` to `=0.14.4` to make it compile on Rust 1.45
## [v0.5.0] - [v0.4.0]
### Misc
#### Changed
- Updated `electrum-client` to version `0.7`
### Wallet
#### Changed
- `FeeRate` constructors `from_sat_per_vb` and `default_min_relay_fee` are now `const` functions
## [v0.4.0] - [v0.3.0] ## [v0.4.0] - [v0.3.0]
### Keys ### Keys
@@ -50,7 +105,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
type to mark for a missing client. type to mark for a missing client.
- Upgrade `tokio` to `1.0`. - Upgrade `tokio` to `1.0`.
#### Transaction Creation Overhaul ### Transaction Creation Overhaul
The `TxBuilder` is now created from the `build_tx` or `build_fee_bump` functions on wallet and the The `TxBuilder` is now created from the `build_tx` or `build_fee_bump` functions on wallet and the
final transaction is created by calling `finish` on the builder. final transaction is created by calling `finish` on the builder.
@@ -61,6 +116,13 @@ final transaction is created by calling `finish` on the builder.
- Added `Wallet::get_utxo` - Added `Wallet::get_utxo`
- Added `Wallet::get_descriptor_for_keychain` - Added `Wallet::get_descriptor_for_keychain`
### `add_foreign_utxo`
- Renamed `UTXO` to `LocalUtxo`
- Added `WeightedUtxo` to replace floating `(UTXO, usize)`.
- Added `Utxo` enum to incorporate both local utxos and foreign utxos
- Added `TxBuilder::add_foreign_utxo` which allows adding a utxo external to the wallet.
### CLI ### CLI
#### Changed #### Changed
- Remove `cli.rs` module, `cli-utils` feature and `repl.rs` example; moved to new [`bdk-cli`](https://github.com/bitcoindevkit/bdk-cli) repository - Remove `cli.rs` module, `cli-utils` feature and `repl.rs` example; moved to new [`bdk-cli`](https://github.com/bitcoindevkit/bdk-cli) repository
@@ -275,3 +337,7 @@ final transaction is created by calling `finish` on the builder.
[v0.2.0]: https://github.com/bitcoindevkit/bdk/compare/0.1.0-beta.1...v0.2.0 [v0.2.0]: https://github.com/bitcoindevkit/bdk/compare/0.1.0-beta.1...v0.2.0
[v0.3.0]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...v0.3.0 [v0.3.0]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...v0.3.0
[v0.4.0]: https://github.com/bitcoindevkit/bdk/compare/v0.3.0...v0.4.0 [v0.4.0]: https://github.com/bitcoindevkit/bdk/compare/v0.3.0...v0.4.0
[v0.5.0]: https://github.com/bitcoindevkit/bdk/compare/v0.4.0...v0.5.0
[v0.5.1]: https://github.com/bitcoindevkit/bdk/compare/v0.5.0...v0.5.1
[v0.6.0]: https://github.com/bitcoindevkit/bdk/compare/v0.5.1...v0.6.0
[v0.7.0]: https://github.com/bitcoindevkit/bdk/compare/v0.6.0...v0.7.0

View File

@@ -46,7 +46,7 @@ Every new feature should be covered by functional tests where possible.
When refactoring, structure your PR to make it easy to review and don't When refactoring, structure your PR to make it easy to review and don't
hesitate to split it into multiple small, focused PRs. hesitate to split it into multiple small, focused PRs.
The Minimal Supported Rust Version is 1.45 (enforced by our CI). The Minimal Supported Rust Version is 1.46 (enforced by our CI).
Commits should cover both the issue fixed and the solution's rationale. Commits should cover both the issue fixed and the solution's rationale.
These [guidelines](https://chris.beams.io/posts/git-commit/) should be kept in mind. These [guidelines](https://chris.beams.io/posts/git-commit/) should be kept in mind.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bdk" name = "bdk"
version = "0.4.1-dev" version = "0.7.1-dev"
edition = "2018" edition = "2018"
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"] authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
homepage = "https://bitcoindevkit.org" homepage = "https://bitcoindevkit.org"
@@ -9,10 +9,10 @@ documentation = "https://docs.rs/bdk"
description = "A modern, lightweight, descriptor-based wallet library" description = "A modern, lightweight, descriptor-based wallet library"
keywords = ["bitcoin", "wallet", "descriptor", "psbt"] keywords = ["bitcoin", "wallet", "descriptor", "psbt"]
readme = "README.md" readme = "README.md"
license = "MIT" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
bdk-macros = "^0.3" bdk-macros = "^0.4"
log = "^0.4" log = "^0.4"
miniscript = "5.1" miniscript = "5.1"
bitcoin = { version = "^0.26", features = ["use-serde"] } bitcoin = { version = "^0.26", features = ["use-serde"] }
@@ -22,7 +22,7 @@ rand = "^0.7"
# Optional dependencies # Optional dependencies
sled = { version = "0.34", optional = true } sled = { version = "0.34", optional = true }
electrum-client = { version = "0.6", optional = true } electrum-client = { version = "0.7", optional = true }
reqwest = { version = "0.11", optional = true, features = ["json"] } reqwest = { version = "0.11", optional = true, features = ["json"] }
futures = { version = "0.3", optional = true } futures = { version = "0.3", optional = true }
async-trait = { version = "0.1", optional = true } async-trait = { version = "0.1", optional = true }
@@ -59,8 +59,8 @@ test-electrum = ["electrum"]
test-md-docs = ["electrum"] test-md-docs = ["electrum"]
[dev-dependencies] [dev-dependencies]
bdk-testutils = "^0.3" bdk-testutils = "0.4"
bdk-testutils-macros = "^0.3" bdk-testutils-macros = "0.6"
serial_test = "0.4" serial_test = "0.4"
lazy_static = "1.4" lazy_static = "1.4"
env_logger = "0.7" env_logger = "0.7"

View File

@@ -20,7 +20,7 @@ As soon as the release is tagged and published, the `release` branch will be mer
## Making the Release ## Making the Release
What follows are notes and procedures that maintaners can refer to when making releases. All the commits and tags must be signed and, ideally, also [timestamped](https://github.com/opentimestamps/opentimestamps-client/blob/master/doc/git-integration.md). What follows are notes and procedures that maintainers can refer to when making releases. All the commits and tags must be signed and, ideally, also [timestamped](https://github.com/opentimestamps/opentimestamps-client/blob/master/doc/git-integration.md).
Pre-`v1.0.0` our "major" releases only affect the "minor" semver value. Accordingly, our "minor" releases will only affect the "patch" value. Pre-`v1.0.0` our "major" releases only affect the "minor" semver value. Accordingly, our "minor" releases will only affect the "patch" value.
@@ -39,7 +39,8 @@ Pre-`v1.0.0` our "major" releases only affect the "minor" semver value. Accordin
11. Publish **all** the updated crates to crates.io. 11. Publish **all** the updated crates to crates.io.
12. Make a new commit to bump the version value to `x.y.(z+1)-dev`. The message should be "Bump version to x.y.(z+1)-dev". 12. Make a new commit to bump the version value to `x.y.(z+1)-dev`. The message should be "Bump version to x.y.(z+1)-dev".
13. Merge the release branch back into `master`. 13. Merge the release branch back into `master`.
14. Create the release on GitHub: go to "tags", click on the dots on the right and select "Create Release". Then set the title to `vx.y.z` and write down some brief release notes. 14. If the `master` branch contains any unreleased changes to the `bdk-macros`, `bdk-testutils`, or `bdk-testutils-macros` crates, change the `bdk` Cargo.toml `[dev-dependencies]` to point to the local path (ie. `bdk-testutils-macros = { path = "./testutils-macros"}`)
15. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs. 15. Create the release on GitHub: go to "tags", click on the dots on the right and select "Create Release". Then set the title to `vx.y.z` and write down some brief release notes.
16. Announce the release on Twitter, Discord and Telegram. 16. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs.
17. Celebrate :tada: 17. Announce the release on Twitter, Discord and Telegram.
18. Celebrate :tada:

29
LICENSE
View File

@@ -1,21 +1,14 @@
MIT License This software is licensed under [Apache 2.0](LICENSE-APACHE) or
[MIT](LICENSE-MIT), at your option.
Copyright (c) 2020 Magical Bitcoin Some files retain their own copyright notice, however, for full authorship
information, see version control history.
Permission is hereby granted, free of charge, to any person obtaining a copy Except as otherwise noted in individual files, all files in this repository are
of this software and associated documentation files (the "Software"), to deal licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
in the Software without restriction, including without limitation the rights http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell http://opensource.org/licenses/MIT>, at your option.
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all You may not use, copy, modify, merge, publish, distribute, sublicense, and/or
copies or substantial portions of the Software. sell copies of this software or any files in this repository except in
accordance with one or both of these licenses.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

201
LICENSE-APACHE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

16
LICENSE-MIT Normal file
View File

@@ -0,0 +1,16 @@
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -9,11 +9,11 @@
<p> <p>
<a href="https://crates.io/crates/bdk"><img alt="Crate Info" src="https://img.shields.io/crates/v/bdk.svg"/></a> <a href="https://crates.io/crates/bdk"><img alt="Crate Info" src="https://img.shields.io/crates/v/bdk.svg"/></a>
<a href="https://github.com/bitcoindevkit/bdk/blob/master/LICENSE"><img alt="MIT Licensed" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a> <a href="https://github.com/bitcoindevkit/bdk/blob/master/LICENSE"><img alt="MIT or Apache-2.0 Licensed" src="https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg"/></a>
<a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a> <a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a>
<a href="https://codecov.io/gh/bitcoindevkit/bdk"><img src="https://codecov.io/gh/bitcoindevkit/bdk/branch/master/graph/badge.svg"/></a> <a href="https://codecov.io/gh/bitcoindevkit/bdk"><img src="https://codecov.io/gh/bitcoindevkit/bdk/branch/master/graph/badge.svg"/></a>
<a href="https://docs.rs/bdk"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk-green"/></a> <a href="https://docs.rs/bdk"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk-green"/></a>
<a href="https://blog.rust-lang.org/2020/07/16/Rust-1.45.0.html"><img alt="Rustc Version 1.45+" src="https://img.shields.io/badge/rustc-1.45%2B-lightgrey.svg"/></a> <a href="https://blog.rust-lang.org/2020/08/27/Rust-1.46.0.html"><img alt="Rustc Version 1.46+" src="https://img.shields.io/badge/rustc-1.46%2B-lightgrey.svg"/></a>
<a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a> <a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
</p> </p>
@@ -67,6 +67,7 @@ fn main() -> Result<(), bdk::Error> {
```rust ```rust
use bdk::{Wallet, database::MemoryDatabase}; use bdk::{Wallet, database::MemoryDatabase};
use bdk::wallet::AddressIndex::New;
fn main() -> Result<(), bdk::Error> { fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new_offline( let wallet = Wallet::new_offline(
@@ -76,9 +77,9 @@ fn main() -> Result<(), bdk::Error> {
MemoryDatabase::default(), MemoryDatabase::default(),
)?; )?;
println!("Address #0: {}", wallet.get_new_address()?); println!("Address #0: {}", wallet.get_address(New)?);
println!("Address #1: {}", wallet.get_new_address()?); println!("Address #1: {}", wallet.get_address(New)?);
println!("Address #2: {}", wallet.get_new_address()?); println!("Address #2: {}", wallet.get_address(New)?);
Ok(()) Ok(())
} }
@@ -92,6 +93,7 @@ use bdk::database::MemoryDatabase;
use bdk::blockchain::{noop_progress, ElectrumBlockchain}; use bdk::blockchain::{noop_progress, ElectrumBlockchain};
use bdk::electrum_client::Client; use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New;
use bitcoin::consensus::serialize; use bitcoin::consensus::serialize;
@@ -107,7 +109,7 @@ fn main() -> Result<(), bdk::Error> {
wallet.sync(noop_progress(), None)?; wallet.sync(noop_progress(), None)?;
let send_to = wallet.get_new_address()?; let send_to = wallet.get_address(New)?;
let (psbt, details) = { let (psbt, details) = {
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@@ -128,7 +130,7 @@ fn main() -> Result<(), bdk::Error> {
### Sign a transaction ### Sign a transaction
```rust,no_run ```rust,no_run
use bdk::{Wallet, database::MemoryDatabase}; use bdk::{Wallet, SignOptions, database::MemoryDatabase};
use bitcoin::consensus::deserialize; use bitcoin::consensus::deserialize;
@@ -141,10 +143,27 @@ fn main() -> Result<(), bdk::Error> {
)?; )?;
let psbt = "..."; let psbt = "...";
let psbt = deserialize(&base64::decode(psbt).unwrap())?; let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
let (signed_psbt, finalized) = wallet.sign(psbt, None)?; let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
Ok(()) Ok(())
} }
``` ```
## License
Licensed under either of
* Apache License, Version 2.0
([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license
([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

View File

@@ -1,16 +1,16 @@
#!/usr/bin/env sh #!/usr/bin/env sh
echo "Starting bitcoin node." echo "Starting bitcoin node."
/root/bitcoind -regtest -server -daemon -fallbackfee=0.0002 -rpcuser=admin -rpcpassword=passw -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0 /root/bitcoind -regtest -server -daemon -fallbackfee=0.0002 -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0 -blockfilterindex=1 -peerblockfilters=1
echo "Waiting for bitcoin node." echo "Waiting for bitcoin node."
until /root/bitcoin-cli -regtest -rpcuser=admin -rpcpassword=passw getblockchaininfo; do until /root/bitcoin-cli -regtest -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS getblockchaininfo; do
sleep 1 sleep 1
done done
/root/bitcoin-cli -regtest -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS createwallet $BDK_RPC_WALLET
echo "Generating 150 bitcoin blocks." echo "Generating 150 bitcoin blocks."
ADDR=$(/root/bitcoin-cli -regtest -rpcuser=admin -rpcpassword=passw getnewaddress) ADDR=$(/root/bitcoin-cli -regtest -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS -rpcwallet=$BDK_RPC_WALLET getnewaddress)
/root/bitcoin-cli -regtest -rpcuser=admin -rpcpassword=passw generatetoaddress 150 $ADDR /root/bitcoin-cli -regtest -rpcuser=$BDK_RPC_USER -rpcpassword=$BDK_RPC_PASS generatetoaddress 150 $ADDR
echo "Starting electrs node." echo "Starting electrs node."
nohup /root/electrs --network regtest --jsonrpc-import & nohup /root/electrs --network regtest --jsonrpc-import &

13
codecov.yaml Normal file
View File

@@ -0,0 +1,13 @@
coverage:
status:
project:
default:
target: auto
threshold: 1%
base: auto
informational: false
patch:
default:
target: auto
threshold: 100%
base: auto

View File

@@ -1,36 +1,24 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::sync::Arc; use std::sync::Arc;
use bdk::bitcoin; use bdk::bitcoin;
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
use bdk::descriptor::HDKeyPaths; use bdk::descriptor::HdKeyPaths;
use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError}; use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError};
use bdk::KeychainKind; use bdk::KeychainKind;
use bdk::Wallet; use bdk::Wallet;
use bdk::wallet::AddressIndex::New;
use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::hex::FromHex;
use bitcoin::util::bip32::Fingerprint; use bitcoin::util::bip32::Fingerprint;
use bitcoin::{Network, Script}; use bitcoin::{Network, Script};
@@ -41,7 +29,7 @@ impl AddressValidator for DummyValidator {
fn validate( fn validate(
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
hd_keypaths: &HDKeyPaths, hd_keypaths: &HdKeyPaths,
script: &Script, script: &Script,
) -> Result<(), AddressValidatorError> { ) -> Result<(), AddressValidatorError> {
let (_, path) = hd_keypaths let (_, path) = hd_keypaths
@@ -65,9 +53,9 @@ fn main() -> Result<(), bdk::Error> {
wallet.add_address_validator(Arc::new(DummyValidator)); wallet.add_address_validator(Arc::new(DummyValidator));
wallet.get_new_address()?; wallet.get_address(New)?;
wallet.get_new_address()?; wallet.get_address(New)?;
wallet.get_new_address()?; wallet.get_address(New)?;
Ok(()) Ok(())
} }

View File

@@ -1,3 +1,14 @@
// Bitcoin Dev Kit
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
use bdk::blockchain::compact_filters::*; use bdk::blockchain::compact_filters::*;
use bdk::blockchain::noop_progress; use bdk::blockchain::noop_progress;
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
extern crate bdk; extern crate bdk;
extern crate bitcoin; extern crate bitcoin;
@@ -41,6 +28,7 @@ use miniscript::policy::Concrete;
use miniscript::Descriptor; use miniscript::Descriptor;
use bdk::database::memory::MemoryDatabase; use bdk::database::memory::MemoryDatabase;
use bdk::wallet::AddressIndex::New;
use bdk::{KeychainKind, Wallet}; use bdk::{KeychainKind, Wallet};
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
@@ -97,13 +85,13 @@ fn main() -> Result<(), Box<dyn Error>> {
let network = matches let network = matches
.value_of("network") .value_of("network")
.and_then(|n| Some(Network::from_str(n))) .map(|n| Network::from_str(n))
.transpose() .transpose()
.unwrap() .unwrap()
.unwrap_or(Network::Testnet); .unwrap_or(Network::Testnet);
let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?; let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?;
info!("... First address: {}", wallet.get_new_address()?); info!("... First address: {}", wallet.get_address(New)?);
if matches.is_present("parsed_policy") { if matches.is_present("parsed_policy") {
let spending_policy = wallet.policies(KeychainKind::External)?; let spending_policy = wallet.policies(KeychainKind::External)?;

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bdk-macros" name = "bdk-macros"
version = "0.3.0" version = "0.4.0"
authors = ["Alekos Filini <alekos.filini@gmail.com>"] authors = ["Alekos Filini <alekos.filini@gmail.com>"]
edition = "2018" edition = "2018"
homepage = "https://bitcoindevkit.org" homepage = "https://bitcoindevkit.org"
@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk-macros" documentation = "https://docs.rs/bdk-macros"
description = "Supporting macros for `bdk`" description = "Supporting macros for `bdk`"
keywords = ["bdk"] keywords = ["bdk"]
license = "MIT" license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#[macro_use] #[macro_use]
extern crate quote; extern crate quote;

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Runtime-checked blockchain types //! Runtime-checked blockchain types
//! //!
@@ -190,7 +177,34 @@ impl_from!(compact_filters::CompactFiltersBlockchain, AnyBlockchain, CompactFilt
/// This allows storing a single configuration that can be loaded into an [`AnyBlockchain`] /// This allows storing a single configuration that can be loaded into an [`AnyBlockchain`]
/// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime /// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime
/// will find this particularly useful. /// will find this particularly useful.
#[derive(Debug, serde::Serialize, serde::Deserialize)] ///
/// This type can be serialized from a JSON object like:
///
/// ```
/// # #[cfg(feature = "electrum")]
/// # {
/// use bdk::blockchain::{electrum::ElectrumBlockchainConfig, AnyBlockchainConfig};
/// let config: AnyBlockchainConfig = serde_json::from_str(
/// r#"{
/// "type" : "electrum",
/// "url" : "ssl://electrum.blockstream.info:50002",
/// "retry": 2
/// }"#,
/// )
/// .unwrap();
/// assert_eq!(
/// config,
/// AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
/// url: "ssl://electrum.blockstream.info:50002".into(),
/// retry: 2,
/// socks5: None,
/// timeout: None
/// })
/// );
/// # }
/// ```
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AnyBlockchainConfig { pub enum AnyBlockchainConfig {
#[cfg(feature = "electrum")] #[cfg(feature = "electrum")]
#[cfg_attr(docsrs, doc(cfg(feature = "electrum")))] #[cfg_attr(docsrs, doc(cfg(feature = "electrum")))]

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Compact Filters //! Compact Filters
//! //!
@@ -83,7 +70,7 @@ mod sync;
use super::{Blockchain, Capability, ConfigurableBlockchain, Progress}; use super::{Blockchain, Capability, ConfigurableBlockchain, Progress};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::error::Error; use crate::error::Error;
use crate::types::{KeychainKind, TransactionDetails, UTXO}; use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
use crate::FeeRate; use crate::FeeRate;
use peer::*; use peer::*;
@@ -194,7 +181,7 @@ impl CompactFiltersBlockchain {
database.get_path_from_script_pubkey(&output.script_pubkey)? database.get_path_from_script_pubkey(&output.script_pubkey)?
{ {
debug!("{} output #{} is mine, adding utxo", tx.txid(), i); debug!("{} output #{} is mine, adding utxo", tx.txid(), i);
updates.set_utxo(&UTXO { updates.set_utxo(&LocalUtxo {
outpoint: OutPoint::new(tx.txid(), i as u32), outpoint: OutPoint::new(tx.txid(), i as u32),
txout: output.clone(), txout: output.clone(),
keychain, keychain,
@@ -239,6 +226,7 @@ impl Blockchain for CompactFiltersBlockchain {
vec![Capability::FullHistory].into_iter().collect() vec![Capability::FullHistory].into_iter().collect()
} }
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
fn setup<D: BatchDatabase, P: 'static + Progress>( fn setup<D: BatchDatabase, P: 'static + Progress>(
&self, &self,
_stop_gap: Option<usize>, // TODO: move to electrum and esplora only _stop_gap: Option<usize>, // TODO: move to electrum and esplora only
@@ -249,7 +237,7 @@ impl Blockchain for CompactFiltersBlockchain {
let skip_blocks = self.skip_blocks.unwrap_or(0); let skip_blocks = self.skip_blocks.unwrap_or(0);
let cf_sync = Arc::new(CFSync::new(Arc::clone(&self.headers), skip_blocks, 0x00)?); let cf_sync = Arc::new(CfSync::new(Arc::clone(&self.headers), skip_blocks, 0x00)?);
let initial_height = self.headers.get_height()?; let initial_height = self.headers.get_height()?;
let total_bundles = (first_peer.get_version().start_height as usize) let total_bundles = (first_peer.get_version().start_height as usize)
@@ -468,7 +456,7 @@ impl Blockchain for CompactFiltersBlockchain {
} }
/// Data to connect to a Bitcoin P2P peer /// Data to connect to a Bitcoin P2P peer
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct BitcoinPeerConfig { pub struct BitcoinPeerConfig {
/// Peer address such as 127.0.0.1:18333 /// Peer address such as 127.0.0.1:18333
pub address: String, pub address: String,
@@ -479,7 +467,7 @@ pub struct BitcoinPeerConfig {
} }
/// Configuration for a [`CompactFiltersBlockchain`] /// Configuration for a [`CompactFiltersBlockchain`]
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct CompactFiltersBlockchainConfig { pub struct CompactFiltersBlockchainConfig {
/// List of peers to try to connect to for asking headers and filters /// List of peers to try to connect to for asking headers and filters
pub peers: Vec<BitcoinPeerConfig>, pub peers: Vec<BitcoinPeerConfig>,
@@ -549,11 +537,11 @@ pub enum CompactFiltersError {
NoPeers, NoPeers,
/// Internal database error /// Internal database error
DB(rocksdb::Error), Db(rocksdb::Error),
/// Internal I/O error /// Internal I/O error
IO(std::io::Error), Io(std::io::Error),
/// Invalid BIP158 filter /// Invalid BIP158 filter
BIP158(bitcoin::util::bip158::Error), Bip158(bitcoin::util::bip158::Error),
/// Internal system time error /// Internal system time error
Time(std::time::SystemTimeError), Time(std::time::SystemTimeError),
@@ -569,9 +557,9 @@ impl fmt::Display for CompactFiltersError {
impl std::error::Error for CompactFiltersError {} impl std::error::Error for CompactFiltersError {}
impl_error!(rocksdb::Error, DB, CompactFiltersError); impl_error!(rocksdb::Error, Db, CompactFiltersError);
impl_error!(std::io::Error, IO, CompactFiltersError); impl_error!(std::io::Error, Io, CompactFiltersError);
impl_error!(bitcoin::util::bip158::Error, BIP158, CompactFiltersError); impl_error!(bitcoin::util::bip158::Error, Bip158, CompactFiltersError);
impl_error!(std::time::SystemTimeError, Time, CompactFiltersError); impl_error!(std::time::SystemTimeError, Time, CompactFiltersError);
impl From<crate::error::Error> for CompactFiltersError { impl From<crate::error::Error> for CompactFiltersError {

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::collections::HashMap; use std::collections::HashMap;
use std::net::{TcpStream, ToSocketAddrs}; use std::net::{TcpStream, ToSocketAddrs};

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt; use std::fmt;
@@ -46,6 +33,8 @@ use bitcoin::BlockHash;
use bitcoin::BlockHeader; use bitcoin::BlockHeader;
use bitcoin::Network; use bitcoin::Network;
use lazy_static::lazy_static;
use super::CompactFiltersError; use super::CompactFiltersError;
lazy_static! { lazy_static! {
@@ -119,7 +108,7 @@ where
} }
fn deserialize(data: &[u8]) -> Result<Self, CompactFiltersError> { fn deserialize(data: &[u8]) -> Result<Self, CompactFiltersError> {
Ok(deserialize(data).map_err(|_| CompactFiltersError::DataCorruption)?) deserialize(data).map_err(|_| CompactFiltersError::DataCorruption)
} }
} }
@@ -131,7 +120,7 @@ impl Encodable for BundleStatus {
BundleStatus::Init => { BundleStatus::Init => {
written += 0x00u8.consensus_encode(&mut e)?; written += 0x00u8.consensus_encode(&mut e)?;
} }
BundleStatus::CFHeaders { cf_headers } => { BundleStatus::CfHeaders { cf_headers } => {
written += 0x01u8.consensus_encode(&mut e)?; written += 0x01u8.consensus_encode(&mut e)?;
written += VarInt(cf_headers.len() as u64).consensus_encode(&mut e)?; written += VarInt(cf_headers.len() as u64).consensus_encode(&mut e)?;
for header in cf_headers { for header in cf_headers {
@@ -182,7 +171,7 @@ impl Decodable for BundleStatus {
cf_headers.push(FilterHeader::consensus_decode(&mut d)?); cf_headers.push(FilterHeader::consensus_decode(&mut d)?);
} }
Ok(BundleStatus::CFHeaders { cf_headers }) Ok(BundleStatus::CfHeaders { cf_headers })
} }
0x02 => { 0x02 => {
let num = VarInt::consensus_decode(&mut d)?; let num = VarInt::consensus_decode(&mut d)?;
@@ -436,15 +425,14 @@ impl ChainStore<Full> {
let key = StoreEntry::BlockHeaderIndex(Some(*block_hash)).get_key(); let key = StoreEntry::BlockHeaderIndex(Some(*block_hash)).get_key();
let data = read_store.get_pinned_cf(cf_handle, key)?; let data = read_store.get_pinned_cf(cf_handle, key)?;
Ok(data data.map(|data| {
.map(|data| { Ok::<_, CompactFiltersError>(usize::from_be_bytes(
Ok::<_, CompactFiltersError>(usize::from_be_bytes( data.as_ref()
data.as_ref() .try_into()
.try_into() .map_err(|_| CompactFiltersError::DataCorruption)?,
.map_err(|_| CompactFiltersError::DataCorruption)?, ))
)) })
}) .transpose()
.transpose()?)
} }
pub fn get_block_hash(&self, height: usize) -> Result<Option<BlockHash>, CompactFiltersError> { pub fn get_block_hash(&self, height: usize) -> Result<Option<BlockHash>, CompactFiltersError> {
@@ -453,13 +441,12 @@ impl ChainStore<Full> {
let key = StoreEntry::BlockHeader(Some(height)).get_key(); let key = StoreEntry::BlockHeader(Some(height)).get_key();
let data = read_store.get_pinned_cf(cf_handle, key)?; let data = read_store.get_pinned_cf(cf_handle, key)?;
Ok(data data.map(|data| {
.map(|data| { let (header, _): (BlockHeader, Uint256) =
let (header, _): (BlockHeader, Uint256) = deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?;
deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?; Ok::<_, CompactFiltersError>(header.block_hash())
Ok::<_, CompactFiltersError>(header.block_hash()) })
}) .transpose()
.transpose()?)
} }
pub fn save_full_block(&self, block: &Block, height: usize) -> Result<(), CompactFiltersError> { pub fn save_full_block(&self, block: &Block, height: usize) -> Result<(), CompactFiltersError> {
@@ -475,10 +462,10 @@ impl ChainStore<Full> {
let key = StoreEntry::Block(Some(height)).get_key(); let key = StoreEntry::Block(Some(height)).get_key();
let opt_block = read_store.get_pinned(key)?; let opt_block = read_store.get_pinned(key)?;
Ok(opt_block opt_block
.map(|data| deserialize(&data)) .map(|data| deserialize(&data))
.transpose() .transpose()
.map_err(|_| CompactFiltersError::DataCorruption)?) .map_err(|_| CompactFiltersError::DataCorruption)
} }
pub fn delete_blocks_until(&self, height: usize) -> Result<(), CompactFiltersError> { pub fn delete_blocks_until(&self, height: usize) -> Result<(), CompactFiltersError> {
@@ -565,14 +552,14 @@ impl<T: StoreType> ChainStore<T> {
let prefix = StoreEntry::BlockHeader(None).get_key(); let prefix = StoreEntry::BlockHeader(None).get_key();
let iterator = read_store.prefix_iterator_cf(cf_handle, prefix); let iterator = read_store.prefix_iterator_cf(cf_handle, prefix);
Ok(iterator iterator
.last() .last()
.map(|(_, v)| -> Result<_, CompactFiltersError> { .map(|(_, v)| -> Result<_, CompactFiltersError> {
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?; let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
Ok(header.block_hash()) Ok(header.block_hash())
}) })
.transpose()?) .transpose()
} }
pub fn apply( pub fn apply(
@@ -636,26 +623,26 @@ impl<T: StoreType> fmt::Debug for ChainStore<T> {
pub enum BundleStatus { pub enum BundleStatus {
Init, Init,
CFHeaders { cf_headers: Vec<FilterHeader> }, CfHeaders { cf_headers: Vec<FilterHeader> },
CFilters { cf_filters: Vec<Vec<u8>> }, CFilters { cf_filters: Vec<Vec<u8>> },
Processed { cf_filters: Vec<Vec<u8>> }, Processed { cf_filters: Vec<Vec<u8>> },
Tip { cf_filters: Vec<Vec<u8>> }, Tip { cf_filters: Vec<Vec<u8>> },
Pruned, Pruned,
} }
pub struct CFStore { pub struct CfStore {
store: Arc<RwLock<DB>>, store: Arc<RwLock<DB>>,
filter_type: u8, filter_type: u8,
} }
type BundleEntry = (BundleStatus, FilterHeader); type BundleEntry = (BundleStatus, FilterHeader);
impl CFStore { impl CfStore {
pub fn new( pub fn new(
headers_store: &ChainStore<Full>, headers_store: &ChainStore<Full>,
filter_type: u8, filter_type: u8,
) -> Result<Self, CompactFiltersError> { ) -> Result<Self, CompactFiltersError> {
let cf_store = CFStore { let cf_store = CfStore {
store: Arc::clone(&headers_store.store), store: Arc::clone(&headers_store.store),
filter_type, filter_type,
}; };
@@ -716,11 +703,11 @@ impl CFStore {
// FIXME: we have to filter manually because rocksdb sometimes returns stuff that doesn't // FIXME: we have to filter manually because rocksdb sometimes returns stuff that doesn't
// have the right prefix // have the right prefix
Ok(iterator iterator
.filter(|(k, _)| k.starts_with(&prefix)) .filter(|(k, _)| k.starts_with(&prefix))
.skip(1) .skip(1)
.map(|(_, data)| Ok::<_, CompactFiltersError>(BundleEntry::deserialize(&data)?.1)) .map(|(_, data)| Ok::<_, CompactFiltersError>(BundleEntry::deserialize(&data)?.1))
.collect::<Result<_, _>>()?) .collect::<Result<_, _>>()
} }
pub fn replace_checkpoints( pub fn replace_checkpoints(
@@ -795,7 +782,7 @@ impl CFStore {
} }
let key = StoreEntry::CFilterTable((self.filter_type, Some(bundle))).get_key(); let key = StoreEntry::CFilterTable((self.filter_type, Some(bundle))).get_key();
let value = (BundleStatus::CFHeaders { cf_headers }, checkpoint); let value = (BundleStatus::CfHeaders { cf_headers }, checkpoint);
read_store.put(key, value.serialize())?; read_store.put(key, value.serialize())?;

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::collections::{BTreeMap, HashMap, VecDeque}; use std::collections::{BTreeMap, HashMap, VecDeque};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -38,22 +25,22 @@ use crate::error::Error;
pub(crate) const BURIED_CONFIRMATIONS: usize = 100; pub(crate) const BURIED_CONFIRMATIONS: usize = 100;
pub struct CFSync { pub struct CfSync {
headers_store: Arc<ChainStore<Full>>, headers_store: Arc<ChainStore<Full>>,
cf_store: Arc<CFStore>, cf_store: Arc<CfStore>,
skip_blocks: usize, skip_blocks: usize,
bundles: Mutex<VecDeque<(BundleStatus, FilterHeader, usize)>>, bundles: Mutex<VecDeque<(BundleStatus, FilterHeader, usize)>>,
} }
impl CFSync { impl CfSync {
pub fn new( pub fn new(
headers_store: Arc<ChainStore<Full>>, headers_store: Arc<ChainStore<Full>>,
skip_blocks: usize, skip_blocks: usize,
filter_type: u8, filter_type: u8,
) -> Result<Self, CompactFiltersError> { ) -> Result<Self, CompactFiltersError> {
let cf_store = Arc::new(CFStore::new(&headers_store, filter_type)?); let cf_store = Arc::new(CfStore::new(&headers_store, filter_type)?);
Ok(CFSync { Ok(CfSync {
headers_store, headers_store,
cf_store, cf_store,
skip_blocks, skip_blocks,
@@ -164,7 +151,7 @@ impl CFSync {
checkpoint, checkpoint,
headers_resp.filter_hashes, headers_resp.filter_hashes,
)? { )? {
BundleStatus::CFHeaders { cf_headers } => cf_headers, BundleStatus::CfHeaders { cf_headers } => cf_headers,
_ => return Err(CompactFiltersError::InvalidResponse), _ => return Err(CompactFiltersError::InvalidResponse),
}; };
@@ -184,7 +171,7 @@ impl CFSync {
.cf_store .cf_store
.advance_to_cf_filters(index, checkpoint, cf_headers, filters)?; .advance_to_cf_filters(index, checkpoint, cf_headers, filters)?;
} }
if let BundleStatus::CFHeaders { cf_headers } = status { if let BundleStatus::CfHeaders { cf_headers } = status {
log::trace!("status: CFHeaders"); log::trace!("status: CFHeaders");
peer.get_cf_filters( peer.get_cf_filters(

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Electrum //! Electrum
//! //!
@@ -46,7 +33,7 @@ use bitcoin::{BlockHeader, Script, Transaction, Txid};
use electrum_client::{Client, ConfigBuilder, ElectrumApi, Socks5Config}; use electrum_client::{Client, ConfigBuilder, ElectrumApi, Socks5Config};
use self::utils::{ELSGetHistoryRes, ElectrumLikeSync}; use self::utils::{ElectrumLikeSync, ElsGetHistoryRes};
use super::*; use super::*;
use crate::database::BatchDatabase; use crate::database::BatchDatabase;
use crate::error::Error; use crate::error::Error;
@@ -120,7 +107,7 @@ impl ElectrumLikeSync for Client {
fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script> + Clone>( fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script> + Clone>(
&self, &self,
scripts: I, scripts: I,
) -> Result<Vec<Vec<ELSGetHistoryRes>>, Error> { ) -> Result<Vec<Vec<ElsGetHistoryRes>>, Error> {
self.batch_script_get_history(scripts) self.batch_script_get_history(scripts)
.map(|v| { .map(|v| {
v.into_iter() v.into_iter()
@@ -129,7 +116,7 @@ impl ElectrumLikeSync for Client {
.map( .map(
|electrum_client::GetHistoryRes { |electrum_client::GetHistoryRes {
height, tx_hash, .. height, tx_hash, ..
}| ELSGetHistoryRes { }| ElsGetHistoryRes {
height, height,
tx_hash, tx_hash,
}, },
@@ -157,7 +144,7 @@ impl ElectrumLikeSync for Client {
} }
/// Configuration for an [`ElectrumBlockchain`] /// Configuration for an [`ElectrumBlockchain`]
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct ElectrumBlockchainConfig { pub struct ElectrumBlockchainConfig {
/// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port /// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port
/// ///

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Esplora //! Esplora
//! //!
@@ -52,7 +39,7 @@ use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::hashes::{sha256, Hash}; use bitcoin::hashes::{sha256, Hash};
use bitcoin::{BlockHash, BlockHeader, Script, Transaction, Txid}; use bitcoin::{BlockHash, BlockHeader, Script, Transaction, Txid};
use self::utils::{ELSGetHistoryRes, ElectrumLikeSync}; use self::utils::{ElectrumLikeSync, ElsGetHistoryRes};
use super::*; use super::*;
use crate::database::BatchDatabase; use crate::database::BatchDatabase;
use crate::error::Error; use crate::error::Error;
@@ -223,7 +210,7 @@ impl UrlClient {
async fn _script_get_history( async fn _script_get_history(
&self, &self,
script: &Script, script: &Script,
) -> Result<Vec<ELSGetHistoryRes>, EsploraError> { ) -> Result<Vec<ElsGetHistoryRes>, EsploraError> {
let mut result = Vec::new(); let mut result = Vec::new();
let scripthash = Self::script_to_scripthash(script); let scripthash = Self::script_to_scripthash(script);
@@ -240,7 +227,7 @@ impl UrlClient {
.json::<Vec<EsploraGetHistory>>() .json::<Vec<EsploraGetHistory>>()
.await? .await?
.into_iter() .into_iter()
.map(|x| ELSGetHistoryRes { .map(|x| ElsGetHistoryRes {
tx_hash: x.txid, tx_hash: x.txid,
height: x.status.block_height.unwrap_or(0) as i32, height: x.status.block_height.unwrap_or(0) as i32,
}), }),
@@ -274,7 +261,7 @@ impl UrlClient {
debug!("... adding {} confirmed transactions", len); debug!("... adding {} confirmed transactions", len);
result.extend(response.into_iter().map(|x| ELSGetHistoryRes { result.extend(response.into_iter().map(|x| ElsGetHistoryRes {
tx_hash: x.txid, tx_hash: x.txid,
height: x.status.block_height.unwrap_or(0) as i32, height: x.status.block_height.unwrap_or(0) as i32,
})); }));
@@ -304,7 +291,7 @@ impl ElectrumLikeSync for UrlClient {
fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script>>( fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script>>(
&self, &self,
scripts: I, scripts: I,
) -> Result<Vec<Vec<ELSGetHistoryRes>>, Error> { ) -> Result<Vec<Vec<ElsGetHistoryRes>>, Error> {
let future = async { let future = async {
let mut results = vec![]; let mut results = vec![];
for chunk in ChunksIterator::new(scripts.into_iter(), self.concurrency as usize) { for chunk in ChunksIterator::new(scripts.into_iter(), self.concurrency as usize) {
@@ -312,7 +299,7 @@ impl ElectrumLikeSync for UrlClient {
for script in chunk { for script in chunk {
futs.push(self._script_get_history(&script)); futs.push(self._script_get_history(&script));
} }
let partial_results: Vec<Vec<ELSGetHistoryRes>> = futs.try_collect().await?; let partial_results: Vec<Vec<ElsGetHistoryRes>> = futs.try_collect().await?;
results.extend(partial_results); results.extend(partial_results);
} }
Ok(stream::iter(results).collect().await) Ok(stream::iter(results).collect().await)
@@ -374,7 +361,7 @@ struct EsploraGetHistory {
} }
/// Configuration for an [`EsploraBlockchain`] /// Configuration for an [`EsploraBlockchain`]
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct EsploraBlockchainConfig { pub struct EsploraBlockchainConfig {
/// Base URL of the esplora service /// Base URL of the esplora service
/// ///

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Blockchain backends //! Blockchain backends
//! //!

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@@ -34,12 +21,12 @@ use bitcoin::{BlockHeader, OutPoint, Script, Transaction, Txid};
use super::*; use super::*;
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::error::Error; use crate::error::Error;
use crate::types::{KeychainKind, TransactionDetails, UTXO}; use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
use crate::wallet::time::Instant; use crate::wallet::time::Instant;
use crate::wallet::utils::ChunksIterator; use crate::wallet::utils::ChunksIterator;
#[derive(Debug)] #[derive(Debug)]
pub struct ELSGetHistoryRes { pub struct ElsGetHistoryRes {
pub height: i32, pub height: i32,
pub tx_hash: Txid, pub tx_hash: Txid,
} }
@@ -50,7 +37,7 @@ pub trait ElectrumLikeSync {
fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script> + Clone>( fn els_batch_script_get_history<'s, I: IntoIterator<Item = &'s Script> + Clone>(
&self, &self,
scripts: I, scripts: I,
) -> Result<Vec<Vec<ELSGetHistoryRes>>, Error>; ) -> Result<Vec<Vec<ElsGetHistoryRes>>, Error>;
fn els_batch_transaction_get<'s, I: IntoIterator<Item = &'s Txid> + Clone>( fn els_batch_transaction_get<'s, I: IntoIterator<Item = &'s Txid> + Clone>(
&self, &self,
@@ -90,7 +77,7 @@ pub trait ElectrumLikeSync {
for (i, chunk) in ChunksIterator::new(script_iter, stop_gap).enumerate() { for (i, chunk) in ChunksIterator::new(script_iter, stop_gap).enumerate() {
// TODO if i == last, should create another chunk of addresses in db // TODO if i == last, should create another chunk of addresses in db
let call_result: Vec<Vec<ELSGetHistoryRes>> = let call_result: Vec<Vec<ElsGetHistoryRes>> =
maybe_await!(self.els_batch_script_get_history(chunk.iter()))?; maybe_await!(self.els_batch_script_get_history(chunk.iter()))?;
let max_index = call_result let max_index = call_result
.iter() .iter()
@@ -100,7 +87,7 @@ pub trait ElectrumLikeSync {
if let Some(max) = max_index { if let Some(max) = max_index {
max_indexes.insert(keychain, max + (i * chunk_size) as u32); max_indexes.insert(keychain, max + (i * chunk_size) as u32);
} }
let flattened: Vec<ELSGetHistoryRes> = call_result.into_iter().flatten().collect(); let flattened: Vec<ElsGetHistoryRes> = call_result.into_iter().flatten().collect();
debug!("#{} of {:?} results:{}", i, keychain, flattened.len()); debug!("#{} of {:?} results:{}", i, keychain, flattened.len());
if flattened.is_empty() { if flattened.is_empty() {
// Didn't find anything in the last `stop_gap` script_pubkeys, breaking // Didn't find anything in the last `stop_gap` script_pubkeys, breaking
@@ -353,7 +340,7 @@ fn save_transaction_details_and_utxos<D: BatchDatabase>(
// this output is ours, we have a path to derive it // this output is ours, we have a path to derive it
if let Some((keychain, _child)) = db.get_path_from_script_pubkey(&output.script_pubkey)? { if let Some((keychain, _child)) = db.get_path_from_script_pubkey(&output.script_pubkey)? {
debug!("{} output #{} is mine, adding utxo", txid, i); debug!("{} output #{} is mine, adding utxo", txid, i);
updates.set_utxo(&UTXO { updates.set_utxo(&LocalUtxo {
outpoint: OutPoint::new(tx.txid(), i as u32), outpoint: OutPoint::new(tx.txid(), i as u32),
txout: output.clone(), txout: output.clone(),
keychain, keychain,

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Runtime-checked database types //! Runtime-checked database types
//! //!
@@ -133,7 +120,7 @@ impl BatchOperations for AnyDatabase {
child child
) )
} }
fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
impl_inner_method!(AnyDatabase, self, set_utxo, utxo) impl_inner_method!(AnyDatabase, self, set_utxo, utxo)
} }
fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> { fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
@@ -165,7 +152,7 @@ impl BatchOperations for AnyDatabase {
) -> Result<Option<(KeychainKind, u32)>, Error> { ) -> Result<Option<(KeychainKind, u32)>, Error> {
impl_inner_method!(AnyDatabase, self, del_path_from_script_pubkey, script) impl_inner_method!(AnyDatabase, self, del_path_from_script_pubkey, script)
} }
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
impl_inner_method!(AnyDatabase, self, del_utxo, outpoint) impl_inner_method!(AnyDatabase, self, del_utxo, outpoint)
} }
fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> { fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
@@ -201,7 +188,7 @@ impl Database for AnyDatabase {
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> { fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
impl_inner_method!(AnyDatabase, self, iter_script_pubkeys, keychain) impl_inner_method!(AnyDatabase, self, iter_script_pubkeys, keychain)
} }
fn iter_utxos(&self) -> Result<Vec<UTXO>, Error> { fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
impl_inner_method!(AnyDatabase, self, iter_utxos) impl_inner_method!(AnyDatabase, self, iter_utxos)
} }
fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> { fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> {
@@ -230,7 +217,7 @@ impl Database for AnyDatabase {
) -> Result<Option<(KeychainKind, u32)>, Error> { ) -> Result<Option<(KeychainKind, u32)>, Error> {
impl_inner_method!(AnyDatabase, self, get_path_from_script_pubkey, script) impl_inner_method!(AnyDatabase, self, get_path_from_script_pubkey, script)
} }
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
impl_inner_method!(AnyDatabase, self, get_utxo, outpoint) impl_inner_method!(AnyDatabase, self, get_utxo, outpoint)
} }
fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> { fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
@@ -257,7 +244,7 @@ impl BatchOperations for AnyBatch {
) -> Result<(), Error> { ) -> Result<(), Error> {
impl_inner_method!(AnyBatch, self, set_script_pubkey, script, keychain, child) impl_inner_method!(AnyBatch, self, set_script_pubkey, script, keychain, child)
} }
fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
impl_inner_method!(AnyBatch, self, set_utxo, utxo) impl_inner_method!(AnyBatch, self, set_utxo, utxo)
} }
fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> { fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
@@ -283,7 +270,7 @@ impl BatchOperations for AnyBatch {
) -> Result<Option<(KeychainKind, u32)>, Error> { ) -> Result<Option<(KeychainKind, u32)>, Error> {
impl_inner_method!(AnyBatch, self, del_path_from_script_pubkey, script) impl_inner_method!(AnyBatch, self, del_path_from_script_pubkey, script)
} }
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
impl_inner_method!(AnyBatch, self, del_utxo, outpoint) impl_inner_method!(AnyBatch, self, del_utxo, outpoint)
} }
fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> { fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
@@ -312,24 +299,17 @@ impl BatchDatabase for AnyDatabase {
} }
} }
fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> { fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> {
// TODO: refactor once `move_ref_pattern` is stable
#[allow(irrefutable_let_patterns)]
match self { match self {
AnyDatabase::Memory(db) => { AnyDatabase::Memory(db) => match batch {
if let AnyBatch::Memory(batch) = batch { AnyBatch::Memory(batch) => db.commit_batch(batch),
db.commit_batch(batch) #[cfg(feature = "key-value-db")]
} else { _ => unimplemented!("Sled batch shouldn't be used with Memory db."),
unimplemented!() },
}
}
#[cfg(feature = "key-value-db")] #[cfg(feature = "key-value-db")]
AnyDatabase::Sled(db) => { AnyDatabase::Sled(db) => match batch {
if let AnyBatch::Sled(batch) = batch { AnyBatch::Sled(batch) => db.commit_batch(batch),
db.commit_batch(batch) _ => unimplemented!("Memory batch shouldn't be used with Sled db."),
} else { },
unimplemented!()
}
}
} }
} }
} }

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::convert::TryInto; use std::convert::TryInto;
@@ -51,8 +38,8 @@ macro_rules! impl_batch_operations {
Ok(()) Ok(())
} }
fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); let key = MapKey::Utxo(Some(&utxo.outpoint)).as_map_key();
let value = json!({ let value = json!({
"t": utxo.txout, "t": utxo.txout,
"i": utxo.keychain, "i": utxo.keychain,
@@ -120,8 +107,8 @@ macro_rules! impl_batch_operations {
} }
} }
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
let key = MapKey::UTXO(Some(outpoint)).as_map_key(); let key = MapKey::Utxo(Some(outpoint)).as_map_key();
let res = self.remove(key); let res = self.remove(key);
let res = $process_delete!(res); let res = $process_delete!(res);
@@ -132,7 +119,7 @@ macro_rules! impl_batch_operations {
let txout = serde_json::from_value(val["t"].take())?; let txout = serde_json::from_value(val["t"].take())?;
let keychain = serde_json::from_value(val["i"].take())?; let keychain = serde_json::from_value(val["i"].take())?;
Ok(Some(UTXO { outpoint: outpoint.clone(), txout, keychain })) Ok(Some(LocalUtxo { outpoint: outpoint.clone(), txout, keychain }))
} }
} }
} }
@@ -234,8 +221,8 @@ impl Database for Tree {
.collect() .collect()
} }
fn iter_utxos(&self) -> Result<Vec<UTXO>, Error> { fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
let key = MapKey::UTXO(None).as_map_key(); let key = MapKey::Utxo(None).as_map_key();
self.scan_prefix(key) self.scan_prefix(key)
.map(|x| -> Result<_, Error> { .map(|x| -> Result<_, Error> {
let (k, v) = x?; let (k, v) = x?;
@@ -245,7 +232,7 @@ impl Database for Tree {
let txout = serde_json::from_value(val["t"].take())?; let txout = serde_json::from_value(val["t"].take())?;
let keychain = serde_json::from_value(val["i"].take())?; let keychain = serde_json::from_value(val["i"].take())?;
Ok(UTXO { Ok(LocalUtxo {
outpoint, outpoint,
txout, txout,
keychain, keychain,
@@ -305,15 +292,15 @@ impl Database for Tree {
.transpose() .transpose()
} }
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
let key = MapKey::UTXO(Some(outpoint)).as_map_key(); let key = MapKey::Utxo(Some(outpoint)).as_map_key();
self.get(key)? self.get(key)?
.map(|b| -> Result<_, Error> { .map(|b| -> Result<_, Error> {
let mut val: serde_json::Value = serde_json::from_slice(&b)?; let mut val: serde_json::Value = serde_json::from_slice(&b)?;
let txout = serde_json::from_value(val["t"].take())?; let txout = serde_json::from_value(val["t"].take())?;
let keychain = serde_json::from_value(val["i"].take())?; let keychain = serde_json::from_value(val["i"].take())?;
Ok(UTXO { Ok(LocalUtxo {
outpoint: *outpoint, outpoint: *outpoint,
txout, txout,
keychain, keychain,

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! In-memory ephemeral database //! In-memory ephemeral database
//! //!
@@ -49,7 +36,7 @@ use crate::types::*;
pub(crate) enum MapKey<'a> { pub(crate) enum MapKey<'a> {
Path((Option<KeychainKind>, Option<u32>)), Path((Option<KeychainKind>, Option<u32>)),
Script(Option<&'a Script>), Script(Option<&'a Script>),
UTXO(Option<&'a OutPoint>), Utxo(Option<&'a OutPoint>),
RawTx(Option<&'a Txid>), RawTx(Option<&'a Txid>),
Transaction(Option<&'a Txid>), Transaction(Option<&'a Txid>),
LastIndex(KeychainKind), LastIndex(KeychainKind),
@@ -67,7 +54,7 @@ impl MapKey<'_> {
v v
} }
MapKey::Script(_) => b"s".to_vec(), MapKey::Script(_) => b"s".to_vec(),
MapKey::UTXO(_) => b"u".to_vec(), MapKey::Utxo(_) => b"u".to_vec(),
MapKey::RawTx(_) => b"r".to_vec(), MapKey::RawTx(_) => b"r".to_vec(),
MapKey::Transaction(_) => b"t".to_vec(), MapKey::Transaction(_) => b"t".to_vec(),
MapKey::LastIndex(st) => [b"c", st.as_ref()].concat(), MapKey::LastIndex(st) => [b"c", st.as_ref()].concat(),
@@ -79,7 +66,7 @@ impl MapKey<'_> {
match self { match self {
MapKey::Path((_, Some(child))) => child.to_be_bytes().to_vec(), MapKey::Path((_, Some(child))) => child.to_be_bytes().to_vec(),
MapKey::Script(Some(s)) => serialize(*s), MapKey::Script(Some(s)) => serialize(*s),
MapKey::UTXO(Some(s)) => serialize(*s), MapKey::Utxo(Some(s)) => serialize(*s),
MapKey::RawTx(Some(s)) => serialize(*s), MapKey::RawTx(Some(s)) => serialize(*s),
MapKey::Transaction(Some(s)) => serialize(*s), MapKey::Transaction(Some(s)) => serialize(*s),
_ => vec![], _ => vec![],
@@ -157,8 +144,8 @@ impl BatchOperations for MemoryDatabase {
Ok(()) Ok(())
} }
fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); let key = MapKey::Utxo(Some(&utxo.outpoint)).as_map_key();
self.map self.map
.insert(key, Box::new((utxo.txout.clone(), utxo.keychain))); .insert(key, Box::new((utxo.txout.clone(), utxo.keychain)));
@@ -223,8 +210,8 @@ impl BatchOperations for MemoryDatabase {
} }
} }
} }
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
let key = MapKey::UTXO(Some(outpoint)).as_map_key(); let key = MapKey::Utxo(Some(outpoint)).as_map_key();
let res = self.map.remove(&key); let res = self.map.remove(&key);
self.deleted_keys.push(key); self.deleted_keys.push(key);
@@ -232,7 +219,7 @@ impl BatchOperations for MemoryDatabase {
None => Ok(None), None => Ok(None),
Some(b) => { Some(b) => {
let (txout, keychain) = b.downcast_ref().cloned().unwrap(); let (txout, keychain) = b.downcast_ref().cloned().unwrap();
Ok(Some(UTXO { Ok(Some(LocalUtxo {
outpoint: *outpoint, outpoint: *outpoint,
txout, txout,
keychain, keychain,
@@ -316,14 +303,14 @@ impl Database for MemoryDatabase {
.collect() .collect()
} }
fn iter_utxos(&self) -> Result<Vec<UTXO>, Error> { fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
let key = MapKey::UTXO(None).as_map_key(); let key = MapKey::Utxo(None).as_map_key();
self.map self.map
.range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key)))) .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
.map(|(k, v)| { .map(|(k, v)| {
let outpoint = deserialize(&k[1..]).unwrap(); let outpoint = deserialize(&k[1..]).unwrap();
let (txout, keychain) = v.downcast_ref().cloned().unwrap(); let (txout, keychain) = v.downcast_ref().cloned().unwrap();
Ok(UTXO { Ok(LocalUtxo {
outpoint, outpoint,
txout, txout,
keychain, keychain,
@@ -382,11 +369,11 @@ impl Database for MemoryDatabase {
})) }))
} }
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
let key = MapKey::UTXO(Some(outpoint)).as_map_key(); let key = MapKey::Utxo(Some(outpoint)).as_map_key();
Ok(self.map.get(&key).map(|b| { Ok(self.map.get(&key).map(|b| {
let (txout, keychain) = b.downcast_ref().cloned().unwrap(); let (txout, keychain) = b.downcast_ref().cloned().unwrap();
UTXO { LocalUtxo {
outpoint: *outpoint, outpoint: *outpoint,
txout, txout,
keychain, keychain,
@@ -502,7 +489,7 @@ macro_rules! populate_test_db {
db.set_tx(&tx_details).unwrap(); db.set_tx(&tx_details).unwrap();
for (vout, out) in tx.output.iter().enumerate() { for (vout, out) in tx.output.iter().enumerate() {
db.set_utxo(&UTXO { db.set_utxo(&LocalUtxo {
txout: out.clone(), txout: out.clone(),
outpoint: OutPoint { outpoint: OutPoint {
txid, txid,

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Database types //! Database types
//! //!
@@ -64,8 +51,8 @@ pub trait BatchOperations {
keychain: KeychainKind, keychain: KeychainKind,
child: u32, child: u32,
) -> Result<(), Error>; ) -> Result<(), Error>;
/// Store a [`UTXO`] /// Store a [`LocalUtxo`]
fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error>; fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error>;
/// Store a raw transaction /// Store a raw transaction
fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>; fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>;
/// Store the metadata of a transaction /// Store the metadata of a transaction
@@ -85,8 +72,8 @@ pub trait BatchOperations {
&mut self, &mut self,
script: &Script, script: &Script,
) -> Result<Option<(KeychainKind, u32)>, Error>; ) -> Result<Option<(KeychainKind, u32)>, Error>;
/// Delete a [`UTXO`] given its [`OutPoint`] /// Delete a [`LocalUtxo`] given its [`OutPoint`]
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>; fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error>;
/// Delete a raw transaction given its [`Txid`] /// Delete a raw transaction given its [`Txid`]
fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>; fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>;
/// Delete the metadata of a transaction and optionally the raw transaction itself /// Delete the metadata of a transaction and optionally the raw transaction itself
@@ -116,8 +103,8 @@ pub trait Database: BatchOperations {
/// Return the list of script_pubkeys /// Return the list of script_pubkeys
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error>; fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error>;
/// Return the list of [`UTXO`]s /// Return the list of [`LocalUtxo`]s
fn iter_utxos(&self) -> Result<Vec<UTXO>, Error>; fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error>;
/// Return the list of raw transactions /// Return the list of raw transactions
fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>; fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>;
/// Return the list of transactions metadata /// Return the list of transactions metadata
@@ -134,8 +121,8 @@ pub trait Database: BatchOperations {
&self, &self,
script: &Script, script: &Script,
) -> Result<Option<(KeychainKind, u32)>, Error>; ) -> Result<Option<(KeychainKind, u32)>, Error>;
/// Fetch a [`UTXO`] given its [`OutPoint`] /// Fetch a [`LocalUtxo`] given its [`OutPoint`]
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>; fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error>;
/// Fetch a raw transaction given its [`Txid`] /// Fetch a raw transaction given its [`Txid`]
fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>; fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
/// Fetch the transaction metadata and optionally also the raw transaction /// Fetch the transaction metadata and optionally also the raw transaction
@@ -227,7 +214,7 @@ pub mod test {
); );
assert_eq!( assert_eq!(
tree.get_path_from_script_pubkey(&script).unwrap(), tree.get_path_from_script_pubkey(&script).unwrap(),
Some((keychain, path.clone())) Some((keychain, path))
); );
} }
@@ -256,7 +243,7 @@ pub mod test {
); );
assert_eq!( assert_eq!(
tree.get_path_from_script_pubkey(&script).unwrap(), tree.get_path_from_script_pubkey(&script).unwrap(),
Some((keychain, path.clone())) Some((keychain, path))
); );
} }
@@ -298,7 +285,7 @@ pub mod test {
value: 133742, value: 133742,
script_pubkey: script, script_pubkey: script,
}; };
let utxo = UTXO { let utxo = LocalUtxo {
txout, txout,
outpoint, outpoint,
keychain: KeychainKind::External, keychain: KeychainKind::External,

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Descriptor checksum //! Descriptor checksum
//! //!

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Derived descriptor keys //! Derived descriptor keys

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Descriptors DSL //! Descriptors DSL
@@ -228,10 +215,11 @@ macro_rules! impl_sortedmulti {
use $crate::keys::IntoDescriptorKey; use $crate::keys::IntoDescriptorKey;
let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
let mut keys = vec![]; let keys = vec![
$( $(
keys.push($key.into_descriptor_key()); $key.into_descriptor_key(),
)* )*
];
keys.into_iter().collect::<Result<Vec<_>, _>>() keys.into_iter().collect::<Result<Vec<_>, _>>()
.map_err($crate::descriptor::DescriptorError::Key) .map_err($crate::descriptor::DescriptorError::Key)
@@ -656,10 +644,11 @@ macro_rules! fragment {
use $crate::keys::IntoDescriptorKey; use $crate::keys::IntoDescriptorKey;
let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
let mut keys = vec![]; let keys = vec![
$( $(
keys.push($key.into_descriptor_key()); $key.into_descriptor_key(),
)* )*
];
keys.into_iter().collect::<Result<Vec<_>, _>>() keys.into_iter().collect::<Result<Vec<_>, _>>()
.map_err($crate::descriptor::DescriptorError::Key) .map_err($crate::descriptor::DescriptorError::Key)
@@ -968,7 +957,7 @@ mod test {
fn test_valid_networks() { fn test_valid_networks() {
let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap(); let path = bip32::DerivationPath::from_str("m/0").unwrap();
let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap(); let desc_key = (xprv, path).into_descriptor_key().unwrap();
let (_desc, _key_map, valid_networks) = descriptor!(pkh(desc_key)).unwrap(); let (_desc, _key_map, valid_networks) = descriptor!(pkh(desc_key)).unwrap();
assert_eq!( assert_eq!(
@@ -978,7 +967,7 @@ mod test {
let xprv = bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap(); let xprv = bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap();
let path = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap(); let path = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap();
let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap(); let desc_key = (xprv, path).into_descriptor_key().unwrap();
let (_desc, _key_map, valid_networks) = descriptor!(wpkh(desc_key)).unwrap(); let (_desc, _key_map, valid_networks) = descriptor!(wpkh(desc_key)).unwrap();
assert_eq!(valid_networks, [Bitcoin].iter().cloned().collect()); assert_eq!(valid_networks, [Bitcoin].iter().cloned().collect());
@@ -1005,12 +994,9 @@ mod test {
descriptor!(sh(wsh(multi(2, desc_key1, desc_key2, desc_key3)))).unwrap(); descriptor!(sh(wsh(multi(2, desc_key1, desc_key2, desc_key3)))).unwrap();
assert_eq!(key_map.len(), 3); assert_eq!(key_map.len(), 3);
let desc_key1: DescriptorKey<Segwitv0> = let desc_key1: DescriptorKey<Segwitv0> = (xprv1, path1).into_descriptor_key().unwrap();
(xprv1, path1.clone()).into_descriptor_key().unwrap(); let desc_key2: DescriptorKey<Segwitv0> = (xprv2, path2).into_descriptor_key().unwrap();
let desc_key2: DescriptorKey<Segwitv0> = let desc_key3: DescriptorKey<Segwitv0> = (xprv3, path3).into_descriptor_key().unwrap();
(xprv2, path2.clone()).into_descriptor_key().unwrap();
let desc_key3: DescriptorKey<Segwitv0> =
(xprv3, path3.clone()).into_descriptor_key().unwrap();
let (key1, _key_map, _valid_networks) = desc_key1.extract(&secp).unwrap(); let (key1, _key_map, _valid_networks) = desc_key1.extract(&secp).unwrap();
let (key2, _key_map, _valid_networks) = desc_key2.extract(&secp).unwrap(); let (key2, _key_map, _valid_networks) = desc_key2.extract(&secp).unwrap();
@@ -1026,7 +1012,7 @@ mod test {
// this compiles // this compiles
let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let xprv = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap(); let path = bip32::DerivationPath::from_str("m/0").unwrap();
let desc_key: DescriptorKey<Legacy> = (xprv, path.clone()).into_descriptor_key().unwrap(); let desc_key: DescriptorKey<Legacy> = (xprv, path).into_descriptor_key().unwrap();
let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap(); let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap();
assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)#yrnz9pp2"); assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)#yrnz9pp2");

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Descriptor errors //! Descriptor errors
@@ -28,11 +15,13 @@
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Invalid HD Key path, such as having a wildcard but a length != 1 /// Invalid HD Key path, such as having a wildcard but a length != 1
InvalidHDKeyPath, InvalidHdKeyPath,
/// The provided descriptor doesn't match its checksum /// The provided descriptor doesn't match its checksum
InvalidDescriptorChecksum, InvalidDescriptorChecksum,
/// The descriptor contains hardened derivation steps on public extended keys /// The descriptor contains hardened derivation steps on public extended keys
HardenedDerivationXpub, HardenedDerivationXpub,
/// The descriptor contains multiple keys with the same BIP32 fingerprint
DuplicatedKeys,
/// Error thrown while working with [`keys`](crate::keys) /// Error thrown while working with [`keys`](crate::keys)
Key(crate::keys::KeyError), Key(crate::keys::KeyError),
@@ -43,11 +32,11 @@ pub enum Error {
InvalidDescriptorCharacter(char), InvalidDescriptorCharacter(char),
/// BIP32 error /// BIP32 error
BIP32(bitcoin::util::bip32::Error), Bip32(bitcoin::util::bip32::Error),
/// Error during base58 decoding /// Error during base58 decoding
Base58(bitcoin::util::base58::Error), Base58(bitcoin::util::base58::Error),
/// Key-related error /// Key-related error
PK(bitcoin::util::key::Error), Pk(bitcoin::util::key::Error),
/// Miniscript error /// Miniscript error
Miniscript(miniscript::Error), Miniscript(miniscript::Error),
/// Hex decoding error /// Hex decoding error
@@ -58,7 +47,7 @@ impl From<crate::keys::KeyError> for Error {
fn from(key_error: crate::keys::KeyError) -> Error { fn from(key_error: crate::keys::KeyError) -> Error {
match key_error { match key_error {
crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner), crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
crate::keys::KeyError::BIP32(inner) => Error::BIP32(inner), crate::keys::KeyError::Bip32(inner) => Error::Bip32(inner),
e => Error::Key(e), e => Error::Key(e),
} }
} }
@@ -72,9 +61,9 @@ impl std::fmt::Display for Error {
impl std::error::Error for Error {} impl std::error::Error for Error {}
impl_error!(bitcoin::util::bip32::Error, BIP32); impl_error!(bitcoin::util::bip32::Error, Bip32);
impl_error!(bitcoin::util::base58::Error, Base58); impl_error!(bitcoin::util::base58::Error, Base58);
impl_error!(bitcoin::util::key::Error, PK); impl_error!(bitcoin::util::key::Error, Pk);
impl_error!(miniscript::Error, Miniscript); impl_error!(miniscript::Error, Miniscript);
impl_error!(bitcoin::hashes::hex::Error, Hex); impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(crate::descriptor::policy::PolicyError, Policy); impl_error!(crate::descriptor::policy::PolicyError, Policy);

View File

@@ -1,33 +1,20 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Descriptors //! Descriptors
//! //!
//! This module contains generic utilities to work with descriptors, plus some re-exported types //! This module contains generic utilities to work with descriptors, plus some re-exported types
//! from [`miniscript`]. //! from [`miniscript`].
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap, HashSet};
use std::ops::Deref; use std::ops::Deref;
use bitcoin::util::bip32::{ use bitcoin::util::bip32::{
@@ -40,6 +27,8 @@ use miniscript::descriptor::{DescriptorPublicKey, DescriptorType, DescriptorXKey
pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0}; pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0};
use miniscript::{DescriptorTrait, ForEachKey, TranslatePk}; use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
use crate::descriptor::policy::BuildSatisfaction;
pub mod checksum; pub mod checksum;
pub(crate) mod derived; pub(crate) mod derived;
#[doc(hidden)] #[doc(hidden)]
@@ -69,7 +58,7 @@ pub type DerivedDescriptor<'s> = Descriptor<DerivedDescriptorKey<'s>>;
/// ///
/// [`psbt::Input`]: bitcoin::util::psbt::Input /// [`psbt::Input`]: bitcoin::util::psbt::Input
/// [`psbt::Output`]: bitcoin::util::psbt::Output /// [`psbt::Output`]: bitcoin::util::psbt::Output
pub type HDKeyPaths = BTreeMap<PublicKey, KeySource>; pub type HdKeyPaths = BTreeMap<PublicKey, KeySource>;
/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`] /// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
pub trait IntoWalletDescriptor { pub trait IntoWalletDescriptor {
@@ -225,6 +214,24 @@ pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
return Err(DescriptorError::HardenedDerivationXpub); return Err(DescriptorError::HardenedDerivationXpub);
} }
// Ensure that there are no duplicated keys
let mut found_keys = HashSet::new();
let descriptor_contains_duplicated_keys = descriptor.for_any_key(|k| {
if let DescriptorPublicKey::XPub(xkey) = k.as_key() {
let fingerprint = xkey.root_fingerprint(secp);
if found_keys.contains(&fingerprint) {
return true;
}
found_keys.insert(fingerprint);
}
false
});
if descriptor_contains_duplicated_keys {
return Err(DescriptorError::DuplicatedKeys);
}
Ok((descriptor, keymap)) Ok((descriptor, keymap))
} }
@@ -250,6 +257,7 @@ pub trait ExtractPolicy {
fn extract_policy( fn extract_policy(
&self, &self,
signers: &SignersContainer, signers: &SignersContainer,
psbt: BuildSatisfaction,
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<Option<Policy>, DescriptorError>; ) -> Result<Option<Policy>, DescriptorError>;
} }
@@ -324,7 +332,7 @@ impl XKeyUtils for DescriptorXKey<ExtendedPrivKey> {
} }
pub(crate) trait DerivedDescriptorMeta { pub(crate) trait DerivedDescriptorMeta {
fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError>; fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HdKeyPaths, DescriptorError>;
} }
pub(crate) trait DescriptorMeta { pub(crate) trait DescriptorMeta {
@@ -332,7 +340,7 @@ pub(crate) trait DescriptorMeta {
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>; fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
fn derive_from_hd_keypaths<'s>( fn derive_from_hd_keypaths<'s>(
&self, &self,
hd_keypaths: &HDKeyPaths, hd_keypaths: &HdKeyPaths,
secp: &'s SecpCtx, secp: &'s SecpCtx,
) -> Option<DerivedDescriptor<'s>>; ) -> Option<DerivedDescriptor<'s>>;
fn derive_from_psbt_input<'s>( fn derive_from_psbt_input<'s>(
@@ -401,7 +409,7 @@ impl DescriptorMeta for ExtendedDescriptor {
fn derive_from_hd_keypaths<'s>( fn derive_from_hd_keypaths<'s>(
&self, &self,
hd_keypaths: &HDKeyPaths, hd_keypaths: &HdKeyPaths,
secp: &'s SecpCtx, secp: &'s SecpCtx,
) -> Option<DerivedDescriptor<'s>> { ) -> Option<DerivedDescriptor<'s>> {
let index: HashMap<_, _> = hd_keypaths.values().map(|(a, b)| (a, b)).collect(); let index: HashMap<_, _> = hd_keypaths.values().map(|(a, b)| (a, b)).collect();
@@ -500,7 +508,7 @@ impl DescriptorMeta for ExtendedDescriptor {
} }
impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> { impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> {
fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError> { fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HdKeyPaths, DescriptorError> {
let mut answer = BTreeMap::new(); let mut answer = BTreeMap::new();
self.for_each_key(|key| { self.for_each_key(|key| {
if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() { if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
@@ -532,7 +540,7 @@ mod test {
use bitcoin::util::{bip32, psbt}; use bitcoin::util::{bip32, psbt};
use super::*; use super::*;
use crate::psbt::PSBTUtils; use crate::psbt::PsbtUtils;
#[test] #[test]
fn test_derive_from_psbt_input_wpkh_wif() { fn test_derive_from_psbt_input_wpkh_wif() {
@@ -783,5 +791,14 @@ mod test {
result.unwrap_err(), result.unwrap_err(),
DescriptorError::HardenedDerivationXpub DescriptorError::HardenedDerivationXpub
)); ));
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/*))";
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DescriptorError::DuplicatedKeys
));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Descriptor templates //! Descriptor templates
//! //!
@@ -75,7 +62,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
secp: &SecpCtx, secp: &SecpCtx,
network: Network, network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
Ok(self.build()?.into_wallet_descriptor(secp, network)?) self.build()?.into_wallet_descriptor(secp, network)
} }
} }
@@ -87,28 +74,29 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet}; /// # use bdk::{Wallet};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::P2PKH; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::P2Pkh;
/// ///
/// let key = /// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// P2PKH(key), /// P2Pkh(key),
/// None, /// None,
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default(), /// MemoryDatabase::default(),
/// )?; /// )?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet.get_new_address()?.to_string(), /// wallet.get_address(New)?.to_string(),
/// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT" /// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
/// ); /// );
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct P2PKH<K: IntoDescriptorKey<Legacy>>(pub K); pub struct P2Pkh<K: IntoDescriptorKey<Legacy>>(pub K);
impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> { impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(descriptor!(pkh(self.0))?) descriptor!(pkh(self.0))
} }
} }
@@ -120,29 +108,30 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet}; /// # use bdk::{Wallet};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::P2WPKH_P2SH; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::P2Wpkh_P2Sh;
/// ///
/// let key = /// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// P2WPKH_P2SH(key), /// P2Wpkh_P2Sh(key),
/// None, /// None,
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default(), /// MemoryDatabase::default(),
/// )?; /// )?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet.get_new_address()?.to_string(), /// wallet.get_address(New)?.to_string(),
/// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5" /// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
/// ); /// );
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub struct P2WPKH_P2SH<K: IntoDescriptorKey<Segwitv0>>(pub K); pub struct P2Wpkh_P2Sh<K: IntoDescriptorKey<Segwitv0>>(pub K);
impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> { impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(descriptor!(sh(wpkh(self.0)))?) descriptor!(sh(wpkh(self.0)))
} }
} }
@@ -154,28 +143,29 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet}; /// # use bdk::{Wallet};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::P2WPKH; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::P2Wpkh;
/// ///
/// let key = /// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// P2WPKH(key), /// P2Wpkh(key),
/// None, /// None,
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default(), /// MemoryDatabase::default(),
/// )?; /// )?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet.get_new_address()?.to_string(), /// wallet.get_address(New)?.to_string(),
/// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd" /// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
/// ); /// );
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct P2WPKH<K: IntoDescriptorKey<Segwitv0>>(pub K); pub struct P2Wpkh<K: IntoDescriptorKey<Segwitv0>>(pub K);
impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> { impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(descriptor!(wpkh(self.0))?) descriptor!(wpkh(self.0))
} }
} }
@@ -183,7 +173,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
/// ///
/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`). /// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
/// ///
/// See [`BIP44Public`] for a template that can work with a `xpub`/`tpub`. /// See [`Bip44Public`] for a template that can work with a `xpub`/`tpub`.
/// ///
/// ## Example /// ## Example
/// ///
@@ -192,25 +182,26 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP44; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP44(key.clone(), KeychainKind::External), /// Bip44(key.clone(), KeychainKind::External),
/// Some(BIP44(key, KeychainKind::Internal)), /// Some(Bip44(key, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); /// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind); pub struct Bip44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> { impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2PKH(legacy::make_bipxx_private(44, self.0, self.1)?).build()?) P2Pkh(legacy::make_bipxx_private(44, self.0, self.1)?).build()
} }
} }
@@ -220,7 +211,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
/// ///
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. /// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
/// ///
/// See [`BIP44`] for a template that does the full derivation, but requires private data /// See [`Bip44`] for a template that does the full derivation, but requires private data
/// for the key. /// for the key.
/// ///
/// ## Example /// ## Example
@@ -230,26 +221,27 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP44Public; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44Public;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP44Public(key.clone(), fingerprint, KeychainKind::External), /// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(BIP44Public(key, fingerprint, KeychainKind::Internal)), /// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); /// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind); pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> { impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2PKH(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build()?) P2Pkh(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build()
} }
} }
@@ -257,7 +249,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
/// ///
/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`). /// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
/// ///
/// See [`BIP49Public`] for a template that can work with a `xpub`/`tpub`. /// See [`Bip49Public`] for a template that can work with a `xpub`/`tpub`.
/// ///
/// ## Example /// ## Example
/// ///
@@ -266,25 +258,26 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP49; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP49(key.clone(), KeychainKind::External), /// Bip49(key.clone(), KeychainKind::External),
/// Some(BIP49(key, KeychainKind::Internal)), /// Some(Bip49(key, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); /// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind); pub struct Bip49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> { impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2WPKH_P2SH(segwit_v0::make_bipxx_private(49, self.0, self.1)?).build()?) P2Wpkh_P2Sh(segwit_v0::make_bipxx_private(49, self.0, self.1)?).build()
} }
} }
@@ -294,7 +287,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
/// ///
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. /// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
/// ///
/// See [`BIP49`] for a template that does the full derivation, but requires private data /// See [`Bip49`] for a template that does the full derivation, but requires private data
/// for the key. /// for the key.
/// ///
/// ## Example /// ## Example
@@ -304,26 +297,27 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP49Public; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49Public;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP49Public(key.clone(), fingerprint, KeychainKind::External), /// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(BIP49Public(key, fingerprint, KeychainKind::Internal)), /// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); /// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind); pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> { impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2WPKH_P2SH(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build()?) P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build()
} }
} }
@@ -331,7 +325,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
/// ///
/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`). /// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
/// ///
/// See [`BIP84Public`] for a template that can work with a `xpub`/`tpub`. /// See [`Bip84Public`] for a template that can work with a `xpub`/`tpub`.
/// ///
/// ## Example /// ## Example
/// ///
@@ -340,25 +334,26 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP84; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP84(key.clone(), KeychainKind::External), /// Bip84(key.clone(), KeychainKind::External),
/// Some(BIP84(key, KeychainKind::Internal)), /// Some(Bip84(key, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); /// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind); pub struct Bip84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> { impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2WPKH(segwit_v0::make_bipxx_private(84, self.0, self.1)?).build()?) P2Wpkh(segwit_v0::make_bipxx_private(84, self.0, self.1)?).build()
} }
} }
@@ -368,7 +363,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
/// ///
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. /// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
/// ///
/// See [`BIP84`] for a template that does the full derivation, but requires private data /// See [`Bip84`] for a template that does the full derivation, but requires private data
/// for the key. /// for the key.
/// ///
/// ## Example /// ## Example
@@ -378,26 +373,27 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
/// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind}; /// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase; /// # use bdk::database::MemoryDatabase;
/// use bdk::template::BIP84Public; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84Public;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new_offline( /// let wallet = Wallet::new_offline(
/// BIP84Public(key.clone(), fingerprint, KeychainKind::External), /// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(BIP84Public(key, fingerprint, KeychainKind::Internal)), /// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet, /// Network::Testnet,
/// MemoryDatabase::default() /// MemoryDatabase::default()
/// )?; /// )?;
/// ///
/// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); /// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg"); /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
/// # Ok::<_, Box<dyn std::error::Error>>(()) /// # Ok::<_, Box<dyn std::error::Error>>(())
/// ``` /// ```
pub struct BIP84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind); pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84Public<K> { impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> { fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
Ok(P2WPKH(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build()?) P2Wpkh(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build()
} }
} }
@@ -440,11 +436,11 @@ macro_rules! expand_make_bipxx {
KeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(), KeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(),
}; };
let mut source_path = Vec::with_capacity(3); let source_path = bip32::DerivationPath::from(vec![
source_path.push(bip32::ChildNumber::from_hardened_idx(bip)?); bip32::ChildNumber::from_hardened_idx(bip)?,
source_path.push(bip32::ChildNumber::from_hardened_idx(0)?); bip32::ChildNumber::from_hardened_idx(0)?,
source_path.push(bip32::ChildNumber::from_hardened_idx(0)?); bip32::ChildNumber::from_hardened_idx(0)?,
let source_path: bip32::DerivationPath = source_path.into(); ]);
Ok((key, (parent_fingerprint, source_path), derivation_path)) Ok((key, (parent_fingerprint, source_path), derivation_path))
} }
@@ -459,11 +455,12 @@ expand_make_bipxx!(segwit_v0, Segwitv0);
mod test { mod test {
// test existing descriptor templates, make sure they are expanded to the right descriptors // test existing descriptor templates, make sure they are expanded to the right descriptors
use std::str::FromStr;
use super::*; use super::*;
use crate::descriptor::derived::AsDerived; use crate::descriptor::derived::AsDerived;
use crate::descriptor::{DescriptorError, DescriptorMeta}; use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::ValidNetworks; use crate::keys::ValidNetworks;
use bitcoin::hashes::core::str::FromStr;
use bitcoin::network::constants::Network::Regtest; use bitcoin::network::constants::Network::Regtest;
use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::Secp256k1;
use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap}; use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap};
@@ -500,7 +497,7 @@ mod test {
bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
.unwrap(); .unwrap();
check( check(
P2PKH(prvkey).build(), P2Pkh(prvkey).build(),
false, false,
true, true,
&["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"], &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
@@ -511,7 +508,7 @@ mod test {
) )
.unwrap(); .unwrap();
check( check(
P2PKH(pubkey).build(), P2Pkh(pubkey).build(),
false, false,
true, true,
&["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"], &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
@@ -525,7 +522,7 @@ mod test {
bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
.unwrap(); .unwrap();
check( check(
P2WPKH_P2SH(prvkey).build(), P2Wpkh_P2Sh(prvkey).build(),
true, true,
true, true,
&["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"], &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
@@ -536,7 +533,7 @@ mod test {
) )
.unwrap(); .unwrap();
check( check(
P2WPKH_P2SH(pubkey).build(), P2Wpkh_P2Sh(pubkey).build(),
true, true,
true, true,
&["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"], &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
@@ -550,7 +547,7 @@ mod test {
bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
.unwrap(); .unwrap();
check( check(
P2WPKH(prvkey).build(), P2Wpkh(prvkey).build(),
true, true,
true, true,
&["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"], &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
@@ -561,7 +558,7 @@ mod test {
) )
.unwrap(); .unwrap();
check( check(
P2WPKH(pubkey).build(), P2Wpkh(pubkey).build(),
true, true,
true, true,
&["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"], &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"],
@@ -573,7 +570,7 @@ mod test {
fn test_bip44_template() { fn test_bip44_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check( check(
BIP44(prvkey, KeychainKind::External).build(), Bip44(prvkey, KeychainKind::External).build(),
false, false,
false, false,
&[ &[
@@ -583,7 +580,7 @@ mod test {
], ],
); );
check( check(
BIP44(prvkey, KeychainKind::Internal).build(), Bip44(prvkey, KeychainKind::Internal).build(),
false, false,
false, false,
&[ &[
@@ -600,7 +597,7 @@ mod test {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap(); let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
check( check(
BIP44Public(pubkey, fingerprint, KeychainKind::External).build(), Bip44Public(pubkey, fingerprint, KeychainKind::External).build(),
false, false,
false, false,
&[ &[
@@ -610,7 +607,7 @@ mod test {
], ],
); );
check( check(
BIP44Public(pubkey, fingerprint, KeychainKind::Internal).build(), Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(),
false, false,
false, false,
&[ &[
@@ -626,7 +623,7 @@ mod test {
fn test_bip49_template() { fn test_bip49_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check( check(
BIP49(prvkey, KeychainKind::External).build(), Bip49(prvkey, KeychainKind::External).build(),
true, true,
false, false,
&[ &[
@@ -636,7 +633,7 @@ mod test {
], ],
); );
check( check(
BIP49(prvkey, KeychainKind::Internal).build(), Bip49(prvkey, KeychainKind::Internal).build(),
true, true,
false, false,
&[ &[
@@ -653,7 +650,7 @@ mod test {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap(); let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
check( check(
BIP49Public(pubkey, fingerprint, KeychainKind::External).build(), Bip49Public(pubkey, fingerprint, KeychainKind::External).build(),
true, true,
false, false,
&[ &[
@@ -663,7 +660,7 @@ mod test {
], ],
); );
check( check(
BIP49Public(pubkey, fingerprint, KeychainKind::Internal).build(), Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(),
true, true,
false, false,
&[ &[
@@ -679,7 +676,7 @@ mod test {
fn test_bip84_template() { fn test_bip84_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check( check(
BIP84(prvkey, KeychainKind::External).build(), Bip84(prvkey, KeychainKind::External).build(),
true, true,
false, false,
&[ &[
@@ -689,7 +686,7 @@ mod test {
], ],
); );
check( check(
BIP84(prvkey, KeychainKind::Internal).build(), Bip84(prvkey, KeychainKind::Internal).build(),
true, true,
false, false,
&[ &[
@@ -706,7 +703,7 @@ mod test {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap(); let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
check( check(
BIP84Public(pubkey, fingerprint, KeychainKind::External).build(), Bip84Public(pubkey, fingerprint, KeychainKind::External).build(),
true, true,
false, false,
&[ &[
@@ -716,7 +713,7 @@ mod test {
], ],
); );
check( check(
BIP84Public(pubkey, fingerprint, KeychainKind::Internal).build(), Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(),
true, true,
false, false,
&[ &[

View File

@@ -1,3 +1,14 @@
// Bitcoin Dev Kit
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
#[doc(include = "../README.md")] #[doc(include = "../README.md")]
#[cfg(doctest)] #[cfg(doctest)]
pub struct ReadmeDoctests; pub struct ReadmeDoctests;

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::fmt; use std::fmt;
@@ -60,7 +47,7 @@ pub enum Error {
/// the desired outputs plus fee, if there is not such combination this error is thrown /// the desired outputs plus fee, if there is not such combination this error is thrown
BnBNoExactMatch, BnBNoExactMatch,
/// Happens when trying to spend an UTXO that is not in the internal database /// Happens when trying to spend an UTXO that is not in the internal database
UnknownUTXO, UnknownUtxo,
/// Thrown when a tx is not found in the internal database /// Thrown when a tx is not found in the internal database
TransactionNotFound, TransactionNotFound,
/// Happens when trying to bump a transaction that is already confirmed /// Happens when trying to bump a transaction that is already confirmed
@@ -110,15 +97,15 @@ pub enum Error {
/// Miniscript error /// Miniscript error
Miniscript(miniscript::Error), Miniscript(miniscript::Error),
/// BIP32 error /// BIP32 error
BIP32(bitcoin::util::bip32::Error), Bip32(bitcoin::util::bip32::Error),
/// An ECDSA error /// An ECDSA error
Secp256k1(bitcoin::secp256k1::Error), Secp256k1(bitcoin::secp256k1::Error),
/// Error serializing or deserializing JSON data /// Error serializing or deserializing JSON data
JSON(serde_json::Error), Json(serde_json::Error),
/// Hex decoding error /// Hex decoding error
Hex(bitcoin::hashes::hex::Error), Hex(bitcoin::hashes::hex::Error),
/// Partially signed bitcoin transaction error /// Partially signed bitcoin transaction error
PSBT(bitcoin::util::psbt::Error), Psbt(bitcoin::util::psbt::Error),
//KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey), //KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
//MissingInputUTXO(usize), //MissingInputUTXO(usize),
@@ -171,7 +158,7 @@ impl From<crate::keys::KeyError> for Error {
fn from(key_error: crate::keys::KeyError) -> Error { fn from(key_error: crate::keys::KeyError) -> Error {
match key_error { match key_error {
crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner), crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
crate::keys::KeyError::BIP32(inner) => Error::BIP32(inner), crate::keys::KeyError::Bip32(inner) => Error::Bip32(inner),
crate::keys::KeyError::InvalidChecksum => Error::ChecksumMismatch, crate::keys::KeyError::InvalidChecksum => Error::ChecksumMismatch,
e => Error::Key(e), e => Error::Key(e),
} }
@@ -180,11 +167,11 @@ impl From<crate::keys::KeyError> for Error {
impl_error!(bitcoin::consensus::encode::Error, Encode); impl_error!(bitcoin::consensus::encode::Error, Encode);
impl_error!(miniscript::Error, Miniscript); impl_error!(miniscript::Error, Miniscript);
impl_error!(bitcoin::util::bip32::Error, BIP32); impl_error!(bitcoin::util::bip32::Error, Bip32);
impl_error!(bitcoin::secp256k1::Error, Secp256k1); impl_error!(bitcoin::secp256k1::Error, Secp256k1);
impl_error!(serde_json::Error, JSON); impl_error!(serde_json::Error, Json);
impl_error!(bitcoin::hashes::hex::Error, Hex); impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(bitcoin::util::psbt::Error, PSBT); impl_error!(bitcoin::util::psbt::Error, Psbt);
#[cfg(feature = "electrum")] #[cfg(feature = "electrum")]
impl_error!(electrum_client::Error, Electrum); impl_error!(electrum_client::Error, Electrum);

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! BIP-0039 //! BIP-0039

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Key formats //! Key formats
@@ -733,7 +720,7 @@ fn expand_multi_keys<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
) -> Result<(Vec<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError> { ) -> Result<(Vec<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError> {
let (pks, key_maps_networks): (Vec<_>, Vec<_>) = pks let (pks, key_maps_networks): (Vec<_>, Vec<_>) = pks
.into_iter() .into_iter()
.map(|key| Ok::<_, KeyError>(key.into_descriptor_key()?.extract(secp)?)) .map(|key| key.into_descriptor_key()?.extract(secp))
.collect::<Result<Vec<_>, _>>()? .collect::<Result<Vec<_>, _>>()?
.into_iter() .into_iter()
.map(|(a, b, c)| (a, (b, c))) .map(|(a, b, c)| (a, (b, c)))
@@ -886,13 +873,13 @@ pub enum KeyError {
Message(String), Message(String),
/// BIP32 error /// BIP32 error
BIP32(bitcoin::util::bip32::Error), Bip32(bitcoin::util::bip32::Error),
/// Miniscript error /// Miniscript error
Miniscript(miniscript::Error), Miniscript(miniscript::Error),
} }
impl_error!(miniscript::Error, Miniscript, KeyError); impl_error!(miniscript::Error, Miniscript, KeyError);
impl_error!(bitcoin::util::bip32::Error, BIP32, KeyError); impl_error!(bitcoin::util::bip32::Error, Bip32, KeyError);
impl std::fmt::Display for KeyError { impl std::fmt::Display for KeyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// rustdoc will warn if there are missing docs // rustdoc will warn if there are missing docs
#![warn(missing_docs)] #![warn(missing_docs)]
@@ -56,7 +43,7 @@
//! interact with the bitcoin P2P network. //! interact with the bitcoin P2P network.
//! //!
//! ```toml //! ```toml
//! bdk = "0.4.0" //! bdk = "0.7.0"
//! ``` //! ```
//! //!
//! ## Sync the balance of a descriptor //! ## Sync the balance of a descriptor
@@ -93,18 +80,19 @@
//! ``` //! ```
//! use bdk::{Wallet}; //! use bdk::{Wallet};
//! use bdk::database::MemoryDatabase; //! use bdk::database::MemoryDatabase;
//! use bdk::wallet::AddressIndex::New;
//! //!
//! fn main() -> Result<(), bdk::Error> { //! fn main() -> Result<(), bdk::Error> {
//! let wallet = Wallet::new_offline( //! let wallet = Wallet::new_offline(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", //! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), //! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
//! bitcoin::Network::Testnet, //! bitcoin::Network::Testnet,
//! MemoryDatabase::default(), //! MemoryDatabase::default(),
//! )?; //! )?;
//! //!
//! println!("Address #0: {}", wallet.get_new_address()?); //! println!("Address #0: {}", wallet.get_address(New)?);
//! println!("Address #1: {}", wallet.get_new_address()?); //! println!("Address #1: {}", wallet.get_address(New)?);
//! println!("Address #2: {}", wallet.get_new_address()?); //! println!("Address #2: {}", wallet.get_address(New)?);
//! //!
//! Ok(()) //! Ok(())
//! } //! }
@@ -122,6 +110,7 @@
//! use bdk::electrum_client::Client; //! use bdk::electrum_client::Client;
//! //!
//! use bitcoin::consensus::serialize; //! use bitcoin::consensus::serialize;
//! use bdk::wallet::AddressIndex::New;
//! //!
//! fn main() -> Result<(), bdk::Error> { //! fn main() -> Result<(), bdk::Error> {
//! let client = Client::new("ssl://electrum.blockstream.info:60002")?; //! let client = Client::new("ssl://electrum.blockstream.info:60002")?;
@@ -135,13 +124,16 @@
//! //!
//! wallet.sync(noop_progress(), None)?; //! wallet.sync(noop_progress(), None)?;
//! //!
//! let send_to = wallet.get_new_address()?; //! let send_to = wallet.get_address(New)?;
//! let (psbt, details) = wallet.build_tx() //! let (psbt, details) = {
//! .add_recipient(send_to.script_pubkey(), 50_000) //! let mut builder = wallet.build_tx();
//! .enable_rbf() //! builder
//! .do_not_spend_change() //! .add_recipient(send_to.script_pubkey(), 50_000)
//! .fee_rate(FeeRate::from_sat_per_vb(5.0)) //! .enable_rbf()
//! .finish()?; //! .do_not_spend_change()
//! .fee_rate(FeeRate::from_sat_per_vb(5.0))
//! builder.finish()?
//! };
//! //!
//! println!("Transaction details: {:#?}", details); //! println!("Transaction details: {:#?}", details);
//! println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt))); //! println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt)));
@@ -169,9 +161,9 @@
//! )?; //! )?;
//! //!
//! let psbt = "..."; //! let psbt = "...";
//! let psbt = deserialize(&base64::decode(psbt).unwrap())?; //! let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
//! //!
//! let (signed_psbt, finalized) = wallet.sign(psbt, None)?; //! let finalized = wallet.sign(&mut psbt, None)?;
//! //!
//! Ok(()) //! Ok(())
//! } //! }
@@ -225,7 +217,6 @@ extern crate async_trait;
extern crate bdk_macros; extern crate bdk_macros;
#[cfg(feature = "compact_filters")] #[cfg(feature = "compact_filters")]
#[macro_use]
extern crate lazy_static; extern crate lazy_static;
#[cfg(feature = "electrum")] #[cfg(feature = "electrum")]
@@ -263,11 +254,12 @@ pub(crate) mod types;
pub mod wallet; pub mod wallet;
pub use descriptor::template; pub use descriptor::template;
pub use descriptor::HDKeyPaths; pub use descriptor::HdKeyPaths;
pub use error::Error; pub use error::Error;
pub use types::*; pub use types::*;
pub use wallet::address_validator; pub use wallet::address_validator;
pub use wallet::signer; pub use wallet::signer;
pub use wallet::signer::SignOptions;
pub use wallet::tx_builder::TxBuilder; pub use wallet::tx_builder::TxBuilder;
pub use wallet::Wallet; pub use wallet::Wallet;

View File

@@ -1,35 +1,22 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::TxOut; use bitcoin::TxOut;
pub trait PSBTUtils { pub trait PsbtUtils {
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>; fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
} }
impl PSBTUtils for PSBT { impl PsbtUtils for PSBT {
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> { fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
let tx = &self.global.unsigned_tx; let tx = &self.global.unsigned_tx;
@@ -50,3 +37,85 @@ impl PSBTUtils for PSBT {
} }
} }
} }
#[cfg(test)]
mod test {
use crate::bitcoin::consensus::deserialize;
use crate::bitcoin::TxIn;
use crate::psbt::PSBT;
use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
use crate::wallet::AddressIndex;
use crate::SignOptions;
// from bip 174
const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA";
#[test]
#[should_panic(expected = "InputIndexOutOfRange")]
fn test_psbt_malformed_psbt_input_legacy() {
let psbt_bip: PSBT = deserialize(&base64::decode(PSBT_STR).unwrap()).unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let send_to = wallet.get_address(AddressIndex::New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let (mut psbt, _) = builder.finish().unwrap();
psbt.inputs.push(psbt_bip.inputs[0].clone());
let options = SignOptions {
trust_witness_utxo: true,
assume_height: None,
};
let _ = wallet.sign(&mut psbt, options).unwrap();
}
#[test]
#[should_panic(expected = "InputIndexOutOfRange")]
fn test_psbt_malformed_psbt_input_segwit() {
let psbt_bip: PSBT = deserialize(&base64::decode(PSBT_STR).unwrap()).unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let send_to = wallet.get_address(AddressIndex::New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let (mut psbt, _) = builder.finish().unwrap();
psbt.inputs.push(psbt_bip.inputs[1].clone());
let options = SignOptions {
trust_witness_utxo: true,
assume_height: None,
};
let _ = wallet.sign(&mut psbt, options).unwrap();
}
#[test]
#[should_panic(expected = "InputIndexOutOfRange")]
fn test_psbt_malformed_tx_input() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let send_to = wallet.get_address(AddressIndex::New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let (mut psbt, _) = builder.finish().unwrap();
psbt.global.unsigned_tx.input.push(TxIn::default());
let options = SignOptions {
trust_witness_utxo: true,
assume_height: None,
};
let _ = wallet.sign(&mut psbt, options).unwrap();
}
#[test]
fn test_psbt_sign_with_finalized() {
let psbt_bip: PSBT = deserialize(&base64::decode(PSBT_STR).unwrap()).unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let send_to = wallet.get_address(AddressIndex::New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let (mut psbt, _) = builder.finish().unwrap();
// add a finalized input
psbt.inputs.push(psbt_bip.inputs[0].clone());
psbt.global
.unsigned_tx
.input
.push(psbt_bip.global.unsigned_tx.input[0].clone());
let _ = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
}
}

View File

@@ -1,31 +1,18 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::convert::AsRef; use std::convert::AsRef;
use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut}; use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
use bitcoin::hash_types::Txid; use bitcoin::{hash_types::Txid, util::psbt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -69,12 +56,12 @@ impl FeeRate {
} }
/// Create a new instance of [`FeeRate`] given a float fee rate in satoshi/vbyte /// Create a new instance of [`FeeRate`] given a float fee rate in satoshi/vbyte
pub fn from_sat_per_vb(sat_per_vb: f32) -> Self { pub const fn from_sat_per_vb(sat_per_vb: f32) -> Self {
FeeRate(sat_per_vb) FeeRate(sat_per_vb)
} }
/// Create a new [`FeeRate`] with the default min relay fee value /// Create a new [`FeeRate`] with the default min relay fee value
pub fn default_min_relay_fee() -> Self { pub const fn default_min_relay_fee() -> Self {
FeeRate(1.0) FeeRate(1.0)
} }
@@ -90,9 +77,11 @@ impl std::default::Default for FeeRate {
} }
} }
/// A wallet unspent output /// An unspent output owned by a [`Wallet`].
///
/// [`Wallet`]: crate::Wallet
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct UTXO { pub struct LocalUtxo {
/// Reference to a transaction output /// Reference to a transaction output
pub outpoint: OutPoint, pub outpoint: OutPoint,
/// Transaction output /// Transaction output
@@ -101,6 +90,64 @@ pub struct UTXO {
pub keychain: KeychainKind, pub keychain: KeychainKind,
} }
/// A [`Utxo`] with its `satisfaction_weight`.
#[derive(Debug, Clone, PartialEq)]
pub struct WeightedUtxo {
/// The weight of the witness data and `scriptSig` expressed in [weight units]. This is used to
/// properly maintain the feerate when adding this input to a transaction during coin selection.
///
/// [weight units]: https://en.bitcoin.it/wiki/Weight_units
pub satisfaction_weight: usize,
/// The UTXO
pub utxo: Utxo,
}
#[derive(Debug, Clone, PartialEq)]
/// An unspent transaction output (UTXO).
pub enum Utxo {
/// A UTXO owned by the local wallet.
Local(LocalUtxo),
/// A UTXO owned by another wallet.
Foreign {
/// The location of the output.
outpoint: OutPoint,
/// The information about the input we require to add it to a PSBT.
// Box it to stop the type being too big.
psbt_input: Box<psbt::Input>,
},
}
impl Utxo {
/// Get the location of the UTXO
pub fn outpoint(&self) -> OutPoint {
match &self {
Utxo::Local(local) => local.outpoint,
Utxo::Foreign { outpoint, .. } => *outpoint,
}
}
/// Get the `TxOut` of the UTXO
pub fn txout(&self) -> &TxOut {
match &self {
Utxo::Local(local) => &local.txout,
Utxo::Foreign {
outpoint,
psbt_input,
} => {
if let Some(prev_tx) = &psbt_input.non_witness_utxo {
return &prev_tx.output[outpoint.vout as usize];
}
if let Some(txout) = &psbt_input.witness_utxo {
return &txout;
}
unreachable!("Foreign UTXOs will always have one of these set")
}
}
}
}
/// A wallet transaction /// A wallet transaction
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct TransactionDetails { pub struct TransactionDetails {
@@ -119,3 +166,14 @@ pub struct TransactionDetails {
/// Confirmed in block height, `None` means unconfirmed /// Confirmed in block height, `None` means unconfirmed
pub height: Option<u32>, pub height: Option<u32>,
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_store_feerate_in_const() {
const _MY_RATE: FeeRate = FeeRate::from_sat_per_vb(10.0);
const _MIN_RELAY: FeeRate = FeeRate::default_min_relay_fee();
}
}

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Address validation callbacks //! Address validation callbacks
//! //!
@@ -33,7 +20,7 @@
//! An address validator can be attached to a [`Wallet`](super::Wallet) by using the //! An address validator can be attached to a [`Wallet`](super::Wallet) by using the
//! [`Wallet::add_address_validator`](super::Wallet::add_address_validator) method, and //! [`Wallet::add_address_validator`](super::Wallet::add_address_validator) method, and
//! whenever a new address is generated (either explicitly by the user with //! whenever a new address is generated (either explicitly by the user with
//! [`Wallet::get_new_address`](super::Wallet::get_new_address) or internally to create a change //! [`Wallet::get_address`](super::Wallet::get_address) or internally to create a change
//! address) all the attached validators will be polled, in sequence. All of them must complete //! address) all the attached validators will be polled, in sequence. All of them must complete
//! successfully to continue. //! successfully to continue.
//! //!
@@ -45,6 +32,7 @@
//! # use bdk::address_validator::*; //! # use bdk::address_validator::*;
//! # use bdk::database::*; //! # use bdk::database::*;
//! # use bdk::*; //! # use bdk::*;
//! # use bdk::wallet::AddressIndex::New;
//! #[derive(Debug)] //! #[derive(Debug)]
//! struct PrintAddressAndContinue; //! struct PrintAddressAndContinue;
//! //!
@@ -52,7 +40,7 @@
//! fn validate( //! fn validate(
//! &self, //! &self,
//! keychain: KeychainKind, //! keychain: KeychainKind,
//! hd_keypaths: &HDKeyPaths, //! hd_keypaths: &HdKeyPaths,
//! script: &Script //! script: &Script
//! ) -> Result<(), AddressValidatorError> { //! ) -> Result<(), AddressValidatorError> {
//! let address = Address::from_script(script, Network::Testnet) //! let address = Address::from_script(script, Network::Testnet)
@@ -70,7 +58,7 @@
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?; //! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue)); //! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
//! //!
//! let address = wallet.get_new_address()?; //! let address = wallet.get_address(New)?;
//! println!("Address: {}", address); //! println!("Address: {}", address);
//! # Ok::<(), bdk::Error>(()) //! # Ok::<(), bdk::Error>(())
//! ``` //! ```
@@ -79,7 +67,7 @@ use std::fmt;
use bitcoin::Script; use bitcoin::Script;
use crate::descriptor::HDKeyPaths; use crate::descriptor::HdKeyPaths;
use crate::types::KeychainKind; use crate::types::KeychainKind;
/// Errors that can be returned to fail the validation of an address /// Errors that can be returned to fail the validation of an address
@@ -117,7 +105,7 @@ pub trait AddressValidator: Send + Sync + fmt::Debug {
fn validate( fn validate(
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
hd_keypaths: &HDKeyPaths, hd_keypaths: &HdKeyPaths,
script: &Script, script: &Script,
) -> Result<(), AddressValidatorError>; ) -> Result<(), AddressValidatorError>;
} }
@@ -128,6 +116,7 @@ mod test {
use super::*; use super::*;
use crate::wallet::test::{get_funded_wallet, get_test_wpkh}; use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
use crate::wallet::AddressIndex::New;
#[derive(Debug)] #[derive(Debug)]
struct TestValidator; struct TestValidator;
@@ -135,7 +124,7 @@ mod test {
fn validate( fn validate(
&self, &self,
_keychain: KeychainKind, _keychain: KeychainKind,
_hd_keypaths: &HDKeyPaths, _hd_keypaths: &HdKeyPaths,
_script: &bitcoin::Script, _script: &bitcoin::Script,
) -> Result<(), AddressValidatorError> { ) -> Result<(), AddressValidatorError> {
Err(AddressValidatorError::InvalidScript) Err(AddressValidatorError::InvalidScript)
@@ -148,7 +137,7 @@ mod test {
let (mut wallet, _, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _, _) = get_funded_wallet(get_test_wpkh());
wallet.add_address_validator(Arc::new(TestValidator)); wallet.add_address_validator(Arc::new(TestValidator));
wallet.get_new_address().unwrap(); wallet.get_address(New).unwrap();
} }
#[test] #[test]

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Coin selection //! Coin selection
//! //!
@@ -50,8 +37,8 @@
//! fn coin_select( //! fn coin_select(
//! &self, //! &self,
//! database: &D, //! database: &D,
//! required_utxos: Vec<(UTXO, usize)>, //! required_utxos: Vec<WeightedUtxo>,
//! optional_utxos: Vec<(UTXO, usize)>, //! optional_utxos: Vec<WeightedUtxo>,
//! fee_rate: FeeRate, //! fee_rate: FeeRate,
//! amount_needed: u64, //! amount_needed: u64,
//! fee_amount: f32, //! fee_amount: f32,
@@ -60,11 +47,10 @@
//! let mut additional_weight = 0; //! let mut additional_weight = 0;
//! let all_utxos_selected = required_utxos //! let all_utxos_selected = required_utxos
//! .into_iter().chain(optional_utxos) //! .into_iter().chain(optional_utxos)
//! .scan((&mut selected_amount, &mut additional_weight), |(selected_amount, additional_weight), (utxo, weight)| { //! .scan((&mut selected_amount, &mut additional_weight), |(selected_amount, additional_weight), weighted_utxo| {
//! **selected_amount += utxo.txout.value; //! **selected_amount += weighted_utxo.utxo.txout().value;
//! **additional_weight += TXIN_BASE_WEIGHT + weight; //! **additional_weight += TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight;
//! //! Some(weighted_utxo.utxo)
//! Some(utxo)
//! }) //! })
//! .collect::<Vec<_>>(); //! .collect::<Vec<_>>();
//! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0; //! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
@@ -75,7 +61,6 @@
//! //!
//! Ok(CoinSelectionResult { //! Ok(CoinSelectionResult {
//! selected: all_utxos_selected, //! selected: all_utxos_selected,
//! selected_amount,
//! fee_amount: fee_amount + additional_fees, //! fee_amount: fee_amount + additional_fees,
//! }) //! })
//! } //! }
@@ -97,15 +82,16 @@
//! # Ok::<(), bdk::Error>(()) //! # Ok::<(), bdk::Error>(())
//! ``` //! ```
use crate::database::Database; use crate::types::FeeRate;
use crate::error::Error; use crate::{database::Database, WeightedUtxo};
use crate::types::{FeeRate, UTXO}; use crate::{error::Error, Utxo};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
#[cfg(not(test))] #[cfg(not(test))]
use rand::thread_rng; use rand::thread_rng;
#[cfg(test)] #[cfg(test)]
use rand::{rngs::StdRng, SeedableRng}; use rand::{rngs::StdRng, SeedableRng};
use std::convert::TryInto;
/// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not /// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
/// overridden /// overridden
@@ -122,13 +108,29 @@ pub(crate) const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4;
#[derive(Debug)] #[derive(Debug)]
pub struct CoinSelectionResult { pub struct CoinSelectionResult {
/// List of outputs selected for use as inputs /// List of outputs selected for use as inputs
pub selected: Vec<UTXO>, pub selected: Vec<Utxo>,
/// Sum of the selected inputs' value
pub selected_amount: u64,
/// Total fee amount in satoshi /// Total fee amount in satoshi
pub fee_amount: f32, pub fee_amount: f32,
} }
impl CoinSelectionResult {
/// The total value of the inputs selected.
pub fn selected_amount(&self) -> u64 {
self.selected.iter().map(|u| u.txout().value).sum()
}
/// The total value of the inputs selected from the local wallet.
pub fn local_selected_amount(&self) -> u64 {
self.selected
.iter()
.filter_map(|u| match u {
Utxo::Local(_) => Some(u.txout().value),
_ => None,
})
.sum()
}
}
/// Trait for generalized coin selection algorithms /// Trait for generalized coin selection algorithms
/// ///
/// This trait can be implemented to make the [`Wallet`](super::Wallet) use a customized coin /// This trait can be implemented to make the [`Wallet`](super::Wallet) use a customized coin
@@ -151,8 +153,8 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug {
fn coin_select( fn coin_select(
&self, &self,
database: &D, database: &D,
required_utxos: Vec<(UTXO, usize)>, required_utxos: Vec<WeightedUtxo>,
optional_utxos: Vec<(UTXO, usize)>, optional_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate, fee_rate: FeeRate,
amount_needed: u64, amount_needed: u64,
fee_amount: f32, fee_amount: f32,
@@ -163,15 +165,15 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug {
/// ///
/// This coin selection algorithm sorts the available UTXOs by value and then picks them starting /// This coin selection algorithm sorts the available UTXOs by value and then picks them starting
/// from the largest ones until the required amount is reached. /// from the largest ones until the required amount is reached.
#[derive(Debug, Default)] #[derive(Debug, Default, Clone, Copy)]
pub struct LargestFirstCoinSelection; pub struct LargestFirstCoinSelection;
impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
fn coin_select( fn coin_select(
&self, &self,
_database: &D, _database: &D,
required_utxos: Vec<(UTXO, usize)>, required_utxos: Vec<WeightedUtxo>,
mut optional_utxos: Vec<(UTXO, usize)>, mut optional_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate, fee_rate: FeeRate,
amount_needed: u64, amount_needed: u64,
mut fee_amount: f32, mut fee_amount: f32,
@@ -188,7 +190,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
// We put the "required UTXOs" first and make sure the optional UTXOs are sorted, // We put the "required UTXOs" first and make sure the optional UTXOs are sorted,
// initially smallest to largest, before being reversed with `.rev()`. // initially smallest to largest, before being reversed with `.rev()`.
let utxos = { let utxos = {
optional_utxos.sort_unstable_by_key(|(utxo, _)| utxo.txout.value); optional_utxos.sort_unstable_by_key(|wu| wu.utxo.txout().value);
required_utxos required_utxos
.into_iter() .into_iter()
.map(|utxo| (true, utxo)) .map(|utxo| (true, utxo))
@@ -201,18 +203,19 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
let selected = utxos let selected = utxos
.scan( .scan(
(&mut selected_amount, &mut fee_amount), (&mut selected_amount, &mut fee_amount),
|(selected_amount, fee_amount), (must_use, (utxo, weight))| { |(selected_amount, fee_amount), (must_use, weighted_utxo)| {
if must_use || **selected_amount < amount_needed + (fee_amount.ceil() as u64) { if must_use || **selected_amount < amount_needed + (fee_amount.ceil() as u64) {
**fee_amount += calc_fee_bytes(TXIN_BASE_WEIGHT + weight); **fee_amount +=
**selected_amount += utxo.txout.value; calc_fee_bytes(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight);
**selected_amount += weighted_utxo.utxo.txout().value;
log::debug!( log::debug!(
"Selected {}, updated fee_amount = `{}`", "Selected {}, updated fee_amount = `{}`",
utxo.outpoint, weighted_utxo.utxo.outpoint(),
fee_amount fee_amount
); );
Some(utxo) Some(weighted_utxo.utxo)
} else { } else {
None None
} }
@@ -231,7 +234,6 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
Ok(CoinSelectionResult { Ok(CoinSelectionResult {
selected, selected,
fee_amount, fee_amount,
selected_amount,
}) })
} }
} }
@@ -239,9 +241,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
// Adds fee information to an UTXO. // Adds fee information to an UTXO.
struct OutputGroup { struct OutputGroup {
utxo: UTXO, weighted_utxo: WeightedUtxo,
// weight needed to satisfy the UTXO, as described in `Descriptor::max_satisfaction_weight`
satisfaction_weight: usize,
// Amount of fees for spending a certain utxo, calculated using a certain FeeRate // Amount of fees for spending a certain utxo, calculated using a certain FeeRate
fee: f32, fee: f32,
// The effective value of the UTXO, i.e., the utxo value minus the fee for spending it // The effective value of the UTXO, i.e., the utxo value minus the fee for spending it
@@ -249,12 +249,12 @@ struct OutputGroup {
} }
impl OutputGroup { impl OutputGroup {
fn new(utxo: UTXO, satisfaction_weight: usize, fee_rate: FeeRate) -> Self { fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self {
let fee = (TXIN_BASE_WEIGHT + satisfaction_weight) as f32 / 4.0 * fee_rate.as_sat_vb(); let fee = (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as f32 / 4.0
let effective_value = utxo.txout.value as i64 - fee.ceil() as i64; * fee_rate.as_sat_vb();
let effective_value = weighted_utxo.utxo.txout().value as i64 - fee.ceil() as i64;
OutputGroup { OutputGroup {
utxo, weighted_utxo,
satisfaction_weight,
effective_value, effective_value,
fee, fee,
} }
@@ -291,8 +291,8 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
fn coin_select( fn coin_select(
&self, &self,
_database: &D, _database: &D,
required_utxos: Vec<(UTXO, usize)>, required_utxos: Vec<WeightedUtxo>,
optional_utxos: Vec<(UTXO, usize)>, optional_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate, fee_rate: FeeRate,
amount_needed: u64, amount_needed: u64,
fee_amount: f32, fee_amount: f32,
@@ -300,36 +300,43 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
// Mapping every (UTXO, usize) to an output group // Mapping every (UTXO, usize) to an output group
let required_utxos: Vec<OutputGroup> = required_utxos let required_utxos: Vec<OutputGroup> = required_utxos
.into_iter() .into_iter()
.map(|u| OutputGroup::new(u.0, u.1, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.collect(); .collect();
// Mapping every (UTXO, usize) to an output group. // Mapping every (UTXO, usize) to an output group.
// Filtering UTXOs with an effective_value < 0, as the fee paid for
// adding them is more than their value
let optional_utxos: Vec<OutputGroup> = optional_utxos let optional_utxos: Vec<OutputGroup> = optional_utxos
.into_iter() .into_iter()
.map(|u| OutputGroup::new(u.0, u.1, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.filter(|u| u.effective_value > 0)
.collect(); .collect();
let curr_value = required_utxos let curr_value = required_utxos
.iter() .iter()
.fold(0, |acc, x| acc + x.effective_value as u64); .fold(0, |acc, x| acc + x.effective_value);
let curr_available_value = optional_utxos let curr_available_value = optional_utxos
.iter() .iter()
.fold(0, |acc, x| acc + x.effective_value as u64); .fold(0, |acc, x| acc + x.effective_value);
let actual_target = fee_amount.ceil() as u64 + amount_needed; let actual_target = fee_amount.ceil() as u64 + amount_needed;
let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_vb(); let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_vb();
if curr_available_value + curr_value < actual_target { let expected = (curr_available_value + curr_value)
.try_into()
.map_err(|_| {
Error::Generic("Sum of UTXO spendable values does not fit into u64".to_string())
})?;
if expected < actual_target {
return Err(Error::InsufficientFunds { return Err(Error::InsufficientFunds {
needed: actual_target, needed: actual_target,
available: curr_available_value + curr_value, available: expected,
}); });
} }
let actual_target = actual_target
.try_into()
.expect("Bitcoin amount to fit into i64");
Ok(self Ok(self
.bnb( .bnb(
required_utxos.clone(), required_utxos.clone(),
@@ -360,9 +367,9 @@ impl BranchAndBoundCoinSelection {
&self, &self,
required_utxos: Vec<OutputGroup>, required_utxos: Vec<OutputGroup>,
mut optional_utxos: Vec<OutputGroup>, mut optional_utxos: Vec<OutputGroup>,
mut curr_value: u64, mut curr_value: i64,
mut curr_available_value: u64, mut curr_available_value: i64,
actual_target: u64, actual_target: i64,
fee_amount: f32, fee_amount: f32,
cost_of_change: f32, cost_of_change: f32,
) -> Result<CoinSelectionResult, Error> { ) -> Result<CoinSelectionResult, Error> {
@@ -388,7 +395,7 @@ impl BranchAndBoundCoinSelection {
// or the selected value is out of range. // or the selected value is out of range.
// Go back and try other branch // Go back and try other branch
if curr_value + curr_available_value < actual_target if curr_value + curr_available_value < actual_target
|| curr_value > actual_target + cost_of_change as u64 || curr_value > actual_target + cost_of_change as i64
{ {
backtrack = true; backtrack = true;
} else if curr_value >= actual_target { } else if curr_value >= actual_target {
@@ -414,8 +421,7 @@ impl BranchAndBoundCoinSelection {
// Walk backwards to find the last included UTXO that still needs to have its omission branch traversed. // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
while let Some(false) = current_selection.last() { while let Some(false) = current_selection.last() {
current_selection.pop(); current_selection.pop();
curr_available_value += curr_available_value += optional_utxos[current_selection.len()].effective_value;
optional_utxos[current_selection.len()].effective_value as u64;
} }
if current_selection.last_mut().is_none() { if current_selection.last_mut().is_none() {
@@ -433,17 +439,17 @@ impl BranchAndBoundCoinSelection {
} }
let utxo = &optional_utxos[current_selection.len() - 1]; let utxo = &optional_utxos[current_selection.len() - 1];
curr_value -= utxo.effective_value as u64; curr_value -= utxo.effective_value;
} else { } else {
// Moving forwards, continuing down this branch // Moving forwards, continuing down this branch
let utxo = &optional_utxos[current_selection.len()]; let utxo = &optional_utxos[current_selection.len()];
// Remove this utxo from the curr_available_value utxo amount // Remove this utxo from the curr_available_value utxo amount
curr_available_value -= utxo.effective_value as u64; curr_available_value -= utxo.effective_value;
// Inclusion branch first (Largest First Exploration) // Inclusion branch first (Largest First Exploration)
current_selection.push(true); current_selection.push(true);
curr_value += utxo.effective_value as u64; curr_value += utxo.effective_value;
} }
} }
@@ -470,8 +476,8 @@ impl BranchAndBoundCoinSelection {
&self, &self,
required_utxos: Vec<OutputGroup>, required_utxos: Vec<OutputGroup>,
mut optional_utxos: Vec<OutputGroup>, mut optional_utxos: Vec<OutputGroup>,
curr_value: u64, curr_value: i64,
actual_target: u64, actual_target: i64,
fee_amount: f32, fee_amount: f32,
) -> CoinSelectionResult { ) -> CoinSelectionResult {
#[cfg(not(test))] #[cfg(not(test))]
@@ -489,7 +495,7 @@ impl BranchAndBoundCoinSelection {
if *curr_value >= actual_target { if *curr_value >= actual_target {
None None
} else { } else {
*curr_value += utxo.effective_value as u64; *curr_value += utxo.effective_value;
Some(utxo) Some(utxo)
} }
}) })
@@ -507,14 +513,12 @@ impl BranchAndBoundCoinSelection {
fee_amount += selected_utxos.iter().map(|u| u.fee).sum::<f32>(); fee_amount += selected_utxos.iter().map(|u| u.fee).sum::<f32>();
let selected = selected_utxos let selected = selected_utxos
.into_iter() .into_iter()
.map(|u| u.utxo) .map(|u| u.weighted_utxo.utxo)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let selected_amount = selected.iter().map(|u| u.txout.value).sum();
CoinSelectionResult { CoinSelectionResult {
selected, selected,
fee_amount, fee_amount,
selected_amount,
} }
} }
} }
@@ -535,12 +539,15 @@ mod test {
const P2WPKH_WITNESS_SIZE: usize = 73 + 33 + 2; const P2WPKH_WITNESS_SIZE: usize = 73 + 33 + 2;
fn get_test_utxos() -> Vec<(UTXO, usize)> { const FEE_AMOUNT: f32 = 50.0;
fn get_test_utxos() -> Vec<WeightedUtxo> {
vec![ vec![
( WeightedUtxo {
UTXO { satisfaction_weight: P2WPKH_WITNESS_SIZE,
utxo: Utxo::Local(LocalUtxo {
outpoint: OutPoint::from_str( outpoint: OutPoint::from_str(
"ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", "0000000000000000000000000000000000000000000000000000000000000000:0",
) )
.unwrap(), .unwrap(),
txout: TxOut { txout: TxOut {
@@ -548,13 +555,27 @@ mod test {
script_pubkey: Script::new(), script_pubkey: Script::new(),
}, },
keychain: KeychainKind::External, keychain: KeychainKind::External,
}, }),
P2WPKH_WITNESS_SIZE, },
), WeightedUtxo {
( satisfaction_weight: P2WPKH_WITNESS_SIZE,
UTXO { utxo: Utxo::Local(LocalUtxo {
outpoint: OutPoint::from_str( outpoint: OutPoint::from_str(
"65d92ddff6b6dc72c89624a6491997714b90f6004f928d875bc0fd53f264fa85:0", "0000000000000000000000000000000000000000000000000000000000000001:0",
)
.unwrap(),
txout: TxOut {
value: FEE_AMOUNT as u64 - 40,
script_pubkey: Script::new(),
},
keychain: KeychainKind::External,
}),
},
WeightedUtxo {
satisfaction_weight: P2WPKH_WITNESS_SIZE,
utxo: Utxo::Local(LocalUtxo {
outpoint: OutPoint::from_str(
"0000000000000000000000000000000000000000000000000000000000000002:0",
) )
.unwrap(), .unwrap(),
txout: TxOut { txout: TxOut {
@@ -562,17 +583,17 @@ mod test {
script_pubkey: Script::new(), script_pubkey: Script::new(),
}, },
keychain: KeychainKind::Internal, keychain: KeychainKind::Internal,
}, }),
P2WPKH_WITNESS_SIZE, },
),
] ]
} }
fn generate_random_utxos(rng: &mut StdRng, utxos_number: usize) -> Vec<(UTXO, usize)> { fn generate_random_utxos(rng: &mut StdRng, utxos_number: usize) -> Vec<WeightedUtxo> {
let mut res = Vec::new(); let mut res = Vec::new();
for _ in 0..utxos_number { for _ in 0..utxos_number {
res.push(( res.push(WeightedUtxo {
UTXO { satisfaction_weight: P2WPKH_WITNESS_SIZE,
utxo: Utxo::Local(LocalUtxo {
outpoint: OutPoint::from_str( outpoint: OutPoint::from_str(
"ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0",
) )
@@ -582,16 +603,16 @@ mod test {
script_pubkey: Script::new(), script_pubkey: Script::new(),
}, },
keychain: KeychainKind::External, keychain: KeychainKind::External,
}, }),
P2WPKH_WITNESS_SIZE, });
));
} }
res res
} }
fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<(UTXO, usize)> { fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<WeightedUtxo> {
let utxo = ( let utxo = WeightedUtxo {
UTXO { satisfaction_weight: P2WPKH_WITNESS_SIZE,
utxo: Utxo::Local(LocalUtxo {
outpoint: OutPoint::from_str( outpoint: OutPoint::from_str(
"ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0",
) )
@@ -601,18 +622,18 @@ mod test {
script_pubkey: Script::new(), script_pubkey: Script::new(),
}, },
keychain: KeychainKind::External, keychain: KeychainKind::External,
}, }),
P2WPKH_WITNESS_SIZE, };
);
vec![utxo; utxos_number] vec![utxo; utxos_number]
} }
fn sum_random_utxos(mut rng: &mut StdRng, utxos: &mut Vec<(UTXO, usize)>) -> u64 { fn sum_random_utxos(mut rng: &mut StdRng, utxos: &mut Vec<WeightedUtxo>) -> u64 {
let utxos_picked_len = rng.gen_range(2, utxos.len() / 2); let utxos_picked_len = rng.gen_range(2, utxos.len() / 2);
utxos.shuffle(&mut rng); utxos.shuffle(&mut rng);
utxos[..utxos_picked_len] utxos[..utxos_picked_len]
.iter() .iter()
.fold(0, |acc, x| acc + x.0.txout.value) .map(|u| u.utxo.txout().value)
.sum()
} }
#[test] #[test]
@@ -631,9 +652,9 @@ mod test {
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 2); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount, 300_000); assert_eq!(result.selected_amount(), 300_010);
assert_eq!(result.fee_amount, 186.0); assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -652,9 +673,9 @@ mod test {
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 2); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount, 300_000); assert_eq!(result.selected_amount(), 300_010);
assert_eq!(result.fee_amount, 186.0); assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -674,8 +695,8 @@ mod test {
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 1); assert_eq!(result.selected.len(), 1);
assert_eq!(result.selected_amount, 200_000); assert_eq!(result.selected_amount(), 200_000);
assert_eq!(result.fee_amount, 118.0); assert!((result.fee_amount - 118.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -734,8 +755,8 @@ mod test {
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount, 300_000); assert_eq!(result.selected_amount(), 300_000);
assert_eq!(result.fee_amount, 254.0); assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -750,13 +771,34 @@ mod test {
utxos, utxos,
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
20_000, 20_000,
50.0, FEE_AMOUNT,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 2); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount, 300_000); assert_eq!(result.selected_amount(), 300_010);
assert_eq!(result.fee_amount, 186.0); assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
}
#[test]
fn test_bnb_coin_selection_optional_are_enough() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let result = BranchAndBoundCoinSelection::default()
.coin_select(
&database,
vec![],
utxos,
FeeRate::from_sat_per_vb(1.0),
299756,
FEE_AMOUNT,
)
.unwrap();
assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300010);
assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -804,7 +846,7 @@ mod test {
.coin_select( .coin_select(
&database, &database,
vec![], vec![],
utxos.clone(), utxos,
FeeRate::from_sat_per_vb(1.0), FeeRate::from_sat_per_vb(1.0),
99932, // first utxo's effective value 99932, // first utxo's effective value
0.0, 0.0,
@@ -812,7 +854,7 @@ mod test {
.unwrap(); .unwrap();
assert_eq!(result.selected.len(), 1); assert_eq!(result.selected.len(), 1);
assert_eq!(result.selected_amount, 100_000); assert_eq!(result.selected_amount(), 100_000);
let input_size = (TXIN_BASE_WEIGHT as f32) / 4.0 + P2WPKH_WITNESS_SIZE as f32 / 4.0; let input_size = (TXIN_BASE_WEIGHT as f32) / 4.0 + P2WPKH_WITNESS_SIZE as f32 / 4.0;
let epsilon = 0.5; let epsilon = 0.5;
assert!((1.0 - (result.fee_amount / input_size)).abs() < epsilon); assert!((1.0 - (result.fee_amount / input_size)).abs() < epsilon);
@@ -837,7 +879,7 @@ mod test {
0.0, 0.0,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected_amount, target_amount); assert_eq!(result.selected_amount(), target_amount);
} }
} }
@@ -847,12 +889,10 @@ mod test {
let fee_rate = FeeRate::from_sat_per_vb(10.0); let fee_rate = FeeRate::from_sat_per_vb(10.0);
let utxos: Vec<OutputGroup> = get_test_utxos() let utxos: Vec<OutputGroup> = get_test_utxos()
.into_iter() .into_iter()
.map(|u| OutputGroup::new(u.0, u.1, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.collect(); .collect();
let curr_available_value = utxos let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
.iter()
.fold(0, |acc, x| acc + x.effective_value as u64);
let size_of_change = 31; let size_of_change = 31;
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb(); let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb();
@@ -875,12 +915,10 @@ mod test {
let fee_rate = FeeRate::from_sat_per_vb(10.0); let fee_rate = FeeRate::from_sat_per_vb(10.0);
let utxos: Vec<OutputGroup> = generate_same_value_utxos(100_000, 100_000) let utxos: Vec<OutputGroup> = generate_same_value_utxos(100_000, 100_000)
.into_iter() .into_iter()
.map(|u| OutputGroup::new(u.0, u.1, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.collect(); .collect();
let curr_available_value = utxos let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
.iter()
.fold(0, |acc, x| acc + x.effective_value as u64);
let size_of_change = 31; let size_of_change = 31;
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb(); let cost_of_change = size_of_change as f32 * fee_rate.as_sat_vb();
@@ -908,18 +946,16 @@ mod test {
let utxos: Vec<_> = generate_same_value_utxos(50_000, 10) let utxos: Vec<_> = generate_same_value_utxos(50_000, 10)
.into_iter() .into_iter()
.map(|u| OutputGroup::new(u.0, u.1, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.collect(); .collect();
let curr_value = 0; let curr_value = 0;
let curr_available_value = utxos let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
.iter()
.fold(0, |acc, x| acc + x.effective_value as u64);
// 2*(value of 1 utxo) - 2*(1 utxo fees with 1.0sat/vbyte fee rate) - // 2*(value of 1 utxo) - 2*(1 utxo fees with 1.0sat/vbyte fee rate) -
// cost_of_change + 5. // cost_of_change + 5.
let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change.ceil() as u64 + 5; let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change.ceil() as i64 + 5;
let result = BranchAndBoundCoinSelection::new(size_of_change) let result = BranchAndBoundCoinSelection::new(size_of_change)
.bnb( .bnb(
@@ -932,8 +968,8 @@ mod test {
cost_of_change, cost_of_change,
) )
.unwrap(); .unwrap();
assert_eq!(result.fee_amount, 186.0); assert!((result.fee_amount - 186.0).abs() < f32::EPSILON);
assert_eq!(result.selected_amount, 100_000); assert_eq!(result.selected_amount(), 100_000);
} }
// TODO: bnb() function should be optimized, and this test should be done with more utxos // TODO: bnb() function should be optimized, and this test should be done with more utxos
@@ -946,17 +982,17 @@ mod test {
for _ in 0..200 { for _ in 0..200 {
let optional_utxos: Vec<_> = generate_random_utxos(&mut rng, 40) let optional_utxos: Vec<_> = generate_random_utxos(&mut rng, 40)
.into_iter() .into_iter()
.map(|u| OutputGroup::new(u.0, u.1, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.collect(); .collect();
let curr_value = 0; let curr_value = 0;
let curr_available_value = optional_utxos let curr_available_value = optional_utxos
.iter() .iter()
.fold(0, |acc, x| acc + x.effective_value as u64); .fold(0, |acc, x| acc + x.effective_value);
let target_amount = optional_utxos[3].effective_value as u64 let target_amount =
+ optional_utxos[23].effective_value as u64; optional_utxos[3].effective_value + optional_utxos[23].effective_value;
let result = BranchAndBoundCoinSelection::new(0) let result = BranchAndBoundCoinSelection::new(0)
.bnb( .bnb(
@@ -969,7 +1005,7 @@ mod test {
0.0, 0.0,
) )
.unwrap(); .unwrap();
assert_eq!(result.selected_amount, target_amount); assert_eq!(result.selected_amount(), target_amount as u64);
} }
} }
@@ -983,21 +1019,20 @@ mod test {
let fee_rate = FeeRate::from_sat_per_vb(1.0); let fee_rate = FeeRate::from_sat_per_vb(1.0);
let utxos: Vec<OutputGroup> = utxos let utxos: Vec<OutputGroup> = utxos
.into_iter() .into_iter()
.map(|u| OutputGroup::new(u.0, u.1, fee_rate)) .map(|u| OutputGroup::new(u, fee_rate))
.collect(); .collect();
let result = BranchAndBoundCoinSelection::default().single_random_draw( let result = BranchAndBoundCoinSelection::default().single_random_draw(
vec![], vec![],
utxos, utxos,
0, 0,
target_amount, target_amount as i64,
50.0, 50.0,
); );
assert!(result.selected_amount > target_amount); assert!(result.selected_amount() > target_amount);
assert_eq!( assert!(
result.fee_amount, (result.fee_amount - (50.0 + result.selected.len() as f32 * 68.0)).abs() < f32::EPSILON
50.0 + result.selected.len() as f32 * 68.0
); );
} }
} }

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Wallet export //! Wallet export
//! //!

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Generalized signers //! Generalized signers
//! //!
@@ -105,7 +92,7 @@ use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::script::Builder as ScriptBuilder; use bitcoin::blockdata::script::Builder as ScriptBuilder;
use bitcoin::hashes::{hash160, Hash}; use bitcoin::hashes::{hash160, Hash};
use bitcoin::secp256k1::{Message, Secp256k1}; use bitcoin::secp256k1::{Message, Secp256k1};
use bitcoin::util::bip32::{ExtendedPrivKey, Fingerprint}; use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint};
use bitcoin::util::{bip143, psbt}; use bitcoin::util::{bip143, psbt};
use bitcoin::{PrivateKey, Script, SigHash, SigHashType}; use bitcoin::{PrivateKey, Script, SigHash, SigHashType};
@@ -159,7 +146,7 @@ pub enum SignerError {
/// The `witness_script` field of the transaction is requied to sign this input /// The `witness_script` field of the transaction is requied to sign this input
MissingWitnessScript, MissingWitnessScript,
/// The fingerprint and derivation path are missing from the psbt input /// The fingerprint and derivation path are missing from the psbt input
MissingHDKeypath, MissingHdKeypath,
} }
impl fmt::Display for SignerError { impl fmt::Display for SignerError {
@@ -219,7 +206,13 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
return Err(SignerError::InputIndexOutOfRange); return Err(SignerError::InputIndexOutOfRange);
} }
let (public_key, deriv_path) = match psbt.inputs[input_index] if psbt.inputs[input_index].final_script_sig.is_some()
|| psbt.inputs[input_index].final_script_witness.is_some()
{
return Ok(());
}
let (public_key, full_path) = match psbt.inputs[input_index]
.bip32_derivation .bip32_derivation
.iter() .iter()
.filter_map(|(pk, &(fingerprint, ref path))| { .filter_map(|(pk, &(fingerprint, ref path))| {
@@ -235,7 +228,17 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
None => return Ok(()), None => return Ok(()),
}; };
let derived_key = self.xkey.derive_priv(&secp, &deriv_path).unwrap(); let derived_key = match self.origin.clone() {
Some((_fingerprint, origin_path)) => {
let deriv_path = DerivationPath::from(
&full_path.into_iter().cloned().collect::<Vec<ChildNumber>>()
[origin_path.len()..],
);
self.xkey.derive_priv(&secp, &deriv_path).unwrap()
}
None => self.xkey.derive_priv(&secp, &full_path).unwrap(),
};
if &derived_key.private_key.public_key(&secp) != public_key { if &derived_key.private_key.public_key(&secp) != public_key {
Err(SignerError::InvalidKey) Err(SignerError::InvalidKey)
} else { } else {
@@ -264,10 +267,16 @@ impl Signer for PrivateKey {
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<(), SignerError> { ) -> Result<(), SignerError> {
let input_index = input_index.unwrap(); let input_index = input_index.unwrap();
if input_index >= psbt.inputs.len() { if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange); return Err(SignerError::InputIndexOutOfRange);
} }
if psbt.inputs[input_index].final_script_sig.is_some()
|| psbt.inputs[input_index].final_script_witness.is_some()
{
return Ok(());
}
let pubkey = self.public_key(&secp); let pubkey = self.public_key(&secp);
if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) { if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
return Ok(()); return Ok(());
@@ -430,6 +439,43 @@ impl SignersContainer {
} }
} }
/// Options for a software signer
///
/// Adjust the behavior of our software signers and the way a transaction is finalized
#[derive(Debug, Clone)]
pub struct SignOptions {
/// Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been
/// provided
///
/// Defaults to `false` to mitigate the "SegWit bug" which chould trick the wallet into
/// paying a fee larger than expected.
///
/// Some wallets, especially if relatively old, might not provide the `non_witness_utxo` for
/// SegWit transactions in the PSBT they generate: in those cases setting this to `true`
/// should correctly produce a signature, at the expense of an increased trust in the creator
/// of the PSBT.
///
/// For more details see: <https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd>
pub trust_witness_utxo: bool,
/// Whether the wallet should assume a specific height has been reached when trying to finalize
/// a transaction
///
/// The wallet will only "use" a timelock to satisfy the spending policy of an input if the
/// timelock height has already been reached. This option allows overriding the "current height" to let the
/// wallet use timelocks in the future to spend a coin.
pub assume_height: Option<u32>,
}
impl Default for SignOptions {
fn default() -> Self {
SignOptions {
trust_witness_utxo: false,
assume_height: None,
}
}
}
pub(crate) trait ComputeSighash { pub(crate) trait ComputeSighash {
fn sighash( fn sighash(
psbt: &psbt::PartiallySignedTransaction, psbt: &psbt::PartiallySignedTransaction,
@@ -442,7 +488,7 @@ impl ComputeSighash for Legacy {
psbt: &psbt::PartiallySignedTransaction, psbt: &psbt::PartiallySignedTransaction,
input_index: usize, input_index: usize,
) -> Result<(SigHash, SigHashType), SignerError> { ) -> Result<(SigHash, SigHashType), SignerError> {
if input_index >= psbt.inputs.len() { if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange); return Err(SignerError::InputIndexOutOfRange);
} }
@@ -490,25 +536,42 @@ impl ComputeSighash for Segwitv0 {
psbt: &psbt::PartiallySignedTransaction, psbt: &psbt::PartiallySignedTransaction,
input_index: usize, input_index: usize,
) -> Result<(SigHash, SigHashType), SignerError> { ) -> Result<(SigHash, SigHashType), SignerError> {
if input_index >= psbt.inputs.len() { if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange); return Err(SignerError::InputIndexOutOfRange);
} }
let psbt_input = &psbt.inputs[input_index]; let psbt_input = &psbt.inputs[input_index];
let tx_input = &psbt.global.unsigned_tx.input[input_index];
let sighash = psbt_input.sighash_type.unwrap_or(SigHashType::All); let sighash = psbt_input.sighash_type.unwrap_or(SigHashType::All);
let witness_utxo = psbt_input // Always try first with the non-witness utxo
.witness_utxo let utxo = if let Some(prev_tx) = &psbt_input.non_witness_utxo {
.as_ref() // Check the provided prev-tx
.ok_or(SignerError::MissingNonWitnessUtxo)?; if prev_tx.txid() != tx_input.previous_output.txid {
let value = witness_utxo.value; return Err(SignerError::InvalidNonWitnessUtxo);
}
// The output should be present, if it's missing the `non_witness_utxo` is invalid
prev_tx
.output
.get(tx_input.previous_output.vout as usize)
.ok_or(SignerError::InvalidNonWitnessUtxo)?
} else if let Some(witness_utxo) = &psbt_input.witness_utxo {
// Fallback to the witness_utxo. If we aren't allowed to use it, signing should fail
// before we get to this point
witness_utxo
} else {
// Nothing has been provided
return Err(SignerError::MissingNonWitnessUtxo);
};
let value = utxo.value;
let script = match psbt_input.witness_script { let script = match psbt_input.witness_script {
Some(ref witness_script) => witness_script.clone(), Some(ref witness_script) => witness_script.clone(),
None => { None => {
if witness_utxo.script_pubkey.is_v0_p2wpkh() { if utxo.script_pubkey.is_v0_p2wpkh() {
p2wpkh_script_code(&witness_utxo.script_pubkey) p2wpkh_script_code(&utxo.script_pubkey)
} else if psbt_input } else if psbt_input
.redeem_script .redeem_script
.as_ref() .as_ref()
@@ -569,6 +632,11 @@ mod signers_container_tests {
use miniscript::ScriptContext; use miniscript::ScriptContext;
use std::str::FromStr; use std::str::FromStr;
fn is_equal(this: &Arc<dyn Signer>, that: &Arc<DummySigner>) -> bool {
let secp = Secp256k1::new();
this.id(&secp) == that.id(&secp)
}
// Signers added with the same ordering (like `Ordering::default`) created from `KeyMap` // Signers added with the same ordering (like `Ordering::default`) created from `KeyMap`
// should be preserved and not overwritten. // should be preserved and not overwritten.
// This happens usually when a set of signers is created from a descriptor with private keys. // This happens usually when a set of signers is created from a descriptor with private keys.
@@ -593,73 +661,58 @@ mod signers_container_tests {
#[test] #[test]
fn signers_sorted_by_ordering() { fn signers_sorted_by_ordering() {
let mut signers = SignersContainer::new(); let mut signers = SignersContainer::new();
let signer1 = Arc::new(DummySigner); let signer1 = Arc::new(DummySigner { number: 1 });
let signer2 = Arc::new(DummySigner); let signer2 = Arc::new(DummySigner { number: 2 });
let signer3 = Arc::new(DummySigner); let signer3 = Arc::new(DummySigner { number: 3 });
signers.add_external( // Mixed order insertions verifies we are not inserting at head or tail.
SignerId::Fingerprint(b"cafe"[..].into()), signers.add_external(SignerId::Dummy(2), SignerOrdering(2), signer2.clone());
SignerOrdering(1), signers.add_external(SignerId::Dummy(1), SignerOrdering(1), signer1.clone());
signer1.clone(), signers.add_external(SignerId::Dummy(3), SignerOrdering(3), signer3.clone());
);
signers.add_external(
SignerId::Fingerprint(b"babe"[..].into()),
SignerOrdering(2),
signer2.clone(),
);
signers.add_external(
SignerId::Fingerprint(b"feed"[..].into()),
SignerOrdering(3),
signer3.clone(),
);
// Check that signers are sorted from lowest to highest ordering // Check that signers are sorted from lowest to highest ordering
let signers = signers.signers(); let signers = signers.signers();
assert_eq!(Arc::as_ptr(signers[0]), Arc::as_ptr(&signer1));
assert_eq!(Arc::as_ptr(signers[1]), Arc::as_ptr(&signer2)); assert!(is_equal(signers[0], &signer1));
assert_eq!(Arc::as_ptr(signers[2]), Arc::as_ptr(&signer3)); assert!(is_equal(signers[1], &signer2));
assert!(is_equal(signers[2], &signer3));
} }
#[test] #[test]
fn find_signer_by_id() { fn find_signer_by_id() {
let mut signers = SignersContainer::new(); let mut signers = SignersContainer::new();
let signer1: Arc<dyn Signer> = Arc::new(DummySigner); let signer1 = Arc::new(DummySigner { number: 1 });
let signer2: Arc<dyn Signer> = Arc::new(DummySigner); let signer2 = Arc::new(DummySigner { number: 2 });
let signer3: Arc<dyn Signer> = Arc::new(DummySigner); let signer3 = Arc::new(DummySigner { number: 3 });
let signer4: Arc<dyn Signer> = Arc::new(DummySigner); let signer4 = Arc::new(DummySigner { number: 3 }); // Same ID as `signer3` but will use lower ordering.
let id1 = SignerId::Fingerprint(b"cafe"[..].into()); let id1 = SignerId::Dummy(1);
let id2 = SignerId::Fingerprint(b"babe"[..].into()); let id2 = SignerId::Dummy(2);
let id3 = SignerId::Fingerprint(b"feed"[..].into()); let id3 = SignerId::Dummy(3);
let id_nonexistent = SignerId::Fingerprint(b"fefe"[..].into()); let id_nonexistent = SignerId::Dummy(999);
signers.add_external(id1.clone(), SignerOrdering(1), signer1.clone()); signers.add_external(id1.clone(), SignerOrdering(1), signer1.clone());
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone()); signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone()); signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
assert!( assert!(matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1)));
matches!(signers.find(id1), Some(signer) if Arc::as_ptr(&signer1) == Arc::as_ptr(signer)) assert!(matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2)));
); assert!(matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3)));
assert!(
matches!(signers.find(id2), Some(signer) if Arc::as_ptr(&signer2) == Arc::as_ptr(signer))
);
assert!(
matches!(signers.find(id3.clone()), Some(signer) if Arc::as_ptr(&signer3) == Arc::as_ptr(signer))
);
// The `signer4` has the same ID as `signer3` but lower ordering. // The `signer4` has the same ID as `signer3` but lower ordering.
// It should be found by `id3` instead of `signer3`. // It should be found by `id3` instead of `signer3`.
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone()); signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
assert!( assert!(matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4)));
matches!(signers.find(id3), Some(signer) if Arc::as_ptr(&signer4) == Arc::as_ptr(signer))
);
// Can't find anything with ID that doesn't exist // Can't find anything with ID that doesn't exist
assert!(matches!(signers.find(id_nonexistent), None)); assert!(matches!(signers.find(id_nonexistent), None));
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
struct DummySigner; struct DummySigner {
number: u64,
}
impl Signer for DummySigner { impl Signer for DummySigner {
fn sign( fn sign(
&self, &self,
@@ -671,7 +724,7 @@ mod signers_container_tests {
} }
fn id(&self, _secp: &SecpCtx) -> SignerId { fn id(&self, _secp: &SecpCtx) -> SignerId {
SignerId::Dummy(42) SignerId::Dummy(self.number)
} }
fn sign_whole_tx(&self) -> bool { fn sign_whole_tx(&self) -> bool {

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Cross-platform time //! Cross-platform time
//! //!

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Transaction builder //! Transaction builder
//! //!
@@ -54,15 +41,15 @@ use std::collections::HashSet;
use std::default::Default; use std::default::Default;
use std::marker::PhantomData; use std::marker::PhantomData;
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; use bitcoin::util::psbt::{self, PartiallySignedTransaction as PSBT};
use bitcoin::{OutPoint, Script, SigHashType, Transaction}; use bitcoin::{OutPoint, Script, SigHashType, Transaction};
use miniscript::descriptor::DescriptorTrait; use miniscript::descriptor::DescriptorTrait;
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
use crate::{database::BatchDatabase, Error, Wallet}; use crate::{database::BatchDatabase, Error, Utxo, Wallet};
use crate::{ use crate::{
types::{FeeRate, KeychainKind, UTXO}, types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo},
TransactionDetails, TransactionDetails,
}; };
/// Context in which the [`TxBuilder`] is valid /// Context in which the [`TxBuilder`] is valid
@@ -129,7 +116,7 @@ impl TxBuilderContext for BumpFee {}
/// [`build_fee_bump`]: Wallet::build_fee_bump /// [`build_fee_bump`]: Wallet::build_fee_bump
/// [`finish`]: Self::finish /// [`finish`]: Self::finish
/// [`coin_selection`]: Self::coin_selection /// [`coin_selection`]: Self::coin_selection
#[derive(Clone, Debug)] #[derive(Debug)]
pub struct TxBuilder<'a, B, D, Cs, Ctx> { pub struct TxBuilder<'a, B, D, Cs, Ctx> {
pub(crate) wallet: &'a Wallet<B, D>, pub(crate) wallet: &'a Wallet<B, D>,
// params and coin_selection are Options not becasue they are optionally set (they are always // params and coin_selection are Options not becasue they are optionally set (they are always
@@ -150,16 +137,16 @@ pub(crate) struct TxParams {
pub(crate) fee_policy: Option<FeePolicy>, pub(crate) fee_policy: Option<FeePolicy>,
pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>, pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>,
pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>, pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>,
pub(crate) utxos: Vec<(UTXO, usize)>, pub(crate) utxos: Vec<WeightedUtxo>,
pub(crate) unspendable: HashSet<OutPoint>, pub(crate) unspendable: HashSet<OutPoint>,
pub(crate) manually_selected_only: bool, pub(crate) manually_selected_only: bool,
pub(crate) sighash: Option<SigHashType>, pub(crate) sighash: Option<SigHashType>,
pub(crate) ordering: TxOrdering, pub(crate) ordering: TxOrdering,
pub(crate) locktime: Option<u32>, pub(crate) locktime: Option<u32>,
pub(crate) rbf: Option<RBFValue>, pub(crate) rbf: Option<RbfValue>,
pub(crate) version: Option<Version>, pub(crate) version: Option<Version>,
pub(crate) change_policy: ChangeSpendPolicy, pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) force_non_witness_utxo: bool, pub(crate) only_witness_utxo: bool,
pub(crate) add_global_xpubs: bool, pub(crate) add_global_xpubs: bool,
pub(crate) include_output_redeem_witness_script: bool, pub(crate) include_output_redeem_witness_script: bool,
pub(crate) bumping_fee: Option<PreviousFee>, pub(crate) bumping_fee: Option<PreviousFee>,
@@ -183,6 +170,17 @@ impl std::default::Default for FeePolicy {
} }
} }
impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> {
fn clone(&self) -> Self {
TxBuilder {
wallet: self.wallet,
params: self.params.clone(),
coin_selection: self.coin_selection.clone(),
phantom: PhantomData,
}
}
}
// methods supported by both contexts, for any CoinSelectionAlgorithm // methods supported by both contexts, for any CoinSelectionAlgorithm
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
TxBuilder<'a, B, D, Cs, Ctx> TxBuilder<'a, B, D, Cs, Ctx>
@@ -280,13 +278,16 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, Error> { pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, Error> {
let utxos = outpoints let utxos = outpoints
.iter() .iter()
.map(|outpoint| self.wallet.get_utxo(*outpoint)?.ok_or(Error::UnknownUTXO)) .map(|outpoint| self.wallet.get_utxo(*outpoint)?.ok_or(Error::UnknownUtxo))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
for utxo in utxos { for utxo in utxos {
let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain); let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain);
let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap(); let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap();
self.params.utxos.push((utxo, satisfaction_weight)); self.params.utxos.push(WeightedUtxo {
satisfaction_weight,
utxo: Utxo::Local(utxo),
});
} }
Ok(self) Ok(self)
@@ -300,6 +301,84 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
self.add_utxos(&[outpoint]) self.add_utxos(&[outpoint])
} }
/// Add a foreign UTXO i.e. a UTXO not owned by this wallet.
///
/// At a minimum to add a foreign UTXO we need:
///
/// 1. `outpoint`: To add it to the raw transaction.
/// 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
/// 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
/// method doesn't verify the value but just takes it as a given -- it is up to you to check
/// that whoever sent you the `input_psbt` was not lying!
///
/// Secondly, you must somehow provide `satisfaction_weight` of the input. Depending on your
/// application it may be important that this be known precisely. If not, a malicious
/// counterparty may fool you into putting in a value that is too low, giving the transaction a
/// lower than expected feerate. They could also fool you into putting a value that is too high
/// causing you to pay a fee that is too high. The party who is broadcasting the transaction can
/// of course check the real input weight matches the expected weight prior to broadcasting.
///
/// To guarantee the `satisfaction_weight` is correct, you can require the party providing the
/// `psbt_input` provide a miniscript descriptor for the input so you can check it against the
/// `script_pubkey` and then ask it for the [`max_satisfaction_weight`].
///
/// This is an **EXPERIMENTAL** feature, API and other major changes are expected.
///
/// # Errors
///
/// This method returns errors in the following circumstances:
///
/// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`.
/// 2. The data in `non_witness_utxo` does not match what is in `outpoint`.
///
/// Note unless you set [`only_witness_utxo`] any `psbt_input` you pass to this method must
/// have `non_witness_utxo` set otherwise you will get an error when [`finish`] is called.
///
/// [`only_witness_utxo`]: Self::only_witness_utxo
/// [`finish`]: Self::finish
/// [`max_satisfaction_weight`]: miniscript::Descriptor::max_satisfaction_weight
pub fn add_foreign_utxo(
&mut self,
outpoint: OutPoint,
psbt_input: psbt::Input,
satisfaction_weight: usize,
) -> Result<&mut Self, Error> {
if psbt_input.witness_utxo.is_none() {
match psbt_input.non_witness_utxo.as_ref() {
Some(tx) => {
if tx.txid() != outpoint.txid {
return Err(Error::Generic(
"Foreign utxo outpoint does not match PSBT input".into(),
));
}
if tx.output.len() <= outpoint.vout as usize {
return Err(Error::InvalidOutpoint(outpoint));
}
}
None => {
return Err(Error::Generic(
"Foreign utxo missing witness_utxo or non_witness_utxo".into(),
))
}
}
}
self.params.utxos.push(WeightedUtxo {
satisfaction_weight,
utxo: Utxo::Foreign {
outpoint,
psbt_input: Box::new(psbt_input),
},
});
Ok(self)
}
/// Only spend utxos added by [`add_utxo`]. /// Only spend utxos added by [`add_utxo`].
/// ///
/// The wallet will **not** add additional utxos to the transaction even if they are needed to /// The wallet will **not** add additional utxos to the transaction even if they are needed to
@@ -385,12 +464,13 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
self self
} }
/// Fill-in the [`psbt::Input::non_witness_utxo`](bitcoin::util::psbt::Input::non_witness_utxo) field even if the wallet only has SegWit /// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::util::psbt::Input::witness_utxo) field when spending from
/// descriptors. /// SegWit descriptors.
/// ///
/// This is useful for signers which always require it, like Trezor hardware wallets. /// This reduces the size of the PSBT, but some signers might reject them due to the lack of
pub fn force_non_witness_utxo(&mut self) -> &mut Self { /// the `non_witness_utxo`.
self.params.force_non_witness_utxo = true; pub fn only_witness_utxo(&mut self) -> &mut Self {
self.params.only_witness_utxo = true;
self self
} }
@@ -444,6 +524,26 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
pub fn finish(self) -> Result<(PSBT, TransactionDetails), Error> { pub fn finish(self) -> Result<(PSBT, TransactionDetails), Error> {
self.wallet.create_tx(self.coin_selection, self.params) self.wallet.create_tx(self.coin_selection, self.params)
} }
/// Enable signaling RBF
///
/// This will use the default nSequence value of `0xFFFFFFFD`.
pub fn enable_rbf(&mut self) -> &mut Self {
self.params.rbf = Some(RbfValue::Default);
self
}
/// Enable signaling RBF with a specific nSequence value
///
/// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
/// and the given `nsequence` is lower than the CSV value.
///
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
/// be a valid nSequence to signal RBF.
pub fn enable_rbf_with_sequence(&mut self, nsequence: u32) -> &mut Self {
self.params.rbf = Some(RbfValue::Value(nsequence));
self
}
} }
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> { impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> {
@@ -479,26 +579,6 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
self self
} }
/// Enable signaling RBF
///
/// This will use the default nSequence value of `0xFFFFFFFD`.
pub fn enable_rbf(&mut self) -> &mut Self {
self.params.rbf = Some(RBFValue::Default);
self
}
/// Enable signaling RBF with a specific nSequence value
///
/// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
/// and the given `nsequence` is lower than the CSV value.
///
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
/// be a valid nSequence to signal RBF.
pub fn enable_rbf_with_sequence(&mut self, nsequence: u32) -> &mut Self {
self.params.rbf = Some(RBFValue::Value(nsequence));
self
}
} }
// methods supported only by bump_fee // methods supported only by bump_fee
@@ -533,7 +613,7 @@ pub enum TxOrdering {
/// Unchanged /// Unchanged
Untouched, Untouched,
/// BIP69 / Lexicographic /// BIP69 / Lexicographic
BIP69Lexicographic, Bip69Lexicographic,
} }
impl Default for TxOrdering { impl Default for TxOrdering {
@@ -559,7 +639,7 @@ impl TxOrdering {
tx.output.shuffle(&mut rng); tx.output.shuffle(&mut rng);
} }
TxOrdering::BIP69Lexicographic => { TxOrdering::Bip69Lexicographic => {
tx.input.sort_unstable_by_key(|txin| { tx.input.sort_unstable_by_key(|txin| {
(txin.previous_output.txid, txin.previous_output.vout) (txin.previous_output.txid, txin.previous_output.vout)
}); });
@@ -586,16 +666,16 @@ impl Default for Version {
/// ///
/// Has a default value of `0xFFFFFFFD` /// Has a default value of `0xFFFFFFFD`
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub(crate) enum RBFValue { pub(crate) enum RbfValue {
Default, Default,
Value(u32), Value(u32),
} }
impl RBFValue { impl RbfValue {
pub(crate) fn get_value(&self) -> u32 { pub(crate) fn get_value(&self) -> u32 {
match self { match self {
RBFValue::Default => 0xFFFFFFFD, RbfValue::Default => 0xFFFFFFFD,
RBFValue::Value(v) => *v, RbfValue::Value(v) => *v,
} }
} }
} }
@@ -618,7 +698,7 @@ impl Default for ChangeSpendPolicy {
} }
impl ChangeSpendPolicy { impl ChangeSpendPolicy {
pub(crate) fn is_satisfied_by(&self, utxo: &UTXO) -> bool { pub(crate) fn is_satisfied_by(&self, utxo: &LocalUtxo) -> bool {
match self { match self {
ChangeSpendPolicy::ChangeAllowed => true, ChangeSpendPolicy::ChangeAllowed => true,
ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal, ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal,
@@ -629,12 +709,12 @@ impl ChangeSpendPolicy {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
const ORDERING_TEST_TX: &'static str = "0200000003c26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f54\ const ORDERING_TEST_TX: &str = "0200000003c26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f54\
85d1fd600f0100000000ffffffffc26f3eb7932f7acddc5ddd26602b77e75160\ 85d1fd600f0100000000ffffffffc26f3eb7932f7acddc5ddd26602b77e75160\
79b03090a16e2c2f5485d1fd600f0000000000ffffffff571fb3e02278217852\ 79b03090a16e2c2f5485d1fd600f0000000000ffffffff571fb3e02278217852\
dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e0500000000ffffffff\ dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e0500000000ffffffff\
03e80300000000000002aaeee80300000000000001aa200300000000000001ff\ 03e80300000000000002aaeee80300000000000001aa200300000000000001ff\
00000000"; 00000000";
macro_rules! ordering_test_tx { macro_rules! ordering_test_tx {
() => { () => {
deserialize::<bitcoin::Transaction>(&Vec::<u8>::from_hex(ORDERING_TEST_TX).unwrap()) deserialize::<bitcoin::Transaction>(&Vec::<u8>::from_hex(ORDERING_TEST_TX).unwrap())
@@ -678,9 +758,9 @@ mod test {
use std::str::FromStr; use std::str::FromStr;
let original_tx = ordering_test_tx!(); let original_tx = ordering_test_tx!();
let mut tx = original_tx.clone(); let mut tx = original_tx;
TxOrdering::BIP69Lexicographic.sort_tx(&mut tx); TxOrdering::Bip69Lexicographic.sort_tx(&mut tx);
assert_eq!( assert_eq!(
tx.input[0].previous_output, tx.input[0].previous_output,
@@ -709,9 +789,9 @@ mod test {
assert_eq!(tx.output[2].script_pubkey, From::from(vec![0xAA, 0xEE])); assert_eq!(tx.output[2].script_pubkey, From::from(vec![0xAA, 0xEE]));
} }
fn get_test_utxos() -> Vec<UTXO> { fn get_test_utxos() -> Vec<LocalUtxo> {
vec![ vec![
UTXO { LocalUtxo {
outpoint: OutPoint { outpoint: OutPoint {
txid: Default::default(), txid: Default::default(),
vout: 0, vout: 0,
@@ -719,7 +799,7 @@ mod test {
txout: Default::default(), txout: Default::default(),
keychain: KeychainKind::External, keychain: KeychainKind::External,
}, },
UTXO { LocalUtxo {
outpoint: OutPoint { outpoint: OutPoint {
txid: Default::default(), txid: Default::default(),
vout: 1, vout: 1,
@@ -736,9 +816,9 @@ mod test {
let filtered = get_test_utxos() let filtered = get_test_utxos()
.into_iter() .into_iter()
.filter(|u| change_spend_policy.is_satisfied_by(u)) .filter(|u| change_spend_policy.is_satisfied_by(u))
.collect::<Vec<_>>(); .count();
assert_eq!(filtered.len(), 2); assert_eq!(filtered, 2);
} }
#[test] #[test]

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::secp256k1::{All, Secp256k1};
@@ -156,8 +143,8 @@ pub struct ChunksIterator<I: Iterator> {
size: usize, size: usize,
} }
#[cfg(any(feature = "electrum", feature = "esplora"))]
impl<I: Iterator> ChunksIterator<I> { impl<I: Iterator> ChunksIterator<I> {
#[allow(dead_code)]
pub fn new(iter: I, size: usize) -> Self { pub fn new(iter: I, size: usize) -> Self {
ChunksIterator { iter, size } ChunksIterator { iter, size }
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bdk-testutils-macros" name = "bdk-testutils-macros"
version = "0.3.0" version = "0.6.0"
authors = ["Alekos Filini <alekos.filini@gmail.com>"] authors = ["Alekos Filini <alekos.filini@gmail.com>"]
edition = "2018" edition = "2018"
homepage = "https://bitcoindevkit.org" homepage = "https://bitcoindevkit.org"
@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk-testutils-macros" documentation = "https://docs.rs/bdk-testutils-macros"
description = "Supporting testing macros for `bdk`" description = "Supporting testing macros for `bdk`"
keywords = ["bdk"] keywords = ["bdk"]
license = "MIT" license = "MIT OR Apache-2.0"
[lib] [lib]
proc-macro = true proc-macro = true

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#[macro_use] #[macro_use]
extern crate quote; extern crate quote;
@@ -84,6 +71,7 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
use #root_ident::database::MemoryDatabase; use #root_ident::database::MemoryDatabase;
use #root_ident::types::KeychainKind; use #root_ident::types::KeychainKind;
use #root_ident::{Wallet, TxBuilder, FeeRate}; use #root_ident::{Wallet, TxBuilder, FeeRate};
use #root_ident::wallet::AddressIndex::New;
use super::*; use super::*;
@@ -309,8 +297,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 25_000); builder.add_recipient(node_addr.script_pubkey(), 25_000);
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
@@ -338,8 +326,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 25_000); builder.add_recipient(node_addr.script_pubkey(), 25_000);
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap(); let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
@@ -379,8 +367,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
for _ in 0..5 { for _ in 0..5 {
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 5_000); builder.add_recipient(node_addr.script_pubkey(), 5_000);
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
@@ -413,8 +401,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf(); builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -423,8 +411,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_fee_bump(details.txid).unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(2.1)); builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
let (new_psbt, new_details) = builder.finish().unwrap(); let (mut new_psbt, new_details) = builder.finish().unwrap();
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -449,8 +437,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf(); builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -459,8 +447,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_fee_bump(details.txid).unwrap(); 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.0));
let (new_psbt, new_details) = builder.finish().unwrap(); let (mut new_psbt, new_details) = builder.finish().unwrap();
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -485,8 +473,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf(); builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -495,8 +483,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_fee_bump(details.txid).unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(10.0)); builder.fee_rate(FeeRate::from_sat_per_vb(10.0));
let (new_psbt, new_details) = builder.finish().unwrap(); let (mut new_psbt, new_details) = builder.finish().unwrap();
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -519,8 +507,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf(); builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -529,10 +517,10 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_fee_bump(details.txid).unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(123.0)); builder.fee_rate(FeeRate::from_sat_per_vb(123.0));
let (new_psbt, new_details) = builder.finish().unwrap(); let (mut new_psbt, new_details) = builder.finish().unwrap();
println!("{:#?}", new_details); println!("{:#?}", new_details);
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -545,7 +533,7 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
#[serial] #[serial]
fn test_sync_receive_coinbase() { fn test_sync_receive_coinbase() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, descriptors, mut test_client) = init_single_sig();
let wallet_addr = wallet.get_new_address().unwrap(); let wallet_addr = wallet.get_address(New).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0); assert_eq!(wallet.get_balance().unwrap(), 0);

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bdk-testutils" name = "bdk-testutils"
version = "0.3.0" version = "0.4.0"
authors = ["Alekos Filini <alekos.filini@gmail.com>"] authors = ["Alekos Filini <alekos.filini@gmail.com>"]
edition = "2018" edition = "2018"
homepage = "https://bitcoindevkit.org" homepage = "https://bitcoindevkit.org"
@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk-testutils" documentation = "https://docs.rs/bdk-testutils"
description = "Supporting testing utilities for `bdk`" description = "Supporting testing utilities for `bdk`"
keywords = ["bdk"] keywords = ["bdk"]
license = "MIT" license = "MIT OR Apache-2.0"
[lib] [lib]
name = "testutils" name = "testutils"

View File

@@ -1,26 +1,13 @@
// Magical Bitcoin Library // Bitcoin Dev Kit
// Written in 2020 by // Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
// Alekos Filini <alekos.filini@gmail.com>
// //
// Copyright (c) 2020 Magical Bitcoin // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// of this software and associated documentation files (the "Software"), to deal // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// in the Software without restriction, including without limitation the rights // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // You may not use this file except in accordance with one or both of these
// copies of the Software, and to permit persons to whom the Software is // licenses.
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#[macro_use] #[macro_use]
extern crate serde_json; extern crate serde_json;
@@ -53,20 +40,20 @@ pub use electrum_client::{Client as ElectrumClient, ElectrumApi};
// TODO: we currently only support env vars, we could also parse a toml file // TODO: we currently only support env vars, we could also parse a toml file
fn get_auth() -> Auth { fn get_auth() -> Auth {
match env::var("MAGICAL_RPC_AUTH").as_ref().map(String::as_ref) { match env::var("BDK_RPC_AUTH").as_ref().map(String::as_ref) {
Ok("USER_PASS") => Auth::UserPass( Ok("USER_PASS") => Auth::UserPass(
env::var("MAGICAL_RPC_USER").unwrap(), env::var("BDK_RPC_USER").unwrap(),
env::var("MAGICAL_RPC_PASS").unwrap(), env::var("BDK_RPC_PASS").unwrap(),
), ),
_ => Auth::CookieFile(PathBuf::from( _ => Auth::CookieFile(PathBuf::from(
env::var("MAGICAL_RPC_COOKIEFILE") env::var("BDK_RPC_COOKIEFILE")
.unwrap_or("/home/user/.bitcoin/regtest/.cookie".to_string()), .unwrap_or_else(|_| "/home/user/.bitcoin/regtest/.cookie".to_string()),
)), )),
} }
} }
pub fn get_electrum_url() -> String { pub fn get_electrum_url() -> String {
env::var("MAGICAL_ELECTRUM_URL").unwrap_or("tcp://127.0.0.1:50001".to_string()) env::var("BDK_ELECTRUM_URL").unwrap_or_else(|_| "tcp://127.0.0.1:50001".to_string())
} }
pub struct TestClient { pub struct TestClient {
@@ -311,8 +298,10 @@ where
impl TestClient { impl TestClient {
pub fn new() -> Self { pub fn new() -> Self {
let url = env::var("MAGICAL_RPC_URL").unwrap_or("127.0.0.1:18443".to_string()); let url = env::var("BDK_RPC_URL").unwrap_or_else(|_| "127.0.0.1:18443".to_string());
let client = RpcClient::new(format!("http://{}", url), get_auth()).unwrap(); let wallet = env::var("BDK_RPC_WALLET").unwrap_or_else(|_| "bdk-test".to_string());
let client =
RpcClient::new(format!("http://{}/wallet/{}", url, wallet), get_auth()).unwrap();
let electrum = ElectrumClient::new(&get_electrum_url()).unwrap(); let electrum = ElectrumClient::new(&get_electrum_url()).unwrap();
TestClient { client, electrum } TestClient { client, electrum }
@@ -347,7 +336,7 @@ impl TestClient {
pub fn receive(&mut self, meta_tx: TestIncomingTx) -> Txid { pub fn receive(&mut self, meta_tx: TestIncomingTx) -> Txid {
assert!( assert!(
meta_tx.output.len() > 0, !meta_tx.output.is_empty(),
"can't create a transaction with no outputs" "can't create a transaction with no outputs"
); );
@@ -360,7 +349,7 @@ impl TestClient {
} }
if self.get_balance(None, None).unwrap() < Amount::from_sat(required_balance) { if self.get_balance(None, None).unwrap() < Amount::from_sat(required_balance) {
panic!("Insufficient funds in bitcoind. Plase generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap()); panic!("Insufficient funds in bitcoind. Please generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap());
} }
// FIXME: core can't create a tx with two outputs to the same address // FIXME: core can't create a tx with two outputs to the same address