Compare commits
63 Commits
get_change
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5c8ce924b | ||
|
|
e3ce50059f | ||
|
|
8a2a6bbcee | ||
|
|
122e6e7140 | ||
|
|
1018bb2b17 | ||
|
|
9ed36875f1 | ||
|
|
502882d27c | ||
|
|
a328607d27 | ||
|
|
f90e3f978e | ||
|
|
68e1b32d81 | ||
|
|
44758f9483 | ||
|
|
c350064dae | ||
|
|
92746440db | ||
|
|
e4eb95fb9c | ||
|
|
c307bacb9c | ||
|
|
a111d25476 | ||
|
|
0621ca89d5 | ||
|
|
adef166b22 | ||
|
|
213f18f7b7 | ||
|
|
8cd055090d | ||
|
|
1b9014846c | ||
|
|
9c0141b5e3 | ||
|
|
2698fc0219 | ||
|
|
bac15bb207 | ||
|
|
06b80fdb15 | ||
|
|
ff6db18726 | ||
|
|
86abd8698f | ||
|
|
0d9c2f76e0 | ||
|
|
63d5bcee93 | ||
|
|
8a98e69e78 | ||
|
|
c6eeb7b989 | ||
|
|
3334c8da07 | ||
|
|
ce09203431 | ||
|
|
cac312d34f | ||
|
|
4b1be68965 | ||
|
|
559cfc4373 | ||
|
|
1e9a684b54 | ||
|
|
52bc63e48f | ||
|
|
9a6db15d26 | ||
|
|
52bcd105eb | ||
|
|
1803f5ea8a | ||
|
|
f2f0efc0b3 | ||
|
|
3e4678d8e3 | ||
|
|
0cc4700bd6 | ||
|
|
660faab1e2 | ||
|
|
45767fcaf7 | ||
|
|
d03aa85108 | ||
|
|
adf7d0c126 | ||
|
|
4291f84d79 | ||
|
|
f0188f49a8 | ||
|
|
edf2f0ce06 | ||
|
|
364ad95e85 | ||
|
|
fbb50ad1c8 | ||
|
|
035307ef54 | ||
|
|
c0e75fc1a8 | ||
|
|
dcd90f8b61 | ||
|
|
410a51355b | ||
|
|
326bfe82a8 | ||
|
|
b23a0747b5 | ||
|
|
552765bb58 | ||
|
|
f3e479fa7f | ||
|
|
5698c683c6 | ||
|
|
a83aa0461c |
3
.github/workflows/cont_integration.yml
vendored
3
.github/workflows/cont_integration.yml
vendored
@@ -28,6 +28,7 @@ jobs:
|
||||
- async-interface
|
||||
- use-esplora-reqwest
|
||||
- sqlite
|
||||
- sqlite-bundled
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -114,7 +115,7 @@ jobs:
|
||||
override: true
|
||||
- name: Test
|
||||
run: cargo test --no-default-features --features ${{ matrix.blockchain.features }} ${{ matrix.blockchain.name }}::bdk_blockchain_tests
|
||||
|
||||
|
||||
check-wasm:
|
||||
name: Check WASM
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -6,10 +6,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0
|
||||
|
||||
|
||||
## [v0.18.0] - [v0.17.0]
|
||||
|
||||
- Add `sqlite-bundled` feature for deployments that need a bundled version of sqlite, ie. for mobile platforms.
|
||||
- Added `Wallet::get_signers()`, `Wallet::descriptor_checksum()` and `Wallet::get_address_validators()`, exposed the `AsDerived` trait.
|
||||
- Deprecate `database::Database::flush()`, the function is only needed for the sled database on mobile, instead for mobile use the sqlite database.
|
||||
- Add `keychain: KeychainKind` to `wallet::AddressInfo`.
|
||||
- Improve key generation traits
|
||||
- Rename `WalletExport` to `FullyNodedExport`, deprecate the former.
|
||||
- Bump `miniscript` dependency version to `^6.1`.
|
||||
|
||||
## [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
|
||||
- Add `is_spent` field to `LocalUtxo`; when we notice that a utxo has been spent we set `is_spent` field to true instead of deleting it from the db.
|
||||
|
||||
### 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]
|
||||
|
||||
- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0
|
||||
|
||||
## [v0.16.0] - [v0.15.0]
|
||||
|
||||
@@ -399,7 +430,6 @@ final transaction is created by calling `finish` on the builder.
|
||||
- Use `MemoryDatabase` in the compiler example
|
||||
- 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
|
||||
[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
|
||||
@@ -416,4 +446,8 @@ final transaction is created by calling `finish` on the builder.
|
||||
[v0.13.0]: https://github.com/bitcoindevkit/bdk/compare/v0.12.0...v0.13.0
|
||||
[v0.14.0]: https://github.com/bitcoindevkit/bdk/compare/v0.13.0...v0.14.0
|
||||
[v0.15.0]: https://github.com/bitcoindevkit/bdk/compare/v0.14.0...v0.15.0
|
||||
[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.17.0]: https://github.com/bitcoindevkit/bdk/compare/v0.16.1...v0.17.0
|
||||
[v0.18.0]: https://github.com/bitcoindevkit/bdk/compare/v0.17.0...v0.18.0
|
||||
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.18.0...HEAD
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bdk"
|
||||
version = "0.16.1-dev"
|
||||
version = "0.19.0-dev"
|
||||
edition = "2018"
|
||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||
homepage = "https://bitcoindevkit.org"
|
||||
@@ -14,7 +14,7 @@ license = "MIT OR Apache-2.0"
|
||||
[dependencies]
|
||||
bdk-macros = "^0.6"
|
||||
log = "^0.4"
|
||||
miniscript = { version = "^6.0", features = ["use-serde"] }
|
||||
miniscript = { version = "^6.1", features = ["use-serde"] }
|
||||
bitcoin = { version = "^0.27", features = ["use-serde", "base64"] }
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
serde_json = { version = "^1.0" }
|
||||
@@ -55,6 +55,7 @@ compiler = ["miniscript/compiler"]
|
||||
verify = ["bitcoinconsensus"]
|
||||
default = ["key-value-db", "electrum"]
|
||||
sqlite = ["rusqlite", "ahash"]
|
||||
sqlite-bundled = ["sqlite", "rusqlite/bundled"]
|
||||
compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
|
||||
key-value-db = ["sled"]
|
||||
all-keys = ["keys-bip39"]
|
||||
@@ -96,7 +97,7 @@ test-md-docs = ["electrum"]
|
||||
lazy_static = "1.4"
|
||||
env_logger = "0.7"
|
||||
clap = "2.33"
|
||||
electrsd = { version= "0.13", features = ["trigger", "bitcoind_22_0"] }
|
||||
electrsd = { version= "0.15", features = ["trigger", "bitcoind_22_0"] }
|
||||
|
||||
[[example]]
|
||||
name = "address_validator"
|
||||
@@ -109,6 +110,11 @@ name = "miniscriptc"
|
||||
path = "examples/compiler.rs"
|
||||
required-features = ["compiler"]
|
||||
|
||||
[[example]]
|
||||
name = "rpcwallet"
|
||||
path = "examples/rpcwallet.rs"
|
||||
required-features = ["keys-bip39", "key-value-db", "rpc"]
|
||||
|
||||
[workspace]
|
||||
members = ["macros"]
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
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
|
||||
use bdk::Wallet;
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
||||
use bdk::blockchain::ElectrumBlockchain;
|
||||
use bdk::SyncOptions;
|
||||
|
||||
use bdk::electrum_client::Client;
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
|
||||
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
||||
let wallet = Wallet::new(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
MemoryDatabase::default(),
|
||||
ElectrumBlockchain::from(client)
|
||||
)?;
|
||||
|
||||
wallet.sync(noop_progress(), None)?;
|
||||
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||
|
||||
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
|
||||
|
||||
@@ -70,7 +70,7 @@ use bdk::{Wallet, database::MemoryDatabase};
|
||||
use bdk::wallet::AddressIndex::New;
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
@@ -88,9 +88,9 @@ fn main() -> Result<(), bdk::Error> {
|
||||
### Create a transaction
|
||||
|
||||
```rust,no_run
|
||||
use bdk::{FeeRate, Wallet};
|
||||
use bdk::{FeeRate, Wallet, SyncOptions};
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
||||
use bdk::blockchain::ElectrumBlockchain;
|
||||
|
||||
use bdk::electrum_client::Client;
|
||||
use bdk::wallet::AddressIndex::New;
|
||||
@@ -98,16 +98,15 @@ use bdk::wallet::AddressIndex::New;
|
||||
use bitcoin::consensus::serialize;
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
|
||||
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
||||
let wallet = Wallet::new(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
MemoryDatabase::default(),
|
||||
ElectrumBlockchain::from(client)
|
||||
)?;
|
||||
|
||||
wallet.sync(noop_progress(), None)?;
|
||||
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||
|
||||
let send_to = wallet.get_address(New)?;
|
||||
let (psbt, details) = {
|
||||
@@ -135,7 +134,7 @@ use bdk::{Wallet, SignOptions, database::MemoryDatabase};
|
||||
use bitcoin::consensus::deserialize;
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
|
||||
@@ -48,8 +48,7 @@ impl AddressValidator for DummyValidator {
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
|
||||
let mut wallet =
|
||||
Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
|
||||
let mut wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
|
||||
|
||||
wallet.add_address_validator(Arc::new(DummyValidator));
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
// licenses.
|
||||
|
||||
use bdk::blockchain::compact_filters::*;
|
||||
use bdk::blockchain::noop_progress;
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::*;
|
||||
use bitcoin::*;
|
||||
@@ -35,9 +34,8 @@ fn main() -> Result<(), CompactFiltersError> {
|
||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)";
|
||||
|
||||
let database = MemoryDatabase::default();
|
||||
let wallet =
|
||||
Arc::new(Wallet::new(descriptor, None, Network::Testnet, database, blockchain).unwrap());
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
let wallet = Arc::new(Wallet::new(descriptor, None, Network::Testnet, database).unwrap());
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
info!("balance: {}", wallet.get_balance()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
.transpose()
|
||||
.unwrap()
|
||||
.unwrap_or(Network::Testnet);
|
||||
let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?;
|
||||
let wallet = Wallet::new(&format!("{}", descriptor), None, network, database)?;
|
||||
|
||||
info!("... First address: {}", wallet.get_address(New)?);
|
||||
|
||||
|
||||
229
examples/rpcwallet.rs
Normal file
229
examples/rpcwallet.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
|
||||
//
|
||||
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||
// You may not use this file except in accordance with one or both of these
|
||||
// licenses.
|
||||
|
||||
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||
use bdk::bitcoin::Amount;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::bitcoincore_rpc::RpcApi;
|
||||
|
||||
use bdk::blockchain::rpc::{Auth, RpcBlockchain, RpcConfig};
|
||||
use bdk::blockchain::ConfigurableBlockchain;
|
||||
|
||||
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
|
||||
use bdk::keys::{DerivableKey, GeneratableKey, GeneratedKey};
|
||||
|
||||
use bdk::miniscript::miniscript::Segwitv0;
|
||||
|
||||
use bdk::sled;
|
||||
use bdk::template::Bip84;
|
||||
use bdk::wallet::{signer::SignOptions, wallet_name_from_descriptor, AddressIndex, SyncOptions};
|
||||
use bdk::KeychainKind;
|
||||
use bdk::Wallet;
|
||||
|
||||
use bdk::blockchain::Blockchain;
|
||||
|
||||
use electrsd;
|
||||
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// This example demonstrates a typical way to create a wallet and work with bdk.
|
||||
///
|
||||
/// This example bdk wallet is connected to a bitcoin core rpc regtest node,
|
||||
/// and will attempt to receive, create and broadcast transactions.
|
||||
///
|
||||
/// To start a bitcoind regtest node programmatically, this example uses
|
||||
/// `electrsd` library, which is also a bdk dev-dependency.
|
||||
///
|
||||
/// But you can start your own bitcoind backend, and the rest of the example should work fine.
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// -- Setting up background bitcoind process
|
||||
|
||||
println!(">> Setting up bitcoind");
|
||||
|
||||
// Start the bitcoind process
|
||||
let bitcoind_conf = electrsd::bitcoind::Conf::default();
|
||||
|
||||
// electrsd will automatically download the bitcoin core binaries
|
||||
let bitcoind_exe =
|
||||
electrsd::bitcoind::downloaded_exe_path().expect("We should always have downloaded path");
|
||||
|
||||
// Launch bitcoind and gather authentication access
|
||||
let bitcoind = electrsd::bitcoind::BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap();
|
||||
let bitcoind_auth = Auth::Cookie {
|
||||
file: bitcoind.params.cookie_file.clone(),
|
||||
};
|
||||
|
||||
// Get a new core address
|
||||
let core_address = bitcoind.client.get_new_address(None, None)?;
|
||||
|
||||
// Generate 101 blocks and use the above address as coinbase
|
||||
bitcoind.client.generate_to_address(101, &core_address)?;
|
||||
|
||||
println!(">> bitcoind setup complete");
|
||||
println!(
|
||||
"Available coins in Core wallet : {}",
|
||||
bitcoind.client.get_balance(None, None)?
|
||||
);
|
||||
|
||||
// -- Setting up the Wallet
|
||||
|
||||
println!("\n>> Setting up BDK wallet");
|
||||
|
||||
// Get a random private key
|
||||
let xprv = generate_random_ext_privkey()?;
|
||||
|
||||
// Use the derived descriptors from the privatekey to
|
||||
// create unique wallet name.
|
||||
// This is a special utility function exposed via `bdk::wallet_name_from_descriptor()`
|
||||
let wallet_name = wallet_name_from_descriptor(
|
||||
Bip84(xprv.clone(), KeychainKind::External),
|
||||
Some(Bip84(xprv.clone(), KeychainKind::Internal)),
|
||||
Network::Regtest,
|
||||
&Secp256k1::new(),
|
||||
)?;
|
||||
|
||||
// Create a database (using default sled type) to store wallet data
|
||||
let mut datadir = PathBuf::from_str("/tmp/")?;
|
||||
datadir.push(".bdk-example");
|
||||
let database = sled::open(datadir)?;
|
||||
let database = database.open_tree(wallet_name.clone())?;
|
||||
|
||||
// Create a RPC configuration of the running bitcoind backend we created in last step
|
||||
// Note: If you are using custom regtest node, use the appropriate url and auth
|
||||
let rpc_config = RpcConfig {
|
||||
url: bitcoind.params.rpc_socket.to_string(),
|
||||
auth: bitcoind_auth,
|
||||
network: Network::Regtest,
|
||||
wallet_name,
|
||||
skip_blocks: None,
|
||||
};
|
||||
|
||||
// Use the above configuration to create a RPC blockchain backend
|
||||
let blockchain = RpcBlockchain::from_config(&rpc_config)?;
|
||||
|
||||
// Combine Database + Descriptor to create the final wallet
|
||||
let wallet = Wallet::new(
|
||||
Bip84(xprv.clone(), KeychainKind::External),
|
||||
Some(Bip84(xprv.clone(), KeychainKind::Internal)),
|
||||
Network::Regtest,
|
||||
database,
|
||||
)?;
|
||||
|
||||
// The `wallet` and the `blockchain` are independent structs.
|
||||
// The wallet will be used to do all wallet level actions
|
||||
// The blockchain can be used to do all blockchain level actions.
|
||||
// For certain actions (like sync) the wallet will ask for a blockchain.
|
||||
|
||||
// Sync the wallet
|
||||
// The first sync is important as this will instantiate the
|
||||
// wallet files.
|
||||
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||
|
||||
println!(">> BDK wallet setup complete.");
|
||||
println!(
|
||||
"Available initial coins in BDK wallet : {} sats",
|
||||
wallet.get_balance()?
|
||||
);
|
||||
|
||||
// -- Wallet transaction demonstration
|
||||
|
||||
println!("\n>> Sending coins: Core --> BDK, 10 BTC");
|
||||
// Get a new address to receive coins
|
||||
let bdk_new_addr = wallet.get_address(AddressIndex::New)?.address;
|
||||
|
||||
// Send 10 BTC from core wallet to bdk wallet
|
||||
bitcoind.client.send_to_address(
|
||||
&bdk_new_addr,
|
||||
Amount::from_btc(10.0)?,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Confirm transaction by generating 1 block
|
||||
bitcoind.client.generate_to_address(1, &core_address)?;
|
||||
|
||||
// Sync the BDK wallet
|
||||
// This time the sync will fetch the new transaction and update it in
|
||||
// wallet database
|
||||
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||
|
||||
println!(">> Received coins in BDK wallet");
|
||||
println!(
|
||||
"Available balance in BDK wallet: {} sats",
|
||||
wallet.get_balance()?
|
||||
);
|
||||
|
||||
println!("\n>> Sending coins: BDK --> Core, 5 BTC");
|
||||
// Attempt to send back 5.0 BTC to core address by creating a transaction
|
||||
//
|
||||
// Transactions are created using a `TxBuilder`.
|
||||
// This helps us to systematically build a transaction with all
|
||||
// required customization.
|
||||
// A full list of APIs offered by `TxBuilder` can be found at
|
||||
// https://docs.rs/bdk/latest/bdk/wallet/tx_builder/struct.TxBuilder.html
|
||||
let mut tx_builder = wallet.build_tx();
|
||||
|
||||
// For a regular transaction, just set the recipient and amount
|
||||
tx_builder.set_recipients(vec![(core_address.script_pubkey(), 500000000)]);
|
||||
|
||||
// Finalize the transaction and extract the PSBT
|
||||
let (mut psbt, _) = tx_builder.finish()?;
|
||||
|
||||
// Set signing option
|
||||
let signopt = SignOptions {
|
||||
assume_height: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Sign the psbt
|
||||
wallet.sign(&mut psbt, signopt)?;
|
||||
|
||||
// Extract the signed transaction
|
||||
let tx = psbt.extract_tx();
|
||||
|
||||
// Broadcast the transaction
|
||||
blockchain.broadcast(&tx)?;
|
||||
|
||||
// Confirm transaction by generating some blocks
|
||||
bitcoind.client.generate_to_address(1, &core_address)?;
|
||||
|
||||
// Sync the BDK wallet
|
||||
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||
|
||||
println!(">> Coins sent to Core wallet");
|
||||
println!(
|
||||
"Remaining BDK wallet balance: {} sats",
|
||||
wallet.get_balance()?
|
||||
);
|
||||
println!("\nCongrats!! you made your first test transaction with bdk and bitcoin core.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Helper function demonstrating privatekey extraction using bip39 mnemonic
|
||||
// The mnemonic can be shown to user to safekeeping and the same wallet
|
||||
// private descriptors can be recreated from it.
|
||||
fn generate_random_ext_privkey() -> Result<impl DerivableKey<Segwitv0> + Clone, Box<dyn Error>> {
|
||||
// a Bip39 passphrase can be set optionally
|
||||
let password = Some("random password".to_string());
|
||||
|
||||
// Generate a random mnemonic, and use that to create a "DerivableKey"
|
||||
let mnemonic: GeneratedKey<_, _> = Mnemonic::generate((WordCount::Words12, Language::English))
|
||||
.map_err(|e| e.expect("Unknown Error"))?;
|
||||
|
||||
// `Ok(mnemonic)` would also work if there's no passphrase and it would
|
||||
// yield the same result as this construct with `password` = `None`.
|
||||
Ok((mnemonic, password))
|
||||
}
|
||||
@@ -16,61 +16,17 @@
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! In this example both `wallet_electrum` and `wallet_esplora` have the same type of
|
||||
//! `Wallet<AnyBlockchain, MemoryDatabase>`. This means that they could both, for instance, be
|
||||
//! assigned to a struct member.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use bitcoin::Network;
|
||||
//! # use bdk::blockchain::*;
|
||||
//! # use bdk::database::MemoryDatabase;
|
||||
//! # use bdk::Wallet;
|
||||
//! # #[cfg(feature = "electrum")]
|
||||
//! # {
|
||||
//! let electrum_blockchain = ElectrumBlockchain::from(electrum_client::Client::new("...")?);
|
||||
//! let wallet_electrum: Wallet<AnyBlockchain, _> = Wallet::new(
|
||||
//! "...",
|
||||
//! None,
|
||||
//! Network::Testnet,
|
||||
//! MemoryDatabase::default(),
|
||||
//! electrum_blockchain.into(),
|
||||
//! )?;
|
||||
//! # }
|
||||
//!
|
||||
//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
|
||||
//! # {
|
||||
//! let esplora_blockchain = EsploraBlockchain::new("...", 20);
|
||||
//! let wallet_esplora: Wallet<AnyBlockchain, _> = Wallet::new(
|
||||
//! "...",
|
||||
//! None,
|
||||
//! Network::Testnet,
|
||||
//! MemoryDatabase::default(),
|
||||
//! esplora_blockchain.into(),
|
||||
//! )?;
|
||||
//! # }
|
||||
//!
|
||||
//! # Ok::<(), bdk::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! When paired with the use of [`ConfigurableBlockchain`], it allows creating wallets with any
|
||||
//! When paired with the use of [`ConfigurableBlockchain`], it allows creating any
|
||||
//! blockchain type supported using a single line of code:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use bitcoin::Network;
|
||||
//! # use bdk::blockchain::*;
|
||||
//! # use bdk::database::MemoryDatabase;
|
||||
//! # use bdk::Wallet;
|
||||
//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
|
||||
//! # {
|
||||
//! let config = serde_json::from_str("...")?;
|
||||
//! let blockchain = AnyBlockchain::from_config(&config)?;
|
||||
//! let wallet = Wallet::new(
|
||||
//! "...",
|
||||
//! None,
|
||||
//! Network::Testnet,
|
||||
//! MemoryDatabase::default(),
|
||||
//! blockchain,
|
||||
//! )?;
|
||||
//! let height = blockchain.get_height();
|
||||
//! # }
|
||||
//! # Ok::<(), bdk::Error>(())
|
||||
//! ```
|
||||
@@ -133,33 +89,55 @@ impl Blockchain for AnyBlockchain {
|
||||
maybe_await!(impl_inner_method!(self, get_capabilities))
|
||||
}
|
||||
|
||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
||||
&self,
|
||||
database: &mut D,
|
||||
progress_update: P,
|
||||
) -> Result<(), Error> {
|
||||
maybe_await!(impl_inner_method!(self, setup, database, progress_update))
|
||||
}
|
||||
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
||||
&self,
|
||||
database: &mut D,
|
||||
progress_update: P,
|
||||
) -> Result<(), Error> {
|
||||
maybe_await!(impl_inner_method!(self, sync, database, progress_update))
|
||||
}
|
||||
|
||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||
maybe_await!(impl_inner_method!(self, get_tx, txid))
|
||||
}
|
||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||
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> {
|
||||
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 sync;
|
||||
|
||||
use super::{Blockchain, Capability, ConfigurableBlockchain, Progress};
|
||||
use crate::blockchain::*;
|
||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
||||
use crate::error::Error;
|
||||
use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
|
||||
@@ -163,11 +163,19 @@ impl CompactFiltersBlockchain {
|
||||
if let Some(previous_output) = database.get_previous_output(&input.previous_output)? {
|
||||
inputs_sum += previous_output.value;
|
||||
|
||||
if database.is_mine(&previous_output.script_pubkey)? {
|
||||
// this output is ours, we have a path to derive it
|
||||
if let Some((keychain, _)) =
|
||||
database.get_path_from_script_pubkey(&previous_output.script_pubkey)?
|
||||
{
|
||||
outgoing += previous_output.value;
|
||||
|
||||
debug!("{} input #{} is mine, removing from utxo", tx.txid(), i);
|
||||
updates.del_utxo(&input.previous_output)?;
|
||||
debug!("{} input #{} is mine, setting utxo as spent", tx.txid(), i);
|
||||
updates.set_utxo(&LocalUtxo {
|
||||
outpoint: input.previous_output,
|
||||
txout: previous_output.clone(),
|
||||
keychain,
|
||||
is_spent: true,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,6 +193,7 @@ impl CompactFiltersBlockchain {
|
||||
outpoint: OutPoint::new(tx.txid(), i as u32),
|
||||
txout: output.clone(),
|
||||
keychain,
|
||||
is_spent: false,
|
||||
})?;
|
||||
incoming += output.value;
|
||||
|
||||
@@ -225,11 +234,38 @@ impl Blockchain for CompactFiltersBlockchain {
|
||||
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.
|
||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
||||
fn wallet_setup<D: BatchDatabase>(
|
||||
&self,
|
||||
database: &mut D,
|
||||
progress_update: P,
|
||||
progress_update: Box<dyn Progress>,
|
||||
) -> Result<(), Error> {
|
||||
let first_peer = &self.peers[0];
|
||||
|
||||
@@ -430,27 +466,6 @@ impl Blockchain for CompactFiltersBlockchain {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||
Ok(self.peers[0]
|
||||
.get_mempool()
|
||||
.get_tx(&Inventory::Transaction(*txid)))
|
||||
}
|
||||
|
||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||
self.peers[0].broadcast_tx(tx.clone())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_height(&self) -> Result<u32, Error> {
|
||||
Ok(self.headers.get_height()? as u32)
|
||||
}
|
||||
|
||||
fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
|
||||
// TODO
|
||||
Ok(FeeRate::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Data to connect to a Bitcoin P2P peer
|
||||
|
||||
@@ -68,10 +68,39 @@ impl Blockchain for ElectrumBlockchain {
|
||||
.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,
|
||||
database: &mut D,
|
||||
_progress_update: P,
|
||||
_progress_update: Box<dyn Progress>,
|
||||
) -> Result<(), Error> {
|
||||
let mut request = script_sync::start(database, self.stop_gap)?;
|
||||
let mut block_times = HashMap::<u32, u32>::new();
|
||||
@@ -207,29 +236,6 @@ impl Blockchain for ElectrumBlockchain {
|
||||
database.commit_batch(batch_update)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||
Ok(self.client.transaction_get(txid).map(Option::Some)?)
|
||||
}
|
||||
|
||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||
Ok(self.client.transaction_broadcast(tx).map(|_| ())?)
|
||||
}
|
||||
|
||||
fn get_height(&self) -> Result<u32, Error> {
|
||||
// TODO: unsubscribe when added to the client, or is there a better call to use here?
|
||||
|
||||
Ok(self
|
||||
.client
|
||||
.block_headers_subscribe()
|
||||
.map(|data| data.height as u32)?)
|
||||
}
|
||||
|
||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||
Ok(FeeRate::from_btc_per_kvb(
|
||||
self.client.estimate_fee(target)? as f32
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct TxCache<'a, 'b, D> {
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct Vin {
|
||||
// None if coinbase
|
||||
pub prevout: Option<PrevOut>,
|
||||
pub scriptsig: Script,
|
||||
#[serde(deserialize_with = "deserialize_witness")]
|
||||
#[serde(deserialize_with = "deserialize_witness", default)]
|
||||
pub witness: Vec<Vec<u8>>,
|
||||
pub sequence: u32,
|
||||
pub is_coinbase: bool,
|
||||
|
||||
@@ -91,10 +91,36 @@ impl Blockchain for EsploraBlockchain {
|
||||
.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,
|
||||
database: &mut D,
|
||||
_progress_update: P,
|
||||
_progress_update: Box<dyn Progress>,
|
||||
) -> Result<(), Error> {
|
||||
use crate::blockchain::script_sync::Request;
|
||||
let mut request = script_sync::start(database, self.stop_gap)?;
|
||||
@@ -180,23 +206,6 @@ impl Blockchain for EsploraBlockchain {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||
Ok(await_or_block!(self.url_client._get_tx(txid))?)
|
||||
}
|
||||
|
||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||
Ok(await_or_block!(self.url_client._broadcast(tx))?)
|
||||
}
|
||||
|
||||
fn get_height(&self) -> Result<u32, Error> {
|
||||
Ok(await_or_block!(self.url_client._get_height())?)
|
||||
}
|
||||
|
||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||
let estimates = await_or_block!(self.url_client._get_fee_estimates())?;
|
||||
super::into_fee_rate(target, estimates)
|
||||
}
|
||||
}
|
||||
|
||||
impl UrlClient {
|
||||
|
||||
@@ -87,10 +87,34 @@ impl Blockchain for EsploraBlockchain {
|
||||
.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,
|
||||
database: &mut D,
|
||||
_progress_update: P,
|
||||
_progress_update: Box<dyn Progress>,
|
||||
) -> Result<(), Error> {
|
||||
use crate::blockchain::script_sync::Request;
|
||||
let mut request = script_sync::start(database, self.stop_gap)?;
|
||||
@@ -103,10 +127,11 @@ impl Blockchain for EsploraBlockchain {
|
||||
.take(self.concurrency as usize)
|
||||
.cloned();
|
||||
|
||||
let handles = scripts.map(move |script| {
|
||||
let mut handles = vec![];
|
||||
for script in scripts {
|
||||
let client = self.url_client.clone();
|
||||
// make each request in its own thread.
|
||||
std::thread::spawn(move || {
|
||||
handles.push(std::thread::spawn(move || {
|
||||
let mut related_txs: Vec<Tx> = client._scripthash_txs(&script, None)?;
|
||||
|
||||
let n_confirmed =
|
||||
@@ -128,10 +153,11 @@ impl Blockchain for EsploraBlockchain {
|
||||
}
|
||||
}
|
||||
Result::<_, Error>::Ok(related_txs)
|
||||
})
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
let txs_per_script: Vec<Vec<Tx>> = handles
|
||||
.into_iter()
|
||||
.map(|handle| handle.join().unwrap())
|
||||
.collect::<Result<_, _>>()?;
|
||||
let mut satisfaction = vec![];
|
||||
@@ -179,24 +205,6 @@ impl Blockchain for EsploraBlockchain {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||
Ok(self.url_client._get_tx(txid)?)
|
||||
}
|
||||
|
||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||
let _txid = self.url_client._broadcast(tx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_height(&self) -> Result<u32, Error> {
|
||||
Ok(self.url_client._get_height()?)
|
||||
}
|
||||
|
||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||
let estimates = self.url_client._get_fee_estimates()?;
|
||||
super::into_fee_rate(target, estimates)
|
||||
}
|
||||
}
|
||||
|
||||
impl UrlClient {
|
||||
|
||||
@@ -86,28 +86,50 @@ pub enum Capability {
|
||||
|
||||
/// Trait that defines the actions that must be supported by a blockchain backend
|
||||
#[maybe_async]
|
||||
pub trait Blockchain {
|
||||
pub trait Blockchain: WalletSync + GetHeight + GetTx {
|
||||
/// Return the set of [`Capability`] supported by this backend
|
||||
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
|
||||
///
|
||||
/// This method is the equivalent of [`Blockchain::sync`], but it's guaranteed to only be
|
||||
/// This method is the equivalent of [`Self::wallet_sync`], but it's guaranteed to only be
|
||||
/// called once, at the first [`Wallet::sync`](crate::wallet::Wallet::sync).
|
||||
///
|
||||
/// The rationale behind the distinction between `sync` and `setup` is that some custom backends
|
||||
/// might need to perform specific actions only the first time they are synced.
|
||||
///
|
||||
/// For types that do not have that distinction, only this method can be implemented, since
|
||||
/// [`Blockchain::sync`] defaults to calling this internally if not overridden.
|
||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
||||
/// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden.
|
||||
/// Populate the internal database with transactions and UTXOs
|
||||
fn wallet_setup<D: BatchDatabase>(
|
||||
&self,
|
||||
database: &mut D,
|
||||
progress_update: P,
|
||||
progress_update: Box<dyn Progress>,
|
||||
) -> Result<(), Error>;
|
||||
/// Populate the internal database with transactions and UTXOs
|
||||
///
|
||||
/// If not overridden, it defaults to calling [`Blockchain::setup`] internally.
|
||||
|
||||
/// If not overridden, it defaults to calling [`Self::wallet_setup`] internally.
|
||||
///
|
||||
/// This method should implement the logic required to iterate over the list of the wallet's
|
||||
/// script_pubkeys using [`Database::iter_script_pubkeys`] and look for relevant transactions
|
||||
@@ -124,23 +146,13 @@ pub trait Blockchain {
|
||||
/// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx
|
||||
/// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo
|
||||
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
|
||||
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
||||
fn wallet_sync<D: BatchDatabase>(
|
||||
&self,
|
||||
database: &mut D,
|
||||
progress_update: P,
|
||||
progress_update: Box<dyn Progress>,
|
||||
) -> Result<(), Error> {
|
||||
maybe_await!(self.setup(database, progress_update))
|
||||
maybe_await!(self.wallet_setup(database, progress_update))
|
||||
}
|
||||
|
||||
/// Fetch a transaction from the blockchain given its txid
|
||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
|
||||
/// Broadcast a transaction
|
||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
|
||||
|
||||
/// Return the current height
|
||||
fn get_height(&self) -> Result<u32, Error>;
|
||||
/// Estimate the fee rate required to confirm a transaction in a given `target` of blocks
|
||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
|
||||
}
|
||||
|
||||
/// Trait for [`Blockchain`] types that can be created given a configuration
|
||||
@@ -155,9 +167,9 @@ pub trait ConfigurableBlockchain: Blockchain + Sized {
|
||||
/// Data sent with a progress update over a [`channel`]
|
||||
pub type ProgressData = (f32, Option<String>);
|
||||
|
||||
/// Trait for types that can receive and process progress updates during [`Blockchain::sync`] and
|
||||
/// [`Blockchain::setup`]
|
||||
pub trait Progress: Send {
|
||||
/// Trait for types that can receive and process progress updates during [`WalletSync::wallet_sync`] and
|
||||
/// [`WalletSync::wallet_setup`]
|
||||
pub trait Progress: Send + 'static + core::fmt::Debug {
|
||||
/// Send a new progress update
|
||||
///
|
||||
/// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an
|
||||
@@ -182,7 +194,7 @@ impl Progress for Sender<ProgressData> {
|
||||
}
|
||||
|
||||
/// Type that implements [`Progress`] and drops every update received
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct NoopProgress;
|
||||
|
||||
/// Create a new instance of [`NoopProgress`]
|
||||
@@ -197,7 +209,7 @@ impl Progress for NoopProgress {
|
||||
}
|
||||
|
||||
/// Type that implements [`Progress`] and logs at level `INFO` every update received
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct LogProgress;
|
||||
|
||||
/// Create a new instance of [`LogProgress`]
|
||||
@@ -223,33 +235,44 @@ impl<T: Blockchain> Blockchain for Arc<T> {
|
||||
maybe_await!(self.deref().get_capabilities())
|
||||
}
|
||||
|
||||
fn setup<D: BatchDatabase, P: 'static + Progress>(
|
||||
&self,
|
||||
database: &mut D,
|
||||
progress_update: P,
|
||||
) -> Result<(), Error> {
|
||||
maybe_await!(self.deref().setup(database, progress_update))
|
||||
}
|
||||
|
||||
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
||||
&self,
|
||||
database: &mut D,
|
||||
progress_update: P,
|
||||
) -> Result<(), Error> {
|
||||
maybe_await!(self.deref().sync(database, progress_update))
|
||||
}
|
||||
|
||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||
maybe_await!(self.deref().get_tx(txid))
|
||||
}
|
||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||
maybe_await!(self.deref().broadcast(tx))
|
||||
}
|
||||
|
||||
fn get_height(&self) -> Result<u32, Error> {
|
||||
maybe_await!(self.deref().get_height())
|
||||
}
|
||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||
maybe_await!(self.deref().estimate_fee(target))
|
||||
}
|
||||
}
|
||||
|
||||
#[maybe_async]
|
||||
impl<T: 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::{Address, Network, OutPoint, Transaction, TxOut, Txid};
|
||||
use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress};
|
||||
use crate::blockchain::*;
|
||||
use crate::database::{BatchDatabase, DatabaseUtils};
|
||||
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
|
||||
use bitcoincore_rpc::json::{
|
||||
@@ -139,10 +139,39 @@ impl Blockchain for RpcBlockchain {
|
||||
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,
|
||||
database: &mut D,
|
||||
progress_update: P,
|
||||
progress_update: Box<dyn Progress>,
|
||||
) -> Result<(), Error> {
|
||||
let mut scripts_pubkeys = database.iter_script_pubkeys(Some(KeychainKind::External))?;
|
||||
scripts_pubkeys.extend(database.iter_script_pubkeys(Some(KeychainKind::Internal))?);
|
||||
@@ -187,13 +216,13 @@ impl Blockchain for RpcBlockchain {
|
||||
}
|
||||
}
|
||||
|
||||
self.sync(database, progress_update)
|
||||
self.wallet_sync(database, progress_update)
|
||||
}
|
||||
|
||||
fn sync<D: BatchDatabase, P: 'static + Progress>(
|
||||
fn wallet_sync<D: BatchDatabase>(
|
||||
&self,
|
||||
db: &mut D,
|
||||
_progress_update: P,
|
||||
_progress_update: Box<dyn Progress>,
|
||||
) -> Result<(), Error> {
|
||||
let mut indexes = HashMap::new();
|
||||
for keykind in &[KeychainKind::External, KeychainKind::Internal] {
|
||||
@@ -220,7 +249,7 @@ impl Blockchain for RpcBlockchain {
|
||||
let mut list_txs_ids = HashSet::new();
|
||||
|
||||
for tx_result in list_txs.iter().filter(|t| {
|
||||
// list_txs returns all conflicting tx we want to
|
||||
// list_txs returns all conflicting txs, we want to
|
||||
// filter out replaced tx => unconfirmed and not in the mempool
|
||||
t.info.confirmations > 0 || self.client.get_mempool_entry(&t.info.txid).is_ok()
|
||||
}) {
|
||||
@@ -289,32 +318,37 @@ 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()
|
||||
.map(|u| {
|
||||
Ok(LocalUtxo {
|
||||
outpoint: OutPoint::new(u.txid, u.vout),
|
||||
keychain: db
|
||||
.get_path_from_script_pubkey(&u.script_pub_key)?
|
||||
.ok_or(Error::TransactionNotFound)?
|
||||
.0,
|
||||
txout: TxOut {
|
||||
value: u.amount.as_sat(),
|
||||
script_pubkey: u.script_pub_key,
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, Error>>()?;
|
||||
.filter_map(
|
||||
|u| match db.get_path_from_script_pubkey(&u.script_pub_key) {
|
||||
Err(e) => Some(Err(e)),
|
||||
Ok(None) => None,
|
||||
Ok(Some(path)) => Some(Ok(LocalUtxo {
|
||||
outpoint: OutPoint::new(u.txid, u.vout),
|
||||
keychain: path.0,
|
||||
txout: TxOut {
|
||||
value: u.amount.as_sat(),
|
||||
script_pubkey: u.script_pub_key,
|
||||
},
|
||||
is_spent: false,
|
||||
})),
|
||||
},
|
||||
)
|
||||
.collect::<Result<HashSet<_>, Error>>()?;
|
||||
|
||||
let spent: HashSet<_> = known_utxos.difference(¤t_utxos).collect();
|
||||
for s in spent {
|
||||
debug!("removing utxo: {:?}", s);
|
||||
db.del_utxo(&s.outpoint)?;
|
||||
for utxo in spent {
|
||||
debug!("setting as spent utxo: {:?}", utxo);
|
||||
let mut spent_utxo = utxo.clone();
|
||||
spent_utxo.is_spent = true;
|
||||
db.set_utxo(&spent_utxo)?;
|
||||
}
|
||||
let received: HashSet<_> = current_utxos.difference(&known_utxos).collect();
|
||||
for s in received {
|
||||
debug!("adding utxo: {:?}", s);
|
||||
db.set_utxo(s)?;
|
||||
for utxo in received {
|
||||
debug!("adding utxo: {:?}", utxo);
|
||||
db.set_utxo(utxo)?;
|
||||
}
|
||||
|
||||
for (keykind, index) in indexes {
|
||||
@@ -324,29 +358,6 @@ impl Blockchain for RpcBlockchain {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||
Ok(Some(self.client.get_raw_transaction(txid, None)?))
|
||||
}
|
||||
|
||||
fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
|
||||
Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
|
||||
}
|
||||
|
||||
fn get_height(&self) -> Result<u32, Error> {
|
||||
Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
|
||||
}
|
||||
|
||||
fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
|
||||
let sat_per_kb = self
|
||||
.client
|
||||
.estimate_smart_fee(target as u16, None)?
|
||||
.fee_rate
|
||||
.ok_or(Error::FeeRateUnavailable)?
|
||||
.as_sat() as f64;
|
||||
|
||||
Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigurableBlockchain for RpcBlockchain {
|
||||
|
||||
@@ -332,7 +332,23 @@ impl<'a, D: BatchDatabase> State<'a, D> {
|
||||
batch.del_tx(txid, true)?;
|
||||
}
|
||||
|
||||
// Set every tx we observed
|
||||
let mut spent_utxos = HashSet::new();
|
||||
|
||||
// track all the spent utxos
|
||||
for finished_tx in &finished_txs {
|
||||
let tx = finished_tx
|
||||
.transaction
|
||||
.as_ref()
|
||||
.expect("transaction will always be present here");
|
||||
for input in &tx.input {
|
||||
spent_utxos.insert(&input.previous_output);
|
||||
}
|
||||
}
|
||||
|
||||
// set every utxo we observed, unless it's already spent
|
||||
// we don't do this in the loop above as we want to know all the spent outputs before
|
||||
// adding the non-spent to the batch in case there are new tranasactions
|
||||
// that spend form each other.
|
||||
for finished_tx in &finished_txs {
|
||||
let tx = finished_tx
|
||||
.transaction
|
||||
@@ -343,30 +359,22 @@ impl<'a, D: BatchDatabase> State<'a, D> {
|
||||
self.db.get_path_from_script_pubkey(&output.script_pubkey)?
|
||||
{
|
||||
// add utxos we own from the new transactions we've seen.
|
||||
let outpoint = OutPoint {
|
||||
txid: finished_tx.txid,
|
||||
vout: i as u32,
|
||||
};
|
||||
|
||||
batch.set_utxo(&LocalUtxo {
|
||||
outpoint: OutPoint {
|
||||
txid: finished_tx.txid,
|
||||
vout: i as u32,
|
||||
},
|
||||
outpoint,
|
||||
txout: output.clone(),
|
||||
keychain,
|
||||
// Is this UTXO in the spent_utxos set?
|
||||
is_spent: spent_utxos.get(&outpoint).is_some(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
batch.set_tx(finished_tx)?;
|
||||
}
|
||||
|
||||
// we don't do this in the loop above since we may want to delete some of the utxos we
|
||||
// just added in case there are new tranasactions that spend form each other.
|
||||
for finished_tx in &finished_txs {
|
||||
let tx = finished_tx
|
||||
.transaction
|
||||
.as_ref()
|
||||
.expect("transaction will always be present here");
|
||||
for input in &tx.input {
|
||||
// Delete any spent utxos
|
||||
batch.del_utxo(&input.previous_output)?;
|
||||
}
|
||||
batch.set_tx(finished_tx)?;
|
||||
}
|
||||
|
||||
for (keychain, last_active_index) in self.last_active_index {
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
//! # use bdk::database::{AnyDatabase, MemoryDatabase};
|
||||
//! # use bdk::{Wallet};
|
||||
//! let memory = MemoryDatabase::default();
|
||||
//! let wallet_memory = Wallet::new_offline("...", None, Network::Testnet, memory)?;
|
||||
//! let wallet_memory = Wallet::new("...", None, Network::Testnet, memory)?;
|
||||
//!
|
||||
//! # #[cfg(feature = "key-value-db")]
|
||||
//! # {
|
||||
//! let sled = sled::open("my-database")?.open_tree("default_tree")?;
|
||||
//! let wallet_sled = Wallet::new_offline("...", None, Network::Testnet, sled)?;
|
||||
//! let wallet_sled = Wallet::new("...", None, Network::Testnet, sled)?;
|
||||
//! # }
|
||||
//! # Ok::<(), bdk::Error>(())
|
||||
//! ```
|
||||
@@ -42,7 +42,7 @@
|
||||
//! # use bdk::{Wallet};
|
||||
//! let config = serde_json::from_str("...")?;
|
||||
//! let database = AnyDatabase::from_config(&config)?;
|
||||
//! let wallet = Wallet::new_offline("...", None, Network::Testnet, database)?;
|
||||
//! let wallet = Wallet::new("...", None, Network::Testnet, database)?;
|
||||
//! # Ok::<(), bdk::Error>(())
|
||||
//! ```
|
||||
|
||||
@@ -61,6 +61,7 @@ macro_rules! impl_from {
|
||||
|
||||
macro_rules! impl_inner_method {
|
||||
( $enum_name:ident, $self:expr, $name:ident $(, $args:expr)* ) => {
|
||||
#[allow(deprecated)]
|
||||
match $self {
|
||||
$enum_name::Memory(inner) => inner.$name( $($args, )* ),
|
||||
#[cfg(feature = "key-value-db")]
|
||||
|
||||
@@ -43,6 +43,7 @@ macro_rules! impl_batch_operations {
|
||||
let value = json!({
|
||||
"t": utxo.txout,
|
||||
"i": utxo.keychain,
|
||||
"s": utxo.is_spent,
|
||||
});
|
||||
self.insert(key, serde_json::to_vec(&value)?)$($after_insert)*;
|
||||
|
||||
@@ -125,8 +126,9 @@ macro_rules! impl_batch_operations {
|
||||
let mut val: serde_json::Value = serde_json::from_slice(&b)?;
|
||||
let txout = serde_json::from_value(val["t"].take())?;
|
||||
let keychain = serde_json::from_value(val["i"].take())?;
|
||||
let is_spent = val.get_mut("s").and_then(|s| s.take().as_bool()).unwrap_or(false);
|
||||
|
||||
Ok(Some(LocalUtxo { outpoint: outpoint.clone(), txout, keychain }))
|
||||
Ok(Some(LocalUtxo { outpoint: outpoint.clone(), txout, keychain, is_spent, }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,11 +248,16 @@ impl Database for Tree {
|
||||
let mut val: serde_json::Value = serde_json::from_slice(&v)?;
|
||||
let txout = serde_json::from_value(val["t"].take())?;
|
||||
let keychain = serde_json::from_value(val["i"].take())?;
|
||||
let is_spent = val
|
||||
.get_mut("s")
|
||||
.and_then(|s| s.take().as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(LocalUtxo {
|
||||
outpoint,
|
||||
txout,
|
||||
keychain,
|
||||
is_spent,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
@@ -314,11 +321,16 @@ impl Database for Tree {
|
||||
let mut val: serde_json::Value = serde_json::from_slice(&b)?;
|
||||
let txout = serde_json::from_value(val["t"].take())?;
|
||||
let keychain = serde_json::from_value(val["i"].take())?;
|
||||
let is_spent = val
|
||||
.get_mut("s")
|
||||
.and_then(|s| s.take().as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(LocalUtxo {
|
||||
outpoint: *outpoint,
|
||||
txout,
|
||||
keychain,
|
||||
is_spent,
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
|
||||
@@ -150,8 +150,10 @@ impl BatchOperations for MemoryDatabase {
|
||||
|
||||
fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
|
||||
let key = MapKey::Utxo(Some(&utxo.outpoint)).as_map_key();
|
||||
self.map
|
||||
.insert(key, Box::new((utxo.txout.clone(), utxo.keychain)));
|
||||
self.map.insert(
|
||||
key,
|
||||
Box::new((utxo.txout.clone(), utxo.keychain, utxo.is_spent)),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -228,11 +230,12 @@ impl BatchOperations for MemoryDatabase {
|
||||
match res {
|
||||
None => Ok(None),
|
||||
Some(b) => {
|
||||
let (txout, keychain) = b.downcast_ref().cloned().unwrap();
|
||||
let (txout, keychain, is_spent) = b.downcast_ref().cloned().unwrap();
|
||||
Ok(Some(LocalUtxo {
|
||||
outpoint: *outpoint,
|
||||
txout,
|
||||
keychain,
|
||||
is_spent,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -326,11 +329,12 @@ impl Database for MemoryDatabase {
|
||||
.range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
|
||||
.map(|(k, v)| {
|
||||
let outpoint = deserialize(&k[1..]).unwrap();
|
||||
let (txout, keychain) = v.downcast_ref().cloned().unwrap();
|
||||
let (txout, keychain, is_spent) = v.downcast_ref().cloned().unwrap();
|
||||
Ok(LocalUtxo {
|
||||
outpoint,
|
||||
txout,
|
||||
keychain,
|
||||
is_spent,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
@@ -389,11 +393,12 @@ impl Database for MemoryDatabase {
|
||||
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
|
||||
let key = MapKey::Utxo(Some(outpoint)).as_map_key();
|
||||
Ok(self.map.get(&key).map(|b| {
|
||||
let (txout, keychain) = b.downcast_ref().cloned().unwrap();
|
||||
let (txout, keychain, is_spent) = b.downcast_ref().cloned().unwrap();
|
||||
LocalUtxo {
|
||||
outpoint: *outpoint,
|
||||
txout,
|
||||
keychain,
|
||||
is_spent,
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -526,6 +531,7 @@ macro_rules! populate_test_db {
|
||||
vout: vout as u32,
|
||||
},
|
||||
keychain: $crate::KeychainKind::External,
|
||||
is_spent: false,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -554,7 +560,7 @@ macro_rules! doctest_wallet {
|
||||
Some(100),
|
||||
);
|
||||
|
||||
$crate::Wallet::new_offline(
|
||||
$crate::Wallet::new(
|
||||
&descriptors.0,
|
||||
descriptors.1.as_ref(),
|
||||
Network::Regtest,
|
||||
|
||||
@@ -159,6 +159,10 @@ pub trait Database: BatchOperations {
|
||||
/// It should insert and return `0` if not present in the database
|
||||
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error>;
|
||||
|
||||
#[deprecated(
|
||||
since = "0.18.0",
|
||||
note = "The flush function is only needed for the sled database on mobile, instead for mobile use the sqlite database."
|
||||
)]
|
||||
/// Force changes to be written to disk
|
||||
fn flush(&mut self) -> Result<(), Error>;
|
||||
}
|
||||
@@ -316,6 +320,7 @@ pub mod test {
|
||||
txout,
|
||||
outpoint,
|
||||
keychain: KeychainKind::External,
|
||||
is_spent: true,
|
||||
};
|
||||
|
||||
tree.set_utxo(&utxo).unwrap();
|
||||
|
||||
@@ -40,6 +40,7 @@ static MIGRATIONS: &[&str] = &[
|
||||
"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;",
|
||||
"ALTER TABLE utxos ADD COLUMN is_spent;",
|
||||
];
|
||||
|
||||
/// Sqlite database stored on filesystem
|
||||
@@ -83,14 +84,16 @@ impl SqliteDatabase {
|
||||
vout: u32,
|
||||
txid: &[u8],
|
||||
script: &[u8],
|
||||
is_spent: bool,
|
||||
) -> Result<i64, Error> {
|
||||
let mut statement = self.connection.prepare_cached("INSERT INTO utxos (value, keychain, vout, txid, script) VALUES (:value, :keychain, :vout, :txid, :script)")?;
|
||||
let mut statement = self.connection.prepare_cached("INSERT INTO utxos (value, keychain, vout, txid, script, is_spent) VALUES (:value, :keychain, :vout, :txid, :script, :is_spent)")?;
|
||||
statement.execute(named_params! {
|
||||
":value": value,
|
||||
":keychain": keychain,
|
||||
":vout": vout,
|
||||
":txid": txid,
|
||||
":script": script
|
||||
":script": script,
|
||||
":is_spent": is_spent,
|
||||
})?;
|
||||
|
||||
Ok(self.connection.last_insert_rowid())
|
||||
@@ -291,7 +294,7 @@ impl SqliteDatabase {
|
||||
fn select_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
|
||||
let mut statement = self
|
||||
.connection
|
||||
.prepare_cached("SELECT value, keychain, vout, txid, script FROM utxos")?;
|
||||
.prepare_cached("SELECT value, keychain, vout, txid, script, is_spent FROM utxos")?;
|
||||
let mut utxos: Vec<LocalUtxo> = vec![];
|
||||
let mut rows = statement.query([])?;
|
||||
while let Some(row) = rows.next()? {
|
||||
@@ -300,6 +303,7 @@ impl SqliteDatabase {
|
||||
let vout = row.get(2)?;
|
||||
let txid: Vec<u8> = row.get(3)?;
|
||||
let script: Vec<u8> = row.get(4)?;
|
||||
let is_spent: bool = row.get(5)?;
|
||||
|
||||
let keychain: KeychainKind = serde_json::from_str(&keychain)?;
|
||||
|
||||
@@ -310,19 +314,16 @@ impl SqliteDatabase {
|
||||
script_pubkey: script.into(),
|
||||
},
|
||||
keychain,
|
||||
is_spent,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(utxos)
|
||||
}
|
||||
|
||||
fn select_utxo_by_outpoint(
|
||||
&self,
|
||||
txid: &[u8],
|
||||
vout: u32,
|
||||
) -> Result<Option<(u64, KeychainKind, Script)>, Error> {
|
||||
fn select_utxo_by_outpoint(&self, txid: &[u8], vout: u32) -> Result<Option<LocalUtxo>, Error> {
|
||||
let mut statement = self.connection.prepare_cached(
|
||||
"SELECT value, keychain, script FROM utxos WHERE txid=:txid AND vout=:vout",
|
||||
"SELECT value, keychain, script, is_spent FROM utxos WHERE txid=:txid AND vout=:vout",
|
||||
)?;
|
||||
let mut rows = statement.query(named_params! {":txid": txid,":vout": vout})?;
|
||||
match rows.next()? {
|
||||
@@ -331,9 +332,18 @@ impl SqliteDatabase {
|
||||
let keychain: String = row.get(1)?;
|
||||
let keychain: KeychainKind = serde_json::from_str(&keychain)?;
|
||||
let script: Vec<u8> = row.get(2)?;
|
||||
let script: Script = script.into();
|
||||
let script_pubkey: Script = script.into();
|
||||
let is_spent: bool = row.get(3)?;
|
||||
|
||||
Ok(Some((value, keychain, script)))
|
||||
Ok(Some(LocalUtxo {
|
||||
outpoint: OutPoint::new(deserialize(txid)?, vout),
|
||||
txout: TxOut {
|
||||
value,
|
||||
script_pubkey,
|
||||
},
|
||||
keychain,
|
||||
is_spent,
|
||||
}))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
@@ -620,6 +630,7 @@ impl BatchOperations for SqliteDatabase {
|
||||
utxo.outpoint.vout,
|
||||
&utxo.outpoint.txid,
|
||||
utxo.txout.script_pubkey.as_bytes(),
|
||||
utxo.is_spent,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -694,16 +705,9 @@ impl BatchOperations for SqliteDatabase {
|
||||
|
||||
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
|
||||
match self.select_utxo_by_outpoint(&outpoint.txid, outpoint.vout)? {
|
||||
Some((value, keychain, script_pubkey)) => {
|
||||
Some(local_utxo) => {
|
||||
self.delete_utxo_by_outpoint(&outpoint.txid, outpoint.vout)?;
|
||||
Ok(Some(LocalUtxo {
|
||||
outpoint: *outpoint,
|
||||
txout: TxOut {
|
||||
value,
|
||||
script_pubkey,
|
||||
},
|
||||
keychain,
|
||||
}))
|
||||
Ok(Some(local_utxo))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
@@ -832,17 +836,7 @@ impl Database for SqliteDatabase {
|
||||
}
|
||||
|
||||
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
|
||||
match self.select_utxo_by_outpoint(&outpoint.txid, outpoint.vout)? {
|
||||
Some((value, keychain, script_pubkey)) => Ok(Some(LocalUtxo {
|
||||
outpoint: *outpoint,
|
||||
txout: TxOut {
|
||||
value,
|
||||
script_pubkey,
|
||||
},
|
||||
keychain,
|
||||
})),
|
||||
None => Ok(None),
|
||||
}
|
||||
self.select_utxo_by_outpoint(&outpoint.txid, outpoint.vout)
|
||||
}
|
||||
|
||||
fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||
|
||||
@@ -10,6 +10,41 @@
|
||||
// licenses.
|
||||
|
||||
//! Derived descriptor keys
|
||||
//!
|
||||
//! The [`DerivedDescriptorKey`] type is a wrapper over the standard [`DescriptorPublicKey`] which
|
||||
//! guarantees that all the extended keys have a fixed derivation path, i.e. all the wildcards have
|
||||
//! been replaced by actual derivation indexes.
|
||||
//!
|
||||
//! The [`AsDerived`] trait provides a quick way to derive descriptors to obtain a
|
||||
//! `Descriptor<DerivedDescriptorKey>` type. This, in turn, can be used to derive public
|
||||
//! keys for arbitrary derivation indexes.
|
||||
//!
|
||||
//! Combining this with [`Wallet::get_signers`], secret keys can also be derived.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! # use std::str::FromStr;
|
||||
//! # use bitcoin::secp256k1::Secp256k1;
|
||||
//! use bdk::descriptor::{AsDerived, DescriptorPublicKey};
|
||||
//! use bdk::miniscript::{ToPublicKey, TranslatePk, MiniscriptKey};
|
||||
//!
|
||||
//! let secp = Secp256k1::gen_new();
|
||||
//!
|
||||
//! let key = DescriptorPublicKey::from_str("[aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/*")?;
|
||||
//! let (descriptor, _, _) = bdk::descriptor!(wpkh(key))?;
|
||||
//!
|
||||
//! // derived: wpkh([aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/42)#3ladd0t2
|
||||
//! let derived = descriptor.as_derived(42, &secp);
|
||||
//! println!("derived: {}", derived);
|
||||
//!
|
||||
//! // with_pks: wpkh(02373ecb54c5e83bd7e0d40adf78b65efaf12fafb13571f0261fc90364eee22e1e)#p4jjgvll
|
||||
//! let with_pks = derived.translate_pk_infallible(|pk| pk.to_public_key(), |pkh| pkh.to_public_key().to_pubkeyhash());
|
||||
//! println!("with_pks: {}", with_pks);
|
||||
//! # Ok::<(), Box<dyn std::error::Error>>(())
|
||||
//! ```
|
||||
//!
|
||||
//! [`Wallet::get_signers`]: crate::wallet::Wallet::get_signers
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
@@ -19,10 +54,7 @@ use std::ops::Deref;
|
||||
use bitcoin::hashes::hash160;
|
||||
use bitcoin::PublicKey;
|
||||
|
||||
pub use miniscript::{
|
||||
descriptor::KeyMap, descriptor::Wildcard, Descriptor, DescriptorPublicKey, Legacy, Miniscript,
|
||||
ScriptContext, Segwitv0,
|
||||
};
|
||||
use miniscript::{descriptor::Wildcard, Descriptor, DescriptorPublicKey};
|
||||
use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk};
|
||||
|
||||
use crate::wallet::utils::SecpCtx;
|
||||
@@ -119,14 +151,19 @@ impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait AsDerived {
|
||||
// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
|
||||
/// Utilities to derive descriptors
|
||||
///
|
||||
/// Check out the [module level] documentation for more.
|
||||
///
|
||||
/// [module level]: crate::descriptor::derived
|
||||
pub trait AsDerived {
|
||||
/// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
|
||||
fn as_derived<'s>(&self, index: u32, secp: &'s SecpCtx)
|
||||
-> Descriptor<DerivedDescriptorKey<'s>>;
|
||||
|
||||
// Transform the keys into `DerivedDescriptorKey`.
|
||||
//
|
||||
// Panics if the descriptor is not "fixed", i.e. if it's derivable
|
||||
/// Transform the keys into `DerivedDescriptorKey`.
|
||||
///
|
||||
/// Panics if the descriptor is not "fixed", i.e. if it's derivable
|
||||
fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>>;
|
||||
}
|
||||
|
||||
|
||||
@@ -336,7 +336,7 @@ macro_rules! apply_modifier {
|
||||
/// syntax is more suitable for a fixed number of items known at compile time, while the other accepts a
|
||||
/// [`Vec`] of items, which makes it more suitable for writing dynamic descriptors.
|
||||
///
|
||||
/// They both produce the descriptor: `wsh(thresh(2,pk(...),s:pk(...),sdv:older(...)))`
|
||||
/// They both produce the descriptor: `wsh(thresh(2,pk(...),s:pk(...),sndv:older(...)))`
|
||||
///
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
@@ -349,7 +349,7 @@ macro_rules! apply_modifier {
|
||||
///
|
||||
/// let (descriptor_a, key_map_a, networks) = bdk::descriptor! {
|
||||
/// wsh (
|
||||
/// thresh(2, pk(my_key_1), s:pk(my_key_2), s:d:v:older(my_timelock))
|
||||
/// thresh(2, pk(my_key_1), s:pk(my_key_2), s:n:d:v:older(my_timelock))
|
||||
/// )
|
||||
/// }?;
|
||||
///
|
||||
@@ -357,7 +357,7 @@ macro_rules! apply_modifier {
|
||||
/// let b_items = vec![
|
||||
/// bdk::fragment!(pk(my_key_1))?,
|
||||
/// bdk::fragment!(s:pk(my_key_2))?,
|
||||
/// bdk::fragment!(s:d:v:older(my_timelock))?,
|
||||
/// bdk::fragment!(s:n:d:v:older(my_timelock))?,
|
||||
/// ];
|
||||
/// let (descriptor_b, mut key_map_b, networks) = bdk::descriptor!(wsh(thresh_vec(2, b_items)))?;
|
||||
///
|
||||
@@ -1048,9 +1048,9 @@ mod test {
|
||||
let private_key =
|
||||
PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
|
||||
let (descriptor, _, _) =
|
||||
descriptor!(wsh(thresh(2,d:v:older(1),s:pk(private_key),s:pk(private_key)))).unwrap();
|
||||
descriptor!(wsh(thresh(2,n:d:v:older(1),s:pk(private_key),s:pk(private_key)))).unwrap();
|
||||
|
||||
assert_eq!(descriptor.to_string(), "wsh(thresh(2,dv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))#cfdcqs3s")
|
||||
assert_eq!(descriptor.to_string(), "wsh(thresh(2,ndv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))#zzk3ux8g")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -21,16 +21,17 @@ use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerpr
|
||||
use bitcoin::util::psbt;
|
||||
use bitcoin::{Network, PublicKey, Script, TxOut};
|
||||
|
||||
use miniscript::descriptor::{
|
||||
DescriptorPublicKey, DescriptorType, DescriptorXKey, InnerXKey, Wildcard,
|
||||
use miniscript::descriptor::{DescriptorType, InnerXKey};
|
||||
pub use miniscript::{
|
||||
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
|
||||
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
|
||||
};
|
||||
pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0};
|
||||
use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
|
||||
|
||||
use crate::descriptor::policy::BuildSatisfaction;
|
||||
|
||||
pub mod checksum;
|
||||
pub(crate) mod derived;
|
||||
pub mod derived;
|
||||
#[doc(hidden)]
|
||||
pub mod dsl;
|
||||
pub mod error;
|
||||
@@ -38,8 +39,7 @@ pub mod policy;
|
||||
pub mod template;
|
||||
|
||||
pub use self::checksum::get_checksum;
|
||||
use self::derived::AsDerived;
|
||||
pub use self::derived::DerivedDescriptorKey;
|
||||
pub use self::derived::{AsDerived, DerivedDescriptorKey};
|
||||
pub use self::error::Error as DescriptorError;
|
||||
pub use self::policy::Policy;
|
||||
use self::template::DescriptorTemplateOut;
|
||||
|
||||
@@ -1501,7 +1501,7 @@ mod test {
|
||||
let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||
|
||||
let desc =
|
||||
descriptor!(wsh(thresh(2,d:v:older(2),s:pk(prvkey_alice),s:pk(prvkey_bob)))).unwrap();
|
||||
descriptor!(wsh(thresh(2,n:d:v:older(2),s:pk(prvkey_alice),s:pk(prvkey_bob)))).unwrap();
|
||||
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
@@ -1513,7 +1513,7 @@ mod test {
|
||||
.address(Network::Testnet)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
"tb1qhpemaacpeu8ajlnh8k9v55ftg0px58r8630fz8t5mypxcwdk5d8sum522g",
|
||||
"tb1qsydsey4hexagwkvercqsmes6yet0ndkyt6uzcphtqnygjd8hmzmsfxrv58",
|
||||
addr.to_string()
|
||||
);
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
|
||||
///
|
||||
/// let key =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new(
|
||||
/// P2Pkh(key),
|
||||
/// None,
|
||||
/// Network::Testnet,
|
||||
@@ -113,7 +113,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
|
||||
///
|
||||
/// let key =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new(
|
||||
/// P2Wpkh_P2Sh(key),
|
||||
/// None,
|
||||
/// Network::Testnet,
|
||||
@@ -148,7 +148,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
|
||||
///
|
||||
/// let key =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new(
|
||||
/// P2Wpkh(key),
|
||||
/// None,
|
||||
/// Network::Testnet,
|
||||
@@ -186,7 +186,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
|
||||
/// use bdk::template::Bip44;
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new(
|
||||
/// Bip44(key.clone(), KeychainKind::External),
|
||||
/// Some(Bip44(key, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -226,7 +226,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new(
|
||||
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -262,7 +262,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
||||
/// use bdk::template::Bip49;
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new(
|
||||
/// Bip49(key.clone(), KeychainKind::External),
|
||||
/// Some(Bip49(key, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -302,7 +302,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new(
|
||||
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -338,7 +338,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
||||
/// use bdk::template::Bip84;
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new(
|
||||
/// Bip84(key.clone(), KeychainKind::External),
|
||||
/// Some(Bip84(key, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -378,7 +378,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new(
|
||||
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
|
||||
@@ -94,6 +94,23 @@ impl<Ctx: ScriptContext> DerivableKey<Ctx> for MnemonicWithPassphrase {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
|
||||
impl<Ctx: ScriptContext> DerivableKey<Ctx> for (GeneratedKey<Mnemonic, Ctx>, Option<String>) {
|
||||
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
|
||||
let (mnemonic, passphrase) = self;
|
||||
(mnemonic.into_key(), passphrase).into_extended_key()
|
||||
}
|
||||
|
||||
fn into_descriptor_key(
|
||||
self,
|
||||
source: Option<bip32::KeySource>,
|
||||
derivation_path: bip32::DerivationPath,
|
||||
) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||
let (mnemonic, passphrase) = self;
|
||||
(mnemonic.into_key(), passphrase).into_descriptor_key(source, derivation_path)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
|
||||
impl<Ctx: ScriptContext> DerivableKey<Ctx> for Mnemonic {
|
||||
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
|
||||
|
||||
@@ -548,6 +548,16 @@ impl<K, Ctx: ScriptContext> Deref for GeneratedKey<K, Ctx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Clone, Ctx: ScriptContext> Clone for GeneratedKey<K, Ctx> {
|
||||
fn clone(&self) -> GeneratedKey<K, Ctx> {
|
||||
GeneratedKey {
|
||||
key: self.key.clone(),
|
||||
valid_networks: self.valid_networks.clone(),
|
||||
phantom: self.phantom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make generated "derivable" keys themselves "derivable". Also make sure they are assigned the
|
||||
// right `valid_networks`.
|
||||
impl<Ctx, K> DerivableKey<Ctx> for GeneratedKey<K, Ctx>
|
||||
|
||||
31
src/lib.rs
31
src/lib.rs
@@ -44,31 +44,32 @@
|
||||
//! interact with the bitcoin P2P network.
|
||||
//!
|
||||
//! ```toml
|
||||
//! bdk = "0.16.0"
|
||||
//! bdk = "0.18.0"
|
||||
//! ```
|
||||
//!
|
||||
//! # Examples
|
||||
#![cfg_attr(
|
||||
feature = "electrum",
|
||||
doc = r##"
|
||||
## Sync the balance of a descriptor
|
||||
|
||||
### Example
|
||||
```no_run
|
||||
use bdk::Wallet;
|
||||
use bdk::{Wallet, SyncOptions};
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
||||
use bdk::blockchain::ElectrumBlockchain;
|
||||
use bdk::electrum_client::Client;
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
|
||||
let blockchain = ElectrumBlockchain::from(client);
|
||||
let wallet = Wallet::new(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
MemoryDatabase::default(),
|
||||
ElectrumBlockchain::from(client)
|
||||
)?;
|
||||
|
||||
wallet.sync(noop_progress(), None)?;
|
||||
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||
|
||||
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
|
||||
|
||||
@@ -80,14 +81,13 @@ fn main() -> Result<(), bdk::Error> {
|
||||
//!
|
||||
//! ## Generate a few addresses
|
||||
//!
|
||||
//! ### Example
|
||||
//! ```
|
||||
//! use bdk::{Wallet};
|
||||
//! use bdk::database::MemoryDatabase;
|
||||
//! use bdk::wallet::AddressIndex::New;
|
||||
//!
|
||||
//! fn main() -> Result<(), bdk::Error> {
|
||||
//! let wallet = Wallet::new_offline(
|
||||
//! let wallet = Wallet::new(
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||
//! bitcoin::Network::Testnet,
|
||||
@@ -106,11 +106,10 @@ fn main() -> Result<(), bdk::Error> {
|
||||
doc = r##"
|
||||
## Create a transaction
|
||||
|
||||
### Example
|
||||
```no_run
|
||||
use bdk::{FeeRate, Wallet};
|
||||
use bdk::{FeeRate, Wallet, SyncOptions};
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::blockchain::{noop_progress, ElectrumBlockchain};
|
||||
use bdk::blockchain::ElectrumBlockchain;
|
||||
use bdk::electrum_client::Client;
|
||||
|
||||
use bitcoin::consensus::serialize;
|
||||
@@ -123,10 +122,10 @@ fn main() -> Result<(), bdk::Error> {
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
MemoryDatabase::default(),
|
||||
ElectrumBlockchain::from(client)
|
||||
)?;
|
||||
let blockchain = ElectrumBlockchain::from(client);
|
||||
|
||||
wallet.sync(noop_progress(), None)?;
|
||||
wallet.sync(&blockchain, SyncOptions::default())?;
|
||||
|
||||
let send_to = wallet.get_address(New)?;
|
||||
let (psbt, details) = {
|
||||
@@ -150,7 +149,6 @@ fn main() -> Result<(), bdk::Error> {
|
||||
//!
|
||||
//! ## Sign a transaction
|
||||
//!
|
||||
//! ### Example
|
||||
//! ```no_run
|
||||
//! use std::str::FromStr;
|
||||
//!
|
||||
@@ -160,7 +158,7 @@ fn main() -> Result<(), bdk::Error> {
|
||||
//! use bdk::database::MemoryDatabase;
|
||||
//!
|
||||
//! fn main() -> Result<(), bdk::Error> {
|
||||
//! let wallet = Wallet::new_offline(
|
||||
//! let wallet = Wallet::new(
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||
//! bitcoin::Network::Testnet,
|
||||
@@ -192,7 +190,7 @@ fn main() -> Result<(), bdk::Error> {
|
||||
//! * `async-interface`: async functions in bdk traits
|
||||
//! * `keys-bip39`: [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic codes for generating deterministic keys
|
||||
//!
|
||||
//! ## Internal features
|
||||
//! # Internal features
|
||||
//!
|
||||
//! These features do not expose any new API, but influence internal implementation aspects of
|
||||
//! BDK.
|
||||
@@ -272,6 +270,7 @@ pub use wallet::address_validator;
|
||||
pub use wallet::signer;
|
||||
pub use wallet::signer::SignOptions;
|
||||
pub use wallet::tx_builder::TxBuilder;
|
||||
pub use wallet::SyncOptions;
|
||||
pub use wallet::Wallet;
|
||||
|
||||
/// Get the version of BDK at runtime
|
||||
|
||||
@@ -320,7 +320,7 @@ impl Default for TestClient {
|
||||
fn default() -> Self {
|
||||
let bitcoind_exe = env::var("BITCOIND_EXE")
|
||||
.ok()
|
||||
.or(bitcoind::downloaded_exe_path())
|
||||
.or(bitcoind::downloaded_exe_path().ok())
|
||||
.expect(
|
||||
"you should provide env var BITCOIND_EXE or specifiy a bitcoind version feature",
|
||||
);
|
||||
@@ -361,10 +361,10 @@ macro_rules! bdk_blockchain_tests {
|
||||
mod bdk_blockchain_tests {
|
||||
use $crate::bitcoin::{Transaction, Network};
|
||||
use $crate::testutils::blockchain_tests::TestClient;
|
||||
use $crate::blockchain::noop_progress;
|
||||
use $crate::blockchain::Blockchain;
|
||||
use $crate::database::MemoryDatabase;
|
||||
use $crate::types::KeychainKind;
|
||||
use $crate::{Wallet, FeeRate};
|
||||
use $crate::{Wallet, FeeRate, SyncOptions};
|
||||
use $crate::testutils;
|
||||
|
||||
use super::*;
|
||||
@@ -375,11 +375,11 @@ macro_rules! bdk_blockchain_tests {
|
||||
$block
|
||||
}
|
||||
|
||||
fn get_wallet_from_descriptors(descriptors: &(String, Option<String>), test_client: &TestClient) -> Wallet<$blockchain, MemoryDatabase> {
|
||||
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new(), get_blockchain(test_client)).unwrap()
|
||||
fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<MemoryDatabase> {
|
||||
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new()).unwrap()
|
||||
}
|
||||
|
||||
fn init_single_sig() -> (Wallet<$blockchain, MemoryDatabase>, (String, Option<String>), TestClient) {
|
||||
fn init_single_sig() -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let descriptors = testutils! {
|
||||
@@ -387,13 +387,14 @@ macro_rules! bdk_blockchain_tests {
|
||||
};
|
||||
|
||||
let test_client = TestClient::default();
|
||||
let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
|
||||
let blockchain = get_blockchain(&test_client);
|
||||
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||
|
||||
// rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool
|
||||
#[cfg(feature = "test-rpc")]
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
(wallet, descriptors, test_client)
|
||||
(wallet, blockchain, descriptors, test_client)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -401,7 +402,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
use std::ops::Deref;
|
||||
use crate::database::Database;
|
||||
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
let tx = testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
@@ -414,7 +415,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
#[cfg(not(feature = "test-rpc"))]
|
||||
assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none");
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated");
|
||||
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
@@ -429,7 +430,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_stop_gap_20() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 5) => 50_000 )
|
||||
@@ -438,7 +439,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
@tx ( (@external descriptors, 25) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance");
|
||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
||||
@@ -446,16 +447,16 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_before_and_after_receive() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 0);
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||
@@ -463,13 +464,13 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_multiple_outputs_same_tx() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
let txid = test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance");
|
||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||
@@ -484,7 +485,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_receive_multi() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
@@ -493,7 +494,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
@tx ( (@external descriptors, 5) => 25_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
||||
@@ -502,32 +503,32 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_address_reuse() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 25_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_receive_rbf_replaced() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
let txid = test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||
@@ -541,7 +542,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
let new_txid = test_client.bump_fee(&txid);
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump");
|
||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump");
|
||||
@@ -559,13 +560,13 @@ macro_rules! bdk_blockchain_tests {
|
||||
#[cfg(not(feature = "esplora"))]
|
||||
#[test]
|
||||
fn test_sync_reorg_block() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
let txid = test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
|
||||
@@ -578,7 +579,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
// Invalidate 1 block
|
||||
test_client.invalidate(1);
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate");
|
||||
|
||||
@@ -589,15 +590,15 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_after_send() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
println!("{}", descriptors.0);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
println!("{}", descriptors.0);
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
@@ -607,8 +608,8 @@ macro_rules! bdk_blockchain_tests {
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
let tx = psbt.extract_tx();
|
||||
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
|
||||
wallet.broadcast(&tx).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&tx).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
|
||||
|
||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
|
||||
@@ -619,24 +620,24 @@ macro_rules! bdk_blockchain_tests {
|
||||
/// The coins should only be received once!
|
||||
#[test]
|
||||
fn test_sync_double_receive() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None), &test_client);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None));
|
||||
// need to sync so rpc can start watching
|
||||
receiver_wallet.sync(noop_progress(), None).unwrap();
|
||||
receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).expect("sync");
|
||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||
let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
|
||||
|
||||
let tx1 = {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf();
|
||||
let (mut psbt, _details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
let (mut psbt, _details) = builder.finish().expect("building first tx");
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing first tx");
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
psbt.extract_tx()
|
||||
};
|
||||
@@ -644,22 +645,22 @@ macro_rules! bdk_blockchain_tests {
|
||||
let tx2 = {
|
||||
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));
|
||||
let (mut psbt, _details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
let (mut psbt, _details) = builder.finish().expect("building replacement tx");
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing replacement tx");
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
psbt.extract_tx()
|
||||
};
|
||||
|
||||
wallet.broadcast(&tx1).unwrap();
|
||||
wallet.broadcast(&tx2).unwrap();
|
||||
blockchain.broadcast(&tx1).expect("broadcasting first");
|
||||
blockchain.broadcast(&tx2).expect("broadcasting replacement");
|
||||
|
||||
receiver_wallet.sync(noop_progress(), None).unwrap();
|
||||
assert_eq!(receiver_wallet.get_balance().unwrap(), 49_000, "should have received coins once and only once");
|
||||
receiver_wallet.sync(&blockchain, SyncOptions::default()).expect("syncing receiver");
|
||||
assert_eq!(receiver_wallet.get_balance().expect("balance"), 49_000, "should have received coins once and only once");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_many_sends_to_a_single_address() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
for _ in 0..4 {
|
||||
// split this up into multiple blocks so rpc doesn't get angry
|
||||
@@ -678,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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_confirmation_time_after_generate() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
println!("{}", descriptors.0);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
println!("{}", descriptors.0);
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
|
||||
let received_txid = test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
|
||||
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||
@@ -701,7 +702,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
assert!(details.confirmation_time.is_none());
|
||||
|
||||
test_client.generate(1, Some(node_addr));
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||
let details = tx_map.get(&received_txid).unwrap();
|
||||
@@ -711,13 +712,13 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_outgoing_from_scratch() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let received_txid = test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
@@ -726,25 +727,26 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
let sent_txid = wallet.broadcast(&psbt.extract_tx()).unwrap();
|
||||
let sent_tx = psbt.extract_tx();
|
||||
blockchain.broadcast(&sent_tx).unwrap();
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
|
||||
|
||||
// empty wallet
|
||||
let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
|
||||
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||
|
||||
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
|
||||
test_client.generate(1, Some(node_addr));
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||
|
||||
let received = tx_map.get(&received_txid).unwrap();
|
||||
assert_eq!(received.received, 50_000, "incorrect received from receiver");
|
||||
assert_eq!(received.sent, 0, "incorrect sent from receiver");
|
||||
|
||||
let sent = tx_map.get(&sent_txid).unwrap();
|
||||
let sent = tx_map.get(&sent_tx.txid()).unwrap();
|
||||
assert_eq!(sent.received, details.received, "incorrect received from sender");
|
||||
assert_eq!(sent.sent, details.sent, "incorrect sent from sender");
|
||||
assert_eq!(sent.fee.unwrap_or(0), details.fee.unwrap_or(0), "incorrect fees from sender");
|
||||
@@ -752,14 +754,14 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_long_change_chain() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
|
||||
let mut total_sent = 0;
|
||||
@@ -769,38 +771,38 @@ macro_rules! bdk_blockchain_tests {
|
||||
let (mut psbt, details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(&psbt.extract_tx()).unwrap();
|
||||
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
total_sent += 5_000 + details.fee.unwrap_or(0);
|
||||
}
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain");
|
||||
|
||||
// empty wallet
|
||||
|
||||
let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
|
||||
let wallet = get_wallet_from_descriptors(&descriptors);
|
||||
|
||||
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
|
||||
test_client.generate(1, Some(node_addr));
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet");
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_bump_fee_basic() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
@@ -808,8 +810,8 @@ macro_rules! bdk_blockchain_tests {
|
||||
let (mut psbt, details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(&psbt.extract_tx()).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
|
||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received");
|
||||
|
||||
@@ -818,8 +820,8 @@ macro_rules! bdk_blockchain_tests {
|
||||
let (mut new_psbt, new_details) = builder.finish().expect("fee bump tx");
|
||||
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
|
||||
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance from received after bump");
|
||||
|
||||
@@ -828,14 +830,14 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_bump_fee_remove_change() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
@@ -843,8 +845,8 @@ macro_rules! bdk_blockchain_tests {
|
||||
let (mut psbt, details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(&psbt.extract_tx()).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect received after send");
|
||||
|
||||
@@ -853,8 +855,8 @@ macro_rules! bdk_blockchain_tests {
|
||||
let (mut new_psbt, new_details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal");
|
||||
assert_eq!(new_details.received, 0, "incorrect received after change removal");
|
||||
|
||||
@@ -863,14 +865,14 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_sync_bump_fee_add_input_simple() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
@@ -878,8 +880,8 @@ macro_rules! bdk_blockchain_tests {
|
||||
let (mut psbt, details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(&psbt.extract_tx()).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
|
||||
|
||||
@@ -888,22 +890,22 @@ macro_rules! bdk_blockchain_tests {
|
||||
let (mut new_psbt, new_details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(new_details.sent, 75_000, "incorrect sent");
|
||||
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_bump_fee_add_input_no_change() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
@@ -911,8 +913,8 @@ macro_rules! bdk_blockchain_tests {
|
||||
let (mut psbt, details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(&psbt.extract_tx()).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&psbt.extract_tx()).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
|
||||
|
||||
@@ -923,8 +925,8 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(new_details.sent, 75_000, "incorrect sent");
|
||||
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input");
|
||||
assert_eq!(new_details.received, 0, "incorrect received after add input");
|
||||
@@ -933,13 +935,13 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
#[test]
|
||||
fn test_add_data() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let _ = test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
@@ -952,22 +954,22 @@ macro_rules! bdk_blockchain_tests {
|
||||
let tx = psbt.extract_tx();
|
||||
let serialized_tx = bitcoin::consensus::encode::serialize(&tx);
|
||||
assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction");
|
||||
let sent_txid = wallet.broadcast(&tx).unwrap();
|
||||
blockchain.broadcast(&tx).unwrap();
|
||||
test_client.generate(1, Some(node_addr));
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send");
|
||||
|
||||
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
|
||||
let _ = tx_map.get(&sent_txid).unwrap();
|
||||
let _ = tx_map.get(&tx.txid()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_receive_coinbase() {
|
||||
let (wallet, _, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, _, mut test_client) = init_single_sig();
|
||||
|
||||
let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance");
|
||||
|
||||
test_client.generate(1, Some(wallet_addr));
|
||||
@@ -980,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");
|
||||
}
|
||||
|
||||
@@ -993,7 +995,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
use bitcoincore_rpc::jsonrpc::serde_json::Value;
|
||||
use bitcoincore_rpc::{Auth, Client, RpcApi};
|
||||
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
|
||||
// TODO remove once rust-bitcoincore-rpc with PR 199 released
|
||||
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199
|
||||
@@ -1056,7 +1058,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance");
|
||||
|
||||
// 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet
|
||||
@@ -1067,8 +1069,8 @@ macro_rules! bdk_blockchain_tests {
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "wallet cannot finalize transaction");
|
||||
let tx = psbt.extract_tx();
|
||||
wallet.broadcast(&tx).unwrap();
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
blockchain.broadcast(&tx).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), details.received, "wallet has incorrect balance after send");
|
||||
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "wallet has incorrect number of txs");
|
||||
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents");
|
||||
@@ -1095,7 +1097,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
// 2.
|
||||
// Core (#2) -> Us (#4)
|
||||
|
||||
let (wallet, _, mut test_client) = init_single_sig();
|
||||
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! {
|
||||
@@ -1106,7 +1108,7 @@ macro_rules! bdk_blockchain_tests {
|
||||
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(noop_progress(), None).unwrap();
|
||||
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);
|
||||
@@ -1117,11 +1119,82 @@ macro_rules! bdk_blockchain_tests {
|
||||
};
|
||||
let txid_2 = test_client.receive(tx);
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
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_double_spend() {
|
||||
// We create a tx and then we try to double spend it; BDK will always allow
|
||||
// us to do so, as it never forgets about spent UTXOs
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
let _ = test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(node_addr.script_pubkey(), 25_000);
|
||||
let (mut psbt, _details) = builder.finish().unwrap();
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
let initial_tx = psbt.extract_tx();
|
||||
let _sent_txid = blockchain.broadcast(&initial_tx).unwrap();
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
for utxo in wallet.list_unspent().unwrap() {
|
||||
// Making sure the TXO we just spent is not returned by list_unspent
|
||||
assert!(utxo.outpoint != initial_tx.input[0].previous_output, "wallet displays spent txo in unspents");
|
||||
}
|
||||
// We can still create a transaction double spending `initial_tx`
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_utxo(initial_tx.input[0].previous_output)
|
||||
.expect("Can't manually add an UTXO spent");
|
||||
test_client.generate(1, Some(node_addr));
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
// Even after confirmation, we can still create a tx double spend it
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_utxo(initial_tx.input[0].previous_output)
|
||||
.expect("Can't manually add an UTXO spent");
|
||||
for utxo in wallet.list_unspent().unwrap() {
|
||||
// Making sure the TXO we just spent is not returned by list_unspent
|
||||
assert!(utxo.outpoint != initial_tx.input[0].previous_output, "wallet displays spent txo in unspents");
|
||||
}
|
||||
}
|
||||
|
||||
#[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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ pub struct LocalUtxo {
|
||||
pub txout: TxOut,
|
||||
/// Type of keychain
|
||||
pub keychain: KeychainKind,
|
||||
/// Whether this UTXO is spent or not
|
||||
pub is_spent: bool,
|
||||
}
|
||||
|
||||
/// A [`Utxo`] with its `satisfaction_weight`.
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
//! }
|
||||
//!
|
||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
|
||||
//!
|
||||
//! let address = wallet.get_address(New)?;
|
||||
|
||||
@@ -569,6 +569,7 @@ mod test {
|
||||
script_pubkey: Script::new(),
|
||||
},
|
||||
keychain: KeychainKind::External,
|
||||
is_spent: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -596,6 +597,7 @@ mod test {
|
||||
script_pubkey: Script::new(),
|
||||
},
|
||||
keychain: KeychainKind::External,
|
||||
is_spent: false,
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -615,6 +617,7 @@ mod test {
|
||||
script_pubkey: Script::new(),
|
||||
},
|
||||
keychain: KeychainKind::External,
|
||||
is_spent: false,
|
||||
}),
|
||||
};
|
||||
vec![utxo; utxos_number]
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
//! "label":"testnet"
|
||||
//! }"#;
|
||||
//!
|
||||
//! let import = WalletExport::from_str(import)?;
|
||||
//! let wallet = Wallet::new_offline(
|
||||
//! let import = FullyNodedExport::from_str(import)?;
|
||||
//! let wallet = Wallet::new(
|
||||
//! &import.descriptor(),
|
||||
//! import.change_descriptor().as_ref(),
|
||||
//! Network::Testnet,
|
||||
@@ -45,13 +45,13 @@
|
||||
//! # use bdk::database::*;
|
||||
//! # use bdk::wallet::export::*;
|
||||
//! # use bdk::*;
|
||||
//! let wallet = Wallet::new_offline(
|
||||
//! let wallet = Wallet::new(
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
|
||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
|
||||
//! Network::Testnet,
|
||||
//! MemoryDatabase::default()
|
||||
//! )?;
|
||||
//! let export = WalletExport::export_wallet(&wallet, "exported wallet", true)
|
||||
//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||
//! .map_err(ToString::to_string)
|
||||
//! .map_err(bdk::Error::Generic)?;
|
||||
//!
|
||||
@@ -64,16 +64,21 @@ use std::str::FromStr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use miniscript::descriptor::{ShInner, WshInner};
|
||||
use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal};
|
||||
use miniscript::{Descriptor, ScriptContext, Terminal};
|
||||
|
||||
use crate::database::BatchDatabase;
|
||||
use crate::types::KeychainKind;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Alias for [`FullyNodedExport`]
|
||||
#[deprecated(since = "0.18.0", note = "Please use [`FullyNodedExport`] instead")]
|
||||
pub type WalletExport = FullyNodedExport;
|
||||
|
||||
/// Structure that contains the export of a wallet
|
||||
///
|
||||
/// For a usage example see [this module](crate::wallet::export)'s documentation.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WalletExport {
|
||||
pub struct FullyNodedExport {
|
||||
descriptor: String,
|
||||
/// Earliest block to rescan when looking for the wallet's transactions
|
||||
pub blockheight: u32,
|
||||
@@ -81,13 +86,13 @@ pub struct WalletExport {
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
impl ToString for WalletExport {
|
||||
impl ToString for FullyNodedExport {
|
||||
fn to_string(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for WalletExport {
|
||||
impl FromStr for FullyNodedExport {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
@@ -99,7 +104,7 @@ fn remove_checksum(s: String) -> String {
|
||||
s.splitn(2, '#').next().map(String::from).unwrap()
|
||||
}
|
||||
|
||||
impl WalletExport {
|
||||
impl FullyNodedExport {
|
||||
/// Export a wallet
|
||||
///
|
||||
/// This function returns an error if it determines that the `wallet`'s descriptor(s) are not
|
||||
@@ -111,14 +116,18 @@ impl WalletExport {
|
||||
///
|
||||
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
|
||||
/// returned will be `0`.
|
||||
pub fn export_wallet<B, D: BatchDatabase>(
|
||||
wallet: &Wallet<B, D>,
|
||||
pub fn export_wallet<D: BatchDatabase>(
|
||||
wallet: &Wallet<D>,
|
||||
label: &str,
|
||||
include_blockheight: bool,
|
||||
) -> Result<Self, &'static str> {
|
||||
let descriptor = wallet
|
||||
.descriptor
|
||||
.to_string_with_secret(&wallet.signers.as_key_map(wallet.secp_ctx()));
|
||||
.get_descriptor_for_keychain(KeychainKind::External)
|
||||
.to_string_with_secret(
|
||||
&wallet
|
||||
.get_signers(KeychainKind::External)
|
||||
.as_key_map(wallet.secp_ctx()),
|
||||
);
|
||||
let descriptor = remove_checksum(descriptor);
|
||||
Self::is_compatible_with_core(&descriptor)?;
|
||||
|
||||
@@ -136,18 +145,30 @@ impl WalletExport {
|
||||
}
|
||||
};
|
||||
|
||||
let export = WalletExport {
|
||||
let export = FullyNodedExport {
|
||||
descriptor,
|
||||
label: label.into(),
|
||||
blockheight,
|
||||
};
|
||||
|
||||
let desc_to_string = |d: &Descriptor<DescriptorPublicKey>| {
|
||||
let descriptor =
|
||||
d.to_string_with_secret(&wallet.change_signers.as_key_map(wallet.secp_ctx()));
|
||||
remove_checksum(descriptor)
|
||||
let change_descriptor = match wallet
|
||||
.public_descriptor(KeychainKind::Internal)
|
||||
.map_err(|_| "Invalid change descriptor")?
|
||||
.is_some()
|
||||
{
|
||||
false => None,
|
||||
true => {
|
||||
let descriptor = wallet
|
||||
.get_descriptor_for_keychain(KeychainKind::Internal)
|
||||
.to_string_with_secret(
|
||||
&wallet
|
||||
.get_signers(KeychainKind::Internal)
|
||||
.as_key_map(wallet.secp_ctx()),
|
||||
);
|
||||
Some(remove_checksum(descriptor))
|
||||
}
|
||||
};
|
||||
if export.change_descriptor() != wallet.change_descriptor.as_ref().map(desc_to_string) {
|
||||
if export.change_descriptor() != change_descriptor {
|
||||
return Err("Incompatible change descriptor");
|
||||
}
|
||||
|
||||
@@ -241,14 +262,14 @@ mod test {
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
descriptor,
|
||||
Some(change_descriptor),
|
||||
Network::Bitcoin,
|
||||
get_test_db(),
|
||||
)
|
||||
.unwrap();
|
||||
let export = WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
|
||||
assert_eq!(export.descriptor(), descriptor);
|
||||
assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
|
||||
@@ -265,9 +286,8 @@ mod test {
|
||||
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
|
||||
let wallet =
|
||||
Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
|
||||
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
let wallet = Wallet::new(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
|
||||
FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -279,14 +299,14 @@ mod test {
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
|
||||
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
descriptor,
|
||||
Some(change_descriptor),
|
||||
Network::Bitcoin,
|
||||
get_test_db(),
|
||||
)
|
||||
.unwrap();
|
||||
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -302,14 +322,14 @@ mod test {
|
||||
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
|
||||
))";
|
||||
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
descriptor,
|
||||
Some(change_descriptor),
|
||||
Network::Testnet,
|
||||
get_test_db(),
|
||||
)
|
||||
.unwrap();
|
||||
let export = WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
|
||||
assert_eq!(export.descriptor(), descriptor);
|
||||
assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
|
||||
@@ -322,14 +342,14 @@ mod test {
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
descriptor,
|
||||
Some(change_descriptor),
|
||||
Network::Bitcoin,
|
||||
get_test_db(),
|
||||
)
|
||||
.unwrap();
|
||||
let export = WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
|
||||
assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}");
|
||||
}
|
||||
@@ -340,7 +360,7 @@ mod test {
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||
|
||||
let import_str = "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}";
|
||||
let export = WalletExport::from_str(import_str).unwrap();
|
||||
let export = FullyNodedExport::from_str(import_str).unwrap();
|
||||
|
||||
assert_eq!(export.descriptor(), descriptor);
|
||||
assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
|
||||
|
||||
@@ -55,7 +55,7 @@ use signer::{SignOptions, Signer, SignerOrdering, SignersContainer};
|
||||
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
|
||||
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx};
|
||||
|
||||
use crate::blockchain::{Blockchain, Progress};
|
||||
use crate::blockchain::{GetHeight, NoopProgress, Progress, WalletSync};
|
||||
use crate::database::memory::MemoryDatabase;
|
||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
|
||||
use crate::descriptor::derived::AsDerived;
|
||||
@@ -75,16 +75,17 @@ const CACHE_ADDR_BATCH_SIZE: u32 = 100;
|
||||
|
||||
/// A Bitcoin wallet
|
||||
///
|
||||
/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
|
||||
/// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets
|
||||
/// needs to operate, like [generating addresses](Wallet::get_address), [returning the balance](Wallet::get_balance),
|
||||
/// [creating transactions](Wallet::build_tx), etc.
|
||||
/// The `Wallet` struct acts as a way of coherently interfacing with output descriptors and related transactions.
|
||||
/// Its main components are:
|
||||
///
|
||||
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
|
||||
/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose
|
||||
/// methods that don't need any interaction with the blockchain to work.
|
||||
/// 1. output *descriptors* from which it can derive addresses.
|
||||
/// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors.
|
||||
/// 3. [`Signer`]s that can contribute signatures to addresses instantiated from the descriptors.
|
||||
///
|
||||
/// [`Database`]: crate::database::Database
|
||||
/// [`Signer`]: crate::signer::Signer
|
||||
#[derive(Debug)]
|
||||
pub struct Wallet<B, D> {
|
||||
pub struct Wallet<D> {
|
||||
descriptor: ExtendedDescriptor,
|
||||
change_descriptor: Option<ExtendedDescriptor>,
|
||||
|
||||
@@ -95,88 +96,11 @@ pub struct Wallet<B, D> {
|
||||
|
||||
network: Network,
|
||||
|
||||
current_height: Option<u32>,
|
||||
|
||||
client: B,
|
||||
database: RefCell<D>,
|
||||
|
||||
secp: SecpCtx,
|
||||
}
|
||||
|
||||
impl<D> Wallet<(), D>
|
||||
where
|
||||
D: BatchDatabase,
|
||||
{
|
||||
/// Create a new "offline" wallet
|
||||
pub fn new_offline<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
network: Network,
|
||||
database: D,
|
||||
) -> Result<Self, Error> {
|
||||
Self::_new(descriptor, change_descriptor, network, database, (), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, D> Wallet<B, D>
|
||||
where
|
||||
D: BatchDatabase,
|
||||
{
|
||||
fn _new<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
network: Network,
|
||||
mut database: D,
|
||||
client: B,
|
||||
current_height: Option<u32>,
|
||||
) -> Result<Self, Error> {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
|
||||
database.check_descriptor_checksum(
|
||||
KeychainKind::External,
|
||||
get_checksum(&descriptor.to_string())?.as_bytes(),
|
||||
)?;
|
||||
let signers = Arc::new(SignersContainer::from(keymap));
|
||||
let (change_descriptor, change_signers) = match change_descriptor {
|
||||
Some(desc) => {
|
||||
let (change_descriptor, change_keymap) =
|
||||
into_wallet_descriptor_checked(desc, &secp, network)?;
|
||||
database.check_descriptor_checksum(
|
||||
KeychainKind::Internal,
|
||||
get_checksum(&change_descriptor.to_string())?.as_bytes(),
|
||||
)?;
|
||||
|
||||
let change_signers = Arc::new(SignersContainer::from(change_keymap));
|
||||
// if !parsed.same_structure(descriptor.as_ref()) {
|
||||
// return Err(Error::DifferentDescriptorStructure);
|
||||
// }
|
||||
|
||||
(Some(change_descriptor), change_signers)
|
||||
}
|
||||
None => (None, Arc::new(SignersContainer::new())),
|
||||
};
|
||||
|
||||
Ok(Wallet {
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
signers,
|
||||
change_signers,
|
||||
address_validators: Vec::new(),
|
||||
network,
|
||||
current_height,
|
||||
client,
|
||||
database: RefCell::new(database),
|
||||
secp,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the Bitcoin network the wallet is using.
|
||||
pub fn network(&self) -> Network {
|
||||
self.network
|
||||
}
|
||||
}
|
||||
|
||||
/// The address index selection strategy to use to derived an address from the wallet's external
|
||||
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
|
||||
#[derive(Debug)]
|
||||
@@ -216,6 +140,8 @@ pub struct AddressInfo {
|
||||
pub index: u32,
|
||||
/// Address
|
||||
pub address: Address,
|
||||
/// Type of keychain
|
||||
pub keychain: KeychainKind,
|
||||
}
|
||||
|
||||
impl Deref for AddressInfo {
|
||||
@@ -232,11 +158,83 @@ impl fmt::Display for AddressInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// offline actions, always available
|
||||
impl<B, D> Wallet<B, D>
|
||||
#[derive(Debug, Default)]
|
||||
/// 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
|
||||
D: BatchDatabase,
|
||||
{
|
||||
#[deprecated = "Just use Wallet::new -- all wallets are offline now!"]
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
@@ -250,6 +248,7 @@ where
|
||||
.map(|address| AddressInfo {
|
||||
address,
|
||||
index: incremented_index,
|
||||
keychain,
|
||||
})
|
||||
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
||||
}
|
||||
@@ -280,6 +279,7 @@ where
|
||||
.map(|address| AddressInfo {
|
||||
address,
|
||||
index: current_index,
|
||||
keychain,
|
||||
})
|
||||
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
||||
}
|
||||
@@ -290,7 +290,11 @@ where
|
||||
self.get_descriptor_for_keychain(keychain)
|
||||
.as_derived(index, &self.secp)
|
||||
.address(self.network)
|
||||
.map(|address| AddressInfo { index, address })
|
||||
.map(|address| AddressInfo {
|
||||
index,
|
||||
address,
|
||||
keychain,
|
||||
})
|
||||
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
||||
}
|
||||
|
||||
@@ -302,7 +306,11 @@ where
|
||||
self.get_descriptor_for_keychain(keychain)
|
||||
.as_derived(index, &self.secp)
|
||||
.address(self.network)
|
||||
.map(|address| AddressInfo { index, address })
|
||||
.map(|address| AddressInfo {
|
||||
index,
|
||||
address,
|
||||
keychain,
|
||||
})
|
||||
.map_err(|_| Error::ScriptDoesntHaveAddressForm)
|
||||
}
|
||||
|
||||
@@ -337,6 +345,50 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
pub fn is_mine(&self, script: &Script) -> Result<bool, Error> {
|
||||
self.database.borrow().is_mine(script)
|
||||
@@ -347,7 +399,13 @@ where
|
||||
/// Note that this method only operates on the internal database, which first needs to be
|
||||
/// [`Wallet::sync`] manually.
|
||||
pub fn list_unspent(&self) -> Result<Vec<LocalUtxo>, Error> {
|
||||
self.database.borrow().iter_utxos()
|
||||
Ok(self
|
||||
.database
|
||||
.borrow()
|
||||
.iter_utxos()?
|
||||
.into_iter()
|
||||
.filter(|l| !l.is_spent)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Returns the `UTXO` owned by this wallet corresponding to `outpoint` if it exists in the
|
||||
@@ -410,6 +468,29 @@ where
|
||||
signers.add_external(signer.id(&self.secp), ordering, signer);
|
||||
}
|
||||
|
||||
/// Get the signers
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk::{Wallet, KeychainKind};
|
||||
/// # use bdk::bitcoin::Network;
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// let wallet = Wallet::new("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet, MemoryDatabase::new())?;
|
||||
/// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
|
||||
/// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
|
||||
/// println!("secret_key: {}", secret_key);
|
||||
/// }
|
||||
///
|
||||
/// Ok::<(), Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub fn get_signers(&self, keychain: KeychainKind) -> Arc<SignersContainer> {
|
||||
match keychain {
|
||||
KeychainKind::External => Arc::clone(&self.signers),
|
||||
KeychainKind::Internal => Arc::clone(&self.change_signers),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an address validator
|
||||
///
|
||||
/// See [the `address_validator` module](address_validator) for an example.
|
||||
@@ -417,6 +498,11 @@ where
|
||||
self.address_validators.push(validator);
|
||||
}
|
||||
|
||||
/// Get the address validators
|
||||
pub fn get_address_validators(&self) -> &[Arc<dyn AddressValidator>] {
|
||||
&self.address_validators
|
||||
}
|
||||
|
||||
/// Start building a transaction.
|
||||
///
|
||||
/// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction.
|
||||
@@ -443,7 +529,7 @@ where
|
||||
/// ```
|
||||
///
|
||||
/// [`TxBuilder`]: crate::TxBuilder
|
||||
pub fn build_tx(&self) -> TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, CreateTx> {
|
||||
pub fn build_tx(&self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> {
|
||||
TxBuilder {
|
||||
wallet: self,
|
||||
params: TxParams::default(),
|
||||
@@ -786,7 +872,7 @@ where
|
||||
pub fn build_fee_bump(
|
||||
&self,
|
||||
txid: Txid,
|
||||
) -> Result<TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
|
||||
) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
|
||||
let mut details = match self.database.borrow().get_tx(&txid, true)? {
|
||||
None => return Err(Error::TransactionNotFound),
|
||||
Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
|
||||
@@ -839,6 +925,7 @@ where
|
||||
outpoint: txin.previous_output,
|
||||
txout,
|
||||
keychain,
|
||||
is_spent: true,
|
||||
};
|
||||
|
||||
Ok(WeightedUtxo {
|
||||
@@ -1017,7 +1104,11 @@ where
|
||||
.borrow()
|
||||
.get_tx(&input.previous_output.txid, false)?
|
||||
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX));
|
||||
let current_height = sign_options.assume_height.or(self.current_height);
|
||||
let last_sync_height = self
|
||||
.database()
|
||||
.get_sync_time()?
|
||||
.map(|sync_time| sync_time.block_time.height);
|
||||
let current_height = sign_options.assume_height.or(last_sync_height);
|
||||
|
||||
debug!(
|
||||
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
|
||||
@@ -1468,94 +1559,35 @@ where
|
||||
pub fn database(&self) -> impl std::ops::Deref<Target = D> + '_ {
|
||||
self.database.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, D> Wallet<B, D>
|
||||
where
|
||||
B: Blockchain,
|
||||
D: BatchDatabase,
|
||||
{
|
||||
/// Create a new "online" wallet
|
||||
#[maybe_async]
|
||||
pub fn new<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
network: Network,
|
||||
database: D,
|
||||
client: B,
|
||||
) -> Result<Self, Error> {
|
||||
let current_height = Some(maybe_await!(client.get_height())? as u32);
|
||||
Self::_new(
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
network,
|
||||
database,
|
||||
client,
|
||||
current_height,
|
||||
)
|
||||
}
|
||||
|
||||
/// Sync the internal database with the blockchain
|
||||
#[maybe_async]
|
||||
pub fn sync<P: 'static + Progress>(
|
||||
pub fn sync<B: WalletSync + GetHeight>(
|
||||
&self,
|
||||
progress_update: P,
|
||||
max_address_param: Option<u32>,
|
||||
blockchain: &B,
|
||||
sync_opts: SyncOptions,
|
||||
) -> Result<(), Error> {
|
||||
debug!("Begin sync...");
|
||||
|
||||
let mut run_setup = false;
|
||||
let SyncOptions { progress } = sync_opts;
|
||||
let progress = progress.unwrap_or_else(|| Box::new(NoopProgress));
|
||||
|
||||
let max_address = match self.descriptor.is_deriveable() {
|
||||
false => 0,
|
||||
true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
|
||||
};
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
let run_setup = self.ensure_addresses_cached(CACHE_ADDR_BATCH_SIZE)?;
|
||||
|
||||
debug!("run_setup: {}", run_setup);
|
||||
// TODO: what if i generate an address first and cache some addresses?
|
||||
// TODO: we should sync if generating an address triggers a new batch to be stored
|
||||
if run_setup {
|
||||
maybe_await!(self
|
||||
.client
|
||||
.setup(self.database.borrow_mut().deref_mut(), progress_update,))?;
|
||||
maybe_await!(
|
||||
blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress,)
|
||||
)?;
|
||||
} else {
|
||||
maybe_await!(self
|
||||
.client
|
||||
.sync(self.database.borrow_mut().deref_mut(), progress_update,))?;
|
||||
maybe_await!(blockchain.wallet_sync(self.database.borrow_mut().deref_mut(), progress,))?;
|
||||
}
|
||||
|
||||
let sync_time = SyncTime {
|
||||
block_time: BlockTime {
|
||||
height: maybe_await!(self.client.get_height())?,
|
||||
height: maybe_await!(blockchain.get_height())?,
|
||||
timestamp: time::get_timestamp(),
|
||||
},
|
||||
};
|
||||
@@ -1565,17 +1597,16 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return a reference to the internal blockchain client
|
||||
pub fn client(&self) -> &B {
|
||||
&self.client
|
||||
}
|
||||
|
||||
/// Broadcast a transaction to the network
|
||||
#[maybe_async]
|
||||
pub fn broadcast(&self, tx: &Transaction) -> Result<Txid, Error> {
|
||||
maybe_await!(self.client.broadcast(tx))?;
|
||||
|
||||
Ok(tx.txid())
|
||||
/// Return the checksum of the public descriptor associated to `keychain`
|
||||
///
|
||||
/// Internally calls [`Self::get_descriptor_for_keychain`] to fetch the right descriptor
|
||||
pub fn descriptor_checksum(&self, keychain: KeychainKind) -> String {
|
||||
self.get_descriptor_for_keychain(keychain)
|
||||
.to_string()
|
||||
.splitn(2, '#')
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1583,12 +1614,12 @@ where
|
||||
pub fn get_funded_wallet(
|
||||
descriptor: &str,
|
||||
) -> (
|
||||
Wallet<(), MemoryDatabase>,
|
||||
Wallet<MemoryDatabase>,
|
||||
(String, Option<String>),
|
||||
bitcoin::Txid,
|
||||
) {
|
||||
let descriptors = testutils!(@descriptors (descriptor));
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
&descriptors.0,
|
||||
None,
|
||||
Network::Regtest,
|
||||
@@ -1638,7 +1669,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_cache_addresses_fixed() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
|
||||
None,
|
||||
Network::Testnet,
|
||||
@@ -1672,7 +1703,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_cache_addresses() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wallet.get_address(New).unwrap().to_string(),
|
||||
@@ -1700,7 +1731,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_cache_addresses_refill() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wallet.get_address(New).unwrap().to_string(),
|
||||
@@ -3798,7 +3829,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_unused_address() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
None, Network::Testnet, db).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -3815,7 +3846,7 @@ pub(crate) mod test {
|
||||
fn test_next_unused_address() {
|
||||
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
|
||||
let descriptors = testutils!(@descriptors (descriptor));
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
&descriptors.0,
|
||||
None,
|
||||
Network::Testnet,
|
||||
@@ -3844,7 +3875,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_peek_address_at_index() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
None, Network::Testnet, db).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -3877,7 +3908,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_peek_address_at_index_not_derivable() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
|
||||
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
|
||||
None, Network::Testnet, db).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -3899,7 +3930,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_reset_address_index() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
None, Network::Testnet, db).unwrap();
|
||||
|
||||
// new index 0
|
||||
@@ -3936,7 +3967,7 @@ pub(crate) mod test {
|
||||
#[test]
|
||||
fn test_returns_index_and_address() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
None, Network::Testnet, db).unwrap();
|
||||
|
||||
// new index 0
|
||||
@@ -3945,6 +3976,7 @@ pub(crate) mod test {
|
||||
AddressInfo {
|
||||
index: 0,
|
||||
address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a").unwrap(),
|
||||
keychain: KeychainKind::External,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3953,7 +3985,8 @@ pub(crate) mod test {
|
||||
wallet.get_address(New).unwrap(),
|
||||
AddressInfo {
|
||||
index: 1,
|
||||
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap()
|
||||
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
|
||||
keychain: KeychainKind::External,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3962,7 +3995,8 @@ pub(crate) mod test {
|
||||
wallet.get_address(Peek(25)).unwrap(),
|
||||
AddressInfo {
|
||||
index: 25,
|
||||
address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap()
|
||||
address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap(),
|
||||
keychain: KeychainKind::External,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3971,7 +4005,8 @@ pub(crate) mod test {
|
||||
wallet.get_address(New).unwrap(),
|
||||
AddressInfo {
|
||||
index: 2,
|
||||
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap()
|
||||
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
|
||||
keychain: KeychainKind::External,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3980,7 +4015,8 @@ pub(crate) mod test {
|
||||
wallet.get_address(Reset(1)).unwrap(),
|
||||
AddressInfo {
|
||||
index: 1,
|
||||
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap()
|
||||
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
|
||||
keychain: KeychainKind::External,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3989,7 +4025,8 @@ pub(crate) mod test {
|
||||
wallet.get_address(New).unwrap(),
|
||||
AddressInfo {
|
||||
index: 2,
|
||||
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap()
|
||||
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
|
||||
keychain: KeychainKind::External,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -4009,7 +4046,7 @@ pub(crate) mod test {
|
||||
fn test_get_address() {
|
||||
use crate::descriptor::template::Bip84;
|
||||
let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
|
||||
let wallet = Wallet::new_offline(
|
||||
let wallet = Wallet::new(
|
||||
Bip84(key, KeychainKind::External),
|
||||
Some(Bip84(key, KeychainKind::Internal)),
|
||||
Network::Regtest,
|
||||
@@ -4018,18 +4055,24 @@ pub(crate) mod test {
|
||||
.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()
|
||||
wallet.get_address(AddressIndex::New).unwrap(),
|
||||
AddressInfo {
|
||||
index: 0,
|
||||
address: Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(),
|
||||
keychain: KeychainKind::External,
|
||||
}
|
||||
);
|
||||
|
||||
let wallet = Wallet::new_offline(
|
||||
assert_eq!(
|
||||
wallet.get_internal_address(AddressIndex::New).unwrap(),
|
||||
AddressInfo {
|
||||
index: 0,
|
||||
address: Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap(),
|
||||
keychain: KeychainKind::Internal,
|
||||
}
|
||||
);
|
||||
|
||||
let wallet = Wallet::new(
|
||||
Bip84(key, KeychainKind::External),
|
||||
None,
|
||||
Network::Regtest,
|
||||
@@ -4038,11 +4081,12 @@ pub(crate) mod test {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wallet
|
||||
.get_internal_address(AddressIndex::New)
|
||||
.unwrap()
|
||||
.address,
|
||||
Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(),
|
||||
wallet.get_internal_address(AddressIndex::New).unwrap(),
|
||||
AddressInfo {
|
||||
index: 0,
|
||||
address: Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(),
|
||||
keychain: KeychainKind::Internal,
|
||||
},
|
||||
"when there's no internal descriptor it should just use external"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
//! let custom_signer = CustomSigner::connect();
|
||||
//!
|
||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
//! wallet.add_signer(
|
||||
//! KeychainKind::External,
|
||||
//! SignerOrdering(200),
|
||||
|
||||
@@ -120,8 +120,8 @@ impl TxBuilderContext for BumpFee {}
|
||||
/// [`finish`]: Self::finish
|
||||
/// [`coin_selection`]: Self::coin_selection
|
||||
#[derive(Debug)]
|
||||
pub struct TxBuilder<'a, B, D, Cs, Ctx> {
|
||||
pub(crate) wallet: &'a Wallet<B, D>,
|
||||
pub struct TxBuilder<'a, D, Cs, Ctx> {
|
||||
pub(crate) wallet: &'a Wallet<D>,
|
||||
pub(crate) params: TxParams,
|
||||
pub(crate) coin_selection: Cs,
|
||||
pub(crate) phantom: PhantomData<Ctx>,
|
||||
@@ -170,7 +170,7 @@ impl std::default::Default for FeePolicy {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> {
|
||||
impl<'a, Cs: Clone, Ctx, D> Clone for TxBuilder<'a, D, Cs, Ctx> {
|
||||
fn clone(&self) -> Self {
|
||||
TxBuilder {
|
||||
wallet: self.wallet,
|
||||
@@ -182,8 +182,8 @@ impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> {
|
||||
}
|
||||
|
||||
// methods supported by both contexts, for any CoinSelectionAlgorithm
|
||||
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
|
||||
TxBuilder<'a, B, D, Cs, Ctx>
|
||||
impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
|
||||
TxBuilder<'a, D, Cs, Ctx>
|
||||
{
|
||||
/// Set a custom fee rate
|
||||
pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
|
||||
@@ -508,7 +508,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
|
||||
pub fn coin_selection<P: CoinSelectionAlgorithm<D>>(
|
||||
self,
|
||||
coin_selection: P,
|
||||
) -> TxBuilder<'a, B, D, P, Ctx> {
|
||||
) -> TxBuilder<'a, D, P, Ctx> {
|
||||
TxBuilder {
|
||||
wallet: self.wallet,
|
||||
params: self.params,
|
||||
@@ -547,7 +547,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> {
|
||||
impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, CreateTx> {
|
||||
/// Replace the recipients already added with a new list
|
||||
pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
|
||||
self.params.recipients = recipients;
|
||||
@@ -614,7 +614,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
|
||||
}
|
||||
|
||||
// methods supported only by bump_fee
|
||||
impl<'a, B, D: BatchDatabase> TxBuilder<'a, B, D, DefaultCoinSelectionAlgorithm, BumpFee> {
|
||||
impl<'a, D: BatchDatabase> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> {
|
||||
/// Explicitly tells the wallet that it is allowed to reduce the fee of the output matching this
|
||||
/// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
|
||||
/// will attempt to find a change output to shrink instead.
|
||||
@@ -838,6 +838,7 @@ mod test {
|
||||
},
|
||||
txout: Default::default(),
|
||||
keychain: KeychainKind::External,
|
||||
is_spent: false,
|
||||
},
|
||||
LocalUtxo {
|
||||
outpoint: OutPoint {
|
||||
@@ -846,6 +847,7 @@ mod test {
|
||||
},
|
||||
txout: Default::default(),
|
||||
keychain: KeychainKind::Internal,
|
||||
is_spent: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::fmt;
|
||||
use bitcoin::consensus::serialize;
|
||||
use bitcoin::{OutPoint, Transaction, Txid};
|
||||
|
||||
use crate::blockchain::Blockchain;
|
||||
use crate::blockchain::GetTx;
|
||||
use crate::database::Database;
|
||||
use crate::error::Error;
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::error::Error;
|
||||
/// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the
|
||||
/// [`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.
|
||||
pub fn verify_tx<D: Database, B: Blockchain>(
|
||||
pub fn verify_tx<D: Database, B: GetTx>(
|
||||
tx: &Transaction,
|
||||
database: &D,
|
||||
blockchain: &B,
|
||||
@@ -104,43 +104,18 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::*;
|
||||
use crate::database::{BatchOperations, MemoryDatabase};
|
||||
use bitcoin::consensus::encode::deserialize;
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::{Transaction, Txid};
|
||||
|
||||
use crate::blockchain::{Blockchain, Capability, Progress};
|
||||
use crate::database::{BatchDatabase, BatchOperations, MemoryDatabase};
|
||||
use crate::FeeRate;
|
||||
|
||||
use super::*;
|
||||
|
||||
struct DummyBlockchain;
|
||||
|
||||
impl Blockchain 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(())
|
||||
}
|
||||
impl GetTx for DummyBlockchain {
|
||||
fn get_tx(&self, _txid: &Txid) -> Result<Option<Transaction>, Error> {
|
||||
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]
|
||||
|
||||
Reference in New Issue
Block a user