Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c175dd2aae | ||
|
|
6b1cbcc4b7 | ||
|
|
afa1ab4ff8 | ||
|
|
632422a3ab | ||
|
|
54f61d17f2 | ||
|
|
5830226216 | ||
|
|
2c77329333 | ||
|
|
3e5bb077ac | ||
|
|
7c06f52a07 | ||
|
|
12e51b3c06 | ||
|
|
2892edf94b | ||
|
|
aa075f0b2f | ||
|
|
8010d692e9 | ||
|
|
b2d7412d6d | ||
|
|
fd51029197 | ||
|
|
711510006b | ||
|
|
d21b6e47ab | ||
|
|
5922c216a1 | ||
|
|
9e29e2d2b1 | ||
|
|
16e832533c | ||
|
|
7f91bcdf1a | ||
|
|
35695d8795 | ||
|
|
756858e882 | ||
|
|
d2ce2714f2 | ||
|
|
3b2b559910 | ||
|
|
3c8416bf31 | ||
|
|
f6f736609f | ||
|
|
5cb0726780 | ||
|
|
8781599740 | ||
|
|
ee8b992f8b | ||
|
|
3d8efbf8bf | ||
|
|
a2e26f1b57 | ||
|
|
5f5744e897 | ||
|
|
e106136227 | ||
|
|
d75d221540 | ||
|
|
548e43d928 | ||
|
|
a348dbdcfe | ||
|
|
b638039655 | ||
|
|
7e085a86dd | ||
|
|
59f795f176 | ||
|
|
2da10382e7 | ||
|
|
6d18502733 | ||
|
|
81b263f235 | ||
|
|
2f38d3e526 | ||
|
|
2ee125655b | ||
|
|
22c39b7b78 | ||
|
|
18f1107c41 | ||
|
|
763bcc22ab | ||
|
|
9e4ca516a8 | ||
|
|
b60465f31e | ||
|
|
1469a3487a | ||
|
|
8c21bcf40a | ||
|
|
c9ed8bdf6c | ||
|
|
919522a456 | ||
|
|
678607e673 | ||
|
|
c06d9f1d33 | ||
|
|
5a6a2cefdd | ||
|
|
3fe2380d6c | ||
|
|
eea8b135a4 | ||
|
|
a685b22aa6 | ||
|
|
c601ae3271 | ||
|
|
c23692824d | ||
|
|
46f7b440f5 | ||
|
|
562fde7953 | ||
|
|
9e508748a3 | ||
|
|
84b8579df5 | ||
|
|
7cb0116c44 | ||
|
|
6e12468b12 | ||
|
|
326b64de3a | ||
|
|
5edf663f3d | ||
|
|
e3dd755396 | ||
|
|
b500cfe4e5 | ||
|
|
10b53a56d7 | ||
|
|
8d1d92e71e | ||
|
|
a41a0030dc | ||
|
|
2459740f72 | ||
|
|
5694b98304 | ||
|
|
aa786fbb21 | ||
|
|
8c570ae7eb | ||
|
|
56a7bc9874 | ||
|
|
dd4bd96f79 | ||
|
|
2caa590438 | ||
|
|
2a53cfc23f | ||
|
|
cf1815a1c0 | ||
|
|
acf157a99a | ||
|
|
fb813427eb | ||
|
|
721748e98f | ||
|
|
976e641ba6 | ||
|
|
7117557dea | ||
|
|
fa013aeb83 | ||
|
|
470d02c81c | ||
|
|
38d1d0b0e2 | ||
|
|
582d2f3814 | ||
|
|
5e0011e1a8 | ||
|
|
39d2bd0d21 | ||
|
|
0e10952b80 | ||
|
|
19d74955e2 | ||
|
|
73a7faf144 | ||
|
|
ea56a87b4b | ||
|
|
67f5f45e07 | ||
|
|
b8680b299d | ||
|
|
e80be49d1e | ||
|
|
fe30716fa2 | ||
|
|
e52550cfec | ||
|
|
f57c0ca98e |
2
.github/workflows/code_coverage.yml
vendored
2
.github/workflows/code_coverage.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
uses: actions-rs/grcov@v0.1.5
|
uses: actions-rs/grcov@v0.1.5
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
with:
|
||||||
file: ${{ steps.coverage.outputs.report }}
|
file: ${{ steps.coverage.outputs.report }}
|
||||||
directory: ./coverage/reports/
|
directory: ./coverage/reports/
|
||||||
|
|||||||
23
.github/workflows/cont_integration.yml
vendored
23
.github/workflows/cont_integration.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
- 1.53.0 # STABLE
|
- 1.56.0 # STABLE
|
||||||
- 1.46.0 # MSRV
|
- 1.46.0 # MSRV
|
||||||
features:
|
features:
|
||||||
- default
|
- default
|
||||||
@@ -26,6 +26,7 @@ jobs:
|
|||||||
- verify
|
- verify
|
||||||
- async-interface
|
- async-interface
|
||||||
- use-esplora-reqwest
|
- use-esplora-reqwest
|
||||||
|
- sqlite
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -78,15 +79,20 @@ jobs:
|
|||||||
run: cargo test --features test-md-docs --no-default-features -- doctest::ReadmeDoctests
|
run: cargo test --features test-md-docs --no-default-features -- doctest::ReadmeDoctests
|
||||||
|
|
||||||
test-blockchains:
|
test-blockchains:
|
||||||
name: Test ${{ matrix.blockchain.name }}
|
name: Blockchain ${{ matrix.blockchain.features }}
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
blockchain:
|
blockchain:
|
||||||
- name: electrum
|
- name: electrum
|
||||||
|
features: test-electrum
|
||||||
- name: rpc
|
- name: rpc
|
||||||
|
features: test-rpc
|
||||||
- name: esplora
|
- name: esplora
|
||||||
|
features: test-esplora,use-esplora-reqwest
|
||||||
|
- name: esplora
|
||||||
|
features: test-esplora,use-esplora-ureq
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -95,8 +101,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/registry
|
~/.cargo/registry
|
||||||
~/.cargo/bitcoin
|
|
||||||
~/.cargo/electrs
|
|
||||||
~/.cargo/git
|
~/.cargo/git
|
||||||
target
|
target
|
||||||
key: ${{ runner.os }}-cargo-${{ github.job }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-${{ github.job }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
||||||
@@ -106,11 +110,11 @@ jobs:
|
|||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test --features test-${{ matrix.blockchain.name }} ${{ matrix.blockchain.name }}::bdk_blockchain_tests
|
run: cargo test --no-default-features --features ${{ matrix.blockchain.features }} ${{ matrix.blockchain.name }}::bdk_blockchain_tests
|
||||||
|
|
||||||
check-wasm:
|
check-wasm:
|
||||||
name: Check WASM
|
name: Check WASM
|
||||||
runs-on: ubuntu-16.04
|
runs-on: ubuntu-20.04
|
||||||
env:
|
env:
|
||||||
CC: clang-10
|
CC: clang-10
|
||||||
CFLAGS: -I/usr/include
|
CFLAGS: -I/usr/include
|
||||||
@@ -127,11 +131,11 @@ jobs:
|
|||||||
key: ${{ runner.os }}-cargo-${{ github.job }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-${{ github.job }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
||||||
# Install a recent version of clang that supports wasm32
|
# Install a recent version of clang that supports wasm32
|
||||||
- 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/focal/ llvm-toolchain-focal-10 main" || exit 1
|
||||||
- run: sudo apt-get update || exit 1
|
- run: sudo apt-get update || exit 1
|
||||||
- run: sudo apt-get install -y libclang-common-10-dev 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 1.53.0 # STABLE
|
run: rustup default 1.56.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
|
||||||
@@ -141,7 +145,6 @@ jobs:
|
|||||||
- name: Check
|
- name: Check
|
||||||
run: cargo check --target wasm32-unknown-unknown --features use-esplora-reqwest --no-default-features
|
run: cargo check --target wasm32-unknown-unknown --features use-esplora-reqwest --no-default-features
|
||||||
|
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Rust fmt
|
name: Rust fmt
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
2
.github/workflows/nightly_docs.yml
vendored
2
.github/workflows/nightly_docs.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Update toolchain
|
- name: Update toolchain
|
||||||
run: rustup update
|
run: rustup update
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: cargo rustdoc --verbose --features=compiler,electrum,esplora,ureq,compact_filters,key-value-db,all-keys -- --cfg docsrs -Dwarnings
|
run: cargo rustdoc --verbose --features=compiler,electrum,esplora,ureq,compact_filters,key-value-db,all-keys,sqlite -- --cfg docsrs -Dwarnings
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [v0.14.0] - [v0.13.0]
|
||||||
|
|
||||||
|
- BIP39 implementation dependency, in `keys::bip39` changed from tiny-bip39 to rust-bip39.
|
||||||
|
- Add new method on the `TxBuilder` to embed data in the transaction via `OP_RETURN`. To allow that a fix to check the dust only on spendable output has been introduced.
|
||||||
|
- Update the `Database` trait to store the last sync timestamp and block height
|
||||||
|
- Rename `ConfirmationTime` to `BlockTime`
|
||||||
|
|
||||||
|
## [v0.13.0] - [v0.12.0]
|
||||||
|
|
||||||
|
- Exposed `get_tx()` method from `Database` to `Wallet`.
|
||||||
|
|
||||||
|
## [v0.12.0] - [v0.11.0]
|
||||||
|
|
||||||
|
- Activate `miniscript/use-serde` feature to allow consumers of the library to access it via the re-exported `miniscript` crate.
|
||||||
|
- Add support for proxies in `EsploraBlockchain`
|
||||||
|
- Added `SqliteDatabase` that implements `Database` backed by a sqlite database using `rusqlite` crate.
|
||||||
|
|
||||||
|
## [v0.11.0] - [v0.10.0]
|
||||||
|
|
||||||
|
- Added `flush` method to the `Database` trait to explicitly flush to disk latest changes on the db.
|
||||||
|
|
||||||
## [v0.10.0] - [v0.9.0]
|
## [v0.10.0] - [v0.9.0]
|
||||||
|
|
||||||
- Added `RpcBlockchain` in the `AnyBlockchain` struct to allow using Rpc backend where `AnyBlockchain` is used (eg `bdk-cli`)
|
- Added `RpcBlockchain` in the `AnyBlockchain` struct to allow using Rpc backend where `AnyBlockchain` is used (eg `bdk-cli`)
|
||||||
@@ -360,7 +381,7 @@ final transaction is created by calling `finish` on the builder.
|
|||||||
- Use `MemoryDatabase` in the compiler example
|
- Use `MemoryDatabase` in the compiler example
|
||||||
- Make the REPL return JSON
|
- Make the REPL return JSON
|
||||||
|
|
||||||
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.9.0...HEAD
|
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.11.0...HEAD
|
||||||
[0.1.0-beta.1]: https://github.com/bitcoindevkit/bdk/compare/96c87ea5...0.1.0-beta.1
|
[0.1.0-beta.1]: https://github.com/bitcoindevkit/bdk/compare/96c87ea5...0.1.0-beta.1
|
||||||
[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
|
||||||
@@ -372,3 +393,7 @@ final transaction is created by calling `finish` on the builder.
|
|||||||
[v0.8.0]: https://github.com/bitcoindevkit/bdk/compare/v0.7.0...v0.8.0
|
[v0.8.0]: https://github.com/bitcoindevkit/bdk/compare/v0.7.0...v0.8.0
|
||||||
[v0.9.0]: https://github.com/bitcoindevkit/bdk/compare/v0.8.0...v0.9.0
|
[v0.9.0]: https://github.com/bitcoindevkit/bdk/compare/v0.8.0...v0.9.0
|
||||||
[v0.10.0]: https://github.com/bitcoindevkit/bdk/compare/v0.9.0...v0.10.0
|
[v0.10.0]: https://github.com/bitcoindevkit/bdk/compare/v0.9.0...v0.10.0
|
||||||
|
[v0.11.0]: https://github.com/bitcoindevkit/bdk/compare/v0.10.0...v0.11.0
|
||||||
|
[v0.12.0]: https://github.com/bitcoindevkit/bdk/compare/v0.11.0...v0.12.0
|
||||||
|
[v0.13.0]: https://github.com/bitcoindevkit/bdk/compare/v0.12.0...v0.13.0
|
||||||
|
[v0.14.0]: https://github.com/bitcoindevkit/bdk/compare/v0.13.0...v0.14.0
|
||||||
|
|||||||
@@ -57,6 +57,21 @@ comment suggesting that you're working on it. If someone is already assigned,
|
|||||||
don't hesitate to ask if the assigned party or previous commenters are still
|
don't hesitate to ask if the assigned party or previous commenters are still
|
||||||
working on it if it has been awhile.
|
working on it if it has been awhile.
|
||||||
|
|
||||||
|
Deprecation policy
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Where possible, breaking existing APIs should be avoided. Instead, add new APIs and
|
||||||
|
use [`#[deprecated]`](https://github.com/rust-lang/rfcs/blob/master/text/1270-deprecation.md)
|
||||||
|
to discourage use of the old one.
|
||||||
|
|
||||||
|
Deprecated APIs are typically maintained for one release cycle. In other words, an
|
||||||
|
API that has been deprecated with the 0.10 release can be expected to be removed in the
|
||||||
|
0.11 release. This allows for smoother upgrades without incurring too much technical
|
||||||
|
debt inside this library.
|
||||||
|
|
||||||
|
If you deprecated an API as part of a contribution, we encourage you to "own" that API
|
||||||
|
and send a follow-up to remove it as part of the next release cycle.
|
||||||
|
|
||||||
Peer review
|
Peer review
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|||||||
39
Cargo.toml
39
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk"
|
name = "bdk"
|
||||||
version = "0.10.0"
|
version = "0.14.0"
|
||||||
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"
|
||||||
@@ -12,31 +12,37 @@ readme = "README.md"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk-macros = "0.5"
|
bdk-macros = "^0.6"
|
||||||
log = "^0.4"
|
log = "^0.4"
|
||||||
miniscript = "5.1"
|
miniscript = { version = "^6.0", features = ["use-serde"] }
|
||||||
bitcoin = { version = "~0.26.2", features = ["use-serde", "base64"] }
|
bitcoin = { version = "^0.27", features = ["use-serde", "base64"] }
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
serde_json = { version = "^1.0" }
|
serde_json = { version = "^1.0" }
|
||||||
rand = "^0.7"
|
rand = "^0.7"
|
||||||
|
|
||||||
# Optional dependencies
|
# Optional dependencies
|
||||||
sled = { version = "0.34", optional = true }
|
sled = { version = "0.34", optional = true }
|
||||||
electrum-client = { version = "0.7", optional = true }
|
electrum-client = { version = "0.8", optional = true }
|
||||||
|
rusqlite = { version = "0.25.3", optional = true }
|
||||||
|
ahash = { version = "=0.7.4", optional = true }
|
||||||
reqwest = { version = "0.11", optional = true, features = ["json"] }
|
reqwest = { version = "0.11", optional = true, features = ["json"] }
|
||||||
ureq = { version = "2.1", default-features = false, features = ["json"], optional = true }
|
ureq = { version = "~2.2.0", features = ["json"], optional = true }
|
||||||
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 }
|
||||||
rocksdb = { version = "0.14", optional = true }
|
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
|
||||||
cc = { version = ">=1.0.64", optional = true }
|
cc = { version = ">=1.0.64", optional = true }
|
||||||
socks = { version = "0.3", optional = true }
|
socks = { version = "0.3", optional = true }
|
||||||
lazy_static = { version = "1.4", optional = true }
|
lazy_static = { version = "1.4", optional = true }
|
||||||
tiny-bip39 = { version = "^0.8", optional = true }
|
|
||||||
zeroize = { version = "<1.4.0", optional = true }
|
bip39 = { version = "1.0.1", optional = true }
|
||||||
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
||||||
|
|
||||||
# Needed by bdk_blockchain_tests macro
|
# Needed by bdk_blockchain_tests macro
|
||||||
bitcoincore-rpc = { version = "0.13", optional = true }
|
bitcoincore-rpc = { version = "0.14", optional = true }
|
||||||
|
|
||||||
|
# Platform-specific dependencies
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
tokio = { version = "1", features = ["rt"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
@@ -48,10 +54,11 @@ minimal = []
|
|||||||
compiler = ["miniscript/compiler"]
|
compiler = ["miniscript/compiler"]
|
||||||
verify = ["bitcoinconsensus"]
|
verify = ["bitcoinconsensus"]
|
||||||
default = ["key-value-db", "electrum"]
|
default = ["key-value-db", "electrum"]
|
||||||
|
sqlite = ["rusqlite", "ahash"]
|
||||||
compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
|
compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
|
||||||
key-value-db = ["sled"]
|
key-value-db = ["sled"]
|
||||||
all-keys = ["keys-bip39"]
|
all-keys = ["keys-bip39"]
|
||||||
keys-bip39 = ["tiny-bip39", "zeroize"]
|
keys-bip39 = ["bip39"]
|
||||||
rpc = ["bitcoincore-rpc"]
|
rpc = ["bitcoincore-rpc"]
|
||||||
|
|
||||||
# We currently provide mulitple implementations of `Blockchain`, all are
|
# We currently provide mulitple implementations of `Blockchain`, all are
|
||||||
@@ -70,8 +77,8 @@ rpc = ["bitcoincore-rpc"]
|
|||||||
async-interface = ["async-trait"]
|
async-interface = ["async-trait"]
|
||||||
electrum = ["electrum-client"]
|
electrum = ["electrum-client"]
|
||||||
# MUST ALSO USE `--no-default-features`.
|
# MUST ALSO USE `--no-default-features`.
|
||||||
use-esplora-reqwest = ["async-interface", "esplora", "reqwest", "futures"]
|
use-esplora-reqwest = ["esplora", "reqwest", "reqwest/socks", "futures"]
|
||||||
use-esplora-ureq = ["esplora", "ureq"]
|
use-esplora-ureq = ["esplora", "ureq", "ureq/socks"]
|
||||||
# Typical configurations will not need to use `esplora` feature directly.
|
# Typical configurations will not need to use `esplora` feature directly.
|
||||||
esplora = []
|
esplora = []
|
||||||
|
|
||||||
@@ -80,14 +87,14 @@ esplora = []
|
|||||||
test-blockchains = ["bitcoincore-rpc", "electrum-client"]
|
test-blockchains = ["bitcoincore-rpc", "electrum-client"]
|
||||||
test-electrum = ["electrum", "electrsd/electrs_0_8_10", "test-blockchains"]
|
test-electrum = ["electrum", "electrsd/electrs_0_8_10", "test-blockchains"]
|
||||||
test-rpc = ["rpc", "electrsd/electrs_0_8_10", "test-blockchains"]
|
test-rpc = ["rpc", "electrsd/electrs_0_8_10", "test-blockchains"]
|
||||||
test-esplora = ["esplora", "ureq", "electrsd/legacy", "electrsd/esplora_a33e97e1", "test-blockchains"]
|
test-esplora = ["electrsd/legacy", "electrsd/esplora_a33e97e1", "test-blockchains"]
|
||||||
test-md-docs = ["electrum"]
|
test-md-docs = ["electrum"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
electrsd = { version= "0.8", features = ["trigger", "bitcoind_0_21_1"] }
|
electrsd = { version= "0.12", features = ["trigger", "bitcoind_22_0"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "address_validator"
|
name = "address_validator"
|
||||||
@@ -103,6 +110,6 @@ required-features = ["compiler"]
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["macros"]
|
members = ["macros"]
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["compiler", "electrum", "esplora", "ureq", "compact_filters", "rpc", "key-value-db", "all-keys", "verify"]
|
features = ["compiler", "electrum", "esplora", "ureq", "compact_filters", "rpc", "key-value-db", "sqlite", "all-keys", "verify"]
|
||||||
# defines the configuration attribute `docsrs`
|
# defines the configuration attribute `docsrs`
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ Pre-`v1.0.0` our "major" releases only affect the "minor" semver value. Accordin
|
|||||||
- If it's a minor issue you can just fix it in the release branch, since it will be merged back to `master` eventually
|
- If it's a minor issue you can just fix it in the release branch, since it will be merged back to `master` eventually
|
||||||
- For bigger issues you can fix them on `master` and then *cherry-pick* the commit to the release branch
|
- For bigger issues you can fix them on `master` and then *cherry-pick* the commit to the release branch
|
||||||
6. Update the changelog with the new release version.
|
6. Update the changelog with the new release version.
|
||||||
7. Update `src/lib.rs` with the new version (line ~59)
|
7. Update `src/lib.rs` with the new version (line ~43)
|
||||||
8. On release day, make a commit on the release branch to bump the version to `x.y.z`. The message should be "Bump version to x.y.z".
|
8. On release day, make a commit on the release branch to bump the version to `x.y.z`. The message should be "Bump version to x.y.z".
|
||||||
9. Add a tag to this commit. The tag name should be `vx.y.z` (for example `v0.5.0`), and the message "Release x.y.z". Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
|
9. Add a tag to this commit. The tag name should be `vx.y.z` (for example `v0.5.0`), and the message "Release x.y.z". Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
|
||||||
10. Push the new commits to the upstream release branch, wait for the CI to finish one last time.
|
10. Push the new commits to the upstream release branch, wait for the CI to finish one last time.
|
||||||
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. 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"}`)
|
14. If the `master` branch contains any unreleased changes to the `bdk-macros` crate, change the `bdk` Cargo.toml `[dependencies]` to point to the local path (ie. `bdk-macros = { path = "./macros"}`)
|
||||||
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.
|
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. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs.
|
16. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs.
|
||||||
17. Announce the release on Twitter, Discord and Telegram.
|
17. Announce the release on Twitter, Discord and Telegram.
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let policy_str = matches.value_of("POLICY").unwrap();
|
let policy_str = matches.value_of("POLICY").unwrap();
|
||||||
info!("Compiling policy: {}", policy_str);
|
info!("Compiling policy: {}", policy_str);
|
||||||
|
|
||||||
let policy = Concrete::<String>::from_str(&policy_str)?;
|
let policy = Concrete::<String>::from_str(policy_str)?;
|
||||||
|
|
||||||
let descriptor = match matches.value_of("TYPE").unwrap() {
|
let descriptor = match matches.value_of("TYPE").unwrap() {
|
||||||
"sh" => Descriptor::new_sh(policy.compile()?)?,
|
"sh" => Descriptor::new_sh(policy.compile()?)?,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk-macros"
|
name = "bdk-macros"
|
||||||
version = "0.5.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"
|
||||||
|
|||||||
@@ -121,3 +121,26 @@ pub fn maybe_await(expr: TokenStream) -> TokenStream {
|
|||||||
|
|
||||||
quoted.into()
|
quoted.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Awaits if target_arch is "wasm32", uses `tokio::Runtime::block_on()` otherwise
|
||||||
|
///
|
||||||
|
/// Requires the `tokio` crate as a dependecy with `rt-core` or `rt-threaded` to build on non-wasm32 platforms.
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn await_or_block(expr: TokenStream) -> TokenStream {
|
||||||
|
let expr: proc_macro2::TokenStream = expr.into();
|
||||||
|
let quoted = quote! {
|
||||||
|
{
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
|
||||||
|
{
|
||||||
|
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(#expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
|
||||||
|
{
|
||||||
|
#expr.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quoted.into()
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ 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, LocalUtxo, TransactionDetails};
|
use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
|
||||||
use crate::{ConfirmationTime, FeeRate};
|
use crate::{BlockTime, FeeRate};
|
||||||
|
|
||||||
use peer::*;
|
use peer::*;
|
||||||
use store::*;
|
use store::*;
|
||||||
@@ -206,7 +206,7 @@ impl CompactFiltersBlockchain {
|
|||||||
transaction: Some(tx.clone()),
|
transaction: Some(tx.clone()),
|
||||||
received: incoming,
|
received: incoming,
|
||||||
sent: outgoing,
|
sent: outgoing,
|
||||||
confirmation_time: ConfirmationTime::new(height, timestamp),
|
confirmation_time: BlockTime::new(height, timestamp),
|
||||||
verified: height.is_some(),
|
verified: height.is_some(),
|
||||||
fee: Some(inputs_sum.saturating_sub(outputs_sum)),
|
fee: Some(inputs_sum.saturating_sub(outputs_sum)),
|
||||||
};
|
};
|
||||||
@@ -254,7 +254,7 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
let total_cost = headers_cost + filters_cost + PROCESS_BLOCKS_COST;
|
let total_cost = headers_cost + filters_cost + PROCESS_BLOCKS_COST;
|
||||||
|
|
||||||
if let Some(snapshot) = sync::sync_headers(
|
if let Some(snapshot) = sync::sync_headers(
|
||||||
Arc::clone(&first_peer),
|
Arc::clone(first_peer),
|
||||||
Arc::clone(&self.headers),
|
Arc::clone(&self.headers),
|
||||||
|new_height| {
|
|new_height| {
|
||||||
let local_headers_cost =
|
let local_headers_cost =
|
||||||
@@ -275,7 +275,7 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
let buried_height = synced_height.saturating_sub(sync::BURIED_CONFIRMATIONS);
|
let buried_height = synced_height.saturating_sub(sync::BURIED_CONFIRMATIONS);
|
||||||
info!("Synced headers to height: {}", synced_height);
|
info!("Synced headers to height: {}", synced_height);
|
||||||
|
|
||||||
cf_sync.prepare_sync(Arc::clone(&first_peer))?;
|
cf_sync.prepare_sync(Arc::clone(first_peer))?;
|
||||||
|
|
||||||
let all_scripts = Arc::new(
|
let all_scripts = Arc::new(
|
||||||
database
|
database
|
||||||
@@ -294,7 +294,7 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
let mut threads = Vec::with_capacity(self.peers.len());
|
let mut threads = Vec::with_capacity(self.peers.len());
|
||||||
for peer in &self.peers {
|
for peer in &self.peers {
|
||||||
let cf_sync = Arc::clone(&cf_sync);
|
let cf_sync = Arc::clone(&cf_sync);
|
||||||
let peer = Arc::clone(&peer);
|
let peer = Arc::clone(peer);
|
||||||
let headers = Arc::clone(&self.headers);
|
let headers = Arc::clone(&self.headers);
|
||||||
let all_scripts = Arc::clone(&all_scripts);
|
let all_scripts = Arc::clone(&all_scripts);
|
||||||
let last_synced_block = Arc::clone(&last_synced_block);
|
let last_synced_block = Arc::clone(&last_synced_block);
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ impl Peer {
|
|||||||
let message_resp = {
|
let message_resp = {
|
||||||
let mut lock = responses.write().unwrap();
|
let mut lock = responses.write().unwrap();
|
||||||
let message_resp = lock.entry(wait_for).or_default();
|
let message_resp = lock.entry(wait_for).or_default();
|
||||||
Arc::clone(&message_resp)
|
Arc::clone(message_resp)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (lock, cvar) = &*message_resp;
|
let (lock, cvar) = &*message_resp;
|
||||||
@@ -379,7 +379,7 @@ impl Peer {
|
|||||||
let message_resp = {
|
let message_resp = {
|
||||||
let mut lock = reader_thread_responses.write().unwrap();
|
let mut lock = reader_thread_responses.write().unwrap();
|
||||||
let message_resp = lock.entry(in_message.cmd()).or_default();
|
let message_resp = lock.entry(in_message.cmd()).or_default();
|
||||||
Arc::clone(&message_resp)
|
Arc::clone(message_resp)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (lock, cvar) = &*message_resp;
|
let (lock, cvar) = &*message_resp;
|
||||||
|
|||||||
@@ -760,7 +760,7 @@ impl CfStore {
|
|||||||
let cf_headers: Vec<FilterHeader> = filter_hashes
|
let cf_headers: Vec<FilterHeader> = filter_hashes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.scan(checkpoint, |prev_header, filter_hash| {
|
.scan(checkpoint, |prev_header, filter_hash| {
|
||||||
let filter_header = filter_hash.filter_header(&prev_header);
|
let filter_header = filter_hash.filter_header(prev_header);
|
||||||
*prev_header = filter_header;
|
*prev_header = filter_header;
|
||||||
|
|
||||||
Some(filter_header)
|
Some(filter_header)
|
||||||
@@ -801,7 +801,7 @@ impl CfStore {
|
|||||||
.zip(headers.into_iter())
|
.zip(headers.into_iter())
|
||||||
.scan(checkpoint, |prev_header, ((_, filter_content), header)| {
|
.scan(checkpoint, |prev_header, ((_, filter_content), header)| {
|
||||||
let filter = BlockFilter::new(&filter_content);
|
let filter = BlockFilter::new(&filter_content);
|
||||||
if header != filter.filter_header(&prev_header) {
|
if header != filter.filter_header(prev_header) {
|
||||||
return Some(Err(CompactFiltersError::InvalidFilter));
|
return Some(Err(CompactFiltersError::InvalidFilter));
|
||||||
}
|
}
|
||||||
*prev_header = header;
|
*prev_header = header;
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ impl CfSync {
|
|||||||
let block_hash = self.headers_store.get_block_hash(height)?.unwrap();
|
let block_hash = self.headers_store.get_block_hash(height)?.unwrap();
|
||||||
|
|
||||||
// TODO: also download random blocks?
|
// TODO: also download random blocks?
|
||||||
if process(&block_hash, &BlockFilter::new(&filter))? {
|
if process(&block_hash, &BlockFilter::new(filter))? {
|
||||||
log::debug!("Downloading block {}", block_hash);
|
log::debug!("Downloading block {}", block_hash);
|
||||||
|
|
||||||
let block = peer
|
let block = peer
|
||||||
|
|||||||
@@ -29,38 +29,16 @@ use bitcoin::{BlockHash, Txid};
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::FeeRate;
|
use crate::FeeRate;
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(feature = "reqwest")]
|
||||||
feature = "esplora",
|
|
||||||
feature = "reqwest",
|
|
||||||
any(feature = "async-interface", target_arch = "wasm32"),
|
|
||||||
))]
|
|
||||||
mod reqwest;
|
mod reqwest;
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(feature = "reqwest")]
|
||||||
feature = "esplora",
|
|
||||||
feature = "reqwest",
|
|
||||||
any(feature = "async-interface", target_arch = "wasm32"),
|
|
||||||
))]
|
|
||||||
pub use self::reqwest::*;
|
pub use self::reqwest::*;
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(feature = "ureq")]
|
||||||
feature = "esplora",
|
|
||||||
not(any(
|
|
||||||
feature = "async-interface",
|
|
||||||
feature = "reqwest",
|
|
||||||
target_arch = "wasm32"
|
|
||||||
)),
|
|
||||||
))]
|
|
||||||
mod ureq;
|
mod ureq;
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(feature = "ureq")]
|
||||||
feature = "esplora",
|
|
||||||
not(any(
|
|
||||||
feature = "async-interface",
|
|
||||||
feature = "reqwest",
|
|
||||||
target_arch = "wasm32"
|
|
||||||
)),
|
|
||||||
))]
|
|
||||||
pub use self::ureq::*;
|
pub use self::ureq::*;
|
||||||
|
|
||||||
fn into_fee_rate(target: usize, estimates: HashMap<String, f64>) -> Result<FeeRate, Error> {
|
fn into_fee_rate(target: usize, estimates: HashMap<String, f64>) -> Result<FeeRate, Error> {
|
||||||
@@ -141,3 +119,11 @@ impl_error!(io::Error, Io, EsploraError);
|
|||||||
impl_error!(std::num::ParseIntError, Parsing, EsploraError);
|
impl_error!(std::num::ParseIntError, Parsing, EsploraError);
|
||||||
impl_error!(consensus::encode::Error, BitcoinEncoding, EsploraError);
|
impl_error!(consensus::encode::Error, BitcoinEncoding, EsploraError);
|
||||||
impl_error!(bitcoin::hashes::hex::Error, Hex, EsploraError);
|
impl_error!(bitcoin::hashes::hex::Error, Hex, EsploraError);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[cfg(feature = "test-esplora")]
|
||||||
|
crate::bdk_blockchain_tests! {
|
||||||
|
fn test_instance(test_client: &TestClient) -> EsploraBlockchain {
|
||||||
|
EsploraBlockchain::new(&format!("http://{}",test_client.electrsd.esplora_url.as_ref().unwrap()), 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,19 +106,19 @@ impl Blockchain for EsploraBlockchain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
Ok(self.url_client._get_tx(txid).await?)
|
Ok(await_or_block!(self.url_client._get_tx(txid))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||||
Ok(self.url_client._broadcast(tx).await?)
|
Ok(await_or_block!(self.url_client._broadcast(tx))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_height(&self) -> Result<u32, Error> {
|
fn get_height(&self) -> Result<u32, Error> {
|
||||||
Ok(self.url_client._get_height().await?)
|
Ok(await_or_block!(self.url_client._get_height())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||||
let estimates = self.url_client._get_fee_estimates().await?;
|
let estimates = await_or_block!(self.url_client._get_fee_estimates())?;
|
||||||
super::into_fee_rate(target, estimates)
|
super::into_fee_rate(target, estimates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,10 +287,10 @@ 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>> = await_or_block!(futs.try_collect())?;
|
||||||
results.extend(partial_results);
|
results.extend(partial_results);
|
||||||
}
|
}
|
||||||
Ok(stream::iter(results).collect().await)
|
Ok(await_or_block!(stream::iter(results).collect()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn els_batch_transaction_get<'s, I: IntoIterator<Item = &'s Txid>>(
|
fn els_batch_transaction_get<'s, I: IntoIterator<Item = &'s Txid>>(
|
||||||
@@ -303,10 +303,10 @@ impl ElectrumLikeSync for UrlClient {
|
|||||||
for txid in chunk {
|
for txid in chunk {
|
||||||
futs.push(self._get_tx_no_opt(txid));
|
futs.push(self._get_tx_no_opt(txid));
|
||||||
}
|
}
|
||||||
let partial_results: Vec<Transaction> = futs.try_collect().await?;
|
let partial_results: Vec<Transaction> = await_or_block!(futs.try_collect())?;
|
||||||
results.extend(partial_results);
|
results.extend(partial_results);
|
||||||
}
|
}
|
||||||
Ok(stream::iter(results).collect().await)
|
Ok(await_or_block!(stream::iter(results).collect()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn els_batch_block_header<I: IntoIterator<Item = u32>>(
|
fn els_batch_block_header<I: IntoIterator<Item = u32>>(
|
||||||
@@ -319,10 +319,10 @@ impl ElectrumLikeSync for UrlClient {
|
|||||||
for height in chunk {
|
for height in chunk {
|
||||||
futs.push(self._get_header(height));
|
futs.push(self._get_header(height));
|
||||||
}
|
}
|
||||||
let partial_results: Vec<BlockHeader> = futs.try_collect().await?;
|
let partial_results: Vec<BlockHeader> = await_or_block!(futs.try_collect())?;
|
||||||
results.extend(partial_results);
|
results.extend(partial_results);
|
||||||
}
|
}
|
||||||
Ok(stream::iter(results).collect().await)
|
Ok(await_or_block!(stream::iter(results).collect()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +333,17 @@ pub struct EsploraBlockchainConfig {
|
|||||||
///
|
///
|
||||||
/// eg. `https://blockstream.info/api/`
|
/// eg. `https://blockstream.info/api/`
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
|
/// Optional URL of the proxy to use to make requests to the Esplora server
|
||||||
|
///
|
||||||
|
/// The string should be formatted as: `<protocol>://<user>:<password>@host:<port>`.
|
||||||
|
///
|
||||||
|
/// Note that the format of this value and the supported protocols change slightly between the
|
||||||
|
/// sync version of esplora (using `ureq`) and the async version (using `reqwest`). For more
|
||||||
|
/// details check with the documentation of the two crates. Both of them are compiled with
|
||||||
|
/// the `socks` feature enabled.
|
||||||
|
///
|
||||||
|
/// The proxy is ignored when targeting `wasm32`.
|
||||||
|
pub proxy: Option<String>,
|
||||||
/// Number of parallel requests sent to the esplora service (default: 4)
|
/// Number of parallel requests sent to the esplora service (default: 4)
|
||||||
pub concurrency: Option<u8>,
|
pub concurrency: Option<u8>,
|
||||||
/// Stop searching addresses for transactions after finding an unused gap of this length.
|
/// Stop searching addresses for transactions after finding an unused gap of this length.
|
||||||
@@ -343,18 +354,19 @@ impl ConfigurableBlockchain for EsploraBlockchain {
|
|||||||
type Config = EsploraBlockchainConfig;
|
type Config = EsploraBlockchainConfig;
|
||||||
|
|
||||||
fn from_config(config: &Self::Config) -> Result<Self, Error> {
|
fn from_config(config: &Self::Config) -> Result<Self, Error> {
|
||||||
|
let map_e = |e: reqwest::Error| Error::Esplora(Box::new(e.into()));
|
||||||
|
|
||||||
let mut blockchain = EsploraBlockchain::new(config.base_url.as_str(), config.stop_gap);
|
let mut blockchain = EsploraBlockchain::new(config.base_url.as_str(), config.stop_gap);
|
||||||
if let Some(concurrency) = config.concurrency {
|
if let Some(concurrency) = config.concurrency {
|
||||||
blockchain.url_client.concurrency = concurrency;
|
blockchain.url_client.concurrency = concurrency;
|
||||||
};
|
}
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
if let Some(proxy) = &config.proxy {
|
||||||
|
blockchain.url_client.client = Client::builder()
|
||||||
|
.proxy(reqwest::Proxy::all(proxy).map_err(map_e)?)
|
||||||
|
.build()
|
||||||
|
.map_err(map_e)?;
|
||||||
|
}
|
||||||
Ok(blockchain)
|
Ok(blockchain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[cfg(feature = "test-esplora")]
|
|
||||||
crate::bdk_blockchain_tests! {
|
|
||||||
fn test_instance(test_client: &TestClient) -> EsploraBlockchain {
|
|
||||||
EsploraBlockchain::new(&format!("http://{}",test_client.electrsd.esplora_url.as_ref().unwrap()), None, 20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use std::time::Duration;
|
|||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
|
|
||||||
use ureq::{Agent, Response};
|
use ureq::{Agent, Proxy, Response};
|
||||||
|
|
||||||
use bitcoin::consensus::{deserialize, serialize};
|
use bitcoin::consensus::{deserialize, serialize};
|
||||||
use bitcoin::hashes::hex::{FromHex, ToHex};
|
use bitcoin::hashes::hex::{FromHex, ToHex};
|
||||||
@@ -59,7 +59,7 @@ impl std::convert::From<UrlClient> for EsploraBlockchain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EsploraBlockchain {
|
impl EsploraBlockchain {
|
||||||
/// Create a new instance of the client from a base URL and `stop_gap`.
|
/// Create a new instance of the client from a base URL and the `stop_gap`.
|
||||||
pub fn new(base_url: &str, stop_gap: usize) -> Self {
|
pub fn new(base_url: &str, stop_gap: usize) -> Self {
|
||||||
EsploraBlockchain {
|
EsploraBlockchain {
|
||||||
url_client: UrlClient {
|
url_client: UrlClient {
|
||||||
@@ -358,6 +358,17 @@ impl ElectrumLikeSync for UrlClient {
|
|||||||
pub struct EsploraBlockchainConfig {
|
pub struct EsploraBlockchainConfig {
|
||||||
/// Base URL of the esplora service eg. `https://blockstream.info/api/`
|
/// Base URL of the esplora service eg. `https://blockstream.info/api/`
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
|
/// Optional URL of the proxy to use to make requests to the Esplora server
|
||||||
|
///
|
||||||
|
/// The string should be formatted as: `<protocol>://<user>:<password>@host:<port>`.
|
||||||
|
///
|
||||||
|
/// Note that the format of this value and the supported protocols change slightly between the
|
||||||
|
/// sync version of esplora (using `ureq`) and the async version (using `reqwest`). For more
|
||||||
|
/// details check with the documentation of the two crates. Both of them are compiled with
|
||||||
|
/// the `socks` feature enabled.
|
||||||
|
///
|
||||||
|
/// The proxy is ignored when targeting `wasm32`.
|
||||||
|
pub proxy: Option<String>,
|
||||||
/// Socket read timeout.
|
/// Socket read timeout.
|
||||||
pub timeout_read: u64,
|
pub timeout_read: u64,
|
||||||
/// Socket write timeout.
|
/// Socket write timeout.
|
||||||
@@ -370,10 +381,18 @@ impl ConfigurableBlockchain for EsploraBlockchain {
|
|||||||
type Config = EsploraBlockchainConfig;
|
type Config = EsploraBlockchainConfig;
|
||||||
|
|
||||||
fn from_config(config: &Self::Config) -> Result<Self, Error> {
|
fn from_config(config: &Self::Config) -> Result<Self, Error> {
|
||||||
let agent: Agent = ureq::AgentBuilder::new()
|
let mut agent_builder = ureq::AgentBuilder::new()
|
||||||
.timeout_read(Duration::from_secs(config.timeout_read))
|
.timeout_read(Duration::from_secs(config.timeout_read))
|
||||||
.timeout_write(Duration::from_secs(config.timeout_write))
|
.timeout_write(Duration::from_secs(config.timeout_write));
|
||||||
.build();
|
|
||||||
Ok(EsploraBlockchain::new(config.base_url.as_str(), config.stop_gap).with_agent(agent))
|
if let Some(proxy) = &config.proxy {
|
||||||
|
agent_builder = agent_builder
|
||||||
|
.proxy(Proxy::new(proxy).map_err(|e| Error::Esplora(Box::new(e.into())))?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
EsploraBlockchain::new(config.base_url.as_str(), config.stop_gap)
|
||||||
|
.with_agent(agent_builder.build()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ impl Progress for NoopProgress {
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct LogProgress;
|
pub struct LogProgress;
|
||||||
|
|
||||||
/// Create a nwe instance of [`LogProgress`]
|
/// Create a new instance of [`LogProgress`]
|
||||||
pub fn log_progress() -> LogProgress {
|
pub fn log_progress() -> LogProgress {
|
||||||
LogProgress
|
LogProgress
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress
|
|||||||
use crate::database::{BatchDatabase, DatabaseUtils};
|
use crate::database::{BatchDatabase, DatabaseUtils};
|
||||||
use crate::descriptor::{get_checksum, IntoWalletDescriptor};
|
use crate::descriptor::{get_checksum, IntoWalletDescriptor};
|
||||||
use crate::wallet::utils::SecpCtx;
|
use crate::wallet::utils::SecpCtx;
|
||||||
use crate::{ConfirmationTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
|
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
|
||||||
use bitcoincore_rpc::json::{
|
use bitcoincore_rpc::json::{
|
||||||
GetAddressInfoResultLabel, ImportMultiOptions, ImportMultiRequest,
|
GetAddressInfoResultLabel, ImportMultiOptions, ImportMultiRequest,
|
||||||
ImportMultiRequestScriptPubkey, ImportMultiRescanSince,
|
ImportMultiRequestScriptPubkey, ImportMultiRescanSince,
|
||||||
@@ -84,7 +84,7 @@ pub struct RpcConfig {
|
|||||||
|
|
||||||
/// This struct is equivalent to [bitcoincore_rpc::Auth] but it implements [serde::Serialize]
|
/// This struct is equivalent to [bitcoincore_rpc::Auth] but it implements [serde::Serialize]
|
||||||
/// To be removed once upstream equivalent is implementing Serialize (json serialization format
|
/// To be removed once upstream equivalent is implementing Serialize (json serialization format
|
||||||
/// should be the same) https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/181
|
/// should be the same), see [rust-bitcoincore-rpc/pull/181](https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/181)
|
||||||
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
@@ -156,7 +156,7 @@ impl Blockchain for RpcBlockchain {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|s| ImportMultiRequest {
|
.map(|s| ImportMultiRequest {
|
||||||
timestamp: ImportMultiRescanSince::Timestamp(0),
|
timestamp: ImportMultiRescanSince::Timestamp(0),
|
||||||
script_pubkey: Some(ImportMultiRequestScriptPubkey::Script(&s)),
|
script_pubkey: Some(ImportMultiRequestScriptPubkey::Script(s)),
|
||||||
watchonly: Some(true),
|
watchonly: Some(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
@@ -169,22 +169,25 @@ impl Blockchain for RpcBlockchain {
|
|||||||
//TODO maybe convenient using import_descriptor for compatible descriptor and import_multi as fallback
|
//TODO maybe convenient using import_descriptor for compatible descriptor and import_multi as fallback
|
||||||
self.client.import_multi(&requests, Some(&options))?;
|
self.client.import_multi(&requests, Some(&options))?;
|
||||||
|
|
||||||
let current_height = self.get_height()?;
|
loop {
|
||||||
|
let current_height = self.get_height()?;
|
||||||
|
|
||||||
// min because block invalidate may cause height to go down
|
// min because block invalidate may cause height to go down
|
||||||
let node_synced = self.get_node_synced_height()?.min(current_height);
|
let node_synced = self.get_node_synced_height()?.min(current_height);
|
||||||
|
|
||||||
//TODO call rescan in chunks (updating node_synced_height) so that in case of
|
let sync_up_to = node_synced.saturating_add(10_000).min(current_height);
|
||||||
// interruption work can be partially recovered
|
|
||||||
debug!(
|
|
||||||
"rescan_blockchain from:{} to:{}",
|
|
||||||
node_synced, current_height
|
|
||||||
);
|
|
||||||
self.client
|
|
||||||
.rescan_blockchain(Some(node_synced as usize), Some(current_height as usize))?;
|
|
||||||
progress_update.update(1.0, None)?;
|
|
||||||
|
|
||||||
self.set_node_synced_height(current_height)?;
|
debug!("rescan_blockchain from:{} to:{}", node_synced, sync_up_to);
|
||||||
|
self.client
|
||||||
|
.rescan_blockchain(Some(node_synced as usize), Some(sync_up_to as usize))?;
|
||||||
|
progress_update.update((sync_up_to as f32) / (current_height as f32), None)?;
|
||||||
|
|
||||||
|
self.set_node_synced_height(sync_up_to)?;
|
||||||
|
|
||||||
|
if sync_up_to == current_height {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.sync(database, progress_update)
|
self.sync(database, progress_update)
|
||||||
}
|
}
|
||||||
@@ -227,7 +230,7 @@ impl Blockchain for RpcBlockchain {
|
|||||||
list_txs_ids.insert(txid);
|
list_txs_ids.insert(txid);
|
||||||
if let Some(mut known_tx) = known_txs.get_mut(&txid) {
|
if let Some(mut known_tx) = known_txs.get_mut(&txid) {
|
||||||
let confirmation_time =
|
let confirmation_time =
|
||||||
ConfirmationTime::new(tx_result.info.blockheight, tx_result.info.blocktime);
|
BlockTime::new(tx_result.info.blockheight, tx_result.info.blocktime);
|
||||||
if confirmation_time != known_tx.confirmation_time {
|
if confirmation_time != known_tx.confirmation_time {
|
||||||
// reorg may change tx height
|
// reorg may change tx height
|
||||||
debug!(
|
debug!(
|
||||||
@@ -235,7 +238,7 @@ impl Blockchain for RpcBlockchain {
|
|||||||
txid, confirmation_time
|
txid, confirmation_time
|
||||||
);
|
);
|
||||||
known_tx.confirmation_time = confirmation_time;
|
known_tx.confirmation_time = confirmation_time;
|
||||||
db.set_tx(&known_tx)?;
|
db.set_tx(known_tx)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO check there is already the raw tx in db?
|
//TODO check there is already the raw tx in db?
|
||||||
@@ -263,7 +266,7 @@ impl Blockchain for RpcBlockchain {
|
|||||||
let td = TransactionDetails {
|
let td = TransactionDetails {
|
||||||
transaction: Some(tx),
|
transaction: Some(tx),
|
||||||
txid: tx_result.info.txid,
|
txid: tx_result.info.txid,
|
||||||
confirmation_time: ConfirmationTime::new(
|
confirmation_time: BlockTime::new(
|
||||||
tx_result.info.blockheight,
|
tx_result.info.blockheight,
|
||||||
tx_result.info.blocktime,
|
tx_result.info.blocktime,
|
||||||
),
|
),
|
||||||
@@ -357,7 +360,7 @@ impl ConfigurableBlockchain for RpcBlockchain {
|
|||||||
let wallet_url = format!("{}/wallet/{}", config.url, &wallet_name);
|
let wallet_url = format!("{}/wallet/{}", config.url, &wallet_name);
|
||||||
debug!("connecting to {} auth:{:?}", wallet_url, config.auth);
|
debug!("connecting to {} auth:{:?}", wallet_url, config.auth);
|
||||||
|
|
||||||
let client = Client::new(wallet_url, config.auth.clone().into())?;
|
let client = Client::new(wallet_url.as_str(), config.auth.clone().into())?;
|
||||||
let loaded_wallets = client.list_wallets()?;
|
let loaded_wallets = client.list_wallets()?;
|
||||||
if loaded_wallets.contains(&wallet_name) {
|
if loaded_wallets.contains(&wallet_name) {
|
||||||
debug!("wallet already loaded {:?}", wallet_name);
|
debug!("wallet already loaded {:?}", wallet_name);
|
||||||
@@ -424,13 +427,13 @@ where
|
|||||||
{
|
{
|
||||||
//TODO check descriptors contains only public keys
|
//TODO check descriptors contains only public keys
|
||||||
let descriptor = descriptor
|
let descriptor = descriptor
|
||||||
.into_wallet_descriptor(&secp, network)?
|
.into_wallet_descriptor(secp, network)?
|
||||||
.0
|
.0
|
||||||
.to_string();
|
.to_string();
|
||||||
let mut wallet_name = get_checksum(&descriptor[..descriptor.find('#').unwrap()])?;
|
let mut wallet_name = get_checksum(&descriptor[..descriptor.find('#').unwrap()])?;
|
||||||
if let Some(change_descriptor) = change_descriptor {
|
if let Some(change_descriptor) = change_descriptor {
|
||||||
let change_descriptor = change_descriptor
|
let change_descriptor = change_descriptor
|
||||||
.into_wallet_descriptor(&secp, network)?
|
.into_wallet_descriptor(secp, network)?
|
||||||
.0
|
.0
|
||||||
.to_string();
|
.to_string();
|
||||||
wallet_name.push_str(
|
wallet_name.push_str(
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ 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::{ConfirmationTime, KeychainKind, LocalUtxo, TransactionDetails};
|
use crate::types::{BlockTime, KeychainKind, LocalUtxo, TransactionDetails};
|
||||||
use crate::wallet::time::Instant;
|
use crate::wallet::time::Instant;
|
||||||
use crate::wallet::utils::ChunksIterator;
|
use crate::wallet::utils::ChunksIterator;
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ pub trait ElectrumLikeSync {
|
|||||||
// check if tx height matches, otherwise updates it. timestamp is not in the if clause
|
// check if tx height matches, otherwise updates it. timestamp is not in the if clause
|
||||||
// because we are not asking headers for confirmed tx we know about
|
// because we are not asking headers for confirmed tx we know about
|
||||||
if tx_details.confirmation_time.as_ref().map(|c| c.height) != height {
|
if tx_details.confirmation_time.as_ref().map(|c| c.height) != height {
|
||||||
let confirmation_time = ConfirmationTime::new(height, timestamp);
|
let confirmation_time = BlockTime::new(height, timestamp);
|
||||||
let mut new_tx_details = tx_details.clone();
|
let mut new_tx_details = tx_details.clone();
|
||||||
new_tx_details.confirmation_time = confirmation_time;
|
new_tx_details.confirmation_time = confirmation_time;
|
||||||
batch.set_tx(&new_tx_details)?;
|
batch.set_tx(&new_tx_details)?;
|
||||||
@@ -359,7 +359,7 @@ fn save_transaction_details_and_utxos<D: BatchDatabase>(
|
|||||||
transaction: Some(tx),
|
transaction: Some(tx),
|
||||||
received: incoming,
|
received: incoming,
|
||||||
sent: outgoing,
|
sent: outgoing,
|
||||||
confirmation_time: ConfirmationTime::new(height, timestamp),
|
confirmation_time: BlockTime::new(height, timestamp),
|
||||||
fee: Some(inputs_sum.saturating_sub(outputs_sum)), /* if the tx is a coinbase, fees would be negative */
|
fee: Some(inputs_sum.saturating_sub(outputs_sum)), /* if the tx is a coinbase, fees would be negative */
|
||||||
verified: height.is_some(),
|
verified: height.is_some(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ macro_rules! impl_inner_method {
|
|||||||
$enum_name::Memory(inner) => inner.$name( $($args, )* ),
|
$enum_name::Memory(inner) => inner.$name( $($args, )* ),
|
||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
$enum_name::Sled(inner) => inner.$name( $($args, )* ),
|
$enum_name::Sled(inner) => inner.$name( $($args, )* ),
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
$enum_name::Sqlite(inner) => inner.$name( $($args, )* ),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,10 +84,15 @@ pub enum AnyDatabase {
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "key-value-db")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "key-value-db")))]
|
||||||
/// Simple key-value embedded database based on [`sled`]
|
/// Simple key-value embedded database based on [`sled`]
|
||||||
Sled(sled::Tree),
|
Sled(sled::Tree),
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
|
||||||
|
/// Sqlite embedded database using [`rusqlite`]
|
||||||
|
Sqlite(sqlite::SqliteDatabase),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from!(memory::MemoryDatabase, AnyDatabase, Memory,);
|
impl_from!(memory::MemoryDatabase, AnyDatabase, Memory,);
|
||||||
impl_from!(sled::Tree, AnyDatabase, Sled, #[cfg(feature = "key-value-db")]);
|
impl_from!(sled::Tree, AnyDatabase, Sled, #[cfg(feature = "key-value-db")]);
|
||||||
|
impl_from!(sqlite::SqliteDatabase, AnyDatabase, Sqlite, #[cfg(feature = "sqlite")]);
|
||||||
|
|
||||||
/// Type that contains any of the [`BatchDatabase::Batch`] types defined by the library
|
/// Type that contains any of the [`BatchDatabase::Batch`] types defined by the library
|
||||||
pub enum AnyBatch {
|
pub enum AnyBatch {
|
||||||
@@ -95,6 +102,10 @@ pub enum AnyBatch {
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "key-value-db")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "key-value-db")))]
|
||||||
/// Simple key-value embedded database based on [`sled`]
|
/// Simple key-value embedded database based on [`sled`]
|
||||||
Sled(<sled::Tree as BatchDatabase>::Batch),
|
Sled(<sled::Tree as BatchDatabase>::Batch),
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
|
||||||
|
/// Sqlite embedded database using [`rusqlite`]
|
||||||
|
Sqlite(<sqlite::SqliteDatabase as BatchDatabase>::Batch),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from!(
|
impl_from!(
|
||||||
@@ -103,6 +114,7 @@ impl_from!(
|
|||||||
Memory,
|
Memory,
|
||||||
);
|
);
|
||||||
impl_from!(<sled::Tree as BatchDatabase>::Batch, AnyBatch, Sled, #[cfg(feature = "key-value-db")]);
|
impl_from!(<sled::Tree as BatchDatabase>::Batch, AnyBatch, Sled, #[cfg(feature = "key-value-db")]);
|
||||||
|
impl_from!(<sqlite::SqliteDatabase as BatchDatabase>::Batch, AnyBatch, Sqlite, #[cfg(feature = "sqlite")]);
|
||||||
|
|
||||||
impl BatchOperations for AnyDatabase {
|
impl BatchOperations for AnyDatabase {
|
||||||
fn set_script_pubkey(
|
fn set_script_pubkey(
|
||||||
@@ -132,6 +144,9 @@ impl BatchOperations for AnyDatabase {
|
|||||||
fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
|
fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
|
||||||
impl_inner_method!(AnyDatabase, self, set_last_index, keychain, value)
|
impl_inner_method!(AnyDatabase, self, set_last_index, keychain, value)
|
||||||
}
|
}
|
||||||
|
fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error> {
|
||||||
|
impl_inner_method!(AnyDatabase, self, set_sync_time, sync_time)
|
||||||
|
}
|
||||||
|
|
||||||
fn del_script_pubkey_from_path(
|
fn del_script_pubkey_from_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -168,6 +183,9 @@ impl BatchOperations for AnyDatabase {
|
|||||||
fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
|
fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
|
||||||
impl_inner_method!(AnyDatabase, self, del_last_index, keychain)
|
impl_inner_method!(AnyDatabase, self, del_last_index, keychain)
|
||||||
}
|
}
|
||||||
|
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
|
||||||
|
impl_inner_method!(AnyDatabase, self, del_sync_time)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database for AnyDatabase {
|
impl Database for AnyDatabase {
|
||||||
@@ -229,10 +247,17 @@ impl Database for AnyDatabase {
|
|||||||
fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
|
fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
|
||||||
impl_inner_method!(AnyDatabase, self, get_last_index, keychain)
|
impl_inner_method!(AnyDatabase, self, get_last_index, keychain)
|
||||||
}
|
}
|
||||||
|
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
|
||||||
|
impl_inner_method!(AnyDatabase, self, get_sync_time)
|
||||||
|
}
|
||||||
|
|
||||||
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
|
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
|
||||||
impl_inner_method!(AnyDatabase, self, increment_last_index, keychain)
|
impl_inner_method!(AnyDatabase, self, increment_last_index, keychain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), Error> {
|
||||||
|
impl_inner_method!(AnyDatabase, self, flush)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BatchOperations for AnyBatch {
|
impl BatchOperations for AnyBatch {
|
||||||
@@ -256,6 +281,9 @@ impl BatchOperations for AnyBatch {
|
|||||||
fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
|
fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
|
||||||
impl_inner_method!(AnyBatch, self, set_last_index, keychain, value)
|
impl_inner_method!(AnyBatch, self, set_last_index, keychain, value)
|
||||||
}
|
}
|
||||||
|
fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error> {
|
||||||
|
impl_inner_method!(AnyBatch, self, set_sync_time, sync_time)
|
||||||
|
}
|
||||||
|
|
||||||
fn del_script_pubkey_from_path(
|
fn del_script_pubkey_from_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -286,6 +314,9 @@ impl BatchOperations for AnyBatch {
|
|||||||
fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
|
fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
|
||||||
impl_inner_method!(AnyBatch, self, del_last_index, keychain)
|
impl_inner_method!(AnyBatch, self, del_last_index, keychain)
|
||||||
}
|
}
|
||||||
|
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
|
||||||
|
impl_inner_method!(AnyBatch, self, del_sync_time)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BatchDatabase for AnyDatabase {
|
impl BatchDatabase for AnyDatabase {
|
||||||
@@ -296,19 +327,26 @@ impl BatchDatabase for AnyDatabase {
|
|||||||
AnyDatabase::Memory(inner) => inner.begin_batch().into(),
|
AnyDatabase::Memory(inner) => inner.begin_batch().into(),
|
||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
AnyDatabase::Sled(inner) => inner.begin_batch().into(),
|
AnyDatabase::Sled(inner) => inner.begin_batch().into(),
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
AnyDatabase::Sqlite(inner) => inner.begin_batch().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> {
|
fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> {
|
||||||
match self {
|
match self {
|
||||||
AnyDatabase::Memory(db) => match batch {
|
AnyDatabase::Memory(db) => match batch {
|
||||||
AnyBatch::Memory(batch) => db.commit_batch(batch),
|
AnyBatch::Memory(batch) => db.commit_batch(batch),
|
||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(any(feature = "key-value-db", feature = "sqlite"))]
|
||||||
_ => unimplemented!("Sled batch shouldn't be used with Memory db."),
|
_ => unimplemented!("Other batch shouldn't be used with Memory db."),
|
||||||
},
|
},
|
||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
AnyDatabase::Sled(db) => match batch {
|
AnyDatabase::Sled(db) => match batch {
|
||||||
AnyBatch::Sled(batch) => db.commit_batch(batch),
|
AnyBatch::Sled(batch) => db.commit_batch(batch),
|
||||||
_ => unimplemented!("Memory batch shouldn't be used with Sled db."),
|
_ => unimplemented!("Other batch shouldn't be used with Sled db."),
|
||||||
|
},
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
AnyDatabase::Sqlite(db) => match batch {
|
||||||
|
AnyBatch::Sqlite(batch) => db.commit_batch(batch),
|
||||||
|
_ => unimplemented!("Other batch shouldn't be used with Sqlite db."),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,6 +371,23 @@ impl ConfigurableDatabase for sled::Tree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration type for a [`sqlite::SqliteDatabase`] database
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct SqliteDbConfiguration {
|
||||||
|
/// Main directory of the db
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
impl ConfigurableDatabase for sqlite::SqliteDatabase {
|
||||||
|
type Config = SqliteDbConfiguration;
|
||||||
|
|
||||||
|
fn from_config(config: &Self::Config) -> Result<Self, Error> {
|
||||||
|
Ok(sqlite::SqliteDatabase::new(config.path.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Type that can contain any of the database configurations defined by the library
|
/// Type that can contain any of the database configurations defined by the library
|
||||||
///
|
///
|
||||||
/// This allows storing a single configuration that can be loaded into an [`AnyDatabase`]
|
/// This allows storing a single configuration that can be loaded into an [`AnyDatabase`]
|
||||||
@@ -346,6 +401,10 @@ pub enum AnyDatabaseConfig {
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "key-value-db")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "key-value-db")))]
|
||||||
/// Simple key-value embedded database based on [`sled`]
|
/// Simple key-value embedded database based on [`sled`]
|
||||||
Sled(SledDbConfiguration),
|
Sled(SledDbConfiguration),
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
|
||||||
|
/// Sqlite embedded database using [`rusqlite`]
|
||||||
|
Sqlite(SqliteDbConfiguration),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurableDatabase for AnyDatabase {
|
impl ConfigurableDatabase for AnyDatabase {
|
||||||
@@ -358,9 +417,14 @@ impl ConfigurableDatabase for AnyDatabase {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
AnyDatabaseConfig::Sled(inner) => AnyDatabase::Sled(sled::Tree::from_config(inner)?),
|
AnyDatabaseConfig::Sled(inner) => AnyDatabase::Sled(sled::Tree::from_config(inner)?),
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
AnyDatabaseConfig::Sqlite(inner) => {
|
||||||
|
AnyDatabase::Sqlite(sqlite::SqliteDatabase::from_config(inner)?)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from!((), AnyDatabaseConfig, Memory,);
|
impl_from!((), AnyDatabaseConfig, Memory,);
|
||||||
impl_from!(SledDbConfiguration, AnyDatabaseConfig, Sled, #[cfg(feature = "key-value-db")]);
|
impl_from!(SledDbConfiguration, AnyDatabaseConfig, Sled, #[cfg(feature = "key-value-db")]);
|
||||||
|
impl_from!(SqliteDbConfiguration, AnyDatabaseConfig, Sqlite, #[cfg(feature = "sqlite")]);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use bitcoin::hash_types::Txid;
|
|||||||
use bitcoin::{OutPoint, Script, Transaction};
|
use bitcoin::{OutPoint, Script, Transaction};
|
||||||
|
|
||||||
use crate::database::memory::MapKey;
|
use crate::database::memory::MapKey;
|
||||||
use crate::database::{BatchDatabase, BatchOperations, Database};
|
use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
@@ -82,6 +82,13 @@ macro_rules! impl_batch_operations {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_sync_time(&mut self, data: SyncTime) -> Result<(), Error> {
|
||||||
|
let key = MapKey::SyncTime.as_map_key();
|
||||||
|
self.insert(key, serde_json::to_vec(&data)?)$($after_insert)*;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn del_script_pubkey_from_path(&mut self, keychain: KeychainKind, path: u32) -> Result<Option<Script>, Error> {
|
fn del_script_pubkey_from_path(&mut self, keychain: KeychainKind, path: u32) -> Result<Option<Script>, Error> {
|
||||||
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
|
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
|
||||||
let res = self.remove(key);
|
let res = self.remove(key);
|
||||||
@@ -168,6 +175,14 @@ macro_rules! impl_batch_operations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
|
||||||
|
let key = MapKey::SyncTime.as_map_key();
|
||||||
|
let res = self.remove(key);
|
||||||
|
let res = $process_delete!(res);
|
||||||
|
|
||||||
|
Ok(res.map(|b| serde_json::from_slice(&b)).transpose()?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,6 +357,14 @@ impl Database for Tree {
|
|||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
|
||||||
|
let key = MapKey::SyncTime.as_map_key();
|
||||||
|
Ok(self
|
||||||
|
.get(key)?
|
||||||
|
.map(|b| serde_json::from_slice(&b))
|
||||||
|
.transpose()?)
|
||||||
|
}
|
||||||
|
|
||||||
// inserts 0 if not present
|
// inserts 0 if not present
|
||||||
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
|
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
|
||||||
let key = MapKey::LastIndex(keychain).as_map_key();
|
let key = MapKey::LastIndex(keychain).as_map_key();
|
||||||
@@ -367,6 +390,10 @@ impl Database for Tree {
|
|||||||
Ok(val)
|
Ok(val)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), Error> {
|
||||||
|
Ok(Tree::flush(self).map(|_| ())?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BatchDatabase for Tree {
|
impl BatchDatabase for Tree {
|
||||||
@@ -466,4 +493,9 @@ mod test {
|
|||||||
fn test_last_index() {
|
fn test_last_index() {
|
||||||
crate::database::test::test_last_index(get_tree());
|
crate::database::test::test_last_index(get_tree());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sync_time() {
|
||||||
|
crate::database::test::test_sync_time(get_tree());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
//! This module defines an in-memory database type called [`MemoryDatabase`] that is based on a
|
//! This module defines an in-memory database type called [`MemoryDatabase`] that is based on a
|
||||||
//! [`BTreeMap`].
|
//! [`BTreeMap`].
|
||||||
|
|
||||||
|
use std::any::Any;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ops::Bound::{Excluded, Included};
|
use std::ops::Bound::{Excluded, Included};
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ use bitcoin::consensus::encode::{deserialize, serialize};
|
|||||||
use bitcoin::hash_types::Txid;
|
use bitcoin::hash_types::Txid;
|
||||||
use bitcoin::{OutPoint, Script, Transaction};
|
use bitcoin::{OutPoint, Script, Transaction};
|
||||||
|
|
||||||
use crate::database::{BatchDatabase, BatchOperations, ConfigurableDatabase, Database};
|
use crate::database::{BatchDatabase, BatchOperations, ConfigurableDatabase, Database, SyncTime};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ use crate::types::*;
|
|||||||
// transactions t<txid> -> tx details
|
// transactions t<txid> -> tx details
|
||||||
// deriv indexes c{i,e} -> u32
|
// deriv indexes c{i,e} -> u32
|
||||||
// descriptor checksum d{i,e} -> vec<u8>
|
// descriptor checksum d{i,e} -> vec<u8>
|
||||||
|
// last sync time l -> { height, timestamp }
|
||||||
|
|
||||||
pub(crate) enum MapKey<'a> {
|
pub(crate) enum MapKey<'a> {
|
||||||
Path((Option<KeychainKind>, Option<u32>)),
|
Path((Option<KeychainKind>, Option<u32>)),
|
||||||
@@ -40,6 +42,7 @@ pub(crate) enum MapKey<'a> {
|
|||||||
RawTx(Option<&'a Txid>),
|
RawTx(Option<&'a Txid>),
|
||||||
Transaction(Option<&'a Txid>),
|
Transaction(Option<&'a Txid>),
|
||||||
LastIndex(KeychainKind),
|
LastIndex(KeychainKind),
|
||||||
|
SyncTime,
|
||||||
DescriptorChecksum(KeychainKind),
|
DescriptorChecksum(KeychainKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +61,7 @@ impl MapKey<'_> {
|
|||||||
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(),
|
||||||
|
MapKey::SyncTime => b"l".to_vec(),
|
||||||
MapKey::DescriptorChecksum(st) => [b"d", st.as_ref()].concat(),
|
MapKey::DescriptorChecksum(st) => [b"d", st.as_ref()].concat(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +114,7 @@ fn after(key: &[u8]) -> Vec<u8> {
|
|||||||
/// [`database`]: crate::database
|
/// [`database`]: crate::database
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct MemoryDatabase {
|
pub struct MemoryDatabase {
|
||||||
map: BTreeMap<Vec<u8>, Box<dyn std::any::Any>>,
|
map: BTreeMap<Vec<u8>, Box<dyn Any + Send + Sync>>,
|
||||||
deleted_keys: Vec<Vec<u8>>,
|
deleted_keys: Vec<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +183,12 @@ impl BatchOperations for MemoryDatabase {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn set_sync_time(&mut self, data: SyncTime) -> Result<(), Error> {
|
||||||
|
let key = MapKey::SyncTime.as_map_key();
|
||||||
|
self.map.insert(key, Box::new(data));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn del_script_pubkey_from_path(
|
fn del_script_pubkey_from_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -269,6 +279,13 @@ impl BatchOperations for MemoryDatabase {
|
|||||||
Some(b) => Ok(Some(*b.downcast_ref().unwrap())),
|
Some(b) => Ok(Some(*b.downcast_ref().unwrap())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
|
||||||
|
let key = MapKey::SyncTime.as_map_key();
|
||||||
|
let res = self.map.remove(&key);
|
||||||
|
self.deleted_keys.push(key);
|
||||||
|
|
||||||
|
Ok(res.map(|b| b.downcast_ref().cloned().unwrap()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database for MemoryDatabase {
|
impl Database for MemoryDatabase {
|
||||||
@@ -406,6 +423,14 @@ impl Database for MemoryDatabase {
|
|||||||
Ok(self.map.get(&key).map(|b| *b.downcast_ref().unwrap()))
|
Ok(self.map.get(&key).map(|b| *b.downcast_ref().unwrap()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
|
||||||
|
let key = MapKey::SyncTime.as_map_key();
|
||||||
|
Ok(self
|
||||||
|
.map
|
||||||
|
.get(&key)
|
||||||
|
.map(|b| b.downcast_ref().cloned().unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
// inserts 0 if not present
|
// inserts 0 if not present
|
||||||
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
|
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
|
||||||
let key = MapKey::LastIndex(keychain).as_map_key();
|
let key = MapKey::LastIndex(keychain).as_map_key();
|
||||||
@@ -419,6 +444,10 @@ impl Database for MemoryDatabase {
|
|||||||
|
|
||||||
Ok(*value)
|
Ok(*value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BatchDatabase for MemoryDatabase {
|
impl BatchDatabase for MemoryDatabase {
|
||||||
@@ -452,20 +481,21 @@ impl ConfigurableDatabase for MemoryDatabase {
|
|||||||
/// don't have `test` set.
|
/// don't have `test` set.
|
||||||
macro_rules! populate_test_db {
|
macro_rules! populate_test_db {
|
||||||
($db:expr, $tx_meta:expr, $current_height:expr$(,)?) => {{
|
($db:expr, $tx_meta:expr, $current_height:expr$(,)?) => {{
|
||||||
|
use std::str::FromStr;
|
||||||
use $crate::database::BatchOperations;
|
use $crate::database::BatchOperations;
|
||||||
let mut db = $db;
|
let mut db = $db;
|
||||||
let tx_meta = $tx_meta;
|
let tx_meta = $tx_meta;
|
||||||
let current_height: Option<u32> = $current_height;
|
let current_height: Option<u32> = $current_height;
|
||||||
let tx = Transaction {
|
let tx = $crate::bitcoin::Transaction {
|
||||||
version: 1,
|
version: 1,
|
||||||
lock_time: 0,
|
lock_time: 0,
|
||||||
input: vec![],
|
input: vec![],
|
||||||
output: tx_meta
|
output: tx_meta
|
||||||
.output
|
.output
|
||||||
.iter()
|
.iter()
|
||||||
.map(|out_meta| bitcoin::TxOut {
|
.map(|out_meta| $crate::bitcoin::TxOut {
|
||||||
value: out_meta.value,
|
value: out_meta.value,
|
||||||
script_pubkey: bitcoin::Address::from_str(&out_meta.to_address)
|
script_pubkey: $crate::bitcoin::Address::from_str(&out_meta.to_address)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.script_pubkey(),
|
.script_pubkey(),
|
||||||
})
|
})
|
||||||
@@ -473,12 +503,12 @@ macro_rules! populate_test_db {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
let confirmation_time = tx_meta.min_confirmations.map(|conf| ConfirmationTime {
|
let confirmation_time = tx_meta.min_confirmations.map(|conf| $crate::BlockTime {
|
||||||
height: current_height.unwrap().checked_sub(conf as u32).unwrap(),
|
height: current_height.unwrap().checked_sub(conf as u32).unwrap(),
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let tx_details = TransactionDetails {
|
let tx_details = $crate::TransactionDetails {
|
||||||
transaction: Some(tx.clone()),
|
transaction: Some(tx.clone()),
|
||||||
txid,
|
txid,
|
||||||
fee: Some(0),
|
fee: Some(0),
|
||||||
@@ -490,13 +520,13 @@ 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(&LocalUtxo {
|
db.set_utxo(&$crate::LocalUtxo {
|
||||||
txout: out.clone(),
|
txout: out.clone(),
|
||||||
outpoint: OutPoint {
|
outpoint: $crate::bitcoin::OutPoint {
|
||||||
txid,
|
txid,
|
||||||
vout: vout as u32,
|
vout: vout as u32,
|
||||||
},
|
},
|
||||||
keychain: KeychainKind::External,
|
keychain: $crate::KeychainKind::External,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -582,4 +612,9 @@ mod test {
|
|||||||
fn test_last_index() {
|
fn test_last_index() {
|
||||||
crate::database::test::test_last_index(get_tree());
|
crate::database::test::test_last_index(get_tree());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sync_time() {
|
||||||
|
crate::database::test::test_sync_time(get_tree());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
//!
|
//!
|
||||||
//! [`Wallet`]: crate::wallet::Wallet
|
//! [`Wallet`]: crate::wallet::Wallet
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use bitcoin::hash_types::Txid;
|
use bitcoin::hash_types::Txid;
|
||||||
use bitcoin::{OutPoint, Script, Transaction, TxOut};
|
use bitcoin::{OutPoint, Script, Transaction, TxOut};
|
||||||
|
|
||||||
@@ -36,9 +38,23 @@ pub use any::{AnyDatabase, AnyDatabaseConfig};
|
|||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
pub(crate) mod keyvalue;
|
pub(crate) mod keyvalue;
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
pub(crate) mod sqlite;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
pub use sqlite::SqliteDatabase;
|
||||||
|
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub use memory::MemoryDatabase;
|
pub use memory::MemoryDatabase;
|
||||||
|
|
||||||
|
/// Blockchain state at the time of syncing
|
||||||
|
///
|
||||||
|
/// Contains only the block time and height at the moment
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct SyncTime {
|
||||||
|
/// Block timestamp and height at the time of sync
|
||||||
|
pub block_time: BlockTime,
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait for operations that can be batched
|
/// Trait for operations that can be batched
|
||||||
///
|
///
|
||||||
/// This trait defines the list of operations that must be implemented on the [`Database`] type and
|
/// This trait defines the list of operations that must be implemented on the [`Database`] type and
|
||||||
@@ -59,6 +75,8 @@ pub trait BatchOperations {
|
|||||||
fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>;
|
fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>;
|
||||||
/// Store the last derivation index for a given keychain.
|
/// Store the last derivation index for a given keychain.
|
||||||
fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error>;
|
fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error>;
|
||||||
|
/// Store the sync time
|
||||||
|
fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Delete a script_pubkey given the keychain and its child number.
|
/// Delete a script_pubkey given the keychain and its child number.
|
||||||
fn del_script_pubkey_from_path(
|
fn del_script_pubkey_from_path(
|
||||||
@@ -84,6 +102,10 @@ pub trait BatchOperations {
|
|||||||
) -> Result<Option<TransactionDetails>, Error>;
|
) -> Result<Option<TransactionDetails>, Error>;
|
||||||
/// Delete the last derivation index for a keychain.
|
/// Delete the last derivation index for a keychain.
|
||||||
fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error>;
|
fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error>;
|
||||||
|
/// Reset the sync time to `None`
|
||||||
|
///
|
||||||
|
/// Returns the removed value
|
||||||
|
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for reading data from a database
|
/// Trait for reading data from a database
|
||||||
@@ -129,11 +151,16 @@ pub trait Database: BatchOperations {
|
|||||||
fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error>;
|
fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error>;
|
||||||
/// Return the last defivation index for a keychain.
|
/// Return the last defivation index for a keychain.
|
||||||
fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error>;
|
fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error>;
|
||||||
|
/// Return the sync time, if present
|
||||||
|
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error>;
|
||||||
|
|
||||||
/// Increment the last derivation index for a keychain and return it
|
/// Increment the last derivation index for a keychain and return it
|
||||||
///
|
///
|
||||||
/// It should insert and return `0` if not present in the database
|
/// It should insert and return `0` if not present in the database
|
||||||
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error>;
|
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error>;
|
||||||
|
|
||||||
|
/// Force changes to be written to disk
|
||||||
|
fn flush(&mut self) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for a database that supports batch operations
|
/// Trait for a database that supports batch operations
|
||||||
@@ -317,7 +344,7 @@ pub mod test {
|
|||||||
received: 1337,
|
received: 1337,
|
||||||
sent: 420420,
|
sent: 420420,
|
||||||
fee: Some(140),
|
fee: Some(140),
|
||||||
confirmation_time: Some(ConfirmationTime {
|
confirmation_time: Some(BlockTime {
|
||||||
timestamp: 123456,
|
timestamp: 123456,
|
||||||
height: 1000,
|
height: 1000,
|
||||||
}),
|
}),
|
||||||
@@ -369,5 +396,25 @@ pub mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn test_sync_time<D: Database>(mut tree: D) {
|
||||||
|
assert!(tree.get_sync_time().unwrap().is_none());
|
||||||
|
|
||||||
|
tree.set_sync_time(SyncTime {
|
||||||
|
block_time: BlockTime {
|
||||||
|
height: 100,
|
||||||
|
timestamp: 1000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let extracted = tree.get_sync_time().unwrap();
|
||||||
|
assert!(extracted.is_some());
|
||||||
|
assert_eq!(extracted.as_ref().unwrap().block_time.height, 100);
|
||||||
|
assert_eq!(extracted.as_ref().unwrap().block_time.timestamp, 1000);
|
||||||
|
|
||||||
|
tree.del_sync_time().unwrap();
|
||||||
|
assert!(tree.get_sync_time().unwrap().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: more tests...
|
// TODO: more tests...
|
||||||
}
|
}
|
||||||
|
|||||||
1033
src/database/sqlite.rs
Normal file
1033
src/database/sqlite.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -571,8 +571,9 @@ macro_rules! fragment {
|
|||||||
( pk ( $key:expr ) ) => ({
|
( pk ( $key:expr ) ) => ({
|
||||||
$crate::fragment!(c:pk_k ( $key ))
|
$crate::fragment!(c:pk_k ( $key ))
|
||||||
});
|
});
|
||||||
( pk_h ( $key_hash:expr ) ) => ({
|
( pk_h ( $key:expr ) ) => ({
|
||||||
$crate::impl_leaf_opcode_value!(PkH, $key_hash)
|
let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
|
||||||
|
$crate::keys::make_pkh($key, &secp)
|
||||||
});
|
});
|
||||||
( after ( $value:expr ) ) => ({
|
( after ( $value:expr ) ) => ({
|
||||||
$crate::impl_leaf_opcode_value!(After, $value)
|
$crate::impl_leaf_opcode_value!(After, $value)
|
||||||
@@ -601,6 +602,9 @@ macro_rules! fragment {
|
|||||||
( and_or ( $( $inner:tt )* ) ) => ({
|
( and_or ( $( $inner:tt )* ) ) => ({
|
||||||
$crate::impl_node_opcode_three!(AndOr, $( $inner )*)
|
$crate::impl_node_opcode_three!(AndOr, $( $inner )*)
|
||||||
});
|
});
|
||||||
|
( andor ( $( $inner:tt )* ) ) => ({
|
||||||
|
$crate::impl_node_opcode_three!(AndOr, $( $inner )*)
|
||||||
|
});
|
||||||
( or_b ( $( $inner:tt )* ) ) => ({
|
( or_b ( $( $inner:tt )* ) ) => ({
|
||||||
$crate::impl_node_opcode_two!(OrB, $( $inner )*)
|
$crate::impl_node_opcode_two!(OrB, $( $inner )*)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,14 +47,12 @@ use bitcoin::util::bip32::Fingerprint;
|
|||||||
use bitcoin::PublicKey;
|
use bitcoin::PublicKey;
|
||||||
|
|
||||||
use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner};
|
use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner};
|
||||||
use miniscript::{
|
use miniscript::{Descriptor, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal};
|
||||||
Descriptor, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal, ToPublicKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
|
|
||||||
use crate::descriptor::{DerivedDescriptorKey, ExtractPolicy};
|
use crate::descriptor::ExtractPolicy;
|
||||||
use crate::wallet::signer::{SignerId, SignersContainer};
|
use crate::wallet::signer::{SignerId, SignersContainer};
|
||||||
use crate::wallet::utils::{self, After, Older, SecpCtx};
|
use crate::wallet::utils::{self, After, Older, SecpCtx};
|
||||||
|
|
||||||
@@ -88,13 +86,6 @@ impl PkOrF {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_key_hash(k: hash160::Hash) -> Self {
|
|
||||||
PkOrF {
|
|
||||||
pubkey_hash: Some(k),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An item that needs to be satisfied
|
/// An item that needs to be satisfied
|
||||||
@@ -779,25 +770,6 @@ fn signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) ->
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature_key(
|
|
||||||
key: &<DescriptorPublicKey as MiniscriptKey>::Hash,
|
|
||||||
signers: &SignersContainer,
|
|
||||||
secp: &SecpCtx,
|
|
||||||
) -> Policy {
|
|
||||||
let key_hash = DerivedDescriptorKey::new(key.clone(), secp)
|
|
||||||
.to_public_key()
|
|
||||||
.to_pubkeyhash();
|
|
||||||
let mut policy: Policy = SatisfiableItem::Signature(PkOrF::from_key_hash(key_hash)).into();
|
|
||||||
|
|
||||||
if signers.find(SignerId::PkHash(key_hash)).is_some() {
|
|
||||||
policy.contribution = Satisfaction::Complete {
|
|
||||||
condition: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
policy
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
|
impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
|
||||||
fn extract_policy(
|
fn extract_policy(
|
||||||
&self,
|
&self,
|
||||||
@@ -809,7 +781,7 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
|
|||||||
// Leaves
|
// Leaves
|
||||||
Terminal::True | Terminal::False => None,
|
Terminal::True | Terminal::False => None,
|
||||||
Terminal::PkK(pubkey) => Some(signature(pubkey, signers, build_sat, secp)),
|
Terminal::PkK(pubkey) => Some(signature(pubkey, signers, build_sat, secp)),
|
||||||
Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, signers, secp)),
|
Terminal::PkH(pubkey_hash) => Some(signature(pubkey_hash, signers, build_sat, secp)),
|
||||||
Terminal::After(value) => {
|
Terminal::After(value) => {
|
||||||
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
|
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
|
||||||
policy.contribution = Satisfaction::Complete {
|
policy.contribution = Satisfaction::Complete {
|
||||||
@@ -1444,6 +1416,7 @@ mod test {
|
|||||||
|
|
||||||
const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
|
const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
|
||||||
const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
|
const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
|
||||||
|
const CAROL_TPRV_STR:&str = "tprv8ZgxMBicQKsPdC3CicFifuLCEyVVdXVUNYorxUWj3iGZ6nimnLAYAY9SYB7ib8rKzRxrCKFcEytCt6szwd2GHnGPRCBLAEAoSVDefSNk4Bt";
|
||||||
const ALICE_BOB_PATH: &str = "m/0'";
|
const ALICE_BOB_PATH: &str = "m/0'";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1602,4 +1575,28 @@ mod test {
|
|||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
|
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_pkh() {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
|
let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||||
|
let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||||
|
let (prvkey_carol, _, _) = setup_keys(CAROL_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||||
|
|
||||||
|
let desc = descriptor!(wsh(c: andor(
|
||||||
|
pk(prvkey_alice),
|
||||||
|
pk_k(prvkey_bob),
|
||||||
|
pk_h(prvkey_carol),
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (wallet_desc, keymap) = desc
|
||||||
|
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||||
|
.unwrap();
|
||||||
|
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||||
|
|
||||||
|
let policy = wallet_desc.extract_policy(&signers_container, BuildSatisfaction::None, &secp);
|
||||||
|
assert!(policy.is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,9 @@ pub enum Error {
|
|||||||
#[cfg(feature = "rpc")]
|
#[cfg(feature = "rpc")]
|
||||||
/// Rpc client error
|
/// Rpc client error
|
||||||
Rpc(bitcoincore_rpc::Error),
|
Rpc(bitcoincore_rpc::Error),
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
/// Rusqlite client error
|
||||||
|
Rusqlite(rusqlite::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
@@ -194,6 +197,8 @@ impl_error!(electrum_client::Error, Electrum);
|
|||||||
impl_error!(sled::Error, Sled);
|
impl_error!(sled::Error, Sled);
|
||||||
#[cfg(feature = "rpc")]
|
#[cfg(feature = "rpc")]
|
||||||
impl_error!(bitcoincore_rpc::Error, Rpc);
|
impl_error!(bitcoincore_rpc::Error, Rpc);
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
impl_error!(rusqlite::Error, Rusqlite);
|
||||||
|
|
||||||
#[cfg(feature = "compact_filters")]
|
#[cfg(feature = "compact_filters")]
|
||||||
impl From<crate::blockchain::compact_filters::CompactFiltersError> for Error {
|
impl From<crate::blockchain::compact_filters::CompactFiltersError> for Error {
|
||||||
|
|||||||
@@ -19,7 +19,23 @@ use bitcoin::Network;
|
|||||||
|
|
||||||
use miniscript::ScriptContext;
|
use miniscript::ScriptContext;
|
||||||
|
|
||||||
pub use bip39::{Language, Mnemonic, MnemonicType, Seed};
|
pub use bip39::{Language, Mnemonic};
|
||||||
|
|
||||||
|
type Seed = [u8; 64];
|
||||||
|
|
||||||
|
/// Type describing entropy length (aka word count) in the mnemonic
|
||||||
|
pub enum WordCount {
|
||||||
|
/// 12 words mnemonic (128 bits entropy)
|
||||||
|
Words12 = 128,
|
||||||
|
/// 15 words mnemonic (160 bits entropy)
|
||||||
|
Words15 = 160,
|
||||||
|
/// 18 words mnemonic (192 bits entropy)
|
||||||
|
Words18 = 192,
|
||||||
|
/// 21 words mnemonic (224 bits entropy)
|
||||||
|
Words21 = 224,
|
||||||
|
/// 24 words mnemonic (256 bits entropy)
|
||||||
|
Words24 = 256,
|
||||||
|
}
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
any_network, DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, KeyError,
|
any_network, DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, KeyError,
|
||||||
@@ -40,7 +56,7 @@ pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
|
||||||
impl<Ctx: ScriptContext> DerivableKey<Ctx> for Seed {
|
impl<Ctx: ScriptContext> DerivableKey<Ctx> for Seed {
|
||||||
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
|
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
|
||||||
Ok(bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self.as_bytes())?.into())
|
Ok(bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self[..])?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_descriptor_key(
|
fn into_descriptor_key(
|
||||||
@@ -60,7 +76,7 @@ impl<Ctx: ScriptContext> DerivableKey<Ctx> for Seed {
|
|||||||
impl<Ctx: ScriptContext> DerivableKey<Ctx> for MnemonicWithPassphrase {
|
impl<Ctx: ScriptContext> DerivableKey<Ctx> for MnemonicWithPassphrase {
|
||||||
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
|
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
|
||||||
let (mnemonic, passphrase) = self;
|
let (mnemonic, passphrase) = self;
|
||||||
let seed = Seed::new(&mnemonic, passphrase.as_deref().unwrap_or(""));
|
let seed: Seed = mnemonic.to_seed(passphrase.as_deref().unwrap_or(""));
|
||||||
|
|
||||||
seed.into_extended_key()
|
seed.into_extended_key()
|
||||||
}
|
}
|
||||||
@@ -101,15 +117,15 @@ impl<Ctx: ScriptContext> DerivableKey<Ctx> for Mnemonic {
|
|||||||
impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
|
impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
|
||||||
type Entropy = [u8; 32];
|
type Entropy = [u8; 32];
|
||||||
|
|
||||||
type Options = (MnemonicType, Language);
|
type Options = (WordCount, Language);
|
||||||
type Error = Option<bip39::ErrorKind>;
|
type Error = Option<bip39::Error>;
|
||||||
|
|
||||||
fn generate_with_entropy(
|
fn generate_with_entropy(
|
||||||
(mnemonic_type, language): Self::Options,
|
(word_count, language): Self::Options,
|
||||||
entropy: Self::Entropy,
|
entropy: Self::Entropy,
|
||||||
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
|
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
|
||||||
let entropy = &entropy.as_ref()[..(mnemonic_type.entropy_bits() / 8)];
|
let entropy = &entropy.as_ref()[..(word_count as usize / 8)];
|
||||||
let mnemonic = Mnemonic::from_entropy(entropy, language).map_err(|e| e.downcast().ok())?;
|
let mnemonic = Mnemonic::from_entropy_in(language, entropy)?;
|
||||||
|
|
||||||
Ok(GeneratedKey::new(mnemonic, any_network()))
|
Ok(GeneratedKey::new(mnemonic, any_network()))
|
||||||
}
|
}
|
||||||
@@ -121,15 +137,17 @@ mod test {
|
|||||||
|
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
|
|
||||||
use bip39::{Language, Mnemonic, MnemonicType};
|
use bip39::{Language, Mnemonic};
|
||||||
|
|
||||||
use crate::keys::{any_network, GeneratableKey, GeneratedKey};
|
use crate::keys::{any_network, GeneratableKey, GeneratedKey};
|
||||||
|
|
||||||
|
use super::WordCount;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_bip39_mnemonic() {
|
fn test_keys_bip39_mnemonic() {
|
||||||
let mnemonic =
|
let mnemonic =
|
||||||
"aim bunker wash balance finish force paper analyst cabin spoon stable organ";
|
"aim bunker wash balance finish force paper analyst cabin spoon stable organ";
|
||||||
let mnemonic = Mnemonic::from_phrase(mnemonic, Language::English).unwrap();
|
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
|
||||||
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
|
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
|
||||||
|
|
||||||
let key = (mnemonic, path);
|
let key = (mnemonic, path);
|
||||||
@@ -143,7 +161,7 @@ mod test {
|
|||||||
fn test_keys_bip39_mnemonic_passphrase() {
|
fn test_keys_bip39_mnemonic_passphrase() {
|
||||||
let mnemonic =
|
let mnemonic =
|
||||||
"aim bunker wash balance finish force paper analyst cabin spoon stable organ";
|
"aim bunker wash balance finish force paper analyst cabin spoon stable organ";
|
||||||
let mnemonic = Mnemonic::from_phrase(mnemonic, Language::English).unwrap();
|
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
|
||||||
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
|
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
|
||||||
|
|
||||||
let key = ((mnemonic, Some("passphrase".into())), path);
|
let key = ((mnemonic, Some("passphrase".into())), path);
|
||||||
@@ -157,7 +175,7 @@ mod test {
|
|||||||
fn test_keys_generate_bip39() {
|
fn test_keys_generate_bip39() {
|
||||||
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
||||||
Mnemonic::generate_with_entropy(
|
Mnemonic::generate_with_entropy(
|
||||||
(MnemonicType::Words12, Language::English),
|
(WordCount::Words12, Language::English),
|
||||||
crate::keys::test::TEST_ENTROPY,
|
crate::keys::test::TEST_ENTROPY,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -169,7 +187,7 @@ mod test {
|
|||||||
|
|
||||||
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
||||||
Mnemonic::generate_with_entropy(
|
Mnemonic::generate_with_entropy(
|
||||||
(MnemonicType::Words24, Language::English),
|
(WordCount::Words24, Language::English),
|
||||||
crate::keys::test::TEST_ENTROPY,
|
crate::keys::test::TEST_ENTROPY,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -180,11 +198,11 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_keys_generate_bip39_random() {
|
fn test_keys_generate_bip39_random() {
|
||||||
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
||||||
Mnemonic::generate((MnemonicType::Words12, Language::English)).unwrap();
|
Mnemonic::generate((WordCount::Words12, Language::English)).unwrap();
|
||||||
assert_eq!(generated_mnemonic.valid_networks, any_network());
|
assert_eq!(generated_mnemonic.valid_networks, any_network());
|
||||||
|
|
||||||
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
|
||||||
Mnemonic::generate((MnemonicType::Words24, Language::English)).unwrap();
|
Mnemonic::generate((WordCount::Words24, Language::English)).unwrap();
|
||||||
assert_eq!(generated_mnemonic.valid_networks, any_network());
|
assert_eq!(generated_mnemonic.valid_networks, any_network());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,9 +460,9 @@ use bdk::keys::bip39::{Mnemonic, Language};
|
|||||||
|
|
||||||
# fn main() -> Result<(), Box<dyn std::error::Error>> {
|
# fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let xkey: ExtendedKey =
|
let xkey: ExtendedKey =
|
||||||
Mnemonic::from_phrase(
|
Mnemonic::parse_in(
|
||||||
|
Language::English,
|
||||||
"jelly crash boy whisper mouse ecology tuna soccer memory million news short",
|
"jelly crash boy whisper mouse ecology tuna soccer memory million news short",
|
||||||
Language::English
|
|
||||||
)?
|
)?
|
||||||
.into_extended_key()?;
|
.into_extended_key()?;
|
||||||
let xprv = xkey.into_xprv(Network::Bitcoin).unwrap();
|
let xprv = xkey.into_xprv(Network::Bitcoin).unwrap();
|
||||||
@@ -753,6 +753,20 @@ pub fn make_pk<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
|||||||
Ok((minisc, key_map, valid_networks))
|
Ok((minisc, key_map, valid_networks))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used internally by `bdk::fragment!` to build `pk_h()` fragments
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn make_pkh<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||||
|
descriptor_key: Pk,
|
||||||
|
secp: &SecpCtx,
|
||||||
|
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
|
||||||
|
let (key, key_map, valid_networks) = descriptor_key.into_descriptor_key()?.extract(secp)?;
|
||||||
|
let minisc = Miniscript::from_ast(Terminal::PkH(key))?;
|
||||||
|
|
||||||
|
minisc.check_minsicript()?;
|
||||||
|
|
||||||
|
Ok((minisc, key_map, valid_networks))
|
||||||
|
}
|
||||||
|
|
||||||
// Used internally by `bdk::fragment!` to build `multi()` fragments
|
// Used internally by `bdk::fragment!` to build `multi()` fragments
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn make_multi<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
pub fn make_multi<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
//! interact with the bitcoin P2P network.
|
//! interact with the bitcoin P2P network.
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! bdk = "0.10.0"
|
//! bdk = "0.14.0"
|
||||||
//! ```
|
//! ```
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
feature = "electrum",
|
feature = "electrum",
|
||||||
@@ -244,6 +244,9 @@ pub extern crate electrum_client;
|
|||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
pub extern crate sled;
|
pub extern crate sled;
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
pub extern crate rusqlite;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub(crate) mod error;
|
pub(crate) mod error;
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ impl PsbtUtils for Psbt {
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::bitcoin::TxIn;
|
use crate::bitcoin::TxIn;
|
||||||
use crate::psbt::Psbt;
|
use crate::psbt::Psbt;
|
||||||
use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
|
|
||||||
use crate::wallet::AddressIndex;
|
use crate::wallet::AddressIndex;
|
||||||
|
use crate::wallet::{get_funded_wallet, test::get_test_wpkh};
|
||||||
use crate::SignOptions;
|
use crate::SignOptions;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
|
|||||||
pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
|
pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
use electrsd::bitcoind::BitcoinD;
|
use electrsd::bitcoind::BitcoinD;
|
||||||
use electrsd::{bitcoind, Conf, ElectrsD};
|
use electrsd::{bitcoind, ElectrsD};
|
||||||
pub use electrum_client::{Client as ElectrumClient, ElectrumApi};
|
pub use electrum_client::{Client as ElectrumClient, ElectrumApi};
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, log_enabled, trace, Level};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
@@ -24,14 +24,15 @@ pub struct TestClient {
|
|||||||
impl TestClient {
|
impl TestClient {
|
||||||
pub fn new(bitcoind_exe: String, electrs_exe: String) -> Self {
|
pub fn new(bitcoind_exe: String, electrs_exe: String) -> Self {
|
||||||
debug!("launching {} and {}", &bitcoind_exe, &electrs_exe);
|
debug!("launching {} and {}", &bitcoind_exe, &electrs_exe);
|
||||||
let bitcoind = BitcoinD::new(bitcoind_exe).unwrap();
|
|
||||||
|
|
||||||
let http_enabled = cfg!(feature = "test-esplora");
|
let mut conf = bitcoind::Conf::default();
|
||||||
|
conf.view_stdout = log_enabled!(Level::Debug);
|
||||||
|
let bitcoind = BitcoinD::with_conf(bitcoind_exe, &conf).unwrap();
|
||||||
|
|
||||||
|
let mut conf = electrsd::Conf::default();
|
||||||
|
conf.view_stderr = log_enabled!(Level::Debug);
|
||||||
|
conf.http_enabled = cfg!(feature = "test-esplora");
|
||||||
|
|
||||||
let conf = Conf {
|
|
||||||
http_enabled,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap();
|
let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap();
|
||||||
|
|
||||||
let node_address = bitcoind.client.get_new_address(None, None).unwrap();
|
let node_address = bitcoind.client.get_new_address(None, None).unwrap();
|
||||||
@@ -144,9 +145,7 @@ impl TestClient {
|
|||||||
|
|
||||||
let bumped: serde_json::Value = self.call("bumpfee", &[txid.to_string().into()]).unwrap();
|
let bumped: serde_json::Value = self.call("bumpfee", &[txid.to_string().into()]).unwrap();
|
||||||
let new_txid = Txid::from_str(&bumped["txid"].as_str().unwrap().to_string()).unwrap();
|
let new_txid = Txid::from_str(&bumped["txid"].as_str().unwrap().to_string()).unwrap();
|
||||||
|
let monitor_script = Script::from_hex(&mut tx.vout[0].script_pub_key.hex.to_hex()).unwrap();
|
||||||
let monitor_script =
|
|
||||||
tx.vout[0].script_pub_key.addresses.as_ref().unwrap()[0].script_pubkey();
|
|
||||||
self.wait_for_tx(new_txid, &monitor_script);
|
self.wait_for_tx(new_txid, &monitor_script);
|
||||||
|
|
||||||
debug!("Bumped {}, new txid {}", txid, new_txid);
|
debug!("Bumped {}, new txid {}", txid, new_txid);
|
||||||
@@ -393,6 +392,9 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_simple() {
|
fn test_sync_simple() {
|
||||||
|
use std::ops::Deref;
|
||||||
|
use crate::database::Database;
|
||||||
|
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
let tx = testutils! {
|
let tx = testutils! {
|
||||||
@@ -401,7 +403,13 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
println!("{:?}", tx);
|
println!("{:?}", tx);
|
||||||
let txid = test_client.receive(tx);
|
let txid = test_client.receive(tx);
|
||||||
|
|
||||||
|
// the RPC blockchain needs to call `sync()` during initialization to import the
|
||||||
|
// addresses (see `init_single_sig()`), so we skip this assertion
|
||||||
|
#[cfg(not(feature = "test-rpc"))]
|
||||||
|
assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none");
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated");
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External, "incorrect keychain kind");
|
assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External, "incorrect keychain kind");
|
||||||
@@ -593,7 +601,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
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));
|
||||||
wallet.broadcast(tx).unwrap();
|
wallet.broadcast(&tx).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
|
||||||
|
|
||||||
@@ -644,7 +652,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
let finalized = wallet.sign(&mut psbt, Default::default()).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();
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
|
||||||
@@ -687,7 +695,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let (mut psbt, details) = builder.finish().unwrap();
|
let (mut psbt, details) = builder.finish().unwrap();
|
||||||
let finalized = wallet.sign(&mut psbt, Default::default()).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();
|
||||||
|
|
||||||
@@ -726,7 +734,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let (mut psbt, details) = builder.finish().unwrap();
|
let (mut psbt, details) = builder.finish().unwrap();
|
||||||
let finalized = wallet.sign(&mut psbt, Default::default()).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();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received");
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received");
|
||||||
@@ -736,7 +744,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let (mut new_psbt, new_details) = builder.finish().unwrap();
|
let (mut new_psbt, new_details) = builder.finish().unwrap();
|
||||||
let finalized = wallet.sign(&mut new_psbt, Default::default()).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();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance from received after bump");
|
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance from received after bump");
|
||||||
@@ -761,7 +769,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let (mut psbt, details) = builder.finish().unwrap();
|
let (mut psbt, details) = builder.finish().unwrap();
|
||||||
let finalized = wallet.sign(&mut psbt, Default::default()).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();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect received after send");
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect received after send");
|
||||||
@@ -771,7 +779,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let (mut new_psbt, new_details) = builder.finish().unwrap();
|
let (mut new_psbt, new_details) = builder.finish().unwrap();
|
||||||
let finalized = wallet.sign(&mut new_psbt, Default::default()).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();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal");
|
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal");
|
||||||
assert_eq!(new_details.received, 0, "incorrect received after change removal");
|
assert_eq!(new_details.received, 0, "incorrect received after change removal");
|
||||||
@@ -796,7 +804,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let (mut psbt, details) = builder.finish().unwrap();
|
let (mut psbt, details) = builder.finish().unwrap();
|
||||||
let finalized = wallet.sign(&mut psbt, Default::default()).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();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||||
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
|
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
|
||||||
@@ -806,7 +814,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let (mut new_psbt, new_details) = builder.finish().unwrap();
|
let (mut new_psbt, new_details) = builder.finish().unwrap();
|
||||||
let finalized = wallet.sign(&mut new_psbt, Default::default()).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();
|
||||||
assert_eq!(new_details.sent, 75_000, "incorrect sent");
|
assert_eq!(new_details.sent, 75_000, "incorrect sent");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input");
|
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input");
|
||||||
@@ -829,7 +837,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let (mut psbt, details) = builder.finish().unwrap();
|
let (mut psbt, details) = builder.finish().unwrap();
|
||||||
let finalized = wallet.sign(&mut psbt, Default::default()).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();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||||
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
|
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
|
||||||
@@ -841,13 +849,44 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
let finalized = wallet.sign(&mut new_psbt, Default::default()).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();
|
||||||
assert_eq!(new_details.sent, 75_000, "incorrect sent");
|
assert_eq!(new_details.sent, 75_000, "incorrect sent");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input");
|
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input");
|
||||||
assert_eq!(new_details.received, 0, "incorrect received after add input");
|
assert_eq!(new_details.received, 0, "incorrect received after add input");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_data() {
|
||||||
|
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||||
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
let _ = test_client.receive(testutils! {
|
||||||
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
|
});
|
||||||
|
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
|
let mut builder = wallet.build_tx();
|
||||||
|
let data = [42u8;80];
|
||||||
|
builder.add_data(&data);
|
||||||
|
let (mut psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
|
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||||
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
|
let tx = psbt.extract_tx();
|
||||||
|
let serialized_tx = bitcoin::consensus::encode::serialize(&tx);
|
||||||
|
assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction");
|
||||||
|
let sent_txid = wallet.broadcast(&tx).unwrap();
|
||||||
|
test_client.generate(1, Some(node_addr));
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||||
|
|
||||||
|
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||||
|
let _ = tx_map.get(&sent_txid).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_receive_coinbase() {
|
fn test_sync_receive_coinbase() {
|
||||||
let (wallet, _, mut test_client) = init_single_sig();
|
let (wallet, _, mut test_client) = init_single_sig();
|
||||||
@@ -870,6 +909,102 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase");
|
assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_send_to_bech32m_addr() {
|
||||||
|
use std::str::FromStr;
|
||||||
|
use serde;
|
||||||
|
use serde_json;
|
||||||
|
use serde::Serialize;
|
||||||
|
use bitcoincore_rpc::jsonrpc::serde_json::Value;
|
||||||
|
use bitcoincore_rpc::{Auth, Client, RpcApi};
|
||||||
|
|
||||||
|
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
|
// TODO remove once rust-bitcoincore-rpc with PR 199 released
|
||||||
|
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199
|
||||||
|
/// Import Descriptor Request
|
||||||
|
#[derive(Serialize, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct ImportDescriptorRequest {
|
||||||
|
pub active: bool,
|
||||||
|
#[serde(rename = "desc")]
|
||||||
|
pub descriptor: String,
|
||||||
|
pub range: [i64; 2],
|
||||||
|
pub next_index: i64,
|
||||||
|
pub timestamp: String,
|
||||||
|
pub internal: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO remove once rust-bitcoincore-rpc with PR 199 released
|
||||||
|
impl ImportDescriptorRequest {
|
||||||
|
/// Create a new Import Descriptor request providing just the descriptor and internal flags
|
||||||
|
pub fn new(descriptor: &str, internal: bool) -> Self {
|
||||||
|
ImportDescriptorRequest {
|
||||||
|
descriptor: descriptor.to_string(),
|
||||||
|
internal,
|
||||||
|
active: true,
|
||||||
|
range: [0, 100],
|
||||||
|
next_index: 0,
|
||||||
|
timestamp: "now".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Create and add descriptors to a test bitcoind node taproot wallet
|
||||||
|
|
||||||
|
// TODO replace once rust-bitcoincore-rpc with PR 174 released
|
||||||
|
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/174
|
||||||
|
let _createwallet_result: Value = test_client.bitcoind.client.call("createwallet", &["taproot_wallet".into(),false.into(),true.into(),serde_json::to_value("").unwrap(), false.into(), true.into()]).unwrap();
|
||||||
|
|
||||||
|
// TODO replace once bitcoind released with support for rust-bitcoincore-rpc PR 174
|
||||||
|
let taproot_wallet_client = Client::new(&test_client.bitcoind.rpc_url_with_wallet("taproot_wallet"), Auth::CookieFile(test_client.bitcoind.params.cookie_file.clone())).unwrap();
|
||||||
|
|
||||||
|
let wallet_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/0/*)#y283ssmn";
|
||||||
|
let change_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/1/*)#47zsd9tt";
|
||||||
|
|
||||||
|
let tr_descriptors = vec![
|
||||||
|
ImportDescriptorRequest::new(wallet_descriptor, false),
|
||||||
|
ImportDescriptorRequest::new(change_descriptor, false),
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO replace once rust-bitcoincore-rpc with PR 199 released
|
||||||
|
let _import_result: Value = taproot_wallet_client.call("importdescriptors", &[serde_json::to_value(tr_descriptors).unwrap()]).unwrap();
|
||||||
|
|
||||||
|
// 2. Get a new bech32m address from test bitcoind node taproot wallet
|
||||||
|
|
||||||
|
// TODO replace once rust-bitcoincore-rpc with PR 199 released
|
||||||
|
let node_addr: bitcoin::Address = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).unwrap();
|
||||||
|
assert_eq!(node_addr, bitcoin::Address::from_str("bcrt1pj5y3f0fu4y7g98k4v63j9n0xvj3lmln0cpwhsjzknm6nt0hr0q7qnzwsy9").unwrap());
|
||||||
|
|
||||||
|
// 3. Send 50_000 sats from test bitcoind node to test BDK wallet
|
||||||
|
|
||||||
|
test_client.receive(testutils! {
|
||||||
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
|
});
|
||||||
|
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance");
|
||||||
|
|
||||||
|
// 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet
|
||||||
|
|
||||||
|
let mut builder = wallet.build_tx();
|
||||||
|
builder.add_recipient(node_addr.script_pubkey(), 25_000);
|
||||||
|
let (mut psbt, details) = builder.finish().unwrap();
|
||||||
|
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||||
|
assert!(finalized, "wallet cannot finalize transaction");
|
||||||
|
let tx = psbt.extract_tx();
|
||||||
|
wallet.broadcast(&tx).unwrap();
|
||||||
|
wallet.sync(noop_progress(), None).unwrap();
|
||||||
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "wallet has incorrect balance after send");
|
||||||
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "wallet has incorrect number of txs");
|
||||||
|
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents");
|
||||||
|
test_client.generate(1, None);
|
||||||
|
|
||||||
|
// 5. Verify 25_000 sats are received by test bitcoind node taproot wallet
|
||||||
|
|
||||||
|
let taproot_balance = taproot_wallet_client.get_balance(None, None).unwrap();
|
||||||
|
assert_eq!(taproot_balance.as_sat(), 25_000, "node has incorrect taproot wallet balance");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ impl TranslateDescriptor for Descriptor<DescriptorPublicKey> {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! testutils {
|
macro_rules! testutils {
|
||||||
( @external $descriptors:expr, $child:expr ) => ({
|
( @external $descriptors:expr, $child:expr ) => ({
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use $crate::bitcoin::secp256k1::Secp256k1;
|
||||||
use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
|
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
|
||||||
|
|
||||||
use $crate::testutils::TranslateDescriptor;
|
use $crate::testutils::TranslateDescriptor;
|
||||||
|
|
||||||
@@ -111,15 +111,15 @@ macro_rules! testutils {
|
|||||||
parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
|
parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
|
||||||
});
|
});
|
||||||
( @internal $descriptors:expr, $child:expr ) => ({
|
( @internal $descriptors:expr, $child:expr ) => ({
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use $crate::bitcoin::secp256k1::Secp256k1;
|
||||||
use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
|
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
|
||||||
|
|
||||||
use $crate::testutils::TranslateDescriptor;
|
use $crate::testutils::TranslateDescriptor;
|
||||||
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
|
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
|
||||||
parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
|
parsed.derive_translated(&secp, $child).address($crate::bitcoin::Network::Regtest).expect("No address form")
|
||||||
});
|
});
|
||||||
( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
|
( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
|
||||||
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
|
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
|
||||||
@@ -145,8 +145,8 @@ macro_rules! testutils {
|
|||||||
let mut seed = [0u8; 32];
|
let mut seed = [0u8; 32];
|
||||||
rand::thread_rng().fill(&mut seed[..]);
|
rand::thread_rng().fill(&mut seed[..]);
|
||||||
|
|
||||||
let key = bitcoin::util::bip32::ExtendedPrivKey::new_master(
|
let key = $crate::bitcoin::util::bip32::ExtendedPrivKey::new_master(
|
||||||
bitcoin::Network::Testnet,
|
$crate::bitcoin::Network::Testnet,
|
||||||
&seed,
|
&seed,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -158,13 +158,13 @@ macro_rules! testutils {
|
|||||||
( @generate_wif ) => ({
|
( @generate_wif ) => ({
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
let mut key = [0u8; bitcoin::secp256k1::constants::SECRET_KEY_SIZE];
|
let mut key = [0u8; $crate::bitcoin::secp256k1::constants::SECRET_KEY_SIZE];
|
||||||
rand::thread_rng().fill(&mut key[..]);
|
rand::thread_rng().fill(&mut key[..]);
|
||||||
|
|
||||||
(bitcoin::PrivateKey {
|
($crate::bitcoin::PrivateKey {
|
||||||
compressed: true,
|
compressed: true,
|
||||||
network: bitcoin::Network::Testnet,
|
network: $crate::bitcoin::Network::Testnet,
|
||||||
key: bitcoin::secp256k1::SecretKey::from_slice(&key).unwrap(),
|
key: $crate::bitcoin::secp256k1::SecretKey::from_slice(&key).unwrap(),
|
||||||
}.to_string(), None::<String>, None::<String>)
|
}.to_string(), None::<String>, None::<String>)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -181,8 +181,8 @@ macro_rules! testutils {
|
|||||||
( @descriptors ( $external_descriptor:expr ) $( ( $internal_descriptor:expr ) )? $( ( @keys $( $keys:tt )* ) )* ) => ({
|
( @descriptors ( $external_descriptor:expr ) $( ( $internal_descriptor:expr ) )? $( ( @keys $( $keys:tt )* ) )* ) => ({
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use miniscript::descriptor::Descriptor;
|
use $crate::miniscript::descriptor::Descriptor;
|
||||||
use miniscript::TranslatePk;
|
use $crate::miniscript::TranslatePk;
|
||||||
|
|
||||||
#[allow(unused_assignments, unused_mut)]
|
#[allow(unused_assignments, unused_mut)]
|
||||||
let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
|
let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
|
||||||
|
|||||||
18
src/types.rs
18
src/types.rs
@@ -210,7 +210,7 @@ pub struct TransactionDetails {
|
|||||||
pub fee: Option<u64>,
|
pub fee: Option<u64>,
|
||||||
/// If the transaction is confirmed, contains height and timestamp of the block containing the
|
/// If the transaction is confirmed, contains height and timestamp of the block containing the
|
||||||
/// transaction, unconfirmed transaction contains `None`.
|
/// transaction, unconfirmed transaction contains `None`.
|
||||||
pub confirmation_time: Option<ConfirmationTime>,
|
pub confirmation_time: Option<BlockTime>,
|
||||||
/// Whether the tx has been verified against the consensus rules
|
/// Whether the tx has been verified against the consensus rules
|
||||||
///
|
///
|
||||||
/// Confirmed txs are considered "verified" by default, while unconfirmed txs are checked to
|
/// Confirmed txs are considered "verified" by default, while unconfirmed txs are checked to
|
||||||
@@ -222,20 +222,26 @@ pub struct TransactionDetails {
|
|||||||
pub verified: bool,
|
pub verified: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block height and timestamp of the block containing the confirmed transaction
|
/// Block height and timestamp of a block
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct ConfirmationTime {
|
pub struct BlockTime {
|
||||||
/// confirmation block height
|
/// confirmation block height
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
/// confirmation block timestamp
|
/// confirmation block timestamp
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfirmationTime {
|
/// **DEPRECATED**: Confirmation time of a transaction
|
||||||
/// Returns `Some` `ConfirmationTime` if both `height` and `timestamp` are `Some`
|
///
|
||||||
|
/// The structure has been renamed to `BlockTime`
|
||||||
|
#[deprecated(note = "This structure has been renamed to `BlockTime`")]
|
||||||
|
pub type ConfirmationTime = BlockTime;
|
||||||
|
|
||||||
|
impl BlockTime {
|
||||||
|
/// Returns `Some` `BlockTime` if both `height` and `timestamp` are `Some`
|
||||||
pub fn new(height: Option<u32>, timestamp: Option<u64>) -> Option<Self> {
|
pub fn new(height: Option<u32>, timestamp: Option<u64>) -> Option<Self> {
|
||||||
match (height, timestamp) {
|
match (height, timestamp) {
|
||||||
(Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }),
|
(Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ mod test {
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
|
|
||||||
use crate::wallet::AddressIndex::New;
|
use crate::wallet::AddressIndex::New;
|
||||||
|
use crate::wallet::{get_funded_wallet, test::get_test_wpkh};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct TestValidator;
|
struct TestValidator;
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ mod test {
|
|||||||
use crate::database::{memory::MemoryDatabase, BatchOperations};
|
use crate::database::{memory::MemoryDatabase, BatchOperations};
|
||||||
use crate::types::TransactionDetails;
|
use crate::types::TransactionDetails;
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
use crate::ConfirmationTime;
|
use crate::BlockTime;
|
||||||
|
|
||||||
fn get_test_db() -> MemoryDatabase {
|
fn get_test_db() -> MemoryDatabase {
|
||||||
let mut db = MemoryDatabase::new();
|
let mut db = MemoryDatabase::new();
|
||||||
@@ -226,7 +226,7 @@ mod test {
|
|||||||
received: 100_000,
|
received: 100_000,
|
||||||
sent: 0,
|
sent: 0,
|
||||||
fee: Some(500),
|
fee: Some(500),
|
||||||
confirmation_time: Some(ConfirmationTime {
|
confirmation_time: Some(BlockTime {
|
||||||
timestamp: 12345678,
|
timestamp: 12345678,
|
||||||
height: 5000,
|
height: 5000,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use std::collections::HashMap;
|
|||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
@@ -55,7 +56,8 @@ use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
|
|||||||
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LIMIT_SATOSHI};
|
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LIMIT_SATOSHI};
|
||||||
|
|
||||||
use crate::blockchain::{Blockchain, Progress};
|
use crate::blockchain::{Blockchain, Progress};
|
||||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
use crate::database::memory::MemoryDatabase;
|
||||||
|
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
|
||||||
use crate::descriptor::derived::AsDerived;
|
use crate::descriptor::derived::AsDerived;
|
||||||
use crate::descriptor::policy::BuildSatisfaction;
|
use crate::descriptor::policy::BuildSatisfaction;
|
||||||
use crate::descriptor::{
|
use crate::descriptor::{
|
||||||
@@ -66,6 +68,7 @@ use crate::descriptor::{
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::psbt::PsbtUtils;
|
use crate::psbt::PsbtUtils;
|
||||||
use crate::signer::SignerError;
|
use crate::signer::SignerError;
|
||||||
|
use crate::testutils;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
|
||||||
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
|
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
|
||||||
@@ -167,6 +170,11 @@ where
|
|||||||
secp,
|
secp,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the Bitcoin network the wallet is using.
|
||||||
|
pub fn network(&self) -> Network {
|
||||||
|
self.network
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The address index selection strategy to use to derived an address from the wallet's external
|
/// The address index selection strategy to use to derived an address from the wallet's external
|
||||||
@@ -315,7 +323,7 @@ where
|
|||||||
|
|
||||||
/// Return the list of unspent outputs of this wallet
|
/// Return the list of unspent outputs of this wallet
|
||||||
///
|
///
|
||||||
/// Note that this methods only operate on the internal database, which first needs to be
|
/// Note that this method only operates on the internal database, which first needs to be
|
||||||
/// [`Wallet::sync`] manually.
|
/// [`Wallet::sync`] manually.
|
||||||
pub fn list_unspent(&self) -> Result<Vec<LocalUtxo>, Error> {
|
pub fn list_unspent(&self) -> Result<Vec<LocalUtxo>, Error> {
|
||||||
self.database.borrow().iter_utxos()
|
self.database.borrow().iter_utxos()
|
||||||
@@ -327,6 +335,21 @@ where
|
|||||||
self.database.borrow().get_utxo(&outpoint)
|
self.database.borrow().get_utxo(&outpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a single transactions made and received by the wallet
|
||||||
|
///
|
||||||
|
/// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
|
||||||
|
/// `include_raw` is `true`.
|
||||||
|
///
|
||||||
|
/// Note that this method only operates on the internal database, which first needs to be
|
||||||
|
/// [`Wallet::sync`] manually.
|
||||||
|
pub fn get_tx(
|
||||||
|
&self,
|
||||||
|
txid: &Txid,
|
||||||
|
include_raw: bool,
|
||||||
|
) -> Result<Option<TransactionDetails>, Error> {
|
||||||
|
self.database.borrow().get_tx(txid, include_raw)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the list of transactions made and received by the wallet
|
/// Return the list of transactions made and received by the wallet
|
||||||
///
|
///
|
||||||
/// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
|
/// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
|
||||||
@@ -578,7 +601,7 @@ where
|
|||||||
let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
|
let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
|
||||||
|
|
||||||
for (index, (script_pubkey, value)) in recipients.enumerate() {
|
for (index, (script_pubkey, value)) in recipients.enumerate() {
|
||||||
if value.is_dust() {
|
if value.is_dust() && !script_pubkey.is_provably_unspendable() {
|
||||||
return Err(Error::OutputBelowDustLimit(index));
|
return Err(Error::OutputBelowDustLimit(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1424,6 +1447,11 @@ where
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return an immutable reference to the internal database
|
||||||
|
pub fn database(&self) -> impl std::ops::Deref<Target = D> + '_ {
|
||||||
|
self.database.borrow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B, D> Wallet<B, D>
|
impl<B, D> Wallet<B, D>
|
||||||
@@ -1526,6 +1554,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sync_time = SyncTime {
|
||||||
|
block_time: BlockTime {
|
||||||
|
height: maybe_await!(self.client.get_height())?,
|
||||||
|
timestamp: time::get_timestamp(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
debug!("Saving `sync_time` = {:?}", sync_time);
|
||||||
|
self.database.borrow_mut().set_sync_time(sync_time)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1534,33 +1571,69 @@ where
|
|||||||
&self.client
|
&self.client
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the Bitcoin network the wallet is using.
|
|
||||||
pub fn network(&self) -> Network {
|
|
||||||
self.network
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Broadcast a transaction to the network
|
/// Broadcast a transaction to the network
|
||||||
#[maybe_async]
|
#[maybe_async]
|
||||||
pub fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
|
pub fn broadcast(&self, tx: &Transaction) -> Result<Txid, Error> {
|
||||||
maybe_await!(self.client.broadcast(&tx))?;
|
maybe_await!(self.client.broadcast(tx))?;
|
||||||
|
|
||||||
Ok(tx.txid())
|
Ok(tx.txid())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a fake wallet that appears to be funded for testing.
|
||||||
|
pub fn get_funded_wallet(
|
||||||
|
descriptor: &str,
|
||||||
|
) -> (
|
||||||
|
Wallet<(), MemoryDatabase>,
|
||||||
|
(String, Option<String>),
|
||||||
|
bitcoin::Txid,
|
||||||
|
) {
|
||||||
|
let descriptors = testutils!(@descriptors (descriptor));
|
||||||
|
let wallet = Wallet::new_offline(
|
||||||
|
&descriptors.0,
|
||||||
|
None,
|
||||||
|
Network::Regtest,
|
||||||
|
MemoryDatabase::new(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let funding_address_kix = 0;
|
||||||
|
|
||||||
|
let tx_meta = testutils! {
|
||||||
|
@tx ( (@external descriptors, funding_address_kix) => 50_000 ) (@confirmations 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.database
|
||||||
|
.borrow_mut()
|
||||||
|
.set_script_pubkey(
|
||||||
|
&bitcoin::Address::from_str(&tx_meta.output.get(0).unwrap().to_address)
|
||||||
|
.unwrap()
|
||||||
|
.script_pubkey(),
|
||||||
|
KeychainKind::External,
|
||||||
|
funding_address_kix,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
wallet
|
||||||
|
.database
|
||||||
|
.borrow_mut()
|
||||||
|
.set_last_index(KeychainKind::External, funding_address_kix)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let txid = crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, Some(100));
|
||||||
|
|
||||||
|
(wallet, descriptors, txid)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use bitcoin::{util::psbt, Network};
|
use bitcoin::{util::psbt, Network};
|
||||||
|
|
||||||
use crate::database::memory::MemoryDatabase;
|
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::types::KeychainKind;
|
use crate::types::KeychainKind;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::signer::{SignOptions, SignerError};
|
use crate::signer::{SignOptions, SignerError};
|
||||||
use crate::testutils;
|
|
||||||
use crate::wallet::AddressIndex::{LastUnused, New, Peek, Reset};
|
use crate::wallet::AddressIndex::{LastUnused, New, Peek, Reset};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1672,50 +1745,6 @@ pub(crate) mod test {
|
|||||||
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))"
|
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))"
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_funded_wallet(
|
|
||||||
descriptor: &str,
|
|
||||||
) -> (
|
|
||||||
Wallet<(), MemoryDatabase>,
|
|
||||||
(String, Option<String>),
|
|
||||||
bitcoin::Txid,
|
|
||||||
) {
|
|
||||||
let descriptors = testutils!(@descriptors (descriptor));
|
|
||||||
let wallet = Wallet::new_offline(
|
|
||||||
&descriptors.0,
|
|
||||||
None,
|
|
||||||
Network::Regtest,
|
|
||||||
MemoryDatabase::new(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let funding_address_kix = 0;
|
|
||||||
|
|
||||||
let tx_meta = testutils! {
|
|
||||||
@tx ( (@external descriptors, funding_address_kix) => 50_000 ) (@confirmations 1)
|
|
||||||
};
|
|
||||||
|
|
||||||
wallet
|
|
||||||
.database
|
|
||||||
.borrow_mut()
|
|
||||||
.set_script_pubkey(
|
|
||||||
&bitcoin::Address::from_str(&tx_meta.output.get(0).unwrap().to_address)
|
|
||||||
.unwrap()
|
|
||||||
.script_pubkey(),
|
|
||||||
KeychainKind::External,
|
|
||||||
funding_address_kix,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
wallet
|
|
||||||
.database
|
|
||||||
.borrow_mut()
|
|
||||||
.set_last_index(KeychainKind::External, funding_address_kix)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let txid = crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, Some(100));
|
|
||||||
|
|
||||||
(wallet, descriptors, txid)
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! assert_fee_rate {
|
macro_rules! assert_fee_rate {
|
||||||
($tx:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({
|
($tx:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({
|
||||||
let mut tx = $tx.clone();
|
let mut tx = $tx.clone();
|
||||||
@@ -2763,7 +2792,7 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the utxos, we know they can't be used anyways
|
// skip saving the utxos, we know they can't be used anyways
|
||||||
details.transaction = Some(tx);
|
details.transaction = Some(tx);
|
||||||
details.confirmation_time = Some(ConfirmationTime {
|
details.confirmation_time = Some(BlockTime {
|
||||||
timestamp: 12345678,
|
timestamp: 12345678,
|
||||||
height: 42,
|
height: 42,
|
||||||
});
|
});
|
||||||
@@ -3965,4 +3994,15 @@ pub(crate) mod test {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sending_to_bip350_bech32m_address() {
|
||||||
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
|
let addr =
|
||||||
|
Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c")
|
||||||
|
.unwrap();
|
||||||
|
let mut builder = wallet.build_tx();
|
||||||
|
builder.add_recipient(addr.script_pubkey(), 45_000);
|
||||||
|
builder.finish().unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -560,6 +560,13 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add data as an output, using OP_RETURN
|
||||||
|
pub fn add_data(&mut self, data: &[u8]) -> &mut Self {
|
||||||
|
let script = Script::new_op_return(data);
|
||||||
|
self.add_recipient(script, 0u64);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the address to *drain* excess coins to.
|
/// Sets the address to *drain* excess coins to.
|
||||||
///
|
///
|
||||||
/// Usually, when there are excess coins they are sent to a change address generated by the
|
/// Usually, when there are excess coins they are sent to a change address generated by the
|
||||||
|
|||||||
Reference in New Issue
Block a user