Compare commits
38 Commits
v0.24.0-rc
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd64d12835 | ||
|
|
7e8e9921a9 | ||
|
|
31fa4f723a | ||
|
|
c741df746b | ||
|
|
3205f0c16d | ||
|
|
5f0870a741 | ||
|
|
5a483472c1 | ||
|
|
8d4cc3920a | ||
|
|
14bc9c0e35 | ||
|
|
2451c00268 | ||
|
|
4cad18bbca | ||
|
|
634a0575cb | ||
|
|
d3d07564f2 | ||
|
|
0b768d6f0b | ||
|
|
ec9aefac6b | ||
|
|
d72aa7ebc0 | ||
|
|
99930af12e | ||
|
|
d6e730f18a | ||
|
|
d1e5b87bfc | ||
|
|
c101dea460 | ||
|
|
9ddd502538 | ||
|
|
a5d345fff2 | ||
|
|
11dcc14374 | ||
|
|
4c5ceaff14 | ||
|
|
b5fcddcf1a | ||
|
|
d570ff2c65 | ||
|
|
21c96c9c81 | ||
|
|
c51d544932 | ||
|
|
5e56c3b3c1 | ||
|
|
235961a934 | ||
|
|
df905a8d5e | ||
|
|
8b68cf9546 | ||
|
|
150f4d6f41 | ||
|
|
1c95ca33a8 | ||
|
|
108edc3a6b | ||
|
|
f99a6b9f43 | ||
|
|
aedbc8c97d | ||
|
|
e9bbb8724f |
4
.github/workflows/cont_integration.yml
vendored
4
.github/workflows/cont_integration.yml
vendored
@@ -173,8 +173,8 @@ jobs:
|
||||
- name: Check fmt
|
||||
run: cargo fmt --all -- --config format_code_in_doc_comments=true --check
|
||||
|
||||
test_harware_wallet:
|
||||
runs-on: ubuntu-latest
|
||||
test_hardware_wallet:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
|
||||
2
.github/workflows/nightly_docs.yml
vendored
2
.github/workflows/nightly_docs.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
target
|
||||
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
||||
- name: Set default toolchain
|
||||
run: rustup default nightly-2022-01-25
|
||||
run: rustup default nightly-2022-12-14
|
||||
- name: Set profile
|
||||
run: rustup set profile minimal
|
||||
- name: Update toolchain
|
||||
|
||||
29
Cargo.toml
29
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bdk"
|
||||
version = "0.24.0"
|
||||
version = "0.26.0"
|
||||
edition = "2018"
|
||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||
homepage = "https://bitcoindevkit.org"
|
||||
@@ -23,7 +23,7 @@ rand = "^0.8"
|
||||
# Optional dependencies
|
||||
sled = { version = "0.34", optional = true }
|
||||
electrum-client = { version = "0.12", optional = true }
|
||||
esplora-client = { version = "0.2", default-features = false, optional = true }
|
||||
esplora-client = { version = "0.3", default-features = false, optional = true }
|
||||
rusqlite = { version = "0.27.0", optional = true }
|
||||
ahash = { version = "0.7.6", optional = true }
|
||||
futures = { version = "0.3", optional = true }
|
||||
@@ -31,7 +31,7 @@ async-trait = { version = "0.1", optional = true }
|
||||
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
|
||||
cc = { version = ">=1.0.64", optional = true }
|
||||
socks = { version = "0.3", optional = true }
|
||||
hwi = { version = "0.3.0", optional = true }
|
||||
hwi = { version = "0.4.0", optional = true, features = [ "use-miniscript"] }
|
||||
|
||||
bip39 = { version = "1.0.1", optional = true }
|
||||
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
||||
@@ -41,7 +41,7 @@ bitcoincore-rpc = { version = "0.16", optional = true }
|
||||
|
||||
# Platform-specific dependencies
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tokio = { version = "1", features = ["rt"] }
|
||||
tokio = { version = "1", features = ["rt", "macros"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = "0.2"
|
||||
@@ -109,6 +109,7 @@ env_logger = "0.7"
|
||||
electrsd = "0.21"
|
||||
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
|
||||
base64 = "^0.13"
|
||||
assert_matches = "1.5.0"
|
||||
|
||||
[[example]]
|
||||
name = "compact_filters_balance"
|
||||
@@ -138,6 +139,26 @@ name = "hardware_signer"
|
||||
path = "examples/hardware_signer.rs"
|
||||
required-features = ["electrum", "hardware-signer"]
|
||||
|
||||
[[example]]
|
||||
name = "electrum_backend"
|
||||
path = "examples/electrum_backend.rs"
|
||||
required-features = ["electrum"]
|
||||
|
||||
[[example]]
|
||||
name = "esplora_backend_synchronous"
|
||||
path = "examples/esplora_backend_synchronous.rs"
|
||||
required-features = ["use-esplora-ureq"]
|
||||
|
||||
[[example]]
|
||||
name = "esplora_backend_asynchronous"
|
||||
path = "examples/esplora_backend_asynchronous.rs"
|
||||
required-features = ["use-esplora-reqwest", "reqwest-default-tls", "async-interface"]
|
||||
|
||||
[[example]]
|
||||
name = "mnemonic_to_descriptors"
|
||||
path = "examples/mnemonic_to_descriptors.rs"
|
||||
required-features = ["all-keys"]
|
||||
|
||||
[workspace]
|
||||
members = ["macros"]
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
15
README.md
15
README.md
@@ -68,12 +68,13 @@ fn main() -> Result<(), bdk::Error> {
|
||||
```rust
|
||||
use bdk::{Wallet, database::MemoryDatabase};
|
||||
use bdk::wallet::AddressIndex::New;
|
||||
use bdk::bitcoin::Network;
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let wallet = Wallet::new(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
Network::Testnet,
|
||||
MemoryDatabase::default(),
|
||||
)?;
|
||||
|
||||
@@ -96,14 +97,15 @@ use bdk::electrum_client::Client;
|
||||
use bdk::wallet::AddressIndex::New;
|
||||
|
||||
use base64;
|
||||
use bitcoin::consensus::serialize;
|
||||
use bdk::bitcoin::consensus::serialize;
|
||||
use bdk::bitcoin::Network;
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
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,
|
||||
Network::Testnet,
|
||||
MemoryDatabase::default(),
|
||||
)?;
|
||||
|
||||
@@ -133,20 +135,21 @@ fn main() -> Result<(), bdk::Error> {
|
||||
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
|
||||
|
||||
use base64;
|
||||
use bitcoin::consensus::deserialize;
|
||||
use bdk::bitcoin::consensus::deserialize;
|
||||
use bdk::bitcoin::Network;
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let wallet = Wallet::new(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
Network::Testnet,
|
||||
MemoryDatabase::default(),
|
||||
)?;
|
||||
|
||||
let psbt = "...";
|
||||
let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
|
||||
|
||||
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
||||
let _finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
87
examples/electrum_backend.rs
Normal file
87
examples/electrum_backend.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::template::Bip84;
|
||||
use bdk::wallet::export::FullyNodedExport;
|
||||
use bdk::{KeychainKind, SyncOptions, Wallet};
|
||||
|
||||
use bdk::electrum_client::Client;
|
||||
use bdk::wallet::AddressIndex;
|
||||
use bitcoin::util::bip32;
|
||||
|
||||
pub mod utils;
|
||||
|
||||
use crate::utils::tx::build_signed_tx;
|
||||
|
||||
/// This will create a wallet from an xpriv and get the balance by connecting to an Electrum server.
|
||||
/// If enough amount is available, this will send a transaction to an address.
|
||||
/// Otherwise, this will display a wallet address to receive funds.
|
||||
///
|
||||
/// This can be run with `cargo run --example electrum_backend` in the root folder.
|
||||
fn main() {
|
||||
let network = Network::Testnet;
|
||||
|
||||
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
|
||||
|
||||
let electrum_url = "ssl://electrum.blockstream.info:60002";
|
||||
|
||||
run(&network, electrum_url, xpriv);
|
||||
}
|
||||
|
||||
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
|
||||
Wallet::new(
|
||||
Bip84(*xpriv, KeychainKind::External),
|
||||
Some(Bip84(*xpriv, KeychainKind::Internal)),
|
||||
*network,
|
||||
MemoryDatabase::default(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn run(network: &Network, electrum_url: &str, xpriv: &str) {
|
||||
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
|
||||
|
||||
// Apparently it works only with Electrs (not EletrumX)
|
||||
let blockchain = ElectrumBlockchain::from(Client::new(electrum_url).unwrap());
|
||||
|
||||
let wallet = create_wallet(network, &xpriv);
|
||||
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
let address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||
|
||||
println!("address: {}", address);
|
||||
|
||||
let balance = wallet.get_balance().unwrap();
|
||||
|
||||
println!("Available coins in BDK wallet : {} sats", balance);
|
||||
|
||||
if balance.confirmed > 6500 {
|
||||
// the wallet sends the amount to itself.
|
||||
let recipient_address = wallet
|
||||
.get_address(AddressIndex::New)
|
||||
.unwrap()
|
||||
.address
|
||||
.to_string();
|
||||
|
||||
let amount = 5359;
|
||||
|
||||
let tx = build_signed_tx(&wallet, &recipient_address, amount);
|
||||
|
||||
blockchain.broadcast(&tx).unwrap();
|
||||
|
||||
println!("tx id: {}", tx.txid());
|
||||
} else {
|
||||
println!("Insufficient Funds. Fund the wallet with the address above");
|
||||
}
|
||||
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||
.map_err(ToString::to_string)
|
||||
.map_err(bdk::Error::Generic)
|
||||
.unwrap();
|
||||
|
||||
println!("------\nWallet Backup: {}", export.to_string());
|
||||
}
|
||||
93
examples/esplora_backend_asynchronous.rs
Normal file
93
examples/esplora_backend_asynchronous.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bdk::blockchain::Blockchain;
|
||||
use bdk::{
|
||||
blockchain::esplora::EsploraBlockchain,
|
||||
database::MemoryDatabase,
|
||||
template::Bip84,
|
||||
wallet::{export::FullyNodedExport, AddressIndex},
|
||||
KeychainKind, SyncOptions, Wallet,
|
||||
};
|
||||
use bitcoin::{
|
||||
util::bip32::{self, ExtendedPrivKey},
|
||||
Network,
|
||||
};
|
||||
|
||||
pub mod utils;
|
||||
|
||||
use crate::utils::tx::build_signed_tx;
|
||||
|
||||
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
|
||||
/// using non blocking asynchronous calls with `reqwest`.
|
||||
/// If enough amount is available, this will send a transaction to an address.
|
||||
/// Otherwise, this will display a wallet address to receive funds.
|
||||
///
|
||||
/// This can be run with `cargo run --no-default-features --features="use-esplora-reqwest, reqwest-default-tls, async-interface" --example esplora_backend_asynchronous`
|
||||
/// in the root folder.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let network = Network::Signet;
|
||||
|
||||
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
|
||||
|
||||
let esplora_url = "https://explorer.bc-2.jp/api";
|
||||
|
||||
run(&network, esplora_url, xpriv).await;
|
||||
}
|
||||
|
||||
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
|
||||
Wallet::new(
|
||||
Bip84(*xpriv, KeychainKind::External),
|
||||
Some(Bip84(*xpriv, KeychainKind::Internal)),
|
||||
*network,
|
||||
MemoryDatabase::default(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn run(network: &Network, esplora_url: &str, xpriv: &str) {
|
||||
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
|
||||
|
||||
let blockchain = EsploraBlockchain::new(esplora_url, 20);
|
||||
|
||||
let wallet = create_wallet(network, &xpriv);
|
||||
|
||||
wallet
|
||||
.sync(&blockchain, SyncOptions::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||
|
||||
println!("address: {}", address);
|
||||
|
||||
let balance = wallet.get_balance().unwrap();
|
||||
|
||||
println!("Available coins in BDK wallet : {} sats", balance);
|
||||
|
||||
if balance.confirmed > 10500 {
|
||||
// the wallet sends the amount to itself.
|
||||
let recipient_address = wallet
|
||||
.get_address(AddressIndex::New)
|
||||
.unwrap()
|
||||
.address
|
||||
.to_string();
|
||||
|
||||
let amount = 9359;
|
||||
|
||||
let tx = build_signed_tx(&wallet, &recipient_address, amount);
|
||||
|
||||
let _ = blockchain.broadcast(&tx);
|
||||
|
||||
println!("tx id: {}", tx.txid());
|
||||
} else {
|
||||
println!("Insufficient Funds. Fund the wallet with the address above");
|
||||
}
|
||||
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||
.map_err(ToString::to_string)
|
||||
.map_err(bdk::Error::Generic)
|
||||
.unwrap();
|
||||
|
||||
println!("------\nWallet Backup: {}", export.to_string());
|
||||
}
|
||||
89
examples/esplora_backend_synchronous.rs
Normal file
89
examples/esplora_backend_synchronous.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bdk::blockchain::Blockchain;
|
||||
use bdk::{
|
||||
blockchain::esplora::EsploraBlockchain,
|
||||
database::MemoryDatabase,
|
||||
template::Bip84,
|
||||
wallet::{export::FullyNodedExport, AddressIndex},
|
||||
KeychainKind, SyncOptions, Wallet,
|
||||
};
|
||||
use bitcoin::{
|
||||
util::bip32::{self, ExtendedPrivKey},
|
||||
Network,
|
||||
};
|
||||
|
||||
pub mod utils;
|
||||
|
||||
use crate::utils::tx::build_signed_tx;
|
||||
|
||||
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
|
||||
/// using blocking calls with `ureq`.
|
||||
/// If enough amount is available, this will send a transaction to an address.
|
||||
/// Otherwise, this will display a wallet address to receive funds.
|
||||
///
|
||||
/// This can be run with `cargo run --features=use-esplora-ureq --example esplora_backend_synchronous`
|
||||
/// in the root folder.
|
||||
fn main() {
|
||||
let network = Network::Signet;
|
||||
|
||||
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
|
||||
|
||||
let esplora_url = "https://explorer.bc-2.jp/api";
|
||||
|
||||
run(&network, esplora_url, xpriv);
|
||||
}
|
||||
|
||||
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
|
||||
Wallet::new(
|
||||
Bip84(*xpriv, KeychainKind::External),
|
||||
Some(Bip84(*xpriv, KeychainKind::Internal)),
|
||||
*network,
|
||||
MemoryDatabase::default(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn run(network: &Network, esplora_url: &str, xpriv: &str) {
|
||||
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
|
||||
|
||||
let blockchain = EsploraBlockchain::new(esplora_url, 20);
|
||||
|
||||
let wallet = create_wallet(network, &xpriv);
|
||||
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
|
||||
let address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||
|
||||
println!("address: {}", address);
|
||||
|
||||
let balance = wallet.get_balance().unwrap();
|
||||
|
||||
println!("Available coins in BDK wallet : {} sats", balance);
|
||||
|
||||
if balance.confirmed > 10500 {
|
||||
// the wallet sends the amount to itself.
|
||||
let recipient_address = wallet
|
||||
.get_address(AddressIndex::New)
|
||||
.unwrap()
|
||||
.address
|
||||
.to_string();
|
||||
|
||||
let amount = 9359;
|
||||
|
||||
let tx = build_signed_tx(&wallet, &recipient_address, amount);
|
||||
|
||||
blockchain.broadcast(&tx).unwrap();
|
||||
|
||||
println!("tx id: {}", tx.txid());
|
||||
} else {
|
||||
println!("Insufficient Funds. Fund the wallet with the address above");
|
||||
}
|
||||
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||
.map_err(ToString::to_string)
|
||||
.map_err(bdk::Error::Generic)
|
||||
.unwrap();
|
||||
|
||||
println!("------\nWallet Backup: {}", export.to_string());
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use bdk::bitcoin::{Address, Network};
|
||||
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::hwi::{types::HWIChain, HWIClient};
|
||||
use bdk::miniscript::{Descriptor, DescriptorPublicKey};
|
||||
use bdk::signer::SignerOrdering;
|
||||
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
|
||||
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
|
||||
@@ -23,26 +24,27 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Hold tight, I'm connecting to your hardware wallet...");
|
||||
|
||||
// Listing all the available hardware wallet devices...
|
||||
let devices = HWIClient::enumerate()?;
|
||||
let first_device = devices
|
||||
.first()
|
||||
.expect("No devices found. Either plug in a hardware wallet, or start a simulator.");
|
||||
let mut devices = HWIClient::enumerate()?;
|
||||
if devices.is_empty() {
|
||||
panic!("No devices found. Either plug in a hardware wallet, or start a simulator.");
|
||||
}
|
||||
let first_device = devices.remove(0)?;
|
||||
// ...and creating a client out of the first one
|
||||
let client = HWIClient::get_client(first_device, true, HWIChain::Test)?;
|
||||
let client = HWIClient::get_client(&first_device, true, HWIChain::Test)?;
|
||||
println!("Look what I found, a {}!", first_device.model);
|
||||
|
||||
// Getting the HW's public descriptors
|
||||
let descriptors = client.get_descriptors(None)?;
|
||||
let descriptors = client.get_descriptors::<Descriptor<DescriptorPublicKey>>(None)?;
|
||||
println!(
|
||||
"The hardware wallet's descriptor is: {}",
|
||||
descriptors.receive[0]
|
||||
);
|
||||
|
||||
// Creating a custom signer from the device
|
||||
let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
|
||||
let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?;
|
||||
let mut wallet = Wallet::new(
|
||||
&descriptors.receive[0],
|
||||
Some(&descriptors.internal[0]),
|
||||
descriptors.receive[0].clone(),
|
||||
Some(descriptors.internal[0].clone()),
|
||||
Network::Testnet,
|
||||
MemoryDatabase::default(),
|
||||
)?;
|
||||
|
||||
60
examples/mnemonic_to_descriptors.rs
Normal file
60
examples/mnemonic_to_descriptors.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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::util::bip32::DerivationPath;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::descriptor;
|
||||
use bdk::descriptor::IntoWalletDescriptor;
|
||||
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
|
||||
use bdk::keys::{GeneratableKey, GeneratedKey};
|
||||
use bdk::miniscript::Tap;
|
||||
use bdk::Error as BDK_Error;
|
||||
use std::error::Error;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// This example demonstrates how to generate a mnemonic phrase
|
||||
/// using BDK and use that to generate a descriptor string.
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
// In this example we are generating a 12 words mnemonic phrase
|
||||
// but it is also possible generate 15, 18, 21 and 24 words
|
||||
// using their respective `WordCount` variant.
|
||||
let mnemonic: GeneratedKey<_, Tap> =
|
||||
Mnemonic::generate((WordCount::Words12, Language::English))
|
||||
.map_err(|_| BDK_Error::Generic("Mnemonic generation error".to_string()))?;
|
||||
|
||||
println!("Mnemonic phrase: {}", *mnemonic);
|
||||
let mnemonic_with_passphrase = (mnemonic, None);
|
||||
|
||||
// define external and internal derivation key path
|
||||
let external_path = DerivationPath::from_str("m/86h/0h/0h/0").unwrap();
|
||||
let internal_path = DerivationPath::from_str("m/86h/0h/0h/1").unwrap();
|
||||
|
||||
// generate external and internal descriptor from mnemonic
|
||||
let (external_descriptor, ext_keymap) =
|
||||
descriptor!(tr((mnemonic_with_passphrase.clone(), external_path)))?
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)?;
|
||||
let (internal_descriptor, int_keymap) =
|
||||
descriptor!(tr((mnemonic_with_passphrase, internal_path)))?
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)?;
|
||||
|
||||
println!("tpub external descriptor: {}", external_descriptor);
|
||||
println!("tpub internal descriptor: {}", internal_descriptor);
|
||||
println!(
|
||||
"tprv external descriptor: {}",
|
||||
external_descriptor.to_string_with_secret(&ext_keymap)
|
||||
);
|
||||
println!(
|
||||
"tprv internal descriptor: {}",
|
||||
internal_descriptor.to_string_with_secret(&int_keymap)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
30
examples/utils/mod.rs
Normal file
30
examples/utils/mod.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
pub(crate) mod tx {
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use bdk::{database::BatchDatabase, SignOptions, Wallet};
|
||||
use bitcoin::{Address, Transaction};
|
||||
|
||||
pub fn build_signed_tx<D: BatchDatabase>(
|
||||
wallet: &Wallet<D>,
|
||||
recipient_address: &str,
|
||||
amount: u64,
|
||||
) -> Transaction {
|
||||
// Create a transaction builder
|
||||
let mut tx_builder = wallet.build_tx();
|
||||
|
||||
let to_address = Address::from_str(recipient_address).unwrap();
|
||||
|
||||
// Set recipient of the transaction
|
||||
tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]);
|
||||
|
||||
// Finalise the transaction and extract PSBT
|
||||
let (mut psbt, _) = tx_builder.finish().unwrap();
|
||||
|
||||
// Sign the above psbt with signing option
|
||||
wallet.sign(&mut psbt, SignOptions::default()).unwrap();
|
||||
|
||||
// Extract the final transaction
|
||||
psbt.extract_tx()
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,8 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
|
||||
/// "type" : "electrum",
|
||||
/// "url" : "ssl://electrum.blockstream.info:50002",
|
||||
/// "retry": 2,
|
||||
/// "stop_gap": 20
|
||||
/// "stop_gap": 20,
|
||||
/// "validate_domain": true
|
||||
/// }"#,
|
||||
/// )
|
||||
/// .unwrap();
|
||||
@@ -190,6 +191,7 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
|
||||
/// socks5: None,
|
||||
/// timeout: None,
|
||||
/// stop_gap: 20,
|
||||
/// validate_domain: true,
|
||||
/// })
|
||||
/// );
|
||||
/// # }
|
||||
|
||||
@@ -136,7 +136,7 @@ impl CfSync {
|
||||
|
||||
let resp = peer.get_cf_headers(0x00, start_height as u32, stop_hash)?;
|
||||
|
||||
assert!(resp.previous_filter_header == checkpoint);
|
||||
assert_eq!(resp.previous_filter_header, checkpoint);
|
||||
status =
|
||||
self.cf_store
|
||||
.advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;
|
||||
|
||||
@@ -281,9 +281,11 @@ impl<'a, 'b, D: Database> TxCache<'a, 'b, D> {
|
||||
.client
|
||||
.batch_transaction_get(need_fetch.clone())
|
||||
.map_err(Error::Electrum)?;
|
||||
for (tx, _txid) in txs.into_iter().zip(need_fetch) {
|
||||
debug_assert_eq!(*_txid, tx.txid());
|
||||
self.cache.insert(tx.txid(), tx);
|
||||
let mut txs: HashMap<_, _> = txs.into_iter().map(|tx| (tx.txid(), tx)).collect();
|
||||
for txid in need_fetch {
|
||||
if let Some(tx) = txs.remove(txid) {
|
||||
self.cache.insert(*txid, tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,6 +312,8 @@ pub struct ElectrumBlockchainConfig {
|
||||
pub timeout: Option<u8>,
|
||||
/// Stop searching addresses for transactions after finding an unused gap of this length
|
||||
pub stop_gap: usize,
|
||||
/// Validate the domain when using SSL
|
||||
pub validate_domain: bool,
|
||||
}
|
||||
|
||||
impl ConfigurableBlockchain for ElectrumBlockchain {
|
||||
@@ -321,6 +325,7 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
|
||||
.retry(config.retry)
|
||||
.timeout(config.timeout)?
|
||||
.socks5(socks5)?
|
||||
.validate_domain(config.validate_domain)
|
||||
.build();
|
||||
|
||||
Ok(ElectrumBlockchain {
|
||||
@@ -415,6 +420,7 @@ mod test {
|
||||
retry: 0,
|
||||
timeout: None,
|
||||
stop_gap: stop_gap,
|
||||
validate_domain: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,17 @@ static MIGRATIONS: &[&str] = &[
|
||||
"CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);",
|
||||
"INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;",
|
||||
"DROP TABLE utxos_old;",
|
||||
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);"
|
||||
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);",
|
||||
// Fix issue https://github.com/bitcoindevkit/bdk/issues/801: drop duplicated script_pubkeys
|
||||
"ALTER TABLE script_pubkeys RENAME TO script_pubkeys_old;",
|
||||
"DROP INDEX idx_keychain_child;",
|
||||
"DROP INDEX idx_script;",
|
||||
"CREATE TABLE script_pubkeys (keychain TEXT, child INTEGER, script BLOB);",
|
||||
"CREATE INDEX idx_keychain_child ON script_pubkeys(keychain, child);",
|
||||
"CREATE INDEX idx_script ON script_pubkeys(script);",
|
||||
"CREATE UNIQUE INDEX idx_script_pks_unique ON script_pubkeys(keychain, child);",
|
||||
"INSERT OR REPLACE INTO script_pubkeys SELECT keychain, child, script FROM script_pubkeys_old;",
|
||||
"DROP TABLE script_pubkeys_old;"
|
||||
];
|
||||
|
||||
/// Sqlite database stored on filesystem
|
||||
@@ -88,7 +98,7 @@ impl SqliteDatabase {
|
||||
child: u32,
|
||||
script: &[u8],
|
||||
) -> Result<i64, Error> {
|
||||
let mut statement = self.connection.prepare_cached("INSERT INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
|
||||
let mut statement = self.connection.prepare_cached("INSERT OR REPLACE INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
|
||||
statement.execute(named_params! {
|
||||
":keychain": keychain,
|
||||
":child": child,
|
||||
@@ -1096,4 +1106,42 @@ pub mod test {
|
||||
fn test_check_descriptor_checksum() {
|
||||
crate::database::test::test_check_descriptor_checksum(get_database());
|
||||
}
|
||||
|
||||
// Issue 801: https://github.com/bitcoindevkit/bdk/issues/801
|
||||
#[test]
|
||||
fn test_unique_spks() {
|
||||
use crate::bitcoin::hashes::hex::FromHex;
|
||||
use crate::database::*;
|
||||
|
||||
let mut db = get_database();
|
||||
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let keychain = KeychainKind::External;
|
||||
|
||||
for _ in 0..100 {
|
||||
db.set_script_pubkey(&script, keychain, path).unwrap();
|
||||
}
|
||||
|
||||
let mut statement = db
|
||||
.connection
|
||||
.prepare_cached(
|
||||
"select keychain,child,count(child) from script_pubkeys group by keychain,child;",
|
||||
)
|
||||
.unwrap();
|
||||
let mut rows = statement.query([]).unwrap();
|
||||
while let Some(row) = rows.next().unwrap() {
|
||||
let keychain: String = row.get(0).unwrap();
|
||||
let child: u32 = row.get(1).unwrap();
|
||||
let count: usize = row.get(2).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
count, 1,
|
||||
"keychain={}, child={}, count={}",
|
||||
keychain, child, count
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ pub fn get_checksum(desc: &str) -> Result<String, DescriptorError> {
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::descriptor::calc_checksum;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
// test calc_checksum() function; it should return the same value as Bitcoin Core
|
||||
#[test]
|
||||
@@ -155,16 +156,16 @@ mod test {
|
||||
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
|
||||
|
||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
|
||||
assert!(matches!(
|
||||
calc_checksum(desc).err(),
|
||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
||||
));
|
||||
assert_matches!(
|
||||
calc_checksum(desc),
|
||||
Err(DescriptorError::InvalidDescriptorChecksum)
|
||||
);
|
||||
|
||||
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
|
||||
assert!(matches!(
|
||||
calc_checksum(desc).err(),
|
||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
||||
));
|
||||
assert_matches!(
|
||||
calc_checksum(desc),
|
||||
Err(DescriptorError::InvalidDescriptorChecksum)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -172,9 +173,9 @@ mod test {
|
||||
let sparkle_heart = unsafe { std::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
|
||||
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
|
||||
|
||||
assert!(matches!(
|
||||
calc_checksum(&invalid_desc).err(),
|
||||
Some(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
|
||||
));
|
||||
assert_matches!(
|
||||
calc_checksum(&invalid_desc),
|
||||
Err(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ use bitcoin::util::{psbt, taproot};
|
||||
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
|
||||
use bitcoin::{Network, TxOut};
|
||||
|
||||
use miniscript::descriptor::{DefiniteDescriptorKey, DescriptorType, InnerXKey, SinglePubKey};
|
||||
use miniscript::descriptor::{
|
||||
DefiniteDescriptorKey, DescriptorSecretKey, DescriptorType, InnerXKey, SinglePubKey,
|
||||
};
|
||||
pub use miniscript::{
|
||||
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
|
||||
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
|
||||
@@ -240,14 +242,34 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
|
||||
);
|
||||
}
|
||||
|
||||
if !self.2.contains(&network) {
|
||||
let (desc, keymap, networks) = self;
|
||||
|
||||
if !networks.contains(&network) {
|
||||
return Err(DescriptorError::Key(KeyError::InvalidNetwork));
|
||||
}
|
||||
|
||||
// fixup the network for keys that need it
|
||||
let translated = self.0.translate_pk(&mut Translator { network })?;
|
||||
// fixup the network for keys that need it in the descriptor
|
||||
let translated = desc.translate_pk(&mut Translator { network })?;
|
||||
// ...and in the key map
|
||||
let fixed_keymap = keymap
|
||||
.into_iter()
|
||||
.map(|(mut k, mut v)| {
|
||||
match (&mut k, &mut v) {
|
||||
(DescriptorPublicKey::XPub(xpub), DescriptorSecretKey::XPrv(xprv)) => {
|
||||
xpub.xkey.network = network;
|
||||
xprv.xkey.network = network;
|
||||
}
|
||||
(_, DescriptorSecretKey::Single(key)) => {
|
||||
key.key.network = network;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok((translated, self.1))
|
||||
(k, v)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok((translated, fixed_keymap))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,6 +581,7 @@ impl DescriptorMeta for ExtendedDescriptor {
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use bitcoin::consensus::encode::deserialize;
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
@@ -682,23 +705,40 @@ mod test {
|
||||
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let xpub = bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap();
|
||||
let xprv = bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3c3gF1DUWpWNr2SG2XrG8oYPpqYh7hoWsJy9NjabErnzriJPpnGHyKz5NgdXmq1KVbqS1r4NXdCoKitWg5e86zqXHa8kxyB").unwrap();
|
||||
let path = bip32::DerivationPath::from_str("m/0").unwrap();
|
||||
|
||||
// here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
|
||||
// we are using an "xpub"
|
||||
let key = (xpub, path).into_descriptor_key().unwrap();
|
||||
let key = (xprv, path.clone()).into_descriptor_key().unwrap();
|
||||
// override it with any. this happens in some key conversions, like bip39
|
||||
let key = key.override_valid_networks(any_network());
|
||||
|
||||
// make a descriptor out of it
|
||||
let desc = crate::descriptor!(wpkh(key)).unwrap();
|
||||
// this should convert the key that supports "any_network" to the right network (testnet)
|
||||
let (wallet_desc, _) = desc
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)#y8p7e8kk");
|
||||
let mut xprv_testnet = xprv;
|
||||
xprv_testnet.network = Network::Testnet;
|
||||
|
||||
let xpub_testnet = bip32::ExtendedPubKey::from_priv(&secp, &xprv_testnet);
|
||||
let desc_pubkey = DescriptorPublicKey::XPub(DescriptorXKey {
|
||||
xkey: xpub_testnet,
|
||||
origin: None,
|
||||
derivation_path: path,
|
||||
wildcard: Wildcard::Unhardened,
|
||||
});
|
||||
|
||||
assert_eq!(wallet_desc.to_string(), "wpkh(tpubD6NzVbkrYhZ4XtJzoDja5snUjBNQRP5B3f4Hyn1T1x6PVPxzzVjvw6nJx2D8RBCxog9GEVjZoyStfepTz7TtKoBVdkCtnc7VCJh9dD4RAU9/0/*)#a3svx0ha");
|
||||
assert_eq!(
|
||||
keymap
|
||||
.get(&desc_pubkey)
|
||||
.map(|key| key.to_public(&secp).unwrap()),
|
||||
Some(desc_pubkey)
|
||||
);
|
||||
}
|
||||
|
||||
// test IntoWalletDescriptor trait from &str with and without checksum appended
|
||||
@@ -724,17 +764,11 @@ mod test {
|
||||
|
||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||
.into_wallet_descriptor(&secp, Network::Testnet);
|
||||
assert!(matches!(
|
||||
desc.err(),
|
||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
||||
));
|
||||
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
|
||||
|
||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||
.into_wallet_descriptor(&secp, Network::Testnet);
|
||||
assert!(matches!(
|
||||
desc.err(),
|
||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
||||
));
|
||||
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
|
||||
}
|
||||
|
||||
// test IntoWalletDescriptor trait from &str with keys from right and wrong network
|
||||
@@ -768,17 +802,11 @@ mod test {
|
||||
|
||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
||||
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
||||
assert!(matches!(
|
||||
desc.err(),
|
||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
||||
));
|
||||
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
|
||||
|
||||
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
|
||||
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
||||
assert!(matches!(
|
||||
desc.err(),
|
||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
||||
));
|
||||
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
|
||||
}
|
||||
|
||||
// test IntoWalletDescriptor trait from the output of the descriptor!() macro
|
||||
@@ -812,11 +840,7 @@ mod test {
|
||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(matches!(
|
||||
result.unwrap_err(),
|
||||
DescriptorError::HardenedDerivationXpub
|
||||
));
|
||||
assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
|
||||
|
||||
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
|
||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||
|
||||
@@ -1139,6 +1139,7 @@ mod test {
|
||||
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
|
||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||
use crate::wallet::signer::SignersContainer;
|
||||
use assert_matches::assert_matches;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
use bitcoin::util::bip32;
|
||||
use bitcoin::Network;
|
||||
@@ -1182,8 +1183,8 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
||||
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||
assert_matches!(&policy.contribution, Satisfaction::None);
|
||||
|
||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||
let (wallet_desc, keymap) = desc
|
||||
@@ -1195,10 +1196,8 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
||||
);
|
||||
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||
assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None);
|
||||
}
|
||||
|
||||
// 2 pub keys descriptor, required 2 prv keys
|
||||
@@ -1217,19 +1216,16 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||
);
|
||||
// TODO should this be "Satisfaction::None" since we have no prv keys?
|
||||
// TODO should items and conditions not be empty?
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||
&& m == &2usize
|
||||
&& items.is_empty()
|
||||
&& conditions.is_empty()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1248,18 +1244,15 @@ mod test {
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||
);
|
||||
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||
&& m == &2usize
|
||||
&& items.len() == 1
|
||||
&& conditions.contains_key(&0)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1281,18 +1274,15 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
||||
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||
);
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||
&& m == &1
|
||||
&& items.len() == 2
|
||||
&& conditions.contains_key(&vec![0])
|
||||
&& conditions.contains_key(&vec![1])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1313,18 +1303,15 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
|
||||
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||
);
|
||||
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||
&& m == &2
|
||||
&& items.len() == 2
|
||||
&& conditions.contains_key(&vec![0,1])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1345,8 +1332,8 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
||||
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||
assert_matches!(&policy.contribution, Satisfaction::None);
|
||||
|
||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||
let (wallet_desc, keymap) = desc
|
||||
@@ -1358,10 +1345,8 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint));
|
||||
assert!(
|
||||
matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
||||
);
|
||||
assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
|
||||
assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None);
|
||||
}
|
||||
|
||||
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
|
||||
@@ -1382,18 +1367,15 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(policy.item, Multisig { keys, threshold } if threshold == 1
|
||||
assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||
);
|
||||
assert!(
|
||||
matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
|
||||
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
|
||||
&& m == 1
|
||||
&& items.len() == 2
|
||||
&& conditions.contains_key(&vec![0])
|
||||
&& conditions.contains_key(&vec![1])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1425,18 +1407,14 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2)
|
||||
);
|
||||
assert_matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2);
|
||||
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
|
||||
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
|
||||
&& m == &2
|
||||
&& items.len() == 3
|
||||
&& conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
|
||||
&& conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
||||
&& conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1599,11 +1577,9 @@ mod test {
|
||||
.unwrap();
|
||||
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
|
||||
|
||||
assert!(
|
||||
matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||
assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||
&& m == &2
|
||||
&& items == &vec![0]
|
||||
)
|
||||
);
|
||||
|
||||
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
|
||||
@@ -1613,11 +1589,9 @@ mod test {
|
||||
.unwrap();
|
||||
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
|
||||
|
||||
assert!(
|
||||
matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||
assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||
&& m == &2
|
||||
&& items == &vec![1]
|
||||
)
|
||||
);
|
||||
|
||||
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
|
||||
@@ -1625,11 +1599,9 @@ mod test {
|
||||
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
|
||||
assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
|
||||
&& m == &2
|
||||
&& items == &vec![0, 1]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1673,11 +1645,9 @@ mod test {
|
||||
.extract_policy(&signers_container, build_sat, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||
assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||
&& m == &2
|
||||
&& items.is_empty()
|
||||
)
|
||||
);
|
||||
//println!("{}", serde_json::to_string(&policy).unwrap());
|
||||
|
||||
@@ -1691,11 +1661,9 @@ mod test {
|
||||
.extract_policy(&signers_container, build_sat_expired, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||
assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||
&& m == &2
|
||||
&& items == &vec![0]
|
||||
)
|
||||
);
|
||||
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
|
||||
|
||||
@@ -1711,11 +1679,9 @@ mod test {
|
||||
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
|
||||
assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
|
||||
&& m == &2
|
||||
&& items == &vec![0, 1]
|
||||
)
|
||||
);
|
||||
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
|
||||
}
|
||||
@@ -1790,12 +1756,8 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
|
||||
);
|
||||
assert!(
|
||||
matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1])
|
||||
);
|
||||
assert_matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1]);
|
||||
|
||||
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
|
||||
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
|
||||
@@ -1887,19 +1849,11 @@ mod test {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
|
||||
);
|
||||
assert!(
|
||||
matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty())
|
||||
);
|
||||
assert_matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||
assert_matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty());
|
||||
|
||||
assert!(
|
||||
matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
|
||||
);
|
||||
assert!(
|
||||
matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1])
|
||||
);
|
||||
assert_matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||
assert_matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1]);
|
||||
|
||||
let satisfied_items = match policy_signed.item {
|
||||
SatisfiableItem::Thresh { items, .. } => items,
|
||||
|
||||
@@ -235,14 +235,17 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
|
||||
/// )?;
|
||||
///
|
||||
/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
|
||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||
|
||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
P2Pkh(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build(network)
|
||||
P2Pkh(legacy::make_bipxx_public(
|
||||
44, self.0, self.1, self.2, network,
|
||||
)?)
|
||||
.build(network)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,14 +314,17 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
||||
/// )?;
|
||||
///
|
||||
/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/0'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
|
||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||
|
||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build(network)
|
||||
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(
|
||||
49, self.0, self.1, self.2, network,
|
||||
)?)
|
||||
.build(network)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,14 +393,17 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
||||
/// )?;
|
||||
///
|
||||
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
|
||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||
|
||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
P2Wpkh(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build(network)
|
||||
P2Wpkh(segwit_v0::make_bipxx_public(
|
||||
84, self.0, self.1, self.2, network,
|
||||
)?)
|
||||
.build(network)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +449,7 @@ macro_rules! expand_make_bipxx {
|
||||
key: K,
|
||||
parent_fingerprint: bip32::Fingerprint,
|
||||
keychain: KeychainKind,
|
||||
network: Network,
|
||||
) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
|
||||
let derivation_path: bip32::DerivationPath = match keychain {
|
||||
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
|
||||
@@ -448,7 +458,10 @@ macro_rules! expand_make_bipxx {
|
||||
|
||||
let source_path = bip32::DerivationPath::from(vec![
|
||||
bip32::ChildNumber::from_hardened_idx(bip)?,
|
||||
bip32::ChildNumber::from_hardened_idx(0)?,
|
||||
match network {
|
||||
Network::Bitcoin => bip32::ChildNumber::from_hardened_idx(0)?,
|
||||
_ => bip32::ChildNumber::from_hardened_idx(1)?,
|
||||
},
|
||||
bip32::ChildNumber::from_hardened_idx(0)?,
|
||||
]);
|
||||
|
||||
@@ -470,6 +483,7 @@ mod test {
|
||||
use super::*;
|
||||
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
||||
use crate::keys::ValidNetworks;
|
||||
use assert_matches::assert_matches;
|
||||
use bitcoin::network::constants::Network::Regtest;
|
||||
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
|
||||
use miniscript::Descriptor;
|
||||
@@ -488,9 +502,9 @@ mod test {
|
||||
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
|
||||
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
||||
let purpose = path.get(0).unwrap();
|
||||
assert!(matches!(purpose, Hardened { index: 44 }));
|
||||
assert_matches!(purpose, Hardened { index: 44 });
|
||||
let coin_type = path.get(1).unwrap();
|
||||
assert!(matches!(coin_type, Hardened { index: 0 }));
|
||||
assert_matches!(coin_type, Hardened { index: 0 });
|
||||
}
|
||||
|
||||
let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
|
||||
@@ -502,9 +516,9 @@ mod test {
|
||||
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
|
||||
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
||||
let purpose = path.get(0).unwrap();
|
||||
assert!(matches!(purpose, Hardened { index: 44 }));
|
||||
assert_matches!(purpose, Hardened { index: 44 });
|
||||
let coin_type = path.get(1).unwrap();
|
||||
assert!(matches!(coin_type, Hardened { index: 1 }));
|
||||
assert_matches!(coin_type, Hardened { index: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -256,6 +256,9 @@ pub extern crate rusqlite;
|
||||
#[macro_use]
|
||||
pub mod testutils;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate assert_matches;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
pub(crate) mod error;
|
||||
|
||||
@@ -23,7 +23,7 @@ pub trait ConfigurableBlockchainTester<B: ConfigurableBlockchain>: Sized {
|
||||
None
|
||||
}
|
||||
|
||||
/// Runs all avaliable tests.
|
||||
/// Runs all available tests.
|
||||
fn run(&self) {
|
||||
let test_client = &mut TestClient::default();
|
||||
|
||||
|
||||
117
src/types.rs
117
src/types.rs
@@ -247,6 +247,20 @@ pub struct TransactionDetails {
|
||||
pub confirmation_time: Option<BlockTime>,
|
||||
}
|
||||
|
||||
impl PartialOrd for TransactionDetails {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for TransactionDetails {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.confirmation_time
|
||||
.cmp(&other.confirmation_time)
|
||||
.then_with(|| self.txid.cmp(&other.txid))
|
||||
}
|
||||
}
|
||||
|
||||
/// Block height and timestamp of a block
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct BlockTime {
|
||||
@@ -256,6 +270,20 @@ pub struct BlockTime {
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl PartialOrd for BlockTime {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for BlockTime {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.height
|
||||
.cmp(&other.height)
|
||||
.then_with(|| self.timestamp.cmp(&other.timestamp))
|
||||
}
|
||||
}
|
||||
|
||||
/// **DEPRECATED**: Confirmation time of a transaction
|
||||
///
|
||||
/// The structure has been renamed to `BlockTime`
|
||||
@@ -334,6 +362,95 @@ impl std::iter::Sum for Balance {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bitcoin::hashes::Hash;
|
||||
|
||||
#[test]
|
||||
fn sort_block_time() {
|
||||
let block_time_a = BlockTime {
|
||||
height: 100,
|
||||
timestamp: 100,
|
||||
};
|
||||
|
||||
let block_time_b = BlockTime {
|
||||
height: 100,
|
||||
timestamp: 110,
|
||||
};
|
||||
|
||||
let block_time_c = BlockTime {
|
||||
height: 0,
|
||||
timestamp: 0,
|
||||
};
|
||||
|
||||
let mut vec = vec![
|
||||
block_time_a.clone(),
|
||||
block_time_b.clone(),
|
||||
block_time_c.clone(),
|
||||
];
|
||||
vec.sort();
|
||||
let expected = vec![block_time_c, block_time_a, block_time_b];
|
||||
|
||||
assert_eq!(vec, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sort_tx_details() {
|
||||
let block_time_a = BlockTime {
|
||||
height: 100,
|
||||
timestamp: 100,
|
||||
};
|
||||
|
||||
let block_time_b = BlockTime {
|
||||
height: 0,
|
||||
timestamp: 0,
|
||||
};
|
||||
|
||||
let tx_details_a = TransactionDetails {
|
||||
transaction: None,
|
||||
txid: Txid::from_inner([0; 32]),
|
||||
received: 0,
|
||||
sent: 0,
|
||||
fee: None,
|
||||
confirmation_time: None,
|
||||
};
|
||||
|
||||
let tx_details_b = TransactionDetails {
|
||||
transaction: None,
|
||||
txid: Txid::from_inner([0; 32]),
|
||||
received: 0,
|
||||
sent: 0,
|
||||
fee: None,
|
||||
confirmation_time: Some(block_time_a),
|
||||
};
|
||||
|
||||
let tx_details_c = TransactionDetails {
|
||||
transaction: None,
|
||||
txid: Txid::from_inner([0; 32]),
|
||||
received: 0,
|
||||
sent: 0,
|
||||
fee: None,
|
||||
confirmation_time: Some(block_time_b.clone()),
|
||||
};
|
||||
|
||||
let tx_details_d = TransactionDetails {
|
||||
transaction: None,
|
||||
txid: Txid::from_inner([1; 32]),
|
||||
received: 0,
|
||||
sent: 0,
|
||||
fee: None,
|
||||
confirmation_time: Some(block_time_b),
|
||||
};
|
||||
|
||||
let mut vec = vec![
|
||||
tx_details_a.clone(),
|
||||
tx_details_b.clone(),
|
||||
tx_details_c.clone(),
|
||||
tx_details_d.clone(),
|
||||
];
|
||||
vec.sort();
|
||||
let expected = vec![tx_details_a, tx_details_c, tx_details_d, tx_details_b];
|
||||
|
||||
assert_eq!(vec, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_store_feerate_in_const() {
|
||||
|
||||
@@ -102,11 +102,11 @@ use crate::{error::Error, Utxo};
|
||||
use bitcoin::consensus::encode::serialize;
|
||||
use bitcoin::Script;
|
||||
|
||||
#[cfg(test)]
|
||||
use assert_matches::assert_matches;
|
||||
use rand::seq::SliceRandom;
|
||||
#[cfg(not(test))]
|
||||
use rand::thread_rng;
|
||||
#[cfg(test)]
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
|
||||
@@ -671,6 +671,7 @@ impl BranchAndBoundCoinSelection {
|
||||
optional_utxos.shuffle(&mut thread_rng());
|
||||
#[cfg(test)]
|
||||
{
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
let seed = [0; 32];
|
||||
let mut rng: StdRng = SeedableRng::from_seed(seed);
|
||||
optional_utxos.shuffle(&mut rng);
|
||||
@@ -1522,24 +1523,22 @@ mod test {
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
|
||||
let err = BranchAndBoundCoinSelection::default()
|
||||
.coin_select(
|
||||
&database,
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(10.0),
|
||||
500_000,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap_err();
|
||||
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||
&database,
|
||||
vec![],
|
||||
utxos,
|
||||
FeeRate::from_sat_per_vb(10.0),
|
||||
500_000,
|
||||
&drain_script,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err,
|
||||
Error::InsufficientFunds {
|
||||
assert_matches!(
|
||||
selection,
|
||||
Err(Error::InsufficientFunds {
|
||||
available: 300_000,
|
||||
..
|
||||
}
|
||||
));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1552,24 +1551,22 @@ mod test {
|
||||
.into_iter()
|
||||
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
|
||||
|
||||
let err = BranchAndBoundCoinSelection::default()
|
||||
.coin_select(
|
||||
&database,
|
||||
required,
|
||||
optional,
|
||||
FeeRate::from_sat_per_vb(10.0),
|
||||
500_000,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap_err();
|
||||
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||
&database,
|
||||
required,
|
||||
optional,
|
||||
FeeRate::from_sat_per_vb(10.0),
|
||||
500_000,
|
||||
&drain_script,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err,
|
||||
Error::InsufficientFunds {
|
||||
assert_matches!(
|
||||
selection,
|
||||
Err(Error::InsufficientFunds {
|
||||
available: 300_010,
|
||||
..
|
||||
}
|
||||
));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1578,23 +1575,21 @@ mod test {
|
||||
let database = MemoryDatabase::default();
|
||||
let drain_script = Script::default();
|
||||
|
||||
let err = BranchAndBoundCoinSelection::default()
|
||||
.coin_select(
|
||||
&database,
|
||||
utxos,
|
||||
vec![],
|
||||
FeeRate::from_sat_per_vb(10_000.0),
|
||||
500_000,
|
||||
&drain_script,
|
||||
)
|
||||
.unwrap_err();
|
||||
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||
&database,
|
||||
utxos,
|
||||
vec![],
|
||||
FeeRate::from_sat_per_vb(10_000.0),
|
||||
500_000,
|
||||
&drain_script,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err,
|
||||
Error::InsufficientFunds {
|
||||
assert_matches!(
|
||||
selection,
|
||||
Err(Error::InsufficientFunds {
|
||||
available: 300_010,
|
||||
..
|
||||
}
|
||||
));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,15 +134,11 @@ impl FullyNodedExport {
|
||||
let blockheight = match wallet.database.borrow().iter_txs(false) {
|
||||
_ if !include_blockheight => 0,
|
||||
Err(_) => 0,
|
||||
Ok(txs) => {
|
||||
let mut heights = txs
|
||||
.into_iter()
|
||||
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(0))
|
||||
.collect::<Vec<_>>();
|
||||
heights.sort_unstable();
|
||||
|
||||
*heights.last().unwrap_or(&0)
|
||||
}
|
||||
Ok(txs) => txs
|
||||
.into_iter()
|
||||
.filter_map(|tx| tx.confirmation_time.map(|c| c.height))
|
||||
.min()
|
||||
.unwrap_or(0),
|
||||
};
|
||||
|
||||
let export = FullyNodedExport {
|
||||
@@ -249,6 +245,22 @@ mod test {
|
||||
fee: Some(500),
|
||||
confirmation_time: Some(BlockTime {
|
||||
timestamp: 12345678,
|
||||
height: 5001,
|
||||
}),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
db.set_tx(&TransactionDetails {
|
||||
transaction: None,
|
||||
txid: Txid::from_str(
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
)
|
||||
.unwrap(),
|
||||
received: 25_000,
|
||||
sent: 0,
|
||||
fee: Some(300),
|
||||
confirmation_time: Some(BlockTime {
|
||||
timestamp: 12345677,
|
||||
height: 5000,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -24,9 +24,12 @@
|
||||
//! # use std::sync::Arc;
|
||||
//! #
|
||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! let devices = HWIClient::enumerate()?;
|
||||
//! let first_device = devices.first().expect("No devices found!");
|
||||
//! let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
|
||||
//! let mut devices = HWIClient::enumerate()?;
|
||||
//! if devices.is_empty() {
|
||||
//! panic!("No devices found!");
|
||||
//! }
|
||||
//! let first_device = devices.remove(0)?;
|
||||
//! let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?;
|
||||
//!
|
||||
//! # let mut wallet = Wallet::new(
|
||||
//! # "",
|
||||
|
||||
@@ -1851,6 +1851,7 @@ pub fn get_funded_wallet(
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use assert_matches::assert_matches;
|
||||
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
|
||||
|
||||
use crate::database::Database;
|
||||
@@ -4425,11 +4426,9 @@ pub(crate) mod test {
|
||||
result.is_err(),
|
||||
"Signing should have failed because the TX uses non-standard sighashes"
|
||||
);
|
||||
assert!(
|
||||
matches!(
|
||||
result.unwrap_err(),
|
||||
Error::Signer(SignerError::NonStandardSighash)
|
||||
),
|
||||
assert_matches!(
|
||||
result,
|
||||
Err(Error::Signer(SignerError::NonStandardSighash)),
|
||||
"Signing failed with the wrong error type"
|
||||
);
|
||||
|
||||
@@ -4912,16 +4911,10 @@ pub(crate) mod test {
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Signing should have failed because the witness_utxo is missing"
|
||||
);
|
||||
assert!(
|
||||
matches!(
|
||||
result.unwrap_err(),
|
||||
Error::Signer(SignerError::MissingWitnessUtxo)
|
||||
),
|
||||
"Signing failed with the wrong error type"
|
||||
assert_matches!(
|
||||
result,
|
||||
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
|
||||
"Signing should have failed with the correct error because the witness_utxo is missing"
|
||||
);
|
||||
|
||||
// restore the witness_utxo
|
||||
@@ -4935,9 +4928,9 @@ pub(crate) mod test {
|
||||
},
|
||||
);
|
||||
|
||||
assert!(result.is_ok(), "Signing should have worked");
|
||||
assert!(
|
||||
result.unwrap(),
|
||||
assert_matches!(
|
||||
result,
|
||||
Ok(true),
|
||||
"Should finalize the input since we can produce signatures"
|
||||
);
|
||||
}
|
||||
@@ -5262,11 +5255,9 @@ pub(crate) mod test {
|
||||
result.is_err(),
|
||||
"Signing should have failed because the TX uses non-standard sighashes"
|
||||
);
|
||||
assert!(
|
||||
matches!(
|
||||
result.unwrap_err(),
|
||||
Error::Signer(SignerError::NonStandardSighash)
|
||||
),
|
||||
assert_matches!(
|
||||
result,
|
||||
Err(Error::Signer(SignerError::NonStandardSighash)),
|
||||
"Signing failed with the wrong error type"
|
||||
);
|
||||
|
||||
@@ -5282,11 +5273,9 @@ pub(crate) mod test {
|
||||
result.is_err(),
|
||||
"Signing should have failed because the witness_utxo is missing"
|
||||
);
|
||||
assert!(
|
||||
matches!(
|
||||
result.unwrap_err(),
|
||||
Error::Signer(SignerError::MissingWitnessUtxo)
|
||||
),
|
||||
assert_matches!(
|
||||
result,
|
||||
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
|
||||
"Signing failed with the wrong error type"
|
||||
);
|
||||
|
||||
@@ -5367,26 +5356,26 @@ pub(crate) mod test {
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
||||
.current_height(confirmation_time);
|
||||
assert!(matches!(
|
||||
builder.finish().unwrap_err(),
|
||||
Error::InsufficientFunds {
|
||||
assert_matches!(
|
||||
builder.finish(),
|
||||
Err(Error::InsufficientFunds {
|
||||
needed: _,
|
||||
available: 0
|
||||
}
|
||||
));
|
||||
})
|
||||
);
|
||||
|
||||
// Still unspendable...
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
||||
.current_height(not_yet_mature_time);
|
||||
assert!(matches!(
|
||||
builder.finish().unwrap_err(),
|
||||
Error::InsufficientFunds {
|
||||
assert_matches!(
|
||||
builder.finish(),
|
||||
Err(Error::InsufficientFunds {
|
||||
needed: _,
|
||||
available: 0
|
||||
}
|
||||
));
|
||||
})
|
||||
);
|
||||
|
||||
// ...Now the coinbase is mature :)
|
||||
let sync_time = SyncTime {
|
||||
@@ -5428,10 +5417,7 @@ pub(crate) mod test {
|
||||
|
||||
builder.add_recipient(addr.script_pubkey(), 0);
|
||||
|
||||
assert!(matches!(
|
||||
builder.finish().unwrap_err(),
|
||||
Error::OutputBelowDustLimit(0)
|
||||
));
|
||||
assert_matches!(builder.finish(), Err(Error::OutputBelowDustLimit(0)));
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
|
||||
@@ -5546,11 +5532,14 @@ pub(crate) mod test {
|
||||
use hwi::types::HWIChain;
|
||||
use hwi::HWIClient;
|
||||
|
||||
let devices = HWIClient::enumerate().unwrap();
|
||||
let device = devices.first().expect("No devices found");
|
||||
let client = HWIClient::get_client(device, true, HWIChain::Regtest).unwrap();
|
||||
let descriptors = client.get_descriptors(None).unwrap();
|
||||
let custom_signer = HWISigner::from_device(device, HWIChain::Regtest).unwrap();
|
||||
let mut devices = HWIClient::enumerate().unwrap();
|
||||
if devices.is_empty() {
|
||||
panic!("No devices found!");
|
||||
}
|
||||
let device = devices.remove(0).unwrap();
|
||||
let client = HWIClient::get_client(&device, true, HWIChain::Regtest).unwrap();
|
||||
let descriptors = client.get_descriptors::<String>(None).unwrap();
|
||||
let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
|
||||
|
||||
let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
|
||||
wallet.add_signer(
|
||||
|
||||
@@ -998,6 +998,7 @@ mod signers_container_tests {
|
||||
use crate::descriptor;
|
||||
use crate::descriptor::IntoWalletDescriptor;
|
||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||
use assert_matches::assert_matches;
|
||||
use bitcoin::secp256k1::{All, Secp256k1};
|
||||
use bitcoin::util::bip32;
|
||||
use bitcoin::Network;
|
||||
@@ -1067,17 +1068,17 @@ mod signers_container_tests {
|
||||
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
|
||||
signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
|
||||
|
||||
assert!(matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1)));
|
||||
assert!(matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2)));
|
||||
assert!(matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3)));
|
||||
assert_matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1));
|
||||
assert_matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2));
|
||||
assert_matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3));
|
||||
|
||||
// The `signer4` has the same ID as `signer3` but lower ordering.
|
||||
// It should be found by `id3` instead of `signer3`.
|
||||
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
|
||||
assert!(matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4)));
|
||||
assert_matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4));
|
||||
|
||||
// Can't find anything with ID that doesn't exist
|
||||
assert!(matches!(signers.find(id_nonexistent), None));
|
||||
assert_matches!(signers.find(id_nonexistent), None);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
||||
@@ -108,6 +108,7 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::database::{BatchOperations, MemoryDatabase};
|
||||
use assert_matches::assert_matches;
|
||||
use bitcoin::consensus::encode::deserialize;
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::{Transaction, Txid};
|
||||
@@ -137,9 +138,7 @@ mod test {
|
||||
}
|
||||
|
||||
let result = verify_tx(&signed_tx, &database, &blockchain);
|
||||
assert!(result.is_err(), "Should fail with missing input tx");
|
||||
assert!(
|
||||
matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid()),
|
||||
assert_matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid(),
|
||||
"Error should be a `MissingInputTx` error"
|
||||
);
|
||||
|
||||
@@ -147,9 +146,9 @@ mod test {
|
||||
database.set_raw_tx(&prev_tx).unwrap();
|
||||
|
||||
let result = verify_tx(&unsigned_tx, &database, &blockchain);
|
||||
assert!(result.is_err(), "Should fail since the TX is unsigned");
|
||||
assert!(
|
||||
matches!(result, Err(VerifyError::Consensus(_))),
|
||||
assert_matches!(
|
||||
result,
|
||||
Err(VerifyError::Consensus(_)),
|
||||
"Error should be a `Consensus` error"
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user