Compare commits
46 Commits
v0.16.1
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce09203431 | ||
|
|
cac312d34f | ||
|
|
559cfc4373 | ||
|
|
52bcd105eb | ||
|
|
3e4678d8e3 | ||
|
|
0cc4700bd6 | ||
|
|
660faab1e2 | ||
|
|
45767fcaf7 | ||
|
|
d03aa85108 | ||
|
|
adf7d0c126 | ||
|
|
4291f84d79 | ||
|
|
f0188f49a8 | ||
|
|
edf2f0ce06 | ||
|
|
fbb50ad1c8 | ||
|
|
035307ef54 | ||
|
|
c0e75fc1a8 | ||
|
|
dcd90f8b61 | ||
|
|
410a51355b | ||
|
|
326bfe82a8 | ||
|
|
b23a0747b5 | ||
|
|
022256c91a | ||
|
|
00f0901bac | ||
|
|
19f028714b | ||
|
|
ad65dd5c23 | ||
|
|
1999d97aeb | ||
|
|
0195bc0636 | ||
|
|
760a6ca1a1 | ||
|
|
552765bb58 | ||
|
|
bfd0d13779 | ||
|
|
128c37595c | ||
|
|
5c5bb7833c | ||
|
|
b04bb590f3 | ||
|
|
0efbece41a | ||
|
|
b6fe01c466 | ||
|
|
1d7ea89d8a | ||
|
|
b05ee78c73 | ||
|
|
53c30b0479 | ||
|
|
6a09075d1a | ||
|
|
61a95d0d15 | ||
|
|
08f312a82f | ||
|
|
acbf0ae08e | ||
|
|
4761155707 | ||
|
|
98a3b3282a | ||
|
|
e745122bf5 | ||
|
|
07c270db03 | ||
|
|
375674ffff |
6
.github/workflows/cont_integration.yml
vendored
6
.github/workflows/cont_integration.yml
vendored
@@ -89,13 +89,13 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
blockchain:
|
blockchain:
|
||||||
- name: electrum
|
- name: electrum
|
||||||
features: test-electrum
|
features: test-electrum,verify
|
||||||
- name: rpc
|
- name: rpc
|
||||||
features: test-rpc
|
features: test-rpc
|
||||||
- name: esplora
|
- name: esplora
|
||||||
features: test-esplora,use-esplora-reqwest
|
features: test-esplora,use-esplora-reqwest,verify
|
||||||
- name: esplora
|
- name: esplora
|
||||||
features: test-esplora,use-esplora-ureq
|
features: test-esplora,use-esplora-ureq,verify
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -6,6 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [v0.17.0] - [v0.16.1]
|
||||||
|
|
||||||
|
- Removed default verification from `wallet::sync`. sync-time verification is added in `script_sync` and is activated by `verify` feature flag.
|
||||||
|
- `verify` flag removed from `TransactionDetails`.
|
||||||
|
- Add `get_internal_address` to allow you to get internal addresses just as you get external addresses.
|
||||||
|
- added `ensure_addresses_cached` to `Wallet` to let offline wallets load and cache addresses in their database
|
||||||
|
|
||||||
|
### Sync API change
|
||||||
|
|
||||||
|
To decouple the `Wallet` from the `Blockchain` we've made major changes:
|
||||||
|
|
||||||
|
- Removed `Blockchain` from Wallet.
|
||||||
|
- Removed `Wallet::broadcast` (just use `Blockchain::broadcast`)
|
||||||
|
- Deprecated `Wallet::new_offline` (all wallets are offline now)
|
||||||
|
- Changed `Wallet::sync` to take a `Blockchain`.
|
||||||
|
- Stop making a request for the block height when calling `Wallet:new`.
|
||||||
|
- Added `SyncOptions` to capture extra (future) arguments to `Wallet::sync`.
|
||||||
|
- Removed `max_addresses` sync parameter which determined how many addresses to cache before syncing since this can just be done with `ensure_addresses_cached`.
|
||||||
|
|
||||||
## [v0.16.1] - [v0.16.0]
|
## [v0.16.1] - [v0.16.0]
|
||||||
|
|
||||||
- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0
|
- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0
|
||||||
@@ -398,7 +417,6 @@ 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.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
|
||||||
@@ -416,4 +434,6 @@ final transaction is created by calling `finish` on the builder.
|
|||||||
[v0.14.0]: https://github.com/bitcoindevkit/bdk/compare/v0.13.0...v0.14.0
|
[v0.14.0]: https://github.com/bitcoindevkit/bdk/compare/v0.13.0...v0.14.0
|
||||||
[v0.15.0]: https://github.com/bitcoindevkit/bdk/compare/v0.14.0...v0.15.0
|
[v0.15.0]: https://github.com/bitcoindevkit/bdk/compare/v0.14.0...v0.15.0
|
||||||
[v0.16.0]: https://github.com/bitcoindevkit/bdk/compare/v0.15.0...v0.16.0
|
[v0.16.0]: https://github.com/bitcoindevkit/bdk/compare/v0.15.0...v0.16.0
|
||||||
[v0.16.1]: https://github.com/bitcoindevkit/bdk/compare/v0.16.0...v0.16.1
|
[v0.16.1]: https://github.com/bitcoindevkit/bdk/compare/v0.16.0...v0.16.1
|
||||||
|
[v0.17.0]: https://github.com/bitcoindevkit/bdk/compare/v0.16.1...v0.17.0
|
||||||
|
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.17.0...HEAD
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk"
|
name = "bdk"
|
||||||
version = "0.16.1"
|
version = "0.17.1-dev"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -41,21 +41,21 @@ The `bdk` library aims to be the core building block for Bitcoin wallets of any
|
|||||||
```rust,no_run
|
```rust,no_run
|
||||||
use bdk::Wallet;
|
use bdk::Wallet;
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
use bdk::blockchain::ElectrumBlockchain;
|
||||||
|
use bdk::SyncOptions;
|
||||||
|
|
||||||
use bdk::electrum_client::Client;
|
use bdk::electrum_client::Client;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
|
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
bitcoin::Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
ElectrumBlockchain::from(client)
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None)?;
|
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||||
|
|
||||||
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
|
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ use bdk::{Wallet, database::MemoryDatabase};
|
|||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet = Wallet::new_offline(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
bitcoin::Network::Testnet,
|
||||||
@@ -88,9 +88,9 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
### Create a transaction
|
### Create a transaction
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
use bdk::{FeeRate, Wallet};
|
use bdk::{FeeRate, Wallet, SyncOptions};
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
use bdk::blockchain::ElectrumBlockchain;
|
||||||
|
|
||||||
use bdk::electrum_client::Client;
|
use bdk::electrum_client::Client;
|
||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
@@ -98,16 +98,15 @@ use bdk::wallet::AddressIndex::New;
|
|||||||
use bitcoin::consensus::serialize;
|
use bitcoin::consensus::serialize;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
|
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
bitcoin::Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
ElectrumBlockchain::from(client)
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None)?;
|
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||||
|
|
||||||
let send_to = wallet.get_address(New)?;
|
let send_to = wallet.get_address(New)?;
|
||||||
let (psbt, details) = {
|
let (psbt, details) = {
|
||||||
@@ -135,7 +134,7 @@ use bdk::{Wallet, SignOptions, database::MemoryDatabase};
|
|||||||
use bitcoin::consensus::deserialize;
|
use bitcoin::consensus::deserialize;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet = Wallet::new_offline(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
bitcoin::Network::Testnet,
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ impl AddressValidator for DummyValidator {
|
|||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
|
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
|
||||||
let mut wallet =
|
let mut wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
|
||||||
Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
|
|
||||||
|
|
||||||
wallet.add_address_validator(Arc::new(DummyValidator));
|
wallet.add_address_validator(Arc::new(DummyValidator));
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
// licenses.
|
// licenses.
|
||||||
|
|
||||||
use bdk::blockchain::compact_filters::*;
|
use bdk::blockchain::compact_filters::*;
|
||||||
use bdk::blockchain::noop_progress;
|
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::*;
|
use bdk::*;
|
||||||
use bitcoin::*;
|
use bitcoin::*;
|
||||||
@@ -35,9 +34,8 @@ fn main() -> Result<(), CompactFiltersError> {
|
|||||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)";
|
let descriptor = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)";
|
||||||
|
|
||||||
let database = MemoryDatabase::default();
|
let database = MemoryDatabase::default();
|
||||||
let wallet =
|
let wallet = Arc::new(Wallet::new(descriptor, None, Network::Testnet, database).unwrap());
|
||||||
Arc::new(Wallet::new(descriptor, None, Network::Testnet, database, blockchain).unwrap());
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
|
||||||
info!("balance: {}", wallet.get_balance()?);
|
info!("balance: {}", wallet.get_balance()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
.transpose()
|
.transpose()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap_or(Network::Testnet);
|
.unwrap_or(Network::Testnet);
|
||||||
let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?;
|
let wallet = Wallet::new(&format!("{}", descriptor), None, network, database)?;
|
||||||
|
|
||||||
info!("... First address: {}", wallet.get_address(New)?);
|
info!("... First address: {}", wallet.get_address(New)?);
|
||||||
|
|
||||||
|
|||||||
@@ -16,61 +16,17 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Example
|
//! ## Example
|
||||||
//!
|
//!
|
||||||
//! In this example both `wallet_electrum` and `wallet_esplora` have the same type of
|
//! When paired with the use of [`ConfigurableBlockchain`], it allows creating any
|
||||||
//! `Wallet<AnyBlockchain, MemoryDatabase>`. This means that they could both, for instance, be
|
|
||||||
//! assigned to a struct member.
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! # use bitcoin::Network;
|
|
||||||
//! # use bdk::blockchain::*;
|
|
||||||
//! # use bdk::database::MemoryDatabase;
|
|
||||||
//! # use bdk::Wallet;
|
|
||||||
//! # #[cfg(feature = "electrum")]
|
|
||||||
//! # {
|
|
||||||
//! let electrum_blockchain = ElectrumBlockchain::from(electrum_client::Client::new("...")?);
|
|
||||||
//! let wallet_electrum: Wallet<AnyBlockchain, _> = Wallet::new(
|
|
||||||
//! "...",
|
|
||||||
//! None,
|
|
||||||
//! Network::Testnet,
|
|
||||||
//! MemoryDatabase::default(),
|
|
||||||
//! electrum_blockchain.into(),
|
|
||||||
//! )?;
|
|
||||||
//! # }
|
|
||||||
//!
|
|
||||||
//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
|
|
||||||
//! # {
|
|
||||||
//! let esplora_blockchain = EsploraBlockchain::new("...", 20);
|
|
||||||
//! let wallet_esplora: Wallet<AnyBlockchain, _> = Wallet::new(
|
|
||||||
//! "...",
|
|
||||||
//! None,
|
|
||||||
//! Network::Testnet,
|
|
||||||
//! MemoryDatabase::default(),
|
|
||||||
//! esplora_blockchain.into(),
|
|
||||||
//! )?;
|
|
||||||
//! # }
|
|
||||||
//!
|
|
||||||
//! # Ok::<(), bdk::Error>(())
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! When paired with the use of [`ConfigurableBlockchain`], it allows creating wallets with any
|
|
||||||
//! blockchain type supported using a single line of code:
|
//! blockchain type supported using a single line of code:
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # use bitcoin::Network;
|
//! # use bitcoin::Network;
|
||||||
//! # use bdk::blockchain::*;
|
//! # use bdk::blockchain::*;
|
||||||
//! # use bdk::database::MemoryDatabase;
|
|
||||||
//! # use bdk::Wallet;
|
|
||||||
//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
|
//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
|
||||||
//! # {
|
//! # {
|
||||||
//! let config = serde_json::from_str("...")?;
|
//! let config = serde_json::from_str("...")?;
|
||||||
//! let blockchain = AnyBlockchain::from_config(&config)?;
|
//! let blockchain = AnyBlockchain::from_config(&config)?;
|
||||||
//! let wallet = Wallet::new(
|
//! let height = blockchain.get_height();
|
||||||
//! "...",
|
|
||||||
//! None,
|
|
||||||
//! Network::Testnet,
|
|
||||||
//! MemoryDatabase::default(),
|
|
||||||
//! blockchain,
|
|
||||||
//! )?;
|
|
||||||
//! # }
|
//! # }
|
||||||
//! # Ok::<(), bdk::Error>(())
|
//! # Ok::<(), bdk::Error>(())
|
||||||
//! ```
|
//! ```
|
||||||
@@ -133,33 +89,55 @@ impl Blockchain for AnyBlockchain {
|
|||||||
maybe_await!(impl_inner_method!(self, get_capabilities))
|
maybe_await!(impl_inner_method!(self, get_capabilities))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
|
||||||
&self,
|
|
||||||
database: &mut D,
|
|
||||||
progress_update: P,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
maybe_await!(impl_inner_method!(self, setup, database, progress_update))
|
|
||||||
}
|
|
||||||
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
|
||||||
&self,
|
|
||||||
database: &mut D,
|
|
||||||
progress_update: P,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
maybe_await!(impl_inner_method!(self, sync, database, progress_update))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
|
||||||
maybe_await!(impl_inner_method!(self, get_tx, txid))
|
|
||||||
}
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||||
maybe_await!(impl_inner_method!(self, broadcast, tx))
|
maybe_await!(impl_inner_method!(self, broadcast, tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||||
|
maybe_await!(impl_inner_method!(self, estimate_fee, target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[maybe_async]
|
||||||
|
impl GetHeight for AnyBlockchain {
|
||||||
fn get_height(&self) -> Result<u32, Error> {
|
fn get_height(&self) -> Result<u32, Error> {
|
||||||
maybe_await!(impl_inner_method!(self, get_height))
|
maybe_await!(impl_inner_method!(self, get_height))
|
||||||
}
|
}
|
||||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
}
|
||||||
maybe_await!(impl_inner_method!(self, estimate_fee, target))
|
|
||||||
|
#[maybe_async]
|
||||||
|
impl GetTx for AnyBlockchain {
|
||||||
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
|
maybe_await!(impl_inner_method!(self, get_tx, txid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[maybe_async]
|
||||||
|
impl WalletSync for AnyBlockchain {
|
||||||
|
fn wallet_sync<D: BatchDatabase>(
|
||||||
|
&self,
|
||||||
|
database: &mut D,
|
||||||
|
progress_update: Box<dyn Progress>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
maybe_await!(impl_inner_method!(
|
||||||
|
self,
|
||||||
|
wallet_sync,
|
||||||
|
database,
|
||||||
|
progress_update
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
|
&self,
|
||||||
|
database: &mut D,
|
||||||
|
progress_update: Box<dyn Progress>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
maybe_await!(impl_inner_method!(
|
||||||
|
self,
|
||||||
|
wallet_setup,
|
||||||
|
database,
|
||||||
|
progress_update
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ mod peer;
|
|||||||
mod store;
|
mod store;
|
||||||
mod sync;
|
mod sync;
|
||||||
|
|
||||||
use super::{Blockchain, Capability, ConfigurableBlockchain, Progress};
|
use crate::blockchain::*;
|
||||||
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};
|
||||||
@@ -207,7 +207,6 @@ impl CompactFiltersBlockchain {
|
|||||||
received: incoming,
|
received: incoming,
|
||||||
sent: outgoing,
|
sent: outgoing,
|
||||||
confirmation_time: BlockTime::new(height, timestamp),
|
confirmation_time: BlockTime::new(height, timestamp),
|
||||||
verified: height.is_some(),
|
|
||||||
fee: Some(inputs_sum.saturating_sub(outputs_sum)),
|
fee: Some(inputs_sum.saturating_sub(outputs_sum)),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,11 +225,38 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
vec![Capability::FullHistory].into_iter().collect()
|
vec![Capability::FullHistory].into_iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||||
|
self.peers[0].broadcast_tx(tx.clone())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
|
||||||
|
// TODO
|
||||||
|
Ok(FeeRate::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetHeight for CompactFiltersBlockchain {
|
||||||
|
fn get_height(&self) -> Result<u32, Error> {
|
||||||
|
Ok(self.headers.get_height()? as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetTx for CompactFiltersBlockchain {
|
||||||
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
|
Ok(self.peers[0]
|
||||||
|
.get_mempool()
|
||||||
|
.get_tx(&Inventory::Transaction(*txid)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletSync for CompactFiltersBlockchain {
|
||||||
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
|
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
|
||||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
progress_update: P,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let first_peer = &self.peers[0];
|
let first_peer = &self.peers[0];
|
||||||
|
|
||||||
@@ -431,27 +457,6 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
|
||||||
Ok(self.peers[0]
|
|
||||||
.get_mempool()
|
|
||||||
.get_tx(&Inventory::Transaction(*txid)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
|
||||||
self.peers[0].broadcast_tx(tx.clone())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_height(&self) -> Result<u32, Error> {
|
|
||||||
Ok(self.headers.get_height()? as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
|
|
||||||
// TODO
|
|
||||||
Ok(FeeRate::default())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data to connect to a Bitcoin P2P peer
|
/// Data to connect to a Bitcoin P2P peer
|
||||||
|
|||||||
@@ -68,10 +68,39 @@ impl Blockchain for ElectrumBlockchain {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase, P: Progress>(
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||||
|
Ok(self.client.transaction_broadcast(tx).map(|_| ())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||||
|
Ok(FeeRate::from_btc_per_kvb(
|
||||||
|
self.client.estimate_fee(target)? as f32
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetHeight for ElectrumBlockchain {
|
||||||
|
fn get_height(&self) -> Result<u32, Error> {
|
||||||
|
// TODO: unsubscribe when added to the client, or is there a better call to use here?
|
||||||
|
|
||||||
|
Ok(self
|
||||||
|
.client
|
||||||
|
.block_headers_subscribe()
|
||||||
|
.map(|data| data.height as u32)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetTx for ElectrumBlockchain {
|
||||||
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
|
Ok(self.client.transaction_get(txid).map(Option::Some)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletSync for ElectrumBlockchain {
|
||||||
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
_progress_update: P,
|
_progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut request = script_sync::start(database, self.stop_gap)?;
|
let mut request = script_sync::start(database, self.stop_gap)?;
|
||||||
let mut block_times = HashMap::<u32, u32>::new();
|
let mut block_times = HashMap::<u32, u32>::new();
|
||||||
@@ -175,6 +204,7 @@ impl Blockchain for ElectrumBlockchain {
|
|||||||
let full_details = full_transactions
|
let full_details = full_transactions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tx| {
|
.map(|tx| {
|
||||||
|
let mut input_index = 0usize;
|
||||||
let prev_outputs = tx
|
let prev_outputs = tx
|
||||||
.input
|
.input
|
||||||
.iter()
|
.iter()
|
||||||
@@ -189,6 +219,7 @@ impl Blockchain for ElectrumBlockchain {
|
|||||||
.output
|
.output
|
||||||
.get(input.previous_output.vout as usize)
|
.get(input.previous_output.vout as usize)
|
||||||
.ok_or_else(electrum_goof)?;
|
.ok_or_else(electrum_goof)?;
|
||||||
|
input_index += 1;
|
||||||
Ok(Some(txout.clone()))
|
Ok(Some(txout.clone()))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, Error>>()?;
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
@@ -205,29 +236,6 @@ impl Blockchain for ElectrumBlockchain {
|
|||||||
database.commit_batch(batch_update)?;
|
database.commit_batch(batch_update)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
|
||||||
Ok(self.client.transaction_get(txid).map(Option::Some)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
|
||||||
Ok(self.client.transaction_broadcast(tx).map(|_| ())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_height(&self) -> Result<u32, Error> {
|
|
||||||
// TODO: unsubscribe when added to the client, or is there a better call to use here?
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.client
|
|
||||||
.block_headers_subscribe()
|
|
||||||
.map(|data| data.height as u32)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
|
||||||
Ok(FeeRate::from_btc_per_kvb(
|
|
||||||
self.client.estimate_fee(target)? as f32
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TxCache<'a, 'b, D> {
|
struct TxCache<'a, 'b, D> {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub struct Vin {
|
|||||||
// None if coinbase
|
// None if coinbase
|
||||||
pub prevout: Option<PrevOut>,
|
pub prevout: Option<PrevOut>,
|
||||||
pub scriptsig: Script,
|
pub scriptsig: Script,
|
||||||
#[serde(deserialize_with = "deserialize_witness")]
|
#[serde(deserialize_with = "deserialize_witness", default)]
|
||||||
pub witness: Vec<Vec<u8>>,
|
pub witness: Vec<Vec<u8>>,
|
||||||
pub sequence: u32,
|
pub sequence: u32,
|
||||||
pub is_coinbase: bool,
|
pub is_coinbase: bool,
|
||||||
|
|||||||
@@ -91,10 +91,36 @@ impl Blockchain for EsploraBlockchain {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase, P: Progress>(
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||||
|
Ok(await_or_block!(self.url_client._broadcast(tx))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||||
|
let estimates = await_or_block!(self.url_client._get_fee_estimates())?;
|
||||||
|
super::into_fee_rate(target, estimates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[maybe_async]
|
||||||
|
impl GetHeight for EsploraBlockchain {
|
||||||
|
fn get_height(&self) -> Result<u32, Error> {
|
||||||
|
Ok(await_or_block!(self.url_client._get_height())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[maybe_async]
|
||||||
|
impl GetTx for EsploraBlockchain {
|
||||||
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
|
Ok(await_or_block!(self.url_client._get_tx(txid))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[maybe_async]
|
||||||
|
impl WalletSync for EsploraBlockchain {
|
||||||
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
_progress_update: P,
|
_progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
use crate::blockchain::script_sync::Request;
|
use crate::blockchain::script_sync::Request;
|
||||||
let mut request = script_sync::start(database, self.stop_gap)?;
|
let mut request = script_sync::start(database, self.stop_gap)?;
|
||||||
@@ -167,9 +193,9 @@ impl Blockchain for EsploraBlockchain {
|
|||||||
.request()
|
.request()
|
||||||
.map(|txid| {
|
.map(|txid| {
|
||||||
let tx = tx_index.get(txid).expect("must be in index");
|
let tx = tx_index.get(txid).expect("must be in index");
|
||||||
(tx.previous_outputs(), tx.to_tx())
|
Ok((tx.previous_outputs(), tx.to_tx()))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect::<Result<_, Error>>()?;
|
||||||
tx_req.satisfy(full_txs)?
|
tx_req.satisfy(full_txs)?
|
||||||
}
|
}
|
||||||
Request::Finish(batch_update) => break batch_update,
|
Request::Finish(batch_update) => break batch_update,
|
||||||
@@ -180,23 +206,6 @@ impl Blockchain for EsploraBlockchain {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
|
||||||
Ok(await_or_block!(self.url_client._get_tx(txid))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
|
||||||
Ok(await_or_block!(self.url_client._broadcast(tx))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_height(&self) -> Result<u32, Error> {
|
|
||||||
Ok(await_or_block!(self.url_client._get_height())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
|
||||||
let estimates = await_or_block!(self.url_client._get_fee_estimates())?;
|
|
||||||
super::into_fee_rate(target, estimates)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UrlClient {
|
impl UrlClient {
|
||||||
|
|||||||
@@ -87,10 +87,34 @@ impl Blockchain for EsploraBlockchain {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase, P: Progress>(
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||||
|
let _txid = self.url_client._broadcast(tx)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||||
|
let estimates = self.url_client._get_fee_estimates()?;
|
||||||
|
super::into_fee_rate(target, estimates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetHeight for EsploraBlockchain {
|
||||||
|
fn get_height(&self) -> Result<u32, Error> {
|
||||||
|
Ok(self.url_client._get_height()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetTx for EsploraBlockchain {
|
||||||
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
|
Ok(self.url_client._get_tx(txid)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletSync for EsploraBlockchain {
|
||||||
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
_progress_update: P,
|
_progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
use crate::blockchain::script_sync::Request;
|
use crate::blockchain::script_sync::Request;
|
||||||
let mut request = script_sync::start(database, self.stop_gap)?;
|
let mut request = script_sync::start(database, self.stop_gap)?;
|
||||||
@@ -166,9 +190,9 @@ impl Blockchain for EsploraBlockchain {
|
|||||||
.request()
|
.request()
|
||||||
.map(|txid| {
|
.map(|txid| {
|
||||||
let tx = tx_index.get(txid).expect("must be in index");
|
let tx = tx_index.get(txid).expect("must be in index");
|
||||||
(tx.previous_outputs(), tx.to_tx())
|
Ok((tx.previous_outputs(), tx.to_tx()))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect::<Result<_, Error>>()?;
|
||||||
tx_req.satisfy(full_txs)?
|
tx_req.satisfy(full_txs)?
|
||||||
}
|
}
|
||||||
Request::Finish(batch_update) => break batch_update,
|
Request::Finish(batch_update) => break batch_update,
|
||||||
@@ -179,24 +203,6 @@ impl Blockchain for EsploraBlockchain {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
|
||||||
Ok(self.url_client._get_tx(txid)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
|
||||||
let _txid = self.url_client._broadcast(tx)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_height(&self) -> Result<u32, Error> {
|
|
||||||
Ok(self.url_client._get_height()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
|
||||||
let estimates = self.url_client._get_fee_estimates()?;
|
|
||||||
super::into_fee_rate(target, estimates)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UrlClient {
|
impl UrlClient {
|
||||||
|
|||||||
@@ -86,28 +86,50 @@ pub enum Capability {
|
|||||||
|
|
||||||
/// Trait that defines the actions that must be supported by a blockchain backend
|
/// Trait that defines the actions that must be supported by a blockchain backend
|
||||||
#[maybe_async]
|
#[maybe_async]
|
||||||
pub trait Blockchain {
|
pub trait Blockchain: WalletSync + GetHeight + GetTx {
|
||||||
/// Return the set of [`Capability`] supported by this backend
|
/// Return the set of [`Capability`] supported by this backend
|
||||||
fn get_capabilities(&self) -> HashSet<Capability>;
|
fn get_capabilities(&self) -> HashSet<Capability>;
|
||||||
|
/// Broadcast a transaction
|
||||||
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
|
||||||
|
/// Estimate the fee rate required to confirm a transaction in a given `target` of blocks
|
||||||
|
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for getting the current height of the blockchain.
|
||||||
|
#[maybe_async]
|
||||||
|
pub trait GetHeight {
|
||||||
|
/// Return the current height
|
||||||
|
fn get_height(&self) -> Result<u32, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[maybe_async]
|
||||||
|
/// Trait for getting a transaction by txid
|
||||||
|
pub trait GetTx {
|
||||||
|
/// Fetch a transaction given its txid
|
||||||
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for blockchains that can sync by updating the database directly.
|
||||||
|
#[maybe_async]
|
||||||
|
pub trait WalletSync {
|
||||||
/// Setup the backend and populate the internal database for the first time
|
/// Setup the backend and populate the internal database for the first time
|
||||||
///
|
///
|
||||||
/// This method is the equivalent of [`Blockchain::sync`], but it's guaranteed to only be
|
/// This method is the equivalent of [`Self::wallet_sync`], but it's guaranteed to only be
|
||||||
/// called once, at the first [`Wallet::sync`](crate::wallet::Wallet::sync).
|
/// called once, at the first [`Wallet::sync`](crate::wallet::Wallet::sync).
|
||||||
///
|
///
|
||||||
/// The rationale behind the distinction between `sync` and `setup` is that some custom backends
|
/// The rationale behind the distinction between `sync` and `setup` is that some custom backends
|
||||||
/// might need to perform specific actions only the first time they are synced.
|
/// might need to perform specific actions only the first time they are synced.
|
||||||
///
|
///
|
||||||
/// For types that do not have that distinction, only this method can be implemented, since
|
/// For types that do not have that distinction, only this method can be implemented, since
|
||||||
/// [`Blockchain::sync`] defaults to calling this internally if not overridden.
|
/// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden.
|
||||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
/// Populate the internal database with transactions and UTXOs
|
||||||
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
progress_update: P,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
/// Populate the internal database with transactions and UTXOs
|
|
||||||
///
|
/// If not overridden, it defaults to calling [`Self::wallet_setup`] internally.
|
||||||
/// If not overridden, it defaults to calling [`Blockchain::setup`] internally.
|
|
||||||
///
|
///
|
||||||
/// This method should implement the logic required to iterate over the list of the wallet's
|
/// This method should implement the logic required to iterate over the list of the wallet's
|
||||||
/// script_pubkeys using [`Database::iter_script_pubkeys`] and look for relevant transactions
|
/// script_pubkeys using [`Database::iter_script_pubkeys`] and look for relevant transactions
|
||||||
@@ -124,23 +146,13 @@ pub trait Blockchain {
|
|||||||
/// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx
|
/// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx
|
||||||
/// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo
|
/// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo
|
||||||
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
|
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
|
||||||
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
fn wallet_sync<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
progress_update: P,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
maybe_await!(self.setup(database, progress_update))
|
maybe_await!(self.wallet_setup(database, progress_update))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch a transaction from the blockchain given its txid
|
|
||||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
|
|
||||||
/// Broadcast a transaction
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
|
|
||||||
|
|
||||||
/// Return the current height
|
|
||||||
fn get_height(&self) -> Result<u32, Error>;
|
|
||||||
/// Estimate the fee rate required to confirm a transaction in a given `target` of blocks
|
|
||||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for [`Blockchain`] types that can be created given a configuration
|
/// Trait for [`Blockchain`] types that can be created given a configuration
|
||||||
@@ -155,9 +167,9 @@ pub trait ConfigurableBlockchain: Blockchain + Sized {
|
|||||||
/// Data sent with a progress update over a [`channel`]
|
/// Data sent with a progress update over a [`channel`]
|
||||||
pub type ProgressData = (f32, Option<String>);
|
pub type ProgressData = (f32, Option<String>);
|
||||||
|
|
||||||
/// Trait for types that can receive and process progress updates during [`Blockchain::sync`] and
|
/// Trait for types that can receive and process progress updates during [`WalletSync::wallet_sync`] and
|
||||||
/// [`Blockchain::setup`]
|
/// [`WalletSync::wallet_setup`]
|
||||||
pub trait Progress: Send {
|
pub trait Progress: Send + 'static + core::fmt::Debug {
|
||||||
/// Send a new progress update
|
/// Send a new progress update
|
||||||
///
|
///
|
||||||
/// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an
|
/// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an
|
||||||
@@ -182,7 +194,7 @@ impl Progress for Sender<ProgressData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Type that implements [`Progress`] and drops every update received
|
/// Type that implements [`Progress`] and drops every update received
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
pub struct NoopProgress;
|
pub struct NoopProgress;
|
||||||
|
|
||||||
/// Create a new instance of [`NoopProgress`]
|
/// Create a new instance of [`NoopProgress`]
|
||||||
@@ -197,7 +209,7 @@ impl Progress for NoopProgress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Type that implements [`Progress`] and logs at level `INFO` every update received
|
/// Type that implements [`Progress`] and logs at level `INFO` every update received
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
pub struct LogProgress;
|
pub struct LogProgress;
|
||||||
|
|
||||||
/// Create a new instance of [`LogProgress`]
|
/// Create a new instance of [`LogProgress`]
|
||||||
@@ -223,33 +235,44 @@ impl<T: Blockchain> Blockchain for Arc<T> {
|
|||||||
maybe_await!(self.deref().get_capabilities())
|
maybe_await!(self.deref().get_capabilities())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
|
||||||
&self,
|
|
||||||
database: &mut D,
|
|
||||||
progress_update: P,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
maybe_await!(self.deref().setup(database, progress_update))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
|
||||||
&self,
|
|
||||||
database: &mut D,
|
|
||||||
progress_update: P,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
maybe_await!(self.deref().sync(database, progress_update))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
|
||||||
maybe_await!(self.deref().get_tx(txid))
|
|
||||||
}
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||||
maybe_await!(self.deref().broadcast(tx))
|
maybe_await!(self.deref().broadcast(tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_height(&self) -> Result<u32, Error> {
|
|
||||||
maybe_await!(self.deref().get_height())
|
|
||||||
}
|
|
||||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||||
maybe_await!(self.deref().estimate_fee(target))
|
maybe_await!(self.deref().estimate_fee(target))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[maybe_async]
|
||||||
|
impl<T: GetTx> GetTx for Arc<T> {
|
||||||
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
|
maybe_await!(self.deref().get_tx(txid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[maybe_async]
|
||||||
|
impl<T: GetHeight> GetHeight for Arc<T> {
|
||||||
|
fn get_height(&self) -> Result<u32, Error> {
|
||||||
|
maybe_await!(self.deref().get_height())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[maybe_async]
|
||||||
|
impl<T: WalletSync> WalletSync for Arc<T> {
|
||||||
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
|
&self,
|
||||||
|
database: &mut D,
|
||||||
|
progress_update: Box<dyn Progress>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
maybe_await!(self.deref().wallet_setup(database, progress_update))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wallet_sync<D: BatchDatabase>(
|
||||||
|
&self,
|
||||||
|
database: &mut D,
|
||||||
|
progress_update: Box<dyn Progress>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
maybe_await!(self.deref().wallet_sync(database, progress_update))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
use crate::bitcoin::consensus::deserialize;
|
use crate::bitcoin::consensus::deserialize;
|
||||||
use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid};
|
use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid};
|
||||||
use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress};
|
use crate::blockchain::*;
|
||||||
use crate::database::{BatchDatabase, DatabaseUtils};
|
use crate::database::{BatchDatabase, DatabaseUtils};
|
||||||
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
|
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
|
||||||
use bitcoincore_rpc::json::{
|
use bitcoincore_rpc::json::{
|
||||||
@@ -139,10 +139,39 @@ impl Blockchain for RpcBlockchain {
|
|||||||
self.capabilities.clone()
|
self.capabilities.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||||
|
Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||||
|
let sat_per_kb = self
|
||||||
|
.client
|
||||||
|
.estimate_smart_fee(target as u16, None)?
|
||||||
|
.fee_rate
|
||||||
|
.ok_or(Error::FeeRateUnavailable)?
|
||||||
|
.as_sat() as f64;
|
||||||
|
|
||||||
|
Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetTx for RpcBlockchain {
|
||||||
|
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
|
Ok(Some(self.client.get_raw_transaction(txid, None)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetHeight for RpcBlockchain {
|
||||||
|
fn get_height(&self) -> Result<u32, Error> {
|
||||||
|
Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletSync for RpcBlockchain {
|
||||||
|
fn wallet_setup<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
database: &mut D,
|
database: &mut D,
|
||||||
progress_update: P,
|
progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut scripts_pubkeys = database.iter_script_pubkeys(Some(KeychainKind::External))?;
|
let mut scripts_pubkeys = database.iter_script_pubkeys(Some(KeychainKind::External))?;
|
||||||
scripts_pubkeys.extend(database.iter_script_pubkeys(Some(KeychainKind::Internal))?);
|
scripts_pubkeys.extend(database.iter_script_pubkeys(Some(KeychainKind::Internal))?);
|
||||||
@@ -187,13 +216,13 @@ impl Blockchain for RpcBlockchain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sync(database, progress_update)
|
self.wallet_sync(database, progress_update)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
fn wallet_sync<D: BatchDatabase>(
|
||||||
&self,
|
&self,
|
||||||
db: &mut D,
|
db: &mut D,
|
||||||
_progress_update: P,
|
_progress_update: Box<dyn Progress>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut indexes = HashMap::new();
|
let mut indexes = HashMap::new();
|
||||||
for keykind in &[KeychainKind::External, KeychainKind::Internal] {
|
for keykind in &[KeychainKind::External, KeychainKind::Internal] {
|
||||||
@@ -257,7 +286,9 @@ impl Blockchain for RpcBlockchain {
|
|||||||
|
|
||||||
for input in tx.input.iter() {
|
for input in tx.input.iter() {
|
||||||
if let Some(previous_output) = db.get_previous_output(&input.previous_output)? {
|
if let Some(previous_output) = db.get_previous_output(&input.previous_output)? {
|
||||||
sent += previous_output.value;
|
if db.is_mine(&previous_output.script_pubkey)? {
|
||||||
|
sent += previous_output.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +302,6 @@ impl Blockchain for RpcBlockchain {
|
|||||||
received,
|
received,
|
||||||
sent,
|
sent,
|
||||||
fee: tx_result.fee.map(|f| f.as_sat().abs() as u64),
|
fee: tx_result.fee.map(|f| f.as_sat().abs() as u64),
|
||||||
verified: true,
|
|
||||||
};
|
};
|
||||||
debug!(
|
debug!(
|
||||||
"saving tx: {} tx_result.fee:{:?} td.fees:{:?}",
|
"saving tx: {} tx_result.fee:{:?} td.fees:{:?}",
|
||||||
@@ -288,22 +318,24 @@ impl Blockchain for RpcBlockchain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_utxos: HashSet<_> = current_utxo
|
// Filter out trasactions that are for script pubkeys that aren't in this wallet.
|
||||||
|
let current_utxos = current_utxo
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|u| {
|
.filter_map(
|
||||||
Ok(LocalUtxo {
|
|u| match db.get_path_from_script_pubkey(&u.script_pub_key) {
|
||||||
outpoint: OutPoint::new(u.txid, u.vout),
|
Err(e) => Some(Err(e)),
|
||||||
keychain: db
|
Ok(None) => None,
|
||||||
.get_path_from_script_pubkey(&u.script_pub_key)?
|
Ok(Some(path)) => Some(Ok(LocalUtxo {
|
||||||
.ok_or(Error::TransactionNotFound)?
|
outpoint: OutPoint::new(u.txid, u.vout),
|
||||||
.0,
|
keychain: path.0,
|
||||||
txout: TxOut {
|
txout: TxOut {
|
||||||
value: u.amount.as_sat(),
|
value: u.amount.as_sat(),
|
||||||
script_pubkey: u.script_pub_key,
|
script_pubkey: u.script_pub_key,
|
||||||
},
|
},
|
||||||
})
|
})),
|
||||||
})
|
},
|
||||||
.collect::<Result<_, Error>>()?;
|
)
|
||||||
|
.collect::<Result<HashSet<_>, Error>>()?;
|
||||||
|
|
||||||
let spent: HashSet<_> = known_utxos.difference(¤t_utxos).collect();
|
let spent: HashSet<_> = known_utxos.difference(¤t_utxos).collect();
|
||||||
for s in spent {
|
for s in spent {
|
||||||
@@ -323,29 +355,6 @@ impl Blockchain for RpcBlockchain {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
|
||||||
Ok(Some(self.client.get_raw_transaction(txid, None)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
|
||||||
Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_height(&self) -> Result<u32, Error> {
|
|
||||||
Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
|
||||||
let sat_per_kb = self
|
|
||||||
.client
|
|
||||||
.estimate_smart_fee(target as u16, None)?
|
|
||||||
.fee_rate
|
|
||||||
.ok_or(Error::FeeRateUnavailable)?
|
|
||||||
.as_sat() as f64;
|
|
||||||
|
|
||||||
Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurableBlockchain for RpcBlockchain {
|
impl ConfigurableBlockchain for RpcBlockchain {
|
||||||
|
|||||||
@@ -178,7 +178,9 @@ impl<'a, D: BatchDatabase> TxReq<'a, D> {
|
|||||||
let mut inputs_sum: u64 = 0;
|
let mut inputs_sum: u64 = 0;
|
||||||
let mut outputs_sum: u64 = 0;
|
let mut outputs_sum: u64 = 0;
|
||||||
|
|
||||||
for (txout, input) in vout.into_iter().zip(tx.input.iter()) {
|
for (txout, (_input_index, input)) in
|
||||||
|
vout.into_iter().zip(tx.input.iter().enumerate())
|
||||||
|
{
|
||||||
let txout = match txout {
|
let txout = match txout {
|
||||||
Some(txout) => txout,
|
Some(txout) => txout,
|
||||||
None => {
|
None => {
|
||||||
@@ -190,7 +192,19 @@ impl<'a, D: BatchDatabase> TxReq<'a, D> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Verify this input if requested via feature flag
|
||||||
|
#[cfg(feature = "verify")]
|
||||||
|
{
|
||||||
|
use crate::wallet::verify::VerifyError;
|
||||||
|
let serialized_tx = bitcoin::consensus::serialize(&tx);
|
||||||
|
bitcoinconsensus::verify(
|
||||||
|
txout.script_pubkey.to_bytes().as_ref(),
|
||||||
|
txout.value,
|
||||||
|
&serialized_tx,
|
||||||
|
_input_index,
|
||||||
|
)
|
||||||
|
.map_err(VerifyError::from)?;
|
||||||
|
}
|
||||||
inputs_sum += txout.value;
|
inputs_sum += txout.value;
|
||||||
if self.state.db.is_mine(&txout.script_pubkey)? {
|
if self.state.db.is_mine(&txout.script_pubkey)? {
|
||||||
sent += txout.value;
|
sent += txout.value;
|
||||||
@@ -214,7 +228,6 @@ impl<'a, D: BatchDatabase> TxReq<'a, D> {
|
|||||||
// we're going to fill this in later
|
// we're going to fill this in later
|
||||||
confirmation_time: None,
|
confirmation_time: None,
|
||||||
fee: Some(fee),
|
fee: Some(fee),
|
||||||
verified: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|||||||
@@ -23,12 +23,12 @@
|
|||||||
//! # use bdk::database::{AnyDatabase, MemoryDatabase};
|
//! # use bdk::database::{AnyDatabase, MemoryDatabase};
|
||||||
//! # use bdk::{Wallet};
|
//! # use bdk::{Wallet};
|
||||||
//! let memory = MemoryDatabase::default();
|
//! let memory = MemoryDatabase::default();
|
||||||
//! let wallet_memory = Wallet::new_offline("...", None, Network::Testnet, memory)?;
|
//! let wallet_memory = Wallet::new("...", None, Network::Testnet, memory)?;
|
||||||
//!
|
//!
|
||||||
//! # #[cfg(feature = "key-value-db")]
|
//! # #[cfg(feature = "key-value-db")]
|
||||||
//! # {
|
//! # {
|
||||||
//! let sled = sled::open("my-database")?.open_tree("default_tree")?;
|
//! let sled = sled::open("my-database")?.open_tree("default_tree")?;
|
||||||
//! let wallet_sled = Wallet::new_offline("...", None, Network::Testnet, sled)?;
|
//! let wallet_sled = Wallet::new("...", None, Network::Testnet, sled)?;
|
||||||
//! # }
|
//! # }
|
||||||
//! # Ok::<(), bdk::Error>(())
|
//! # Ok::<(), bdk::Error>(())
|
||||||
//! ```
|
//! ```
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
//! # use bdk::{Wallet};
|
//! # use bdk::{Wallet};
|
||||||
//! let config = serde_json::from_str("...")?;
|
//! let config = serde_json::from_str("...")?;
|
||||||
//! let database = AnyDatabase::from_config(&config)?;
|
//! let database = AnyDatabase::from_config(&config)?;
|
||||||
//! let wallet = Wallet::new_offline("...", None, Network::Testnet, database)?;
|
//! let wallet = Wallet::new("...", None, Network::Testnet, database)?;
|
||||||
//! # Ok::<(), bdk::Error>(())
|
//! # Ok::<(), bdk::Error>(())
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
|||||||
@@ -515,7 +515,6 @@ macro_rules! populate_test_db {
|
|||||||
received: 0,
|
received: 0,
|
||||||
sent: 0,
|
sent: 0,
|
||||||
confirmation_time,
|
confirmation_time,
|
||||||
verified: current_height.is_some(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
db.set_tx(&tx_details).unwrap();
|
db.set_tx(&tx_details).unwrap();
|
||||||
@@ -555,7 +554,7 @@ macro_rules! doctest_wallet {
|
|||||||
Some(100),
|
Some(100),
|
||||||
);
|
);
|
||||||
|
|
||||||
$crate::Wallet::new_offline(
|
$crate::Wallet::new(
|
||||||
&descriptors.0,
|
&descriptors.0,
|
||||||
descriptors.1.as_ref(),
|
descriptors.1.as_ref(),
|
||||||
Network::Regtest,
|
Network::Regtest,
|
||||||
|
|||||||
@@ -348,7 +348,6 @@ pub mod test {
|
|||||||
timestamp: 123456,
|
timestamp: 123456,
|
||||||
height: 1000,
|
height: 1000,
|
||||||
}),
|
}),
|
||||||
verified: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.set_tx(&tx_details).unwrap();
|
tree.set_tx(&tx_details).unwrap();
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ static MIGRATIONS: &[&str] = &[
|
|||||||
"CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain);",
|
"CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain);",
|
||||||
"CREATE TABLE checksums (keychain TEXT, checksum BLOB);",
|
"CREATE TABLE checksums (keychain TEXT, checksum BLOB);",
|
||||||
"CREATE INDEX idx_checksums_keychain ON checksums(keychain);",
|
"CREATE INDEX idx_checksums_keychain ON checksums(keychain);",
|
||||||
"CREATE TABLE sync_time (id INTEGER PRIMARY KEY, height INTEGER, timestamp INTEGER);"
|
"CREATE TABLE sync_time (id INTEGER PRIMARY KEY, height INTEGER, timestamp INTEGER);",
|
||||||
|
"ALTER TABLE transaction_details RENAME TO transaction_details_old;",
|
||||||
|
"CREATE TABLE transaction_details (txid BLOB, timestamp INTEGER, received INTEGER, sent INTEGER, fee INTEGER, height INTEGER);",
|
||||||
|
"INSERT INTO transaction_details SELECT txid, timestamp, received, sent, fee, height FROM transaction_details_old;",
|
||||||
|
"DROP TABLE transaction_details_old;",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sqlite database stored on filesystem
|
/// Sqlite database stored on filesystem
|
||||||
@@ -127,7 +131,7 @@ impl SqliteDatabase {
|
|||||||
|
|
||||||
let txid: &[u8] = &transaction.txid;
|
let txid: &[u8] = &transaction.txid;
|
||||||
|
|
||||||
let mut statement = self.connection.prepare_cached("INSERT INTO transaction_details (txid, timestamp, received, sent, fee, height, verified) VALUES (:txid, :timestamp, :received, :sent, :fee, :height, :verified)")?;
|
let mut statement = self.connection.prepare_cached("INSERT INTO transaction_details (txid, timestamp, received, sent, fee, height) VALUES (:txid, :timestamp, :received, :sent, :fee, :height)")?;
|
||||||
|
|
||||||
statement.execute(named_params! {
|
statement.execute(named_params! {
|
||||||
":txid": txid,
|
":txid": txid,
|
||||||
@@ -136,7 +140,6 @@ impl SqliteDatabase {
|
|||||||
":sent": transaction.sent,
|
":sent": transaction.sent,
|
||||||
":fee": transaction.fee,
|
":fee": transaction.fee,
|
||||||
":height": height,
|
":height": height,
|
||||||
":verified": transaction.verified
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(self.connection.last_insert_rowid())
|
Ok(self.connection.last_insert_rowid())
|
||||||
@@ -153,7 +156,7 @@ impl SqliteDatabase {
|
|||||||
|
|
||||||
let txid: &[u8] = &transaction.txid;
|
let txid: &[u8] = &transaction.txid;
|
||||||
|
|
||||||
let mut statement = self.connection.prepare_cached("UPDATE transaction_details SET timestamp=:timestamp, received=:received, sent=:sent, fee=:fee, height=:height, verified=:verified WHERE txid=:txid")?;
|
let mut statement = self.connection.prepare_cached("UPDATE transaction_details SET timestamp=:timestamp, received=:received, sent=:sent, fee=:fee, height=:height WHERE txid=:txid")?;
|
||||||
|
|
||||||
statement.execute(named_params! {
|
statement.execute(named_params! {
|
||||||
":txid": txid,
|
":txid": txid,
|
||||||
@@ -162,7 +165,6 @@ impl SqliteDatabase {
|
|||||||
":sent": transaction.sent,
|
":sent": transaction.sent,
|
||||||
":fee": transaction.fee,
|
":fee": transaction.fee,
|
||||||
":height": height,
|
":height": height,
|
||||||
":verified": transaction.verified,
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -367,7 +369,7 @@ impl SqliteDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn select_transaction_details_with_raw(&self) -> Result<Vec<TransactionDetails>, Error> {
|
fn select_transaction_details_with_raw(&self) -> Result<Vec<TransactionDetails>, Error> {
|
||||||
let mut statement = self.connection.prepare_cached("SELECT transaction_details.txid, transaction_details.timestamp, transaction_details.received, transaction_details.sent, transaction_details.fee, transaction_details.height, transaction_details.verified, transactions.raw_tx FROM transaction_details, transactions WHERE transaction_details.txid = transactions.txid")?;
|
let mut statement = self.connection.prepare_cached("SELECT transaction_details.txid, transaction_details.timestamp, transaction_details.received, transaction_details.sent, transaction_details.fee, transaction_details.height, transactions.raw_tx FROM transaction_details, transactions WHERE transaction_details.txid = transactions.txid")?;
|
||||||
let mut transaction_details: Vec<TransactionDetails> = vec![];
|
let mut transaction_details: Vec<TransactionDetails> = vec![];
|
||||||
let mut rows = statement.query([])?;
|
let mut rows = statement.query([])?;
|
||||||
while let Some(row) = rows.next()? {
|
while let Some(row) = rows.next()? {
|
||||||
@@ -378,7 +380,6 @@ impl SqliteDatabase {
|
|||||||
let sent: u64 = row.get(3)?;
|
let sent: u64 = row.get(3)?;
|
||||||
let fee: Option<u64> = row.get(4)?;
|
let fee: Option<u64> = row.get(4)?;
|
||||||
let height: Option<u32> = row.get(5)?;
|
let height: Option<u32> = row.get(5)?;
|
||||||
let verified: bool = row.get(6)?;
|
|
||||||
let raw_tx: Option<Vec<u8>> = row.get(7)?;
|
let raw_tx: Option<Vec<u8>> = row.get(7)?;
|
||||||
let tx: Option<Transaction> = match raw_tx {
|
let tx: Option<Transaction> = match raw_tx {
|
||||||
Some(raw_tx) => {
|
Some(raw_tx) => {
|
||||||
@@ -400,7 +401,6 @@ impl SqliteDatabase {
|
|||||||
sent,
|
sent,
|
||||||
fee,
|
fee,
|
||||||
confirmation_time,
|
confirmation_time,
|
||||||
verified,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(transaction_details)
|
Ok(transaction_details)
|
||||||
@@ -408,7 +408,7 @@ impl SqliteDatabase {
|
|||||||
|
|
||||||
fn select_transaction_details(&self) -> Result<Vec<TransactionDetails>, Error> {
|
fn select_transaction_details(&self) -> Result<Vec<TransactionDetails>, Error> {
|
||||||
let mut statement = self.connection.prepare_cached(
|
let mut statement = self.connection.prepare_cached(
|
||||||
"SELECT txid, timestamp, received, sent, fee, height, verified FROM transaction_details",
|
"SELECT txid, timestamp, received, sent, fee, height FROM transaction_details",
|
||||||
)?;
|
)?;
|
||||||
let mut transaction_details: Vec<TransactionDetails> = vec![];
|
let mut transaction_details: Vec<TransactionDetails> = vec![];
|
||||||
let mut rows = statement.query([])?;
|
let mut rows = statement.query([])?;
|
||||||
@@ -420,7 +420,6 @@ impl SqliteDatabase {
|
|||||||
let sent: u64 = row.get(3)?;
|
let sent: u64 = row.get(3)?;
|
||||||
let fee: Option<u64> = row.get(4)?;
|
let fee: Option<u64> = row.get(4)?;
|
||||||
let height: Option<u32> = row.get(5)?;
|
let height: Option<u32> = row.get(5)?;
|
||||||
let verified: bool = row.get(6)?;
|
|
||||||
|
|
||||||
let confirmation_time = match (height, timestamp) {
|
let confirmation_time = match (height, timestamp) {
|
||||||
(Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
|
(Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
|
||||||
@@ -434,7 +433,6 @@ impl SqliteDatabase {
|
|||||||
sent,
|
sent,
|
||||||
fee,
|
fee,
|
||||||
confirmation_time,
|
confirmation_time,
|
||||||
verified,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(transaction_details)
|
Ok(transaction_details)
|
||||||
@@ -444,7 +442,7 @@ impl SqliteDatabase {
|
|||||||
&self,
|
&self,
|
||||||
txid: &[u8],
|
txid: &[u8],
|
||||||
) -> Result<Option<TransactionDetails>, Error> {
|
) -> Result<Option<TransactionDetails>, Error> {
|
||||||
let mut statement = self.connection.prepare_cached("SELECT transaction_details.timestamp, transaction_details.received, transaction_details.sent, transaction_details.fee, transaction_details.height, transaction_details.verified, transactions.raw_tx FROM transaction_details, transactions WHERE transaction_details.txid=transactions.txid AND transaction_details.txid=:txid")?;
|
let mut statement = self.connection.prepare_cached("SELECT transaction_details.timestamp, transaction_details.received, transaction_details.sent, transaction_details.fee, transaction_details.height, transactions.raw_tx FROM transaction_details, transactions WHERE transaction_details.txid=transactions.txid AND transaction_details.txid=:txid")?;
|
||||||
let mut rows = statement.query(named_params! { ":txid": txid })?;
|
let mut rows = statement.query(named_params! { ":txid": txid })?;
|
||||||
|
|
||||||
match rows.next()? {
|
match rows.next()? {
|
||||||
@@ -454,9 +452,8 @@ impl SqliteDatabase {
|
|||||||
let sent: u64 = row.get(2)?;
|
let sent: u64 = row.get(2)?;
|
||||||
let fee: Option<u64> = row.get(3)?;
|
let fee: Option<u64> = row.get(3)?;
|
||||||
let height: Option<u32> = row.get(4)?;
|
let height: Option<u32> = row.get(4)?;
|
||||||
let verified: bool = row.get(5)?;
|
|
||||||
|
|
||||||
let raw_tx: Option<Vec<u8>> = row.get(6)?;
|
let raw_tx: Option<Vec<u8>> = row.get(5)?;
|
||||||
let tx: Option<Transaction> = match raw_tx {
|
let tx: Option<Transaction> = match raw_tx {
|
||||||
Some(raw_tx) => {
|
Some(raw_tx) => {
|
||||||
let tx: Transaction = deserialize(&raw_tx)?;
|
let tx: Transaction = deserialize(&raw_tx)?;
|
||||||
@@ -477,7 +474,6 @@ impl SqliteDatabase {
|
|||||||
sent,
|
sent,
|
||||||
fee,
|
fee,
|
||||||
confirmation_time,
|
confirmation_time,
|
||||||
verified,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
|
|||||||
@@ -17,13 +17,13 @@
|
|||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use bitcoin::util::bip32::{
|
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
|
||||||
ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource,
|
|
||||||
};
|
|
||||||
use bitcoin::util::psbt;
|
use bitcoin::util::psbt;
|
||||||
use bitcoin::{Network, PublicKey, Script, TxOut};
|
use bitcoin::{Network, PublicKey, Script, TxOut};
|
||||||
|
|
||||||
use miniscript::descriptor::{DescriptorPublicKey, DescriptorType, DescriptorXKey, Wildcard};
|
use miniscript::descriptor::{
|
||||||
|
DescriptorPublicKey, DescriptorType, DescriptorXKey, InnerXKey, Wildcard,
|
||||||
|
};
|
||||||
pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0};
|
pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0};
|
||||||
use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
|
use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
|
||||||
|
|
||||||
@@ -267,41 +267,10 @@ pub(crate) trait XKeyUtils {
|
|||||||
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint;
|
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: `InnerXKey` was made private in rust-miniscript, so we have to implement this manually on
|
impl<T> XKeyUtils for DescriptorXKey<T>
|
||||||
// both `ExtendedPubKey` and `ExtendedPrivKey`.
|
where
|
||||||
//
|
T: InnerXKey,
|
||||||
// Revert back to using the trait once https://github.com/rust-bitcoin/rust-miniscript/pull/230 is
|
{
|
||||||
// released
|
|
||||||
impl XKeyUtils for DescriptorXKey<ExtendedPubKey> {
|
|
||||||
fn full_path(&self, append: &[ChildNumber]) -> DerivationPath {
|
|
||||||
let full_path = match self.origin {
|
|
||||||
Some((_, ref path)) => path
|
|
||||||
.into_iter()
|
|
||||||
.chain(self.derivation_path.into_iter())
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
None => self.derivation_path.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.wildcard != Wildcard::None {
|
|
||||||
full_path
|
|
||||||
.into_iter()
|
|
||||||
.chain(append.iter())
|
|
||||||
.cloned()
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
full_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn root_fingerprint(&self, _: &SecpCtx) -> Fingerprint {
|
|
||||||
match self.origin {
|
|
||||||
Some((fingerprint, _)) => fingerprint,
|
|
||||||
None => self.xkey.fingerprint(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl XKeyUtils for DescriptorXKey<ExtendedPrivKey> {
|
|
||||||
fn full_path(&self, append: &[ChildNumber]) -> DerivationPath {
|
fn full_path(&self, append: &[ChildNumber]) -> DerivationPath {
|
||||||
let full_path = match self.origin {
|
let full_path = match self.origin {
|
||||||
Some((_, ref path)) => path
|
Some((_, ref path)) => path
|
||||||
@@ -326,7 +295,7 @@ impl XKeyUtils for DescriptorXKey<ExtendedPrivKey> {
|
|||||||
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint {
|
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint {
|
||||||
match self.origin {
|
match self.origin {
|
||||||
Some((fingerprint, _)) => fingerprint,
|
Some((fingerprint, _)) => fingerprint,
|
||||||
None => self.xkey.fingerprint(secp),
|
None => self.xkey.xkey_fingerprint(secp),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
|
|||||||
///
|
///
|
||||||
/// let key =
|
/// let key =
|
||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let wallet = Wallet::new_offline(
|
/// let wallet = Wallet::new(
|
||||||
/// P2Pkh(key),
|
/// P2Pkh(key),
|
||||||
/// None,
|
/// None,
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -113,7 +113,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
|
|||||||
///
|
///
|
||||||
/// let key =
|
/// let key =
|
||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let wallet = Wallet::new_offline(
|
/// let wallet = Wallet::new(
|
||||||
/// P2Wpkh_P2Sh(key),
|
/// P2Wpkh_P2Sh(key),
|
||||||
/// None,
|
/// None,
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -148,7 +148,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
|
|||||||
///
|
///
|
||||||
/// let key =
|
/// let key =
|
||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let wallet = Wallet::new_offline(
|
/// let wallet = Wallet::new(
|
||||||
/// P2Wpkh(key),
|
/// P2Wpkh(key),
|
||||||
/// None,
|
/// None,
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -186,7 +186,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
|
|||||||
/// use bdk::template::Bip44;
|
/// use bdk::template::Bip44;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||||
/// let wallet = Wallet::new_offline(
|
/// let wallet = Wallet::new(
|
||||||
/// Bip44(key.clone(), KeychainKind::External),
|
/// Bip44(key.clone(), KeychainKind::External),
|
||||||
/// Some(Bip44(key, KeychainKind::Internal)),
|
/// Some(Bip44(key, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -226,7 +226,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
|
|||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
||||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||||
/// let wallet = Wallet::new_offline(
|
/// let wallet = Wallet::new(
|
||||||
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
|
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
|
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -262,7 +262,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
|||||||
/// use bdk::template::Bip49;
|
/// use bdk::template::Bip49;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||||
/// let wallet = Wallet::new_offline(
|
/// let wallet = Wallet::new(
|
||||||
/// Bip49(key.clone(), KeychainKind::External),
|
/// Bip49(key.clone(), KeychainKind::External),
|
||||||
/// Some(Bip49(key, KeychainKind::Internal)),
|
/// Some(Bip49(key, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -302,7 +302,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
|||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
||||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||||
/// let wallet = Wallet::new_offline(
|
/// let wallet = Wallet::new(
|
||||||
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
|
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
|
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -338,7 +338,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
|||||||
/// use bdk::template::Bip84;
|
/// use bdk::template::Bip84;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||||
/// let wallet = Wallet::new_offline(
|
/// let wallet = Wallet::new(
|
||||||
/// Bip84(key.clone(), KeychainKind::External),
|
/// Bip84(key.clone(), KeychainKind::External),
|
||||||
/// Some(Bip84(key, KeychainKind::Internal)),
|
/// Some(Bip84(key, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -378,7 +378,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
|||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||||
/// let wallet = Wallet::new_offline(
|
/// let wallet = Wallet::new(
|
||||||
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
|
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
|
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
|
|||||||
23
src/lib.rs
23
src/lib.rs
@@ -44,7 +44,7 @@
|
|||||||
//! interact with the bitcoin P2P network.
|
//! interact with the bitcoin P2P network.
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! bdk = "0.16.1"
|
//! bdk = "0.17.0"
|
||||||
//! ```
|
//! ```
|
||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
feature = "electrum",
|
feature = "electrum",
|
||||||
@@ -53,22 +53,22 @@
|
|||||||
|
|
||||||
### Example
|
### Example
|
||||||
```no_run
|
```no_run
|
||||||
use bdk::Wallet;
|
use bdk::{Wallet, SyncOptions};
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
use bdk::blockchain::ElectrumBlockchain;
|
||||||
use bdk::electrum_client::Client;
|
use bdk::electrum_client::Client;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
|
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
|
||||||
|
let blockchain = ElectrumBlockchain::from(client);
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
bitcoin::Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
ElectrumBlockchain::from(client)
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None)?;
|
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||||
|
|
||||||
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
|
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
//! use bdk::wallet::AddressIndex::New;
|
//! use bdk::wallet::AddressIndex::New;
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<(), bdk::Error> {
|
//! fn main() -> Result<(), bdk::Error> {
|
||||||
//! let wallet = Wallet::new_offline(
|
//! let wallet = Wallet::new(
|
||||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
//! bitcoin::Network::Testnet,
|
//! bitcoin::Network::Testnet,
|
||||||
@@ -108,9 +108,9 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
|
|
||||||
### Example
|
### Example
|
||||||
```no_run
|
```no_run
|
||||||
use bdk::{FeeRate, Wallet};
|
use bdk::{FeeRate, Wallet, SyncOptions};
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
use bdk::blockchain::ElectrumBlockchain;
|
||||||
use bdk::electrum_client::Client;
|
use bdk::electrum_client::Client;
|
||||||
|
|
||||||
use bitcoin::consensus::serialize;
|
use bitcoin::consensus::serialize;
|
||||||
@@ -123,10 +123,10 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
bitcoin::Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
ElectrumBlockchain::from(client)
|
|
||||||
)?;
|
)?;
|
||||||
|
let blockchain = ElectrumBlockchain::from(client);
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None)?;
|
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||||
|
|
||||||
let send_to = wallet.get_address(New)?;
|
let send_to = wallet.get_address(New)?;
|
||||||
let (psbt, details) = {
|
let (psbt, details) = {
|
||||||
@@ -160,7 +160,7 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
//! use bdk::database::MemoryDatabase;
|
//! use bdk::database::MemoryDatabase;
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<(), bdk::Error> {
|
//! fn main() -> Result<(), bdk::Error> {
|
||||||
//! let wallet = Wallet::new_offline(
|
//! let wallet = Wallet::new(
|
||||||
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||||
//! bitcoin::Network::Testnet,
|
//! bitcoin::Network::Testnet,
|
||||||
@@ -272,6 +272,7 @@ pub use wallet::address_validator;
|
|||||||
pub use wallet::signer;
|
pub use wallet::signer;
|
||||||
pub use wallet::signer::SignOptions;
|
pub use wallet::signer::SignOptions;
|
||||||
pub use wallet::tx_builder::TxBuilder;
|
pub use wallet::tx_builder::TxBuilder;
|
||||||
|
pub use wallet::SyncOptions;
|
||||||
pub use wallet::Wallet;
|
pub use wallet::Wallet;
|
||||||
|
|
||||||
/// Get the version of BDK at runtime
|
/// Get the version of BDK at runtime
|
||||||
|
|||||||
@@ -90,13 +90,19 @@ impl TestClient {
|
|||||||
map.insert(out.to_address.clone(), Amount::from_sat(out.value));
|
map.insert(out.to_address.clone(), Amount::from_sat(out.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let input: Vec<_> = meta_tx
|
||||||
|
.input
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.into_raw_tx_input())
|
||||||
|
.collect();
|
||||||
|
|
||||||
if self.get_balance(None, None).unwrap() < Amount::from_sat(required_balance) {
|
if self.get_balance(None, None).unwrap() < Amount::from_sat(required_balance) {
|
||||||
panic!("Insufficient funds in bitcoind. Please generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap());
|
panic!("Insufficient funds in bitcoind. Please generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: core can't create a tx with two outputs to the same address
|
// FIXME: core can't create a tx with two outputs to the same address
|
||||||
let tx = self
|
let tx = self
|
||||||
.create_raw_transaction_hex(&[], &map, meta_tx.locktime, meta_tx.replaceable)
|
.create_raw_transaction_hex(&input, &map, meta_tx.locktime, meta_tx.replaceable)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let tx = self.fund_raw_transaction(tx, None, None).unwrap();
|
let tx = self.fund_raw_transaction(tx, None, None).unwrap();
|
||||||
let mut tx: Transaction = deserialize(&tx.hex).unwrap();
|
let mut tx: Transaction = deserialize(&tx.hex).unwrap();
|
||||||
@@ -353,12 +359,12 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
fn $_fn_name:ident ( $( $test_client:ident : &TestClient )? $(,)? ) -> $blockchain:ty $block:block) => {
|
fn $_fn_name:ident ( $( $test_client:ident : &TestClient )? $(,)? ) -> $blockchain:ty $block:block) => {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod bdk_blockchain_tests {
|
mod bdk_blockchain_tests {
|
||||||
use $crate::bitcoin::Network;
|
use $crate::bitcoin::{Transaction, Network};
|
||||||
use $crate::testutils::blockchain_tests::TestClient;
|
use $crate::testutils::blockchain_tests::TestClient;
|
||||||
use $crate::blockchain::noop_progress;
|
use $crate::blockchain::Blockchain;
|
||||||
use $crate::database::MemoryDatabase;
|
use $crate::database::MemoryDatabase;
|
||||||
use $crate::types::KeychainKind;
|
use $crate::types::KeychainKind;
|
||||||
use $crate::{Wallet, FeeRate};
|
use $crate::{Wallet, FeeRate, SyncOptions};
|
||||||
use $crate::testutils;
|
use $crate::testutils;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -369,11 +375,11 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
$block
|
$block
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_wallet_from_descriptors(descriptors: &(String, Option<String>), test_client: &TestClient) -> Wallet<$blockchain, MemoryDatabase> {
|
fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<MemoryDatabase> {
|
||||||
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new(), get_blockchain(test_client)).unwrap()
|
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_single_sig() -> (Wallet<$blockchain, MemoryDatabase>, (String, Option<String>), TestClient) {
|
fn init_single_sig() -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
|
||||||
let _ = env_logger::try_init();
|
let _ = env_logger::try_init();
|
||||||
|
|
||||||
let descriptors = testutils! {
|
let descriptors = testutils! {
|
||||||
@@ -381,13 +387,14 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let test_client = TestClient::default();
|
let test_client = TestClient::default();
|
||||||
let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
|
let blockchain = get_blockchain(&test_client);
|
||||||
|
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||||
|
|
||||||
// rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool
|
// rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool
|
||||||
#[cfg(feature = "test-rpc")]
|
#[cfg(feature = "test-rpc")]
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
(wallet, descriptors, test_client)
|
(wallet, blockchain, descriptors, test_client)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -395,7 +402,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
|
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
let tx = testutils! {
|
let tx = testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
@@ -408,7 +415,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
#[cfg(not(feature = "test-rpc"))]
|
#[cfg(not(feature = "test-rpc"))]
|
||||||
assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none");
|
assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none");
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated");
|
assert!(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");
|
||||||
@@ -423,7 +430,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_stop_gap_20() {
|
fn test_sync_stop_gap_20() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 5) => 50_000 )
|
@tx ( (@external descriptors, 5) => 50_000 )
|
||||||
@@ -432,7 +439,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
@tx ( (@external descriptors, 25) => 50_000 )
|
@tx ( (@external descriptors, 25) => 50_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
||||||
@@ -440,16 +447,16 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_before_and_after_receive() {
|
fn test_sync_before_and_after_receive() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 0);
|
assert_eq!(wallet.get_balance().unwrap(), 0);
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||||
@@ -457,13 +464,13 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_multiple_outputs_same_tx() {
|
fn test_sync_multiple_outputs_same_tx() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
let txid = test_client.receive(testutils! {
|
let txid = test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 )
|
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||||
@@ -478,7 +485,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_receive_multi() {
|
fn test_sync_receive_multi() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
@@ -487,7 +494,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
@tx ( (@external descriptors, 5) => 25_000 )
|
@tx ( (@external descriptors, 5) => 25_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
||||||
@@ -496,32 +503,32 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_address_reuse() {
|
fn test_sync_address_reuse() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 25_000 )
|
@tx ( (@external descriptors, 0) => 25_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_receive_rbf_replaced() {
|
fn test_sync_receive_rbf_replaced() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
let txid = test_client.receive(testutils! {
|
let txid = test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true )
|
@tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||||
@@ -535,7 +542,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
let new_txid = test_client.bump_fee(&txid);
|
let new_txid = test_client.bump_fee(&txid);
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump");
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump");
|
||||||
@@ -553,13 +560,13 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
#[cfg(not(feature = "esplora"))]
|
#[cfg(not(feature = "esplora"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_reorg_block() {
|
fn test_sync_reorg_block() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
let txid = test_client.receive(testutils! {
|
let txid = test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true )
|
@tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||||
@@ -572,7 +579,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
// Invalidate 1 block
|
// Invalidate 1 block
|
||||||
test_client.invalidate(1);
|
test_client.invalidate(1);
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate");
|
||||||
|
|
||||||
@@ -583,15 +590,15 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_after_send() {
|
fn test_sync_after_send() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
println!("{}", descriptors.0);
|
println!("{}", descriptors.0);
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
@@ -601,8 +608,8 @@ 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();
|
blockchain.broadcast(&tx).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
|
||||||
|
|
||||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
||||||
@@ -613,24 +620,24 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
/// The coins should only be received once!
|
/// The coins should only be received once!
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_double_receive() {
|
fn test_sync_double_receive() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None), &test_client);
|
let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None));
|
||||||
// need to sync so rpc can start watching
|
// need to sync so rpc can start watching
|
||||||
receiver_wallet.sync(noop_progress(), None).unwrap();
|
receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
|
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).expect("sync");
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||||
let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
|
let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
|
||||||
|
|
||||||
let tx1 = {
|
let tx1 = {
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf();
|
builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf();
|
||||||
let (mut psbt, _details) = builder.finish().unwrap();
|
let (mut psbt, _details) = builder.finish().expect("building first tx");
|
||||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing first tx");
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
psbt.extract_tx()
|
psbt.extract_tx()
|
||||||
};
|
};
|
||||||
@@ -638,22 +645,22 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let tx2 = {
|
let tx2 = {
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf().fee_rate(FeeRate::from_sat_per_vb(5.0));
|
builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf().fee_rate(FeeRate::from_sat_per_vb(5.0));
|
||||||
let (mut psbt, _details) = builder.finish().unwrap();
|
let (mut psbt, _details) = builder.finish().expect("building replacement tx");
|
||||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing replacement tx");
|
||||||
assert!(finalized, "Cannot finalize transaction");
|
assert!(finalized, "Cannot finalize transaction");
|
||||||
psbt.extract_tx()
|
psbt.extract_tx()
|
||||||
};
|
};
|
||||||
|
|
||||||
wallet.broadcast(&tx1).unwrap();
|
blockchain.broadcast(&tx1).expect("broadcasting first");
|
||||||
wallet.broadcast(&tx2).unwrap();
|
blockchain.broadcast(&tx2).expect("broadcasting replacement");
|
||||||
|
|
||||||
receiver_wallet.sync(noop_progress(), None).unwrap();
|
receiver_wallet.sync(&blockchain, SyncOptions::default()).expect("syncing receiver");
|
||||||
assert_eq!(receiver_wallet.get_balance().unwrap(), 49_000, "should have received coins once and only once");
|
assert_eq!(receiver_wallet.get_balance().expect("balance"), 49_000, "should have received coins once and only once");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_many_sends_to_a_single_address() {
|
fn test_sync_many_sends_to_a_single_address() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
for _ in 0..4 {
|
for _ in 0..4 {
|
||||||
// split this up into multiple blocks so rpc doesn't get angry
|
// split this up into multiple blocks so rpc doesn't get angry
|
||||||
@@ -672,22 +679,22 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 100_000);
|
assert_eq!(wallet.get_balance().unwrap(), 100_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_confirmation_time_after_generate() {
|
fn test_update_confirmation_time_after_generate() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
println!("{}", descriptors.0);
|
println!("{}", descriptors.0);
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
let received_txid = test_client.receive(testutils! {
|
let received_txid = test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||||
@@ -695,7 +702,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
assert!(details.confirmation_time.is_none());
|
assert!(details.confirmation_time.is_none());
|
||||||
|
|
||||||
test_client.generate(1, Some(node_addr));
|
test_client.generate(1, Some(node_addr));
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||||
let details = tx_map.get(&received_txid).unwrap();
|
let details = tx_map.get(&received_txid).unwrap();
|
||||||
@@ -705,13 +712,13 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_outgoing_from_scratch() {
|
fn test_sync_outgoing_from_scratch() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
let received_txid = test_client.receive(testutils! {
|
let received_txid = test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
@@ -720,25 +727,26 @@ 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_tx = psbt.extract_tx();
|
||||||
|
blockchain.broadcast(&sent_tx).unwrap();
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
|
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
|
||||||
|
|
||||||
// empty wallet
|
// empty wallet
|
||||||
let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
|
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||||
|
|
||||||
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
|
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
|
||||||
test_client.generate(1, Some(node_addr));
|
test_client.generate(1, Some(node_addr));
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||||
|
|
||||||
let received = tx_map.get(&received_txid).unwrap();
|
let received = tx_map.get(&received_txid).unwrap();
|
||||||
assert_eq!(received.received, 50_000, "incorrect received from receiver");
|
assert_eq!(received.received, 50_000, "incorrect received from receiver");
|
||||||
assert_eq!(received.sent, 0, "incorrect sent from receiver");
|
assert_eq!(received.sent, 0, "incorrect sent from receiver");
|
||||||
|
|
||||||
let sent = tx_map.get(&sent_txid).unwrap();
|
let sent = tx_map.get(&sent_tx.txid()).unwrap();
|
||||||
assert_eq!(sent.received, details.received, "incorrect received from sender");
|
assert_eq!(sent.received, details.received, "incorrect received from sender");
|
||||||
assert_eq!(sent.sent, details.sent, "incorrect sent from sender");
|
assert_eq!(sent.sent, details.sent, "incorrect sent from sender");
|
||||||
assert_eq!(sent.fee.unwrap_or(0), details.fee.unwrap_or(0), "incorrect fees from sender");
|
assert_eq!(sent.fee.unwrap_or(0), details.fee.unwrap_or(0), "incorrect fees from sender");
|
||||||
@@ -746,14 +754,14 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_long_change_chain() {
|
fn test_sync_long_change_chain() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut total_sent = 0;
|
let mut total_sent = 0;
|
||||||
@@ -763,38 +771,38 @@ 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();
|
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
total_sent += 5_000 + details.fee.unwrap_or(0);
|
total_sent += 5_000 + details.fee.unwrap_or(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain");
|
||||||
|
|
||||||
// empty wallet
|
// empty wallet
|
||||||
|
|
||||||
let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
|
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||||
|
|
||||||
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
|
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
|
||||||
test_client.generate(1, Some(node_addr));
|
test_client.generate(1, Some(node_addr));
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_bump_fee_basic() {
|
fn test_sync_bump_fee_basic() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
|
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
@@ -802,18 +810,18 @@ 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();
|
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 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");
|
||||||
|
|
||||||
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
let mut builder = wallet.build_fee_bump(details.txid).unwrap();
|
||||||
builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
|
builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
|
||||||
let (mut new_psbt, new_details) = builder.finish().unwrap();
|
let (mut new_psbt, new_details) = builder.finish().expect("fee bump tx");
|
||||||
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();
|
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
|
assert_eq!(wallet.get_balance().unwrap(), 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");
|
||||||
|
|
||||||
@@ -822,14 +830,14 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_bump_fee_remove_change() {
|
fn test_sync_bump_fee_remove_change() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
|
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
@@ -837,8 +845,8 @@ 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();
|
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
assert_eq!(wallet.get_balance().unwrap(), 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");
|
||||||
|
|
||||||
@@ -847,8 +855,8 @@ 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();
|
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).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");
|
||||||
|
|
||||||
@@ -857,14 +865,14 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_bump_fee_add_input_simple() {
|
fn test_sync_bump_fee_add_input_simple() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
|
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
@@ -872,8 +880,8 @@ 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();
|
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
assert_eq!(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");
|
||||||
|
|
||||||
@@ -882,22 +890,22 @@ 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();
|
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).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");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_bump_fee_add_input_no_change() {
|
fn test_sync_bump_fee_add_input_no_change() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
|
|
||||||
test_client.receive(testutils! {
|
test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
|
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
@@ -905,8 +913,8 @@ 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();
|
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
assert_eq!(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");
|
||||||
|
|
||||||
@@ -917,8 +925,8 @@ 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();
|
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).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");
|
||||||
@@ -927,13 +935,13 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_data() {
|
fn test_add_data() {
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
let node_addr = test_client.get_node_address(None);
|
let node_addr = test_client.get_node_address(None);
|
||||||
let _ = test_client.receive(testutils! {
|
let _ = test_client.receive(testutils! {
|
||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
@@ -946,22 +954,22 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
let serialized_tx = bitcoin::consensus::encode::serialize(&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");
|
assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction");
|
||||||
let sent_txid = wallet.broadcast(&tx).unwrap();
|
blockchain.broadcast(&tx).unwrap();
|
||||||
test_client.generate(1, Some(node_addr));
|
test_client.generate(1, Some(node_addr));
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
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 = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||||
let _ = tx_map.get(&sent_txid).unwrap();
|
let _ = tx_map.get(&tx.txid()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sync_receive_coinbase() {
|
fn test_sync_receive_coinbase() {
|
||||||
let (wallet, _, mut test_client) = init_single_sig();
|
let (wallet, blockchain, _, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
|
let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance");
|
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance");
|
||||||
|
|
||||||
test_client.generate(1, Some(wallet_addr));
|
test_client.generate(1, Some(wallet_addr));
|
||||||
@@ -974,7 +982,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase");
|
assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,7 +995,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
use bitcoincore_rpc::jsonrpc::serde_json::Value;
|
use bitcoincore_rpc::jsonrpc::serde_json::Value;
|
||||||
use bitcoincore_rpc::{Auth, Client, RpcApi};
|
use bitcoincore_rpc::{Auth, Client, RpcApi};
|
||||||
|
|
||||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||||
|
|
||||||
// TODO remove once rust-bitcoincore-rpc with PR 199 released
|
// TODO remove once rust-bitcoincore-rpc with PR 199 released
|
||||||
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199
|
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199
|
||||||
@@ -1050,7 +1058,7 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
@tx ( (@external descriptors, 0) => 50_000 )
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
});
|
});
|
||||||
|
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance");
|
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
|
// 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet
|
||||||
@@ -1061,8 +1069,8 @@ 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, "wallet cannot finalize transaction");
|
assert!(finalized, "wallet cannot finalize transaction");
|
||||||
let tx = psbt.extract_tx();
|
let tx = psbt.extract_tx();
|
||||||
wallet.broadcast(&tx).unwrap();
|
blockchain.broadcast(&tx).unwrap();
|
||||||
wallet.sync(noop_progress(), None).unwrap();
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "wallet has incorrect balance after send");
|
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_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");
|
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents");
|
||||||
@@ -1073,6 +1081,79 @@ macro_rules! bdk_blockchain_tests {
|
|||||||
let taproot_balance = taproot_wallet_client.get_balance(None, None).unwrap();
|
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");
|
assert_eq!(taproot_balance.as_sat(), 25_000, "node has incorrect taproot wallet balance");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tx_chain() {
|
||||||
|
use bitcoincore_rpc::RpcApi;
|
||||||
|
use bitcoin::consensus::encode::deserialize;
|
||||||
|
use $crate::wallet::AddressIndex;
|
||||||
|
|
||||||
|
// Here we want to test that we set correctly the send and receive
|
||||||
|
// fields in the transaction object. For doing so, we create two
|
||||||
|
// different txs, the second one spending from the first:
|
||||||
|
// 1.
|
||||||
|
// Core (#1) -> Core (#2)
|
||||||
|
// -> Us (#3)
|
||||||
|
// 2.
|
||||||
|
// Core (#2) -> Us (#4)
|
||||||
|
|
||||||
|
let (wallet, blockchain, _, mut test_client) = init_single_sig();
|
||||||
|
let bdk_address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||||
|
let core_address = test_client.get_new_address(None, None).unwrap();
|
||||||
|
let tx = testutils! {
|
||||||
|
@tx ( (@addr bdk_address.clone()) => 50_000, (@addr core_address.clone()) => 40_000 )
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tx one: from Core #1 to Core #2 and Us #3.
|
||||||
|
let txid_1 = test_client.receive(tx);
|
||||||
|
let tx_1: Transaction = deserialize(&test_client.get_transaction(&txid_1, None).unwrap().hex).unwrap();
|
||||||
|
let vout_1 = tx_1.output.into_iter().position(|o| o.script_pubkey == core_address.script_pubkey()).unwrap() as u32;
|
||||||
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
let tx_1 = wallet.list_transactions(false).unwrap().into_iter().find(|tx| tx.txid == txid_1).unwrap();
|
||||||
|
assert_eq!(tx_1.received, 50_000);
|
||||||
|
assert_eq!(tx_1.sent, 0);
|
||||||
|
|
||||||
|
// Tx two: from Core #2 to Us #4.
|
||||||
|
let tx = testutils! {
|
||||||
|
@tx ( (@addr bdk_address) => 10_000 ) ( @inputs (txid_1,vout_1))
|
||||||
|
};
|
||||||
|
let txid_2 = test_client.receive(tx);
|
||||||
|
|
||||||
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
let tx_2 = wallet.list_transactions(false).unwrap().into_iter().find(|tx| tx.txid == txid_2).unwrap();
|
||||||
|
assert_eq!(tx_2.received, 10_000);
|
||||||
|
assert_eq!(tx_2.sent, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_send_receive_pkh() {
|
||||||
|
let descriptors = ("pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None);
|
||||||
|
let mut test_client = TestClient::default();
|
||||||
|
let blockchain = get_blockchain(&test_client);
|
||||||
|
|
||||||
|
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||||
|
#[cfg(feature = "test-rpc")]
|
||||||
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
|
let _ = test_client.receive(testutils! {
|
||||||
|
@tx ( (@external descriptors, 0) => 50_000 )
|
||||||
|
});
|
||||||
|
|
||||||
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
||||||
|
|
||||||
|
let tx = {
|
||||||
|
let mut builder = wallet.build_tx();
|
||||||
|
builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000);
|
||||||
|
let (mut psbt, _details) = builder.finish().unwrap();
|
||||||
|
wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||||
|
psbt.extract_tx()
|
||||||
|
};
|
||||||
|
blockchain.broadcast(&tx).unwrap();
|
||||||
|
|
||||||
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,37 @@
|
|||||||
pub mod blockchain_tests;
|
pub mod blockchain_tests;
|
||||||
|
|
||||||
use bitcoin::secp256k1::{Secp256k1, Verification};
|
use bitcoin::secp256k1::{Secp256k1, Verification};
|
||||||
use bitcoin::{Address, PublicKey};
|
use bitcoin::{Address, PublicKey, Txid};
|
||||||
|
|
||||||
use miniscript::descriptor::DescriptorPublicKey;
|
use miniscript::descriptor::DescriptorPublicKey;
|
||||||
use miniscript::{Descriptor, MiniscriptKey, TranslatePk};
|
use miniscript::{Descriptor, MiniscriptKey, TranslatePk};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TestIncomingInput {
|
||||||
|
pub txid: Txid,
|
||||||
|
pub vout: u32,
|
||||||
|
pub sequence: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestIncomingInput {
|
||||||
|
pub fn new(txid: Txid, vout: u32, sequence: Option<u32>) -> Self {
|
||||||
|
Self {
|
||||||
|
txid,
|
||||||
|
vout,
|
||||||
|
sequence,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "test-blockchains")]
|
||||||
|
pub fn into_raw_tx_input(self) -> bitcoincore_rpc::json::CreateRawTransactionInput {
|
||||||
|
bitcoincore_rpc::json::CreateRawTransactionInput {
|
||||||
|
txid: self.txid,
|
||||||
|
vout: self.vout,
|
||||||
|
sequence: self.sequence,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TestIncomingOutput {
|
pub struct TestIncomingOutput {
|
||||||
pub value: u64,
|
pub value: u64,
|
||||||
@@ -37,6 +63,7 @@ impl TestIncomingOutput {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TestIncomingTx {
|
pub struct TestIncomingTx {
|
||||||
|
pub input: Vec<TestIncomingInput>,
|
||||||
pub output: Vec<TestIncomingOutput>,
|
pub output: Vec<TestIncomingOutput>,
|
||||||
pub min_confirmations: Option<u64>,
|
pub min_confirmations: Option<u64>,
|
||||||
pub locktime: Option<i64>,
|
pub locktime: Option<i64>,
|
||||||
@@ -45,12 +72,14 @@ pub struct TestIncomingTx {
|
|||||||
|
|
||||||
impl TestIncomingTx {
|
impl TestIncomingTx {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
input: Vec<TestIncomingInput>,
|
||||||
output: Vec<TestIncomingOutput>,
|
output: Vec<TestIncomingOutput>,
|
||||||
min_confirmations: Option<u64>,
|
min_confirmations: Option<u64>,
|
||||||
locktime: Option<i64>,
|
locktime: Option<i64>,
|
||||||
replaceable: Option<bool>,
|
replaceable: Option<bool>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
input,
|
||||||
output,
|
output,
|
||||||
min_confirmations,
|
min_confirmations,
|
||||||
locktime,
|
locktime,
|
||||||
@@ -58,6 +87,10 @@ impl TestIncomingTx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_input(&mut self, input: TestIncomingInput) {
|
||||||
|
self.input.push(input);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_output(&mut self, output: TestIncomingOutput) {
|
pub fn add_output(&mut self, output: TestIncomingOutput) {
|
||||||
self.output.push(output);
|
self.output.push(output);
|
||||||
}
|
}
|
||||||
@@ -123,16 +156,21 @@ macro_rules! testutils {
|
|||||||
});
|
});
|
||||||
( @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) });
|
||||||
|
( @addr $addr:expr ) => ({ $addr });
|
||||||
|
|
||||||
( @tx ( $( ( $( $addr:tt )* ) => $amount:expr ),+ ) $( ( @locktime $locktime:expr ) )? $( ( @confirmations $confirmations:expr ) )? $( ( @replaceable $replaceable:expr ) )? ) => ({
|
( @tx ( $( ( $( $addr:tt )* ) => $amount:expr ),+ ) $( ( @inputs $( ($txid:expr, $vout:expr) ),+ ) )? $( ( @locktime $locktime:expr ) )? $( ( @confirmations $confirmations:expr ) )? $( ( @replaceable $replaceable:expr ) )? ) => ({
|
||||||
let outs = vec![$( $crate::testutils::TestIncomingOutput::new($amount, testutils!( $($addr)* ))),+];
|
let outs = vec![$( $crate::testutils::TestIncomingOutput::new($amount, testutils!( $($addr)* ))),+];
|
||||||
|
let _ins: Vec<$crate::testutils::TestIncomingInput> = vec![];
|
||||||
|
$(
|
||||||
|
let _ins = vec![$( $crate::testutils::TestIncomingInput { txid: $txid, vout: $vout, sequence: None }),+];
|
||||||
|
)?
|
||||||
|
|
||||||
let locktime = None::<i64>$(.or(Some($locktime)))?;
|
let locktime = None::<i64>$(.or(Some($locktime)))?;
|
||||||
|
|
||||||
let min_confirmations = None::<u64>$(.or(Some($confirmations)))?;
|
let min_confirmations = None::<u64>$(.or(Some($confirmations)))?;
|
||||||
let replaceable = None::<bool>$(.or(Some($replaceable)))?;
|
let replaceable = None::<bool>$(.or(Some($replaceable)))?;
|
||||||
|
|
||||||
$crate::testutils::TestIncomingTx::new(outs, min_confirmations, locktime, replaceable)
|
$crate::testutils::TestIncomingTx::new(_ins, outs, min_confirmations, locktime, replaceable)
|
||||||
});
|
});
|
||||||
|
|
||||||
( @literal $key:expr ) => ({
|
( @literal $key:expr ) => ({
|
||||||
|
|||||||
@@ -211,15 +211,6 @@ pub struct TransactionDetails {
|
|||||||
/// 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<BlockTime>,
|
pub confirmation_time: Option<BlockTime>,
|
||||||
/// Whether the tx has been verified against the consensus rules
|
|
||||||
///
|
|
||||||
/// Confirmed txs are considered "verified" by default, while unconfirmed txs are checked to
|
|
||||||
/// ensure an unstrusted [`Blockchain`](crate::blockchain::Blockchain) backend can't trick the
|
|
||||||
/// wallet into using an invalid tx as an RBF template.
|
|
||||||
///
|
|
||||||
/// The check is only performed when the `verify` feature is enabled.
|
|
||||||
#[serde(default = "bool::default")] // default to `false` if not specified
|
|
||||||
pub verified: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block height and timestamp of a block
|
/// Block height and timestamp of a block
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||||
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||||
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
|
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
|
||||||
//!
|
//!
|
||||||
//! let address = wallet.get_address(New)?;
|
//! let address = wallet.get_address(New)?;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
//! }"#;
|
//! }"#;
|
||||||
//!
|
//!
|
||||||
//! let import = WalletExport::from_str(import)?;
|
//! let import = WalletExport::from_str(import)?;
|
||||||
//! let wallet = Wallet::new_offline(
|
//! let wallet = Wallet::new(
|
||||||
//! &import.descriptor(),
|
//! &import.descriptor(),
|
||||||
//! import.change_descriptor().as_ref(),
|
//! import.change_descriptor().as_ref(),
|
||||||
//! Network::Testnet,
|
//! Network::Testnet,
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
//! # use bdk::database::*;
|
//! # use bdk::database::*;
|
||||||
//! # use bdk::wallet::export::*;
|
//! # use bdk::wallet::export::*;
|
||||||
//! # use bdk::*;
|
//! # use bdk::*;
|
||||||
//! let wallet = Wallet::new_offline(
|
//! let wallet = Wallet::new(
|
||||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
|
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
|
||||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
|
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
|
||||||
//! Network::Testnet,
|
//! Network::Testnet,
|
||||||
@@ -111,8 +111,8 @@ impl WalletExport {
|
|||||||
///
|
///
|
||||||
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
|
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
|
||||||
/// returned will be `0`.
|
/// returned will be `0`.
|
||||||
pub fn export_wallet<B, D: BatchDatabase>(
|
pub fn export_wallet<D: BatchDatabase>(
|
||||||
wallet: &Wallet<B, D>,
|
wallet: &Wallet<D>,
|
||||||
label: &str,
|
label: &str,
|
||||||
include_blockheight: bool,
|
include_blockheight: bool,
|
||||||
) -> Result<Self, &'static str> {
|
) -> Result<Self, &'static str> {
|
||||||
@@ -230,7 +230,6 @@ mod test {
|
|||||||
timestamp: 12345678,
|
timestamp: 12345678,
|
||||||
height: 5000,
|
height: 5000,
|
||||||
}),
|
}),
|
||||||
verified: true,
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -242,7 +241,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||||
|
|
||||||
let wallet = Wallet::new_offline(
|
let wallet = Wallet::new(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Bitcoin,
|
Network::Bitcoin,
|
||||||
@@ -266,8 +265,7 @@ mod test {
|
|||||||
|
|
||||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||||
|
|
||||||
let wallet =
|
let wallet = Wallet::new(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
|
||||||
Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
|
|
||||||
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +278,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
|
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
|
||||||
|
|
||||||
let wallet = Wallet::new_offline(
|
let wallet = Wallet::new(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Bitcoin,
|
Network::Bitcoin,
|
||||||
@@ -303,7 +301,7 @@ mod test {
|
|||||||
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
|
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
|
||||||
))";
|
))";
|
||||||
|
|
||||||
let wallet = Wallet::new_offline(
|
let wallet = Wallet::new(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
@@ -323,7 +321,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||||
|
|
||||||
let wallet = Wallet::new_offline(
|
let wallet = Wallet::new(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Bitcoin,
|
Network::Bitcoin,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ use signer::{SignOptions, Signer, SignerOrdering, SignersContainer};
|
|||||||
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
|
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
|
||||||
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx};
|
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx};
|
||||||
|
|
||||||
use crate::blockchain::{Blockchain, Progress};
|
use crate::blockchain::{GetHeight, NoopProgress, Progress, WalletSync};
|
||||||
use crate::database::memory::MemoryDatabase;
|
use crate::database::memory::MemoryDatabase;
|
||||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
|
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
|
||||||
use crate::descriptor::derived::AsDerived;
|
use crate::descriptor::derived::AsDerived;
|
||||||
@@ -75,16 +75,17 @@ const CACHE_ADDR_BATCH_SIZE: u32 = 100;
|
|||||||
|
|
||||||
/// A Bitcoin wallet
|
/// A Bitcoin wallet
|
||||||
///
|
///
|
||||||
/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
|
/// The `Wallet` struct acts as a way of coherently interfacing with output descriptors and related transactions.
|
||||||
/// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets
|
/// Its main components are:
|
||||||
/// needs to operate, like [generating addresses](Wallet::get_address), [returning the balance](Wallet::get_balance),
|
|
||||||
/// [creating transactions](Wallet::build_tx), etc.
|
|
||||||
///
|
///
|
||||||
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
|
/// 1. output *descriptors* from which it can derive addresses.
|
||||||
/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose
|
/// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors.
|
||||||
/// methods that don't need any interaction with the blockchain to work.
|
/// 3. [`Signer`]s that can contribute signatures to addresses instantiated from the descriptors.
|
||||||
|
///
|
||||||
|
/// [`Database`]: crate::database::Database
|
||||||
|
/// [`Signer`]: crate::signer::Signer
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Wallet<B, D> {
|
pub struct Wallet<D> {
|
||||||
descriptor: ExtendedDescriptor,
|
descriptor: ExtendedDescriptor,
|
||||||
change_descriptor: Option<ExtendedDescriptor>,
|
change_descriptor: Option<ExtendedDescriptor>,
|
||||||
|
|
||||||
@@ -95,88 +96,11 @@ pub struct Wallet<B, D> {
|
|||||||
|
|
||||||
network: Network,
|
network: Network,
|
||||||
|
|
||||||
current_height: Option<u32>,
|
|
||||||
|
|
||||||
client: B,
|
|
||||||
database: RefCell<D>,
|
database: RefCell<D>,
|
||||||
|
|
||||||
secp: SecpCtx,
|
secp: SecpCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Wallet<(), D>
|
|
||||||
where
|
|
||||||
D: BatchDatabase,
|
|
||||||
{
|
|
||||||
/// Create a new "offline" wallet
|
|
||||||
pub fn new_offline<E: IntoWalletDescriptor>(
|
|
||||||
descriptor: E,
|
|
||||||
change_descriptor: Option<E>,
|
|
||||||
network: Network,
|
|
||||||
database: D,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
Self::_new(descriptor, change_descriptor, network, database, (), None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, D> Wallet<B, D>
|
|
||||||
where
|
|
||||||
D: BatchDatabase,
|
|
||||||
{
|
|
||||||
fn _new<E: IntoWalletDescriptor>(
|
|
||||||
descriptor: E,
|
|
||||||
change_descriptor: Option<E>,
|
|
||||||
network: Network,
|
|
||||||
mut database: D,
|
|
||||||
client: B,
|
|
||||||
current_height: Option<u32>,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let secp = Secp256k1::new();
|
|
||||||
|
|
||||||
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
|
|
||||||
database.check_descriptor_checksum(
|
|
||||||
KeychainKind::External,
|
|
||||||
get_checksum(&descriptor.to_string())?.as_bytes(),
|
|
||||||
)?;
|
|
||||||
let signers = Arc::new(SignersContainer::from(keymap));
|
|
||||||
let (change_descriptor, change_signers) = match change_descriptor {
|
|
||||||
Some(desc) => {
|
|
||||||
let (change_descriptor, change_keymap) =
|
|
||||||
into_wallet_descriptor_checked(desc, &secp, network)?;
|
|
||||||
database.check_descriptor_checksum(
|
|
||||||
KeychainKind::Internal,
|
|
||||||
get_checksum(&change_descriptor.to_string())?.as_bytes(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let change_signers = Arc::new(SignersContainer::from(change_keymap));
|
|
||||||
// if !parsed.same_structure(descriptor.as_ref()) {
|
|
||||||
// return Err(Error::DifferentDescriptorStructure);
|
|
||||||
// }
|
|
||||||
|
|
||||||
(Some(change_descriptor), change_signers)
|
|
||||||
}
|
|
||||||
None => (None, Arc::new(SignersContainer::new())),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Wallet {
|
|
||||||
descriptor,
|
|
||||||
change_descriptor,
|
|
||||||
signers,
|
|
||||||
change_signers,
|
|
||||||
address_validators: Vec::new(),
|
|
||||||
network,
|
|
||||||
current_height,
|
|
||||||
client,
|
|
||||||
database: RefCell::new(database),
|
|
||||||
secp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the Bitcoin network the wallet is using.
|
|
||||||
pub fn network(&self) -> Network {
|
|
||||||
self.network
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The address index selection strategy to use to derived an address from the wallet's external
|
/// The address index selection strategy to use to derived an address from the wallet's external
|
||||||
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
|
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -232,17 +156,89 @@ impl fmt::Display for AddressInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// offline actions, always available
|
#[derive(Debug, Default)]
|
||||||
impl<B, D> Wallet<B, D>
|
/// Options to a [`sync`].
|
||||||
|
///
|
||||||
|
/// [`sync`]: Wallet::sync
|
||||||
|
pub struct SyncOptions {
|
||||||
|
/// The progress tracker which may be informed when progress is made.
|
||||||
|
pub progress: Option<Box<dyn Progress>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> Wallet<D>
|
||||||
where
|
where
|
||||||
D: BatchDatabase,
|
D: BatchDatabase,
|
||||||
{
|
{
|
||||||
// Return a newly derived address using the external descriptor
|
#[deprecated = "Just use Wallet::new -- all wallets are offline now!"]
|
||||||
fn get_new_address(&self) -> Result<AddressInfo, Error> {
|
/// Create a new "offline" wallet
|
||||||
let incremented_index = self.fetch_and_increment_index(KeychainKind::External)?;
|
pub fn new_offline<E: IntoWalletDescriptor>(
|
||||||
|
descriptor: E,
|
||||||
|
change_descriptor: Option<E>,
|
||||||
|
network: Network,
|
||||||
|
database: D,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
Self::new(descriptor, change_descriptor, network, database)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a wallet.
|
||||||
|
///
|
||||||
|
/// The only way this can fail is if the descriptors passed in do not match the checksums in `database`.
|
||||||
|
pub fn new<E: IntoWalletDescriptor>(
|
||||||
|
descriptor: E,
|
||||||
|
change_descriptor: Option<E>,
|
||||||
|
network: Network,
|
||||||
|
mut database: D,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
|
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
|
||||||
|
database.check_descriptor_checksum(
|
||||||
|
KeychainKind::External,
|
||||||
|
get_checksum(&descriptor.to_string())?.as_bytes(),
|
||||||
|
)?;
|
||||||
|
let signers = Arc::new(SignersContainer::from(keymap));
|
||||||
|
let (change_descriptor, change_signers) = match change_descriptor {
|
||||||
|
Some(desc) => {
|
||||||
|
let (change_descriptor, change_keymap) =
|
||||||
|
into_wallet_descriptor_checked(desc, &secp, network)?;
|
||||||
|
database.check_descriptor_checksum(
|
||||||
|
KeychainKind::Internal,
|
||||||
|
get_checksum(&change_descriptor.to_string())?.as_bytes(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let change_signers = Arc::new(SignersContainer::from(change_keymap));
|
||||||
|
// if !parsed.same_structure(descriptor.as_ref()) {
|
||||||
|
// return Err(Error::DifferentDescriptorStructure);
|
||||||
|
// }
|
||||||
|
|
||||||
|
(Some(change_descriptor), change_signers)
|
||||||
|
}
|
||||||
|
None => (None, Arc::new(SignersContainer::new())),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Wallet {
|
||||||
|
descriptor,
|
||||||
|
change_descriptor,
|
||||||
|
signers,
|
||||||
|
change_signers,
|
||||||
|
address_validators: Vec::new(),
|
||||||
|
network,
|
||||||
|
database: RefCell::new(database),
|
||||||
|
secp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the Bitcoin network the wallet is using.
|
||||||
|
pub fn network(&self) -> Network {
|
||||||
|
self.network
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a newly derived address for the specified `keychain`.
|
||||||
|
fn get_new_address(&self, keychain: KeychainKind) -> Result<AddressInfo, Error> {
|
||||||
|
let incremented_index = self.fetch_and_increment_index(keychain)?;
|
||||||
|
|
||||||
let address_result = self
|
let address_result = self
|
||||||
.descriptor
|
.get_descriptor_for_keychain(keychain)
|
||||||
.as_derived(incremented_index, &self.secp)
|
.as_derived(incremented_index, &self.secp)
|
||||||
.address(self.network);
|
.address(self.network);
|
||||||
|
|
||||||
@@ -254,12 +250,14 @@ where
|
|||||||
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the the last previously derived address if it has not been used in a received
|
// Return the the last previously derived address for `keychain` if it has not been used in a
|
||||||
// transaction. Otherwise return a new address using [`Wallet::get_new_address`].
|
// received transaction. Otherwise return a new address using [`Wallet::get_new_address`].
|
||||||
fn get_unused_address(&self) -> Result<AddressInfo, Error> {
|
fn get_unused_address(&self, keychain: KeychainKind) -> Result<AddressInfo, Error> {
|
||||||
let current_index = self.fetch_index(KeychainKind::External)?;
|
let current_index = self.fetch_index(keychain)?;
|
||||||
|
|
||||||
let derived_key = self.descriptor.as_derived(current_index, &self.secp);
|
let derived_key = self
|
||||||
|
.get_descriptor_for_keychain(keychain)
|
||||||
|
.as_derived(current_index, &self.secp);
|
||||||
|
|
||||||
let script_pubkey = derived_key.script_pubkey();
|
let script_pubkey = derived_key.script_pubkey();
|
||||||
|
|
||||||
@@ -271,7 +269,7 @@ where
|
|||||||
.any(|o| o.script_pubkey == script_pubkey);
|
.any(|o| o.script_pubkey == script_pubkey);
|
||||||
|
|
||||||
if found_used {
|
if found_used {
|
||||||
self.get_new_address()
|
self.get_new_address(keychain)
|
||||||
} else {
|
} else {
|
||||||
derived_key
|
derived_key
|
||||||
.address(self.network)
|
.address(self.network)
|
||||||
@@ -283,21 +281,21 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return derived address for the external descriptor at a specific index
|
// Return derived address for the descriptor of given [`KeychainKind`] at a specific index
|
||||||
fn peek_address(&self, index: u32) -> Result<AddressInfo, Error> {
|
fn peek_address(&self, index: u32, keychain: KeychainKind) -> Result<AddressInfo, Error> {
|
||||||
self.descriptor
|
self.get_descriptor_for_keychain(keychain)
|
||||||
.as_derived(index, &self.secp)
|
.as_derived(index, &self.secp)
|
||||||
.address(self.network)
|
.address(self.network)
|
||||||
.map(|address| AddressInfo { index, address })
|
.map(|address| AddressInfo { index, address })
|
||||||
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return derived address for the external descriptor at a specific index and reset current
|
// Return derived address for `keychain` at a specific index and reset current
|
||||||
// address index
|
// address index
|
||||||
fn reset_address(&self, index: u32) -> Result<AddressInfo, Error> {
|
fn reset_address(&self, index: u32, keychain: KeychainKind) -> Result<AddressInfo, Error> {
|
||||||
self.set_index(KeychainKind::External, index)?;
|
self.set_index(keychain, index)?;
|
||||||
|
|
||||||
self.descriptor
|
self.get_descriptor_for_keychain(keychain)
|
||||||
.as_derived(index, &self.secp)
|
.as_derived(index, &self.secp)
|
||||||
.address(self.network)
|
.address(self.network)
|
||||||
.map(|address| AddressInfo { index, address })
|
.map(|address| AddressInfo { index, address })
|
||||||
@@ -308,14 +306,77 @@ where
|
|||||||
/// available address index selection strategies. If none of the keys in the descriptor are derivable
|
/// available address index selection strategies. If none of the keys in the descriptor are derivable
|
||||||
/// (ie. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
|
/// (ie. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
|
||||||
pub fn get_address(&self, address_index: AddressIndex) -> Result<AddressInfo, Error> {
|
pub fn get_address(&self, address_index: AddressIndex) -> Result<AddressInfo, Error> {
|
||||||
|
self._get_address(address_index, KeychainKind::External)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a derived address using the internal (change) descriptor.
|
||||||
|
///
|
||||||
|
/// If the wallet doesn't have an internal descriptor it will use the external descriptor.
|
||||||
|
///
|
||||||
|
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
|
||||||
|
/// in the descriptor are derivable (ie. does not end with /*) then the same address will always
|
||||||
|
/// be returned for any [`AddressIndex`].
|
||||||
|
pub fn get_internal_address(&self, address_index: AddressIndex) -> Result<AddressInfo, Error> {
|
||||||
|
self._get_address(address_index, KeychainKind::Internal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _get_address(
|
||||||
|
&self,
|
||||||
|
address_index: AddressIndex,
|
||||||
|
keychain: KeychainKind,
|
||||||
|
) -> Result<AddressInfo, Error> {
|
||||||
match address_index {
|
match address_index {
|
||||||
AddressIndex::New => self.get_new_address(),
|
AddressIndex::New => self.get_new_address(keychain),
|
||||||
AddressIndex::LastUnused => self.get_unused_address(),
|
AddressIndex::LastUnused => self.get_unused_address(keychain),
|
||||||
AddressIndex::Peek(index) => self.peek_address(index),
|
AddressIndex::Peek(index) => self.peek_address(index, keychain),
|
||||||
AddressIndex::Reset(index) => self.reset_address(index),
|
AddressIndex::Reset(index) => self.reset_address(index, keychain),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensures that there are at least `max_addresses` addresses cached in the database if the
|
||||||
|
/// descriptor is derivable, or 1 address if it is not.
|
||||||
|
/// Will return `Ok(true)` if there are new addresses generated (either external or internal),
|
||||||
|
/// and `Ok(false)` if all the required addresses are already cached. This function is useful to
|
||||||
|
/// explicitly cache addresses in a wallet to do things like check [`Wallet::is_mine`] on
|
||||||
|
/// transaction output scripts.
|
||||||
|
pub fn ensure_addresses_cached(&self, max_addresses: u32) -> Result<bool, Error> {
|
||||||
|
let mut new_addresses_cached = false;
|
||||||
|
let max_address = match self.descriptor.is_deriveable() {
|
||||||
|
false => 0,
|
||||||
|
true => max_addresses,
|
||||||
|
};
|
||||||
|
debug!("max_address {}", max_address);
|
||||||
|
if self
|
||||||
|
.database
|
||||||
|
.borrow()
|
||||||
|
.get_script_pubkey_from_path(KeychainKind::External, max_address.saturating_sub(1))?
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
debug!("caching external addresses");
|
||||||
|
new_addresses_cached = true;
|
||||||
|
self.cache_addresses(KeychainKind::External, 0, max_address)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(change_descriptor) = &self.change_descriptor {
|
||||||
|
let max_address = match change_descriptor.is_deriveable() {
|
||||||
|
false => 0,
|
||||||
|
true => max_addresses,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self
|
||||||
|
.database
|
||||||
|
.borrow()
|
||||||
|
.get_script_pubkey_from_path(KeychainKind::Internal, max_address.saturating_sub(1))?
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
debug!("caching internal addresses");
|
||||||
|
new_addresses_cached = true;
|
||||||
|
self.cache_addresses(KeychainKind::Internal, 0, max_address)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(new_addresses_cached)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return whether or not a `script` is part of this wallet (either internal or external)
|
/// Return whether or not a `script` is part of this wallet (either internal or external)
|
||||||
pub fn is_mine(&self, script: &Script) -> Result<bool, Error> {
|
pub fn is_mine(&self, script: &Script) -> Result<bool, Error> {
|
||||||
self.database.borrow().is_mine(script)
|
self.database.borrow().is_mine(script)
|
||||||
@@ -422,7 +483,7 @@ where
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`TxBuilder`]: crate::TxBuilder
|
/// [`TxBuilder`]: crate::TxBuilder
|
||||||
pub fn build_tx(&self) -> TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, CreateTx> {
|
pub fn build_tx(&self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> {
|
||||||
TxBuilder {
|
TxBuilder {
|
||||||
wallet: self,
|
wallet: self,
|
||||||
params: TxParams::default(),
|
params: TxParams::default(),
|
||||||
@@ -662,7 +723,10 @@ where
|
|||||||
let mut drain_output = {
|
let mut drain_output = {
|
||||||
let script_pubkey = match params.drain_to {
|
let script_pubkey = match params.drain_to {
|
||||||
Some(ref drain_recipient) => drain_recipient.clone(),
|
Some(ref drain_recipient) => drain_recipient.clone(),
|
||||||
None => self.get_change_address()?,
|
None => self
|
||||||
|
.get_internal_address(AddressIndex::New)?
|
||||||
|
.address
|
||||||
|
.script_pubkey(),
|
||||||
};
|
};
|
||||||
|
|
||||||
TxOut {
|
TxOut {
|
||||||
@@ -712,7 +776,6 @@ where
|
|||||||
received,
|
received,
|
||||||
sent,
|
sent,
|
||||||
fee: Some(fee_amount),
|
fee: Some(fee_amount),
|
||||||
verified: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((psbt, transaction_details))
|
Ok((psbt, transaction_details))
|
||||||
@@ -763,7 +826,7 @@ where
|
|||||||
pub fn build_fee_bump(
|
pub fn build_fee_bump(
|
||||||
&self,
|
&self,
|
||||||
txid: Txid,
|
txid: Txid,
|
||||||
) -> Result<TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
|
) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
|
||||||
let mut details = match self.database.borrow().get_tx(&txid, true)? {
|
let mut details = match self.database.borrow().get_tx(&txid, true)? {
|
||||||
None => return Err(Error::TransactionNotFound),
|
None => return Err(Error::TransactionNotFound),
|
||||||
Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
|
Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
|
||||||
@@ -994,7 +1057,11 @@ where
|
|||||||
.borrow()
|
.borrow()
|
||||||
.get_tx(&input.previous_output.txid, false)?
|
.get_tx(&input.previous_output.txid, false)?
|
||||||
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX));
|
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX));
|
||||||
let current_height = sign_options.assume_height.or(self.current_height);
|
let last_sync_height = self
|
||||||
|
.database()
|
||||||
|
.get_sync_time()?
|
||||||
|
.map(|sync_time| sync_time.block_time.height);
|
||||||
|
let current_height = sign_options.assume_height.or(last_sync_height);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
|
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
|
||||||
@@ -1092,13 +1159,6 @@ where
|
|||||||
.map(|(desc, child)| desc.as_derived(child, &self.secp)))
|
.map(|(desc, child)| desc.as_derived(child, &self.secp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_change_address(&self) -> Result<Script, Error> {
|
|
||||||
let (desc, keychain) = self._get_descriptor_for_keychain(KeychainKind::Internal);
|
|
||||||
let index = self.fetch_and_increment_index(keychain)?;
|
|
||||||
|
|
||||||
Ok(desc.as_derived(index, &self.secp).script_pubkey())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
|
fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
|
||||||
let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
|
let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
|
||||||
let index = match descriptor.is_deriveable() {
|
let index = match descriptor.is_deriveable() {
|
||||||
@@ -1452,111 +1512,35 @@ where
|
|||||||
pub fn database(&self) -> impl std::ops::Deref<Target = D> + '_ {
|
pub fn database(&self) -> impl std::ops::Deref<Target = D> + '_ {
|
||||||
self.database.borrow()
|
self.database.borrow()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<B, D> Wallet<B, D>
|
|
||||||
where
|
|
||||||
B: Blockchain,
|
|
||||||
D: BatchDatabase,
|
|
||||||
{
|
|
||||||
/// Create a new "online" wallet
|
|
||||||
#[maybe_async]
|
|
||||||
pub fn new<E: IntoWalletDescriptor>(
|
|
||||||
descriptor: E,
|
|
||||||
change_descriptor: Option<E>,
|
|
||||||
network: Network,
|
|
||||||
database: D,
|
|
||||||
client: B,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let current_height = Some(maybe_await!(client.get_height())? as u32);
|
|
||||||
Self::_new(
|
|
||||||
descriptor,
|
|
||||||
change_descriptor,
|
|
||||||
network,
|
|
||||||
database,
|
|
||||||
client,
|
|
||||||
current_height,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sync the internal database with the blockchain
|
/// Sync the internal database with the blockchain
|
||||||
#[maybe_async]
|
#[maybe_async]
|
||||||
pub fn sync<P: 'static + Progress>(
|
pub fn sync<B: WalletSync + GetHeight>(
|
||||||
&self,
|
&self,
|
||||||
progress_update: P,
|
blockchain: &B,
|
||||||
max_address_param: Option<u32>,
|
sync_opts: SyncOptions,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
debug!("Begin sync...");
|
debug!("Begin sync...");
|
||||||
|
|
||||||
let mut run_setup = false;
|
let SyncOptions { progress } = sync_opts;
|
||||||
|
let progress = progress.unwrap_or_else(|| Box::new(NoopProgress));
|
||||||
|
|
||||||
let max_address = match self.descriptor.is_deriveable() {
|
let run_setup = self.ensure_addresses_cached(CACHE_ADDR_BATCH_SIZE)?;
|
||||||
false => 0,
|
|
||||||
true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
|
|
||||||
};
|
|
||||||
debug!("max_address {}", max_address);
|
|
||||||
if self
|
|
||||||
.database
|
|
||||||
.borrow()
|
|
||||||
.get_script_pubkey_from_path(KeychainKind::External, max_address.saturating_sub(1))?
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
debug!("caching external addresses");
|
|
||||||
run_setup = true;
|
|
||||||
self.cache_addresses(KeychainKind::External, 0, max_address)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(change_descriptor) = &self.change_descriptor {
|
|
||||||
let max_address = match change_descriptor.is_deriveable() {
|
|
||||||
false => 0,
|
|
||||||
true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
|
|
||||||
};
|
|
||||||
|
|
||||||
if self
|
|
||||||
.database
|
|
||||||
.borrow()
|
|
||||||
.get_script_pubkey_from_path(KeychainKind::Internal, max_address.saturating_sub(1))?
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
debug!("caching internal addresses");
|
|
||||||
run_setup = true;
|
|
||||||
self.cache_addresses(KeychainKind::Internal, 0, max_address)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("run_setup: {}", run_setup);
|
debug!("run_setup: {}", run_setup);
|
||||||
// TODO: what if i generate an address first and cache some addresses?
|
// TODO: what if i generate an address first and cache some addresses?
|
||||||
// TODO: we should sync if generating an address triggers a new batch to be stored
|
// TODO: we should sync if generating an address triggers a new batch to be stored
|
||||||
if run_setup {
|
if run_setup {
|
||||||
maybe_await!(self
|
maybe_await!(
|
||||||
.client
|
blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress,)
|
||||||
.setup(self.database.borrow_mut().deref_mut(), progress_update,))?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
maybe_await!(self
|
maybe_await!(blockchain.wallet_sync(self.database.borrow_mut().deref_mut(), progress,))?;
|
||||||
.client
|
|
||||||
.sync(self.database.borrow_mut().deref_mut(), progress_update,))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "verify")]
|
|
||||||
{
|
|
||||||
debug!("Verifying transactions...");
|
|
||||||
for mut tx in self.database.borrow().iter_txs(true)? {
|
|
||||||
if !tx.verified {
|
|
||||||
verify::verify_tx(
|
|
||||||
tx.transaction.as_ref().ok_or(Error::TransactionNotFound)?,
|
|
||||||
self.database.borrow().deref(),
|
|
||||||
&self.client,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
tx.verified = true;
|
|
||||||
self.database.borrow_mut().set_tx(&tx)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let sync_time = SyncTime {
|
let sync_time = SyncTime {
|
||||||
block_time: BlockTime {
|
block_time: BlockTime {
|
||||||
height: maybe_await!(self.client.get_height())?,
|
height: maybe_await!(blockchain.get_height())?,
|
||||||
timestamp: time::get_timestamp(),
|
timestamp: time::get_timestamp(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1565,31 +1549,18 @@ where
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to the internal blockchain client
|
|
||||||
pub fn client(&self) -> &B {
|
|
||||||
&self.client
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Broadcast a transaction to the network
|
|
||||||
#[maybe_async]
|
|
||||||
pub fn broadcast(&self, tx: &Transaction) -> Result<Txid, Error> {
|
|
||||||
maybe_await!(self.client.broadcast(tx))?;
|
|
||||||
|
|
||||||
Ok(tx.txid())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a fake wallet that appears to be funded for testing.
|
/// Return a fake wallet that appears to be funded for testing.
|
||||||
pub fn get_funded_wallet(
|
pub fn get_funded_wallet(
|
||||||
descriptor: &str,
|
descriptor: &str,
|
||||||
) -> (
|
) -> (
|
||||||
Wallet<(), MemoryDatabase>,
|
Wallet<MemoryDatabase>,
|
||||||
(String, Option<String>),
|
(String, Option<String>),
|
||||||
bitcoin::Txid,
|
bitcoin::Txid,
|
||||||
) {
|
) {
|
||||||
let descriptors = testutils!(@descriptors (descriptor));
|
let descriptors = testutils!(@descriptors (descriptor));
|
||||||
let wallet = Wallet::new_offline(
|
let wallet = Wallet::new(
|
||||||
&descriptors.0,
|
&descriptors.0,
|
||||||
None,
|
None,
|
||||||
Network::Regtest,
|
Network::Regtest,
|
||||||
@@ -1639,7 +1610,7 @@ pub(crate) mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_cache_addresses_fixed() {
|
fn test_cache_addresses_fixed() {
|
||||||
let db = MemoryDatabase::new();
|
let db = MemoryDatabase::new();
|
||||||
let wallet = Wallet::new_offline(
|
let wallet = Wallet::new(
|
||||||
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
|
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
|
||||||
None,
|
None,
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
@@ -1673,7 +1644,7 @@ pub(crate) mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_cache_addresses() {
|
fn test_cache_addresses() {
|
||||||
let db = MemoryDatabase::new();
|
let db = MemoryDatabase::new();
|
||||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
wallet.get_address(New).unwrap().to_string(),
|
wallet.get_address(New).unwrap().to_string(),
|
||||||
@@ -1701,7 +1672,7 @@ pub(crate) mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_cache_addresses_refill() {
|
fn test_cache_addresses_refill() {
|
||||||
let db = MemoryDatabase::new();
|
let db = MemoryDatabase::new();
|
||||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
wallet.get_address(New).unwrap().to_string(),
|
wallet.get_address(New).unwrap().to_string(),
|
||||||
@@ -3799,7 +3770,7 @@ pub(crate) mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_unused_address() {
|
fn test_unused_address() {
|
||||||
let db = MemoryDatabase::new();
|
let db = MemoryDatabase::new();
|
||||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||||
None, Network::Testnet, db).unwrap();
|
None, Network::Testnet, db).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -3816,7 +3787,7 @@ pub(crate) mod test {
|
|||||||
fn test_next_unused_address() {
|
fn test_next_unused_address() {
|
||||||
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
|
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
|
||||||
let descriptors = testutils!(@descriptors (descriptor));
|
let descriptors = testutils!(@descriptors (descriptor));
|
||||||
let wallet = Wallet::new_offline(
|
let wallet = Wallet::new(
|
||||||
&descriptors.0,
|
&descriptors.0,
|
||||||
None,
|
None,
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
@@ -3845,7 +3816,7 @@ pub(crate) mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_peek_address_at_index() {
|
fn test_peek_address_at_index() {
|
||||||
let db = MemoryDatabase::new();
|
let db = MemoryDatabase::new();
|
||||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||||
None, Network::Testnet, db).unwrap();
|
None, Network::Testnet, db).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -3878,7 +3849,7 @@ pub(crate) mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_peek_address_at_index_not_derivable() {
|
fn test_peek_address_at_index_not_derivable() {
|
||||||
let db = MemoryDatabase::new();
|
let db = MemoryDatabase::new();
|
||||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
|
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
|
||||||
None, Network::Testnet, db).unwrap();
|
None, Network::Testnet, db).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -3900,7 +3871,7 @@ pub(crate) mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_reset_address_index() {
|
fn test_reset_address_index() {
|
||||||
let db = MemoryDatabase::new();
|
let db = MemoryDatabase::new();
|
||||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||||
None, Network::Testnet, db).unwrap();
|
None, Network::Testnet, db).unwrap();
|
||||||
|
|
||||||
// new index 0
|
// new index 0
|
||||||
@@ -3937,7 +3908,7 @@ pub(crate) mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_returns_index_and_address() {
|
fn test_returns_index_and_address() {
|
||||||
let db = MemoryDatabase::new();
|
let db = MemoryDatabase::new();
|
||||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||||
None, Network::Testnet, db).unwrap();
|
None, Network::Testnet, db).unwrap();
|
||||||
|
|
||||||
// new index 0
|
// new index 0
|
||||||
@@ -4005,6 +3976,48 @@ pub(crate) mod test {
|
|||||||
builder.add_recipient(addr.script_pubkey(), 45_000);
|
builder.add_recipient(addr.script_pubkey(), 45_000);
|
||||||
builder.finish().unwrap();
|
builder.finish().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_address() {
|
||||||
|
use crate::descriptor::template::Bip84;
|
||||||
|
let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
|
||||||
|
let wallet = Wallet::new(
|
||||||
|
Bip84(key, KeychainKind::External),
|
||||||
|
Some(Bip84(key, KeychainKind::Internal)),
|
||||||
|
Network::Regtest,
|
||||||
|
MemoryDatabase::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
wallet.get_address(AddressIndex::New).unwrap().address,
|
||||||
|
Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
wallet
|
||||||
|
.get_internal_address(AddressIndex::New)
|
||||||
|
.unwrap()
|
||||||
|
.address,
|
||||||
|
Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let wallet = Wallet::new(
|
||||||
|
Bip84(key, KeychainKind::External),
|
||||||
|
None,
|
||||||
|
Network::Regtest,
|
||||||
|
MemoryDatabase::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
wallet
|
||||||
|
.get_internal_address(AddressIndex::New)
|
||||||
|
.unwrap()
|
||||||
|
.address,
|
||||||
|
Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(),
|
||||||
|
"when there's no internal descriptor it should just use external"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deterministically generate a unique name given the descriptors defining the wallet
|
/// Deterministically generate a unique name given the descriptors defining the wallet
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
//! let custom_signer = CustomSigner::connect();
|
//! let custom_signer = CustomSigner::connect();
|
||||||
//!
|
//!
|
||||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||||
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||||
//! wallet.add_signer(
|
//! wallet.add_signer(
|
||||||
//! KeychainKind::External,
|
//! KeychainKind::External,
|
||||||
//! SignerOrdering(200),
|
//! SignerOrdering(200),
|
||||||
|
|||||||
@@ -120,8 +120,8 @@ impl TxBuilderContext for BumpFee {}
|
|||||||
/// [`finish`]: Self::finish
|
/// [`finish`]: Self::finish
|
||||||
/// [`coin_selection`]: Self::coin_selection
|
/// [`coin_selection`]: Self::coin_selection
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TxBuilder<'a, B, D, Cs, Ctx> {
|
pub struct TxBuilder<'a, D, Cs, Ctx> {
|
||||||
pub(crate) wallet: &'a Wallet<B, D>,
|
pub(crate) wallet: &'a Wallet<D>,
|
||||||
pub(crate) params: TxParams,
|
pub(crate) params: TxParams,
|
||||||
pub(crate) coin_selection: Cs,
|
pub(crate) coin_selection: Cs,
|
||||||
pub(crate) phantom: PhantomData<Ctx>,
|
pub(crate) phantom: PhantomData<Ctx>,
|
||||||
@@ -170,7 +170,7 @@ impl std::default::Default for FeePolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> {
|
impl<'a, Cs: Clone, Ctx, D> Clone for TxBuilder<'a, D, Cs, Ctx> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
TxBuilder {
|
TxBuilder {
|
||||||
wallet: self.wallet,
|
wallet: self.wallet,
|
||||||
@@ -182,8 +182,8 @@ impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// methods supported by both contexts, for any CoinSelectionAlgorithm
|
// methods supported by both contexts, for any CoinSelectionAlgorithm
|
||||||
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
|
impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
|
||||||
TxBuilder<'a, B, D, Cs, Ctx>
|
TxBuilder<'a, D, Cs, Ctx>
|
||||||
{
|
{
|
||||||
/// Set a custom fee rate
|
/// Set a custom fee rate
|
||||||
pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
|
pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
|
||||||
@@ -508,7 +508,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
|
|||||||
pub fn coin_selection<P: CoinSelectionAlgorithm<D>>(
|
pub fn coin_selection<P: CoinSelectionAlgorithm<D>>(
|
||||||
self,
|
self,
|
||||||
coin_selection: P,
|
coin_selection: P,
|
||||||
) -> TxBuilder<'a, B, D, P, Ctx> {
|
) -> TxBuilder<'a, D, P, Ctx> {
|
||||||
TxBuilder {
|
TxBuilder {
|
||||||
wallet: self.wallet,
|
wallet: self.wallet,
|
||||||
params: self.params,
|
params: self.params,
|
||||||
@@ -547,7 +547,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> {
|
impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, CreateTx> {
|
||||||
/// Replace the recipients already added with a new list
|
/// Replace the recipients already added with a new list
|
||||||
pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
|
pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
|
||||||
self.params.recipients = recipients;
|
self.params.recipients = recipients;
|
||||||
@@ -614,7 +614,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// methods supported only by bump_fee
|
// methods supported only by bump_fee
|
||||||
impl<'a, B, D: BatchDatabase> TxBuilder<'a, B, D, DefaultCoinSelectionAlgorithm, BumpFee> {
|
impl<'a, D: BatchDatabase> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> {
|
||||||
/// Explicitly tells the wallet that it is allowed to reduce the fee of the output matching this
|
/// Explicitly tells the wallet that it is allowed to reduce the fee of the output matching this
|
||||||
/// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
|
/// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
|
||||||
/// will attempt to find a change output to shrink instead.
|
/// will attempt to find a change output to shrink instead.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use std::fmt;
|
|||||||
use bitcoin::consensus::serialize;
|
use bitcoin::consensus::serialize;
|
||||||
use bitcoin::{OutPoint, Transaction, Txid};
|
use bitcoin::{OutPoint, Transaction, Txid};
|
||||||
|
|
||||||
use crate::blockchain::Blockchain;
|
use crate::blockchain::GetTx;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ use crate::error::Error;
|
|||||||
/// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the
|
/// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the
|
||||||
/// [`Blockchain`] backend, the method could fail when called with old "historical" transactions or
|
/// [`Blockchain`] backend, the method could fail when called with old "historical" transactions or
|
||||||
/// with unconfirmed transactions that have been evicted from the backend's memory.
|
/// with unconfirmed transactions that have been evicted from the backend's memory.
|
||||||
pub fn verify_tx<D: Database, B: Blockchain>(
|
pub fn verify_tx<D: Database, B: GetTx>(
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
database: &D,
|
database: &D,
|
||||||
blockchain: &B,
|
blockchain: &B,
|
||||||
@@ -104,43 +104,18 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::collections::HashSet;
|
use super::*;
|
||||||
|
use crate::database::{BatchOperations, MemoryDatabase};
|
||||||
use bitcoin::consensus::encode::deserialize;
|
use bitcoin::consensus::encode::deserialize;
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::{Transaction, Txid};
|
use bitcoin::{Transaction, Txid};
|
||||||
|
|
||||||
use crate::blockchain::{Blockchain, Capability, Progress};
|
|
||||||
use crate::database::{BatchDatabase, BatchOperations, MemoryDatabase};
|
|
||||||
use crate::FeeRate;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
struct DummyBlockchain;
|
struct DummyBlockchain;
|
||||||
|
|
||||||
impl Blockchain for DummyBlockchain {
|
impl GetTx for DummyBlockchain {
|
||||||
fn get_capabilities(&self) -> HashSet<Capability> {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
|
||||||
&self,
|
|
||||||
_database: &mut D,
|
|
||||||
_progress_update: P,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn get_tx(&self, _txid: &Txid) -> Result<Option<Transaction>, Error> {
|
fn get_tx(&self, _txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
fn broadcast(&self, _tx: &Transaction) -> Result<(), Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn get_height(&self) -> Result<u32, Error> {
|
|
||||||
Ok(42)
|
|
||||||
}
|
|
||||||
fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
|
|
||||||
Ok(FeeRate::default_min_relay_fee())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user