Compare commits
23 Commits
release/0.
...
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 |
2
.github/workflows/nightly_docs.yml
vendored
2
.github/workflows/nightly_docs.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
target
|
target
|
||||||
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
||||||
- name: Set default toolchain
|
- name: Set default toolchain
|
||||||
run: rustup default nightly-2022-01-25
|
run: rustup default nightly-2022-12-14
|
||||||
- name: Set profile
|
- name: Set profile
|
||||||
run: rustup set profile minimal
|
run: rustup set profile minimal
|
||||||
- name: Update toolchain
|
- name: Update toolchain
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk"
|
name = "bdk"
|
||||||
version = "0.25.0"
|
version = "0.26.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
@@ -23,7 +23,7 @@ rand = "^0.8"
|
|||||||
# Optional dependencies
|
# Optional dependencies
|
||||||
sled = { version = "0.34", optional = true }
|
sled = { version = "0.34", optional = true }
|
||||||
electrum-client = { version = "0.12", 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 }
|
rusqlite = { version = "0.27.0", optional = true }
|
||||||
ahash = { version = "0.7.6", optional = true }
|
ahash = { version = "0.7.6", optional = true }
|
||||||
futures = { version = "0.3", 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 }
|
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
|
||||||
cc = { version = ">=1.0.64", optional = true }
|
cc = { version = ">=1.0.64", optional = true }
|
||||||
socks = { version = "0.3", optional = true }
|
socks = { version = "0.3", optional = true }
|
||||||
hwi = { version = "0.3.0", optional = true }
|
hwi = { version = "0.4.0", optional = true, features = [ "use-miniscript"] }
|
||||||
|
|
||||||
bip39 = { version = "1.0.1", optional = true }
|
bip39 = { version = "1.0.1", optional = true }
|
||||||
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
||||||
@@ -109,6 +109,7 @@ env_logger = "0.7"
|
|||||||
electrsd = "0.21"
|
electrsd = "0.21"
|
||||||
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
|
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
|
||||||
base64 = "^0.13"
|
base64 = "^0.13"
|
||||||
|
assert_matches = "1.5.0"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "compact_filters_balance"
|
name = "compact_filters_balance"
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -68,12 +68,13 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
```rust
|
```rust
|
||||||
use bdk::{Wallet, database::MemoryDatabase};
|
use bdk::{Wallet, database::MemoryDatabase};
|
||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -96,14 +97,15 @@ use bdk::electrum_client::Client;
|
|||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
|
|
||||||
use base64;
|
use base64;
|
||||||
use bitcoin::consensus::serialize;
|
use bdk::bitcoin::consensus::serialize;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -133,20 +135,21 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
|
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
|
||||||
|
|
||||||
use base64;
|
use base64;
|
||||||
use bitcoin::consensus::deserialize;
|
use bdk::bitcoin::consensus::deserialize;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let psbt = "...";
|
let psbt = "...";
|
||||||
let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use bdk::bitcoin::{Address, Network};
|
|||||||
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::hwi::{types::HWIChain, HWIClient};
|
use bdk::hwi::{types::HWIChain, HWIClient};
|
||||||
|
use bdk::miniscript::{Descriptor, DescriptorPublicKey};
|
||||||
use bdk::signer::SignerOrdering;
|
use bdk::signer::SignerOrdering;
|
||||||
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
|
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
|
||||||
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
|
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...");
|
println!("Hold tight, I'm connecting to your hardware wallet...");
|
||||||
|
|
||||||
// Listing all the available hardware wallet devices...
|
// Listing all the available hardware wallet devices...
|
||||||
let devices = HWIClient::enumerate()?;
|
let mut devices = HWIClient::enumerate()?;
|
||||||
let first_device = devices
|
if devices.is_empty() {
|
||||||
.first()
|
panic!("No devices found. Either plug in a hardware wallet, or start a simulator.");
|
||||||
.expect("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
|
// ...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);
|
println!("Look what I found, a {}!", first_device.model);
|
||||||
|
|
||||||
// Getting the HW's public descriptors
|
// Getting the HW's public descriptors
|
||||||
let descriptors = client.get_descriptors(None)?;
|
let descriptors = client.get_descriptors::<Descriptor<DescriptorPublicKey>>(None)?;
|
||||||
println!(
|
println!(
|
||||||
"The hardware wallet's descriptor is: {}",
|
"The hardware wallet's descriptor is: {}",
|
||||||
descriptors.receive[0]
|
descriptors.receive[0]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Creating a custom signer from the device
|
// 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(
|
let mut wallet = Wallet::new(
|
||||||
&descriptors.receive[0],
|
descriptors.receive[0].clone(),
|
||||||
Some(&descriptors.internal[0]),
|
Some(descriptors.internal[0].clone()),
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -178,7 +178,8 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
|
|||||||
/// "type" : "electrum",
|
/// "type" : "electrum",
|
||||||
/// "url" : "ssl://electrum.blockstream.info:50002",
|
/// "url" : "ssl://electrum.blockstream.info:50002",
|
||||||
/// "retry": 2,
|
/// "retry": 2,
|
||||||
/// "stop_gap": 20
|
/// "stop_gap": 20,
|
||||||
|
/// "validate_domain": true
|
||||||
/// }"#,
|
/// }"#,
|
||||||
/// )
|
/// )
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
@@ -190,6 +191,7 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
|
|||||||
/// socks5: None,
|
/// socks5: None,
|
||||||
/// timeout: None,
|
/// timeout: None,
|
||||||
/// stop_gap: 20,
|
/// 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)?;
|
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 =
|
status =
|
||||||
self.cf_store
|
self.cf_store
|
||||||
.advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;
|
.advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;
|
||||||
|
|||||||
@@ -281,9 +281,11 @@ impl<'a, 'b, D: Database> TxCache<'a, 'b, D> {
|
|||||||
.client
|
.client
|
||||||
.batch_transaction_get(need_fetch.clone())
|
.batch_transaction_get(need_fetch.clone())
|
||||||
.map_err(Error::Electrum)?;
|
.map_err(Error::Electrum)?;
|
||||||
for (tx, _txid) in txs.into_iter().zip(need_fetch) {
|
let mut txs: HashMap<_, _> = txs.into_iter().map(|tx| (tx.txid(), tx)).collect();
|
||||||
debug_assert_eq!(*_txid, tx.txid());
|
for txid in need_fetch {
|
||||||
self.cache.insert(tx.txid(), tx);
|
if let Some(tx) = txs.remove(txid) {
|
||||||
|
self.cache.insert(*txid, tx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,6 +312,8 @@ pub struct ElectrumBlockchainConfig {
|
|||||||
pub timeout: Option<u8>,
|
pub timeout: Option<u8>,
|
||||||
/// Stop searching addresses for transactions after finding an unused gap of this length
|
/// Stop searching addresses for transactions after finding an unused gap of this length
|
||||||
pub stop_gap: usize,
|
pub stop_gap: usize,
|
||||||
|
/// Validate the domain when using SSL
|
||||||
|
pub validate_domain: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurableBlockchain for ElectrumBlockchain {
|
impl ConfigurableBlockchain for ElectrumBlockchain {
|
||||||
@@ -321,6 +325,7 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
|
|||||||
.retry(config.retry)
|
.retry(config.retry)
|
||||||
.timeout(config.timeout)?
|
.timeout(config.timeout)?
|
||||||
.socks5(socks5)?
|
.socks5(socks5)?
|
||||||
|
.validate_domain(config.validate_domain)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Ok(ElectrumBlockchain {
|
Ok(ElectrumBlockchain {
|
||||||
@@ -415,6 +420,7 @@ mod test {
|
|||||||
retry: 0,
|
retry: 0,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
stop_gap: stop_gap,
|
stop_gap: stop_gap,
|
||||||
|
validate_domain: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1137,12 +1137,10 @@ pub mod test {
|
|||||||
let child: u32 = row.get(1).unwrap();
|
let child: u32 = row.get(1).unwrap();
|
||||||
let count: usize = row.get(2).unwrap();
|
let count: usize = row.get(2).unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_eq!(
|
||||||
count == 1,
|
count, 1,
|
||||||
"keychain={}, child={}, count={}",
|
"keychain={}, child={}, count={}",
|
||||||
keychain,
|
keychain, child, count
|
||||||
child,
|
|
||||||
count
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ pub fn get_checksum(desc: &str) -> Result<String, DescriptorError> {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::descriptor::calc_checksum;
|
use crate::descriptor::calc_checksum;
|
||||||
|
use assert_matches::assert_matches;
|
||||||
|
|
||||||
// test calc_checksum() function; it should return the same value as Bitcoin Core
|
// test calc_checksum() function; it should return the same value as Bitcoin Core
|
||||||
#[test]
|
#[test]
|
||||||
@@ -155,16 +156,16 @@ mod test {
|
|||||||
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
|
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
|
||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
calc_checksum(desc).err(),
|
calc_checksum(desc),
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
Err(DescriptorError::InvalidDescriptorChecksum)
|
||||||
));
|
);
|
||||||
|
|
||||||
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
|
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
calc_checksum(desc).err(),
|
calc_checksum(desc),
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
Err(DescriptorError::InvalidDescriptorChecksum)
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -172,9 +173,9 @@ mod test {
|
|||||||
let sparkle_heart = unsafe { std::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
|
let sparkle_heart = unsafe { std::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
|
||||||
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
|
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
calc_checksum(&invalid_desc).err(),
|
calc_checksum(&invalid_desc),
|
||||||
Some(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
|
Err(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -581,6 +581,7 @@ impl DescriptorMeta for ExtendedDescriptor {
|
|||||||
mod test {
|
mod test {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::consensus::encode::deserialize;
|
use bitcoin::consensus::encode::deserialize;
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
@@ -763,17 +764,11 @@ mod test {
|
|||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||||
.into_wallet_descriptor(&secp, Network::Testnet);
|
.into_wallet_descriptor(&secp, Network::Testnet);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
|
||||||
));
|
|
||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||||
.into_wallet_descriptor(&secp, Network::Testnet);
|
.into_wallet_descriptor(&secp, Network::Testnet);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test IntoWalletDescriptor trait from &str with keys from right and wrong network
|
// test IntoWalletDescriptor trait from &str with keys from right and wrong network
|
||||||
@@ -807,17 +802,11 @@ mod test {
|
|||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
||||||
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
|
||||||
));
|
|
||||||
|
|
||||||
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
|
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
|
||||||
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test IntoWalletDescriptor trait from the output of the descriptor!() macro
|
// test IntoWalletDescriptor trait from the output of the descriptor!() macro
|
||||||
@@ -851,11 +840,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
||||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
|
||||||
assert!(matches!(
|
|
||||||
result.unwrap_err(),
|
|
||||||
DescriptorError::HardenedDerivationXpub
|
|
||||||
));
|
|
||||||
|
|
||||||
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
|
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
|
||||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
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::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
|
||||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||||
use crate::wallet::signer::SignersContainer;
|
use crate::wallet::signer::SignersContainer;
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::Network;
|
use bitcoin::Network;
|
||||||
@@ -1182,8 +1183,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
assert_matches!(&policy.contribution, Satisfaction::None);
|
||||||
|
|
||||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||||
let (wallet_desc, keymap) = desc
|
let (wallet_desc, keymap) = desc
|
||||||
@@ -1195,10 +1196,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None);
|
||||||
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 pub keys descriptor, required 2 prv keys
|
// 2 pub keys descriptor, required 2 prv keys
|
||||||
@@ -1217,19 +1216,16 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& 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 this be "Satisfaction::None" since we have no prv keys?
|
||||||
// TODO should items and conditions not be empty?
|
// TODO should items and conditions not be empty?
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||||
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
|
||||||
&& m == &2usize
|
&& m == &2usize
|
||||||
&& items.is_empty()
|
&& items.is_empty()
|
||||||
&& conditions.is_empty()
|
&& conditions.is_empty()
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1248,18 +1244,15 @@ mod test {
|
|||||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||||
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
|
||||||
&& m == &2usize
|
&& m == &2usize
|
||||||
&& items.len() == 1
|
&& items.len() == 1
|
||||||
&& conditions.contains_key(&0)
|
&& conditions.contains_key(&0)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1281,18 +1274,15 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
|
||||||
&& m == &1
|
&& m == &1
|
||||||
&& items.len() == 2
|
&& items.len() == 2
|
||||||
&& conditions.contains_key(&vec![0])
|
&& conditions.contains_key(&vec![0])
|
||||||
&& conditions.contains_key(&vec![1])
|
&& conditions.contains_key(&vec![1])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1313,18 +1303,15 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items.len() == 2
|
&& items.len() == 2
|
||||||
&& conditions.contains_key(&vec![0,1])
|
&& conditions.contains_key(&vec![0,1])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1345,8 +1332,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
assert_matches!(&policy.contribution, Satisfaction::None);
|
||||||
|
|
||||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||||
let (wallet_desc, keymap) = desc
|
let (wallet_desc, keymap) = desc
|
||||||
@@ -1358,10 +1345,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint));
|
assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
|
||||||
assert!(
|
assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None);
|
||||||
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
|
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
|
||||||
@@ -1382,18 +1367,15 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
|
||||||
matches!(policy.item, Multisig { keys, threshold } if threshold == 1
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
|
||||||
matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
|
|
||||||
&& m == 1
|
&& m == 1
|
||||||
&& items.len() == 2
|
&& items.len() == 2
|
||||||
&& conditions.contains_key(&vec![0])
|
&& conditions.contains_key(&vec![0])
|
||||||
&& conditions.contains_key(&vec![1])
|
&& conditions.contains_key(&vec![1])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1425,18 +1407,14 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2);
|
||||||
matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2)
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
|
||||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items.len() == 3
|
&& items.len() == 3
|
||||||
&& conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
|
&& 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![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
||||||
&& conditions.get(&vec![1,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();
|
.unwrap();
|
||||||
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
|
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||||
matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0]
|
&& items == &vec![0]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
|
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
|
||||||
@@ -1613,11 +1589,9 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
|
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||||
matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![1]
|
&& items == &vec![1]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
|
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
|
||||||
@@ -1625,11 +1599,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
|
||||||
matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0, 1]
|
&& items == &vec![0, 1]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1673,11 +1645,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, build_sat, &secp)
|
.extract_policy(&signers_container, build_sat, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||||
matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items.is_empty()
|
&& items.is_empty()
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy).unwrap());
|
//println!("{}", serde_json::to_string(&policy).unwrap());
|
||||||
|
|
||||||
@@ -1691,11 +1661,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, build_sat_expired, &secp)
|
.extract_policy(&signers_container, build_sat_expired, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||||
matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0]
|
&& items == &vec![0]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
|
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
|
||||||
|
|
||||||
@@ -1711,11 +1679,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
|
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
|
||||||
matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0, 1]
|
&& items == &vec![0, 1]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
|
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
|
||||||
}
|
}
|
||||||
@@ -1790,12 +1756,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||||
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.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1])
|
|
||||||
);
|
|
||||||
|
|
||||||
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
|
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
|
||||||
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
|
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
|
||||||
@@ -1887,19 +1849,11 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||||
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.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||||
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.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1])
|
|
||||||
);
|
|
||||||
|
|
||||||
let satisfied_items = match policy_signed.item {
|
let satisfied_items = match policy_signed.item {
|
||||||
SatisfiableItem::Thresh { items, .. } => items,
|
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.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>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
||||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
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.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>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
||||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
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.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>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
||||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
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,
|
key: K,
|
||||||
parent_fingerprint: bip32::Fingerprint,
|
parent_fingerprint: bip32::Fingerprint,
|
||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
|
network: Network,
|
||||||
) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
|
) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
|
||||||
let derivation_path: bip32::DerivationPath = match keychain {
|
let derivation_path: bip32::DerivationPath = match keychain {
|
||||||
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
|
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![
|
let source_path = bip32::DerivationPath::from(vec![
|
||||||
bip32::ChildNumber::from_hardened_idx(bip)?,
|
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)?,
|
bip32::ChildNumber::from_hardened_idx(0)?,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -470,6 +483,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
||||||
use crate::keys::ValidNetworks;
|
use crate::keys::ValidNetworks;
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::network::constants::Network::Regtest;
|
use bitcoin::network::constants::Network::Regtest;
|
||||||
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
|
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
|
||||||
use miniscript::Descriptor;
|
use miniscript::Descriptor;
|
||||||
@@ -488,9 +502,9 @@ mod test {
|
|||||||
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
|
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
|
||||||
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
||||||
let purpose = path.get(0).unwrap();
|
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();
|
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();
|
let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
|
||||||
@@ -502,9 +516,9 @@ mod test {
|
|||||||
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
|
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
|
||||||
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
||||||
let purpose = path.get(0).unwrap();
|
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();
|
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]
|
#[macro_use]
|
||||||
pub mod testutils;
|
pub mod testutils;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate assert_matches;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub(crate) mod error;
|
pub(crate) mod error;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub trait ConfigurableBlockchainTester<B: ConfigurableBlockchain>: Sized {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs all avaliable tests.
|
/// Runs all available tests.
|
||||||
fn run(&self) {
|
fn run(&self) {
|
||||||
let test_client = &mut TestClient::default();
|
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>,
|
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
|
/// Block height and timestamp of a block
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct BlockTime {
|
pub struct BlockTime {
|
||||||
@@ -256,6 +270,20 @@ pub struct BlockTime {
|
|||||||
pub timestamp: u64,
|
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
|
/// **DEPRECATED**: Confirmation time of a transaction
|
||||||
///
|
///
|
||||||
/// The structure has been renamed to `BlockTime`
|
/// The structure has been renamed to `BlockTime`
|
||||||
@@ -334,6 +362,95 @@ impl std::iter::Sum for Balance {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn can_store_feerate_in_const() {
|
fn can_store_feerate_in_const() {
|
||||||
|
|||||||
@@ -102,11 +102,11 @@ use crate::{error::Error, Utxo};
|
|||||||
use bitcoin::consensus::encode::serialize;
|
use bitcoin::consensus::encode::serialize;
|
||||||
use bitcoin::Script;
|
use bitcoin::Script;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
#[cfg(test)]
|
|
||||||
use rand::{rngs::StdRng, SeedableRng};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
@@ -671,6 +671,7 @@ impl BranchAndBoundCoinSelection {
|
|||||||
optional_utxos.shuffle(&mut thread_rng());
|
optional_utxos.shuffle(&mut thread_rng());
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
{
|
{
|
||||||
|
use rand::{rngs::StdRng, SeedableRng};
|
||||||
let seed = [0; 32];
|
let seed = [0; 32];
|
||||||
let mut rng: StdRng = SeedableRng::from_seed(seed);
|
let mut rng: StdRng = SeedableRng::from_seed(seed);
|
||||||
optional_utxos.shuffle(&mut rng);
|
optional_utxos.shuffle(&mut rng);
|
||||||
@@ -1522,24 +1523,22 @@ mod test {
|
|||||||
let database = MemoryDatabase::default();
|
let database = MemoryDatabase::default();
|
||||||
let drain_script = Script::default();
|
let drain_script = Script::default();
|
||||||
|
|
||||||
let err = BranchAndBoundCoinSelection::default()
|
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||||
.coin_select(
|
&database,
|
||||||
&database,
|
vec![],
|
||||||
vec![],
|
utxos,
|
||||||
utxos,
|
FeeRate::from_sat_per_vb(10.0),
|
||||||
FeeRate::from_sat_per_vb(10.0),
|
500_000,
|
||||||
500_000,
|
&drain_script,
|
||||||
&drain_script,
|
);
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
err,
|
selection,
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
available: 300_000,
|
available: 300_000,
|
||||||
..
|
..
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1552,24 +1551,22 @@ mod test {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
|
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
|
||||||
|
|
||||||
let err = BranchAndBoundCoinSelection::default()
|
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||||
.coin_select(
|
&database,
|
||||||
&database,
|
required,
|
||||||
required,
|
optional,
|
||||||
optional,
|
FeeRate::from_sat_per_vb(10.0),
|
||||||
FeeRate::from_sat_per_vb(10.0),
|
500_000,
|
||||||
500_000,
|
&drain_script,
|
||||||
&drain_script,
|
);
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
err,
|
selection,
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
available: 300_010,
|
available: 300_010,
|
||||||
..
|
..
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1578,23 +1575,21 @@ mod test {
|
|||||||
let database = MemoryDatabase::default();
|
let database = MemoryDatabase::default();
|
||||||
let drain_script = Script::default();
|
let drain_script = Script::default();
|
||||||
|
|
||||||
let err = BranchAndBoundCoinSelection::default()
|
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||||
.coin_select(
|
&database,
|
||||||
&database,
|
utxos,
|
||||||
utxos,
|
vec![],
|
||||||
vec![],
|
FeeRate::from_sat_per_vb(10_000.0),
|
||||||
FeeRate::from_sat_per_vb(10_000.0),
|
500_000,
|
||||||
500_000,
|
&drain_script,
|
||||||
&drain_script,
|
);
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
err,
|
selection,
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
available: 300_010,
|
available: 300_010,
|
||||||
..
|
..
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,12 @@
|
|||||||
//! # use std::sync::Arc;
|
//! # use std::sync::Arc;
|
||||||
//! #
|
//! #
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! let devices = HWIClient::enumerate()?;
|
//! let mut devices = HWIClient::enumerate()?;
|
||||||
//! let first_device = devices.first().expect("No devices found!");
|
//! if devices.is_empty() {
|
||||||
//! let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
|
//! 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(
|
//! # let mut wallet = Wallet::new(
|
||||||
//! # "",
|
//! # "",
|
||||||
|
|||||||
@@ -1851,6 +1851,7 @@ pub fn get_funded_wallet(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
|
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
|
||||||
|
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
@@ -4425,11 +4426,9 @@ pub(crate) mod test {
|
|||||||
result.is_err(),
|
result.is_err(),
|
||||||
"Signing should have failed because the TX uses non-standard sighashes"
|
"Signing should have failed because the TX uses non-standard sighashes"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
matches!(
|
result,
|
||||||
result.unwrap_err(),
|
Err(Error::Signer(SignerError::NonStandardSighash)),
|
||||||
Error::Signer(SignerError::NonStandardSighash)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
"Signing failed with the wrong error type"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -4912,16 +4911,10 @@ pub(crate) mod test {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
result.is_err(),
|
result,
|
||||||
"Signing should have failed because the witness_utxo is missing"
|
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
|
||||||
);
|
"Signing should have failed with the correct error because the witness_utxo is missing"
|
||||||
assert!(
|
|
||||||
matches!(
|
|
||||||
result.unwrap_err(),
|
|
||||||
Error::Signer(SignerError::MissingWitnessUtxo)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// restore the witness_utxo
|
// restore the witness_utxo
|
||||||
@@ -4935,9 +4928,9 @@ pub(crate) mod test {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(result.is_ok(), "Signing should have worked");
|
assert_matches!(
|
||||||
assert!(
|
result,
|
||||||
result.unwrap(),
|
Ok(true),
|
||||||
"Should finalize the input since we can produce signatures"
|
"Should finalize the input since we can produce signatures"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -5262,11 +5255,9 @@ pub(crate) mod test {
|
|||||||
result.is_err(),
|
result.is_err(),
|
||||||
"Signing should have failed because the TX uses non-standard sighashes"
|
"Signing should have failed because the TX uses non-standard sighashes"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
matches!(
|
result,
|
||||||
result.unwrap_err(),
|
Err(Error::Signer(SignerError::NonStandardSighash)),
|
||||||
Error::Signer(SignerError::NonStandardSighash)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
"Signing failed with the wrong error type"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -5282,11 +5273,9 @@ pub(crate) mod test {
|
|||||||
result.is_err(),
|
result.is_err(),
|
||||||
"Signing should have failed because the witness_utxo is missing"
|
"Signing should have failed because the witness_utxo is missing"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
matches!(
|
result,
|
||||||
result.unwrap_err(),
|
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
|
||||||
Error::Signer(SignerError::MissingWitnessUtxo)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
"Signing failed with the wrong error type"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -5367,26 +5356,26 @@ pub(crate) mod test {
|
|||||||
builder
|
builder
|
||||||
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
||||||
.current_height(confirmation_time);
|
.current_height(confirmation_time);
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
builder.finish().unwrap_err(),
|
builder.finish(),
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
needed: _,
|
needed: _,
|
||||||
available: 0
|
available: 0
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
|
|
||||||
// Still unspendable...
|
// Still unspendable...
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder
|
builder
|
||||||
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
||||||
.current_height(not_yet_mature_time);
|
.current_height(not_yet_mature_time);
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
builder.finish().unwrap_err(),
|
builder.finish(),
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
needed: _,
|
needed: _,
|
||||||
available: 0
|
available: 0
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
|
|
||||||
// ...Now the coinbase is mature :)
|
// ...Now the coinbase is mature :)
|
||||||
let sync_time = SyncTime {
|
let sync_time = SyncTime {
|
||||||
@@ -5428,10 +5417,7 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
builder.add_recipient(addr.script_pubkey(), 0);
|
builder.add_recipient(addr.script_pubkey(), 0);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(builder.finish(), Err(Error::OutputBelowDustLimit(0)));
|
||||||
builder.finish().unwrap_err(),
|
|
||||||
Error::OutputBelowDustLimit(0)
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
|
|
||||||
@@ -5546,11 +5532,14 @@ pub(crate) mod test {
|
|||||||
use hwi::types::HWIChain;
|
use hwi::types::HWIChain;
|
||||||
use hwi::HWIClient;
|
use hwi::HWIClient;
|
||||||
|
|
||||||
let devices = HWIClient::enumerate().unwrap();
|
let mut devices = HWIClient::enumerate().unwrap();
|
||||||
let device = devices.first().expect("No devices found");
|
if devices.is_empty() {
|
||||||
let client = HWIClient::get_client(device, true, HWIChain::Regtest).unwrap();
|
panic!("No devices found!");
|
||||||
let descriptors = client.get_descriptors(None).unwrap();
|
}
|
||||||
let custom_signer = HWISigner::from_device(device, HWIChain::Regtest).unwrap();
|
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]);
|
let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
|
||||||
wallet.add_signer(
|
wallet.add_signer(
|
||||||
|
|||||||
@@ -998,6 +998,7 @@ mod signers_container_tests {
|
|||||||
use crate::descriptor;
|
use crate::descriptor;
|
||||||
use crate::descriptor::IntoWalletDescriptor;
|
use crate::descriptor::IntoWalletDescriptor;
|
||||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::secp256k1::{All, Secp256k1};
|
use bitcoin::secp256k1::{All, Secp256k1};
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::Network;
|
use bitcoin::Network;
|
||||||
@@ -1067,17 +1068,17 @@ mod signers_container_tests {
|
|||||||
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
|
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
|
||||||
signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
|
signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
|
||||||
|
|
||||||
assert!(matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1)));
|
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(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(id3.clone()), Some(signer) if is_equal(signer, &signer3));
|
||||||
|
|
||||||
// The `signer4` has the same ID as `signer3` but lower ordering.
|
// The `signer4` has the same ID as `signer3` but lower ordering.
|
||||||
// It should be found by `id3` instead of `signer3`.
|
// It should be found by `id3` instead of `signer3`.
|
||||||
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
|
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
|
||||||
assert!(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
|
// 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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::database::{BatchOperations, MemoryDatabase};
|
use crate::database::{BatchOperations, MemoryDatabase};
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::consensus::encode::deserialize;
|
use bitcoin::consensus::encode::deserialize;
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::{Transaction, Txid};
|
use bitcoin::{Transaction, Txid};
|
||||||
@@ -137,9 +138,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = verify_tx(&signed_tx, &database, &blockchain);
|
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"
|
"Error should be a `MissingInputTx` error"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -147,9 +146,9 @@ mod test {
|
|||||||
database.set_raw_tx(&prev_tx).unwrap();
|
database.set_raw_tx(&prev_tx).unwrap();
|
||||||
|
|
||||||
let result = verify_tx(&unsigned_tx, &database, &blockchain);
|
let result = verify_tx(&unsigned_tx, &database, &blockchain);
|
||||||
assert!(result.is_err(), "Should fail since the TX is unsigned");
|
assert_matches!(
|
||||||
assert!(
|
result,
|
||||||
matches!(result, Err(VerifyError::Consensus(_))),
|
Err(VerifyError::Consensus(_)),
|
||||||
"Error should be a `Consensus` error"
|
"Error should be a `Consensus` error"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user