Merge branch 'master' into feature/more-getters

This commit is contained in:
Steve Myers 2022-04-04 19:32:55 -07:00
commit 8cd055090d
No known key found for this signature in database
GPG Key ID: 8105A46B22C2D051
6 changed files with 290 additions and 27 deletions

View File

@ -28,6 +28,7 @@ jobs:
- async-interface - async-interface
- use-esplora-reqwest - use-esplora-reqwest
- sqlite - sqlite
- sqlite-bundled
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -114,7 +115,7 @@ jobs:
override: true override: true
- name: Test - name: Test
run: cargo test --no-default-features --features ${{ matrix.blockchain.features }} ${{ matrix.blockchain.name }}::bdk_blockchain_tests run: cargo test --no-default-features --features ${{ matrix.blockchain.features }} ${{ matrix.blockchain.name }}::bdk_blockchain_tests
check-wasm: check-wasm:
name: Check WASM name: Check WASM
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04

View File

@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
- Add `sqlite-bundled` feature for deployments that need a bundled version of sqlite, ie. for mobile platforms.
- Added `Wallet::get_signers()`, `Wallet::descriptor_checksum()` and `Wallet::get_address_validators()`, exposed the `AsDerived` trait. - Added `Wallet::get_signers()`, `Wallet::descriptor_checksum()` and `Wallet::get_address_validators()`, exposed the `AsDerived` trait.
## [v0.17.0] - [v0.16.1] ## [v0.17.0] - [v0.16.1]
@ -440,4 +440,4 @@ final transaction is created by calling `finish` on the builder.
[v0.16.0]: https://github.com/bitcoindevkit/bdk/compare/v0.15.0...v0.16.0 [v0.16.0]: https://github.com/bitcoindevkit/bdk/compare/v0.15.0...v0.16.0
[v0.16.1]: https://github.com/bitcoindevkit/bdk/compare/v0.16.0...v0.16.1 [v0.16.1]: https://github.com/bitcoindevkit/bdk/compare/v0.16.0...v0.16.1
[v0.17.0]: https://github.com/bitcoindevkit/bdk/compare/v0.16.1...v0.17.0 [v0.17.0]: https://github.com/bitcoindevkit/bdk/compare/v0.16.1...v0.17.0
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.17.0...HEAD [unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.17.0...HEAD

View File

@ -55,6 +55,7 @@ compiler = ["miniscript/compiler"]
verify = ["bitcoinconsensus"] verify = ["bitcoinconsensus"]
default = ["key-value-db", "electrum"] default = ["key-value-db", "electrum"]
sqlite = ["rusqlite", "ahash"] sqlite = ["rusqlite", "ahash"]
sqlite-bundled = ["sqlite", "rusqlite/bundled"]
compact_filters = ["rocksdb", "socks", "lazy_static", "cc"] compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
key-value-db = ["sled"] key-value-db = ["sled"]
all-keys = ["keys-bip39"] all-keys = ["keys-bip39"]
@ -109,6 +110,11 @@ name = "miniscriptc"
path = "examples/compiler.rs" path = "examples/compiler.rs"
required-features = ["compiler"] required-features = ["compiler"]
[[example]]
name = "rpcwallet"
path = "examples/rpcwallet.rs"
required-features = ["keys-bip39", "key-value-db", "rpc"]
[workspace] [workspace]
members = ["macros"] members = ["macros"]
[package.metadata.docs.rs] [package.metadata.docs.rs]

233
examples/rpcwallet.rs Normal file
View File

@ -0,0 +1,233 @@
// 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::ExtendedPrivKey;
use bdk::bitcoin::Amount;
use bdk::bitcoin::Network;
use bdk::bitcoincore_rpc::RpcApi;
use bdk::blockchain::rpc::{Auth, RpcBlockchain, RpcConfig};
use bdk::blockchain::ConfigurableBlockchain;
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey};
use bdk::miniscript::miniscript::Segwitv0;
use bdk::sled;
use bdk::template::Bip84;
use bdk::wallet::{signer::SignOptions, wallet_name_from_descriptor, AddressIndex, SyncOptions};
use bdk::KeychainKind;
use bdk::Wallet;
use bdk::blockchain::Blockchain;
use electrsd;
use std::error::Error;
use std::path::PathBuf;
use std::str::FromStr;
/// This example demonstrates a typical way to create a wallet and work with bdk.
///
/// This example bdk wallet is connected to a bitcoin core rpc regtest node,
/// and will attempt to receive, create and broadcast transactions.
///
/// To start a bitcoind regtest node programmatically, this example uses
/// `electrsd` library, which is also a bdk dev-dependency.
///
/// But you can start your own bitcoind backend, and the rest of the example should work fine.
fn main() -> Result<(), Box<dyn Error>> {
// -- Setting up background bitcoind process
println!(">> Setting up bitcoind");
// Start the bitcoind process
let bitcoind_conf = electrsd::bitcoind::Conf::default();
// electrsd will automatically download the bitcoin core binaries
let bitcoind_exe =
electrsd::bitcoind::downloaded_exe_path().expect("We should always have downloaded path");
// Launch bitcoind and gather authentication access
let bitcoind = electrsd::bitcoind::BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap();
let bitcoind_auth = Auth::Cookie {
file: bitcoind.params.cookie_file.clone(),
};
// Get a new core address
let core_address = bitcoind.client.get_new_address(None, None)?;
// Generate 101 blocks and use the above address as coinbase
bitcoind.client.generate_to_address(101, &core_address)?;
println!(">> bitcoind setup complete");
println!(
"Available coins in Core wallet : {}",
bitcoind.client.get_balance(None, None)?
);
// -- Setting up the Wallet
println!("\n>> Setting up BDK wallet");
// Get a random private key
let xprv = generate_random_ext_privkey()?;
// Use the derived descriptors from the privatekey to
// create unique wallet name.
// This is a special utility function exposed via `bdk::wallet_name_from_descriptor()`
let wallet_name = wallet_name_from_descriptor(
Bip84(xprv, KeychainKind::External),
Some(Bip84(xprv, KeychainKind::Internal)),
Network::Regtest,
&Secp256k1::new(),
)?;
// Create a database (using default sled type) to store wallet data
let mut datadir = PathBuf::from_str("/tmp/")?;
datadir.push(".bdk-example");
let database = sled::open(datadir)?;
let database = database.open_tree(wallet_name.clone())?;
// Create a RPC configuration of the running bitcoind backend we created in last step
// Note: If you are using custom regtest node, use the appropriate url and auth
let rpc_config = RpcConfig {
url: bitcoind.params.rpc_socket.to_string(),
auth: bitcoind_auth,
network: Network::Regtest,
wallet_name,
skip_blocks: None,
};
// Use the above configuration to create a RPC blockchain backend
let blockchain = RpcBlockchain::from_config(&rpc_config)?;
// Combine Database + Descriptor to create the final wallet
let wallet = Wallet::new(
Bip84(xprv, KeychainKind::External),
Some(Bip84(xprv, KeychainKind::Internal)),
Network::Regtest,
database,
)?;
// The `wallet` and the `blockchain` are independent structs.
// The wallet will be used to do all wallet level actions
// The blockchain can be used to do all blockchain level actions.
// For certain actions (like sync) the wallet will ask for a blockchain.
// Sync the wallet
// The first sync is important as this will instantiate the
// wallet files.
wallet.sync(&blockchain, SyncOptions::default())?;
println!(">> BDK wallet setup complete.");
println!(
"Available initial coins in BDK wallet : {} sats",
wallet.get_balance()?
);
// -- Wallet transaction demonstration
println!("\n>> Sending coins: Core --> BDK, 10 BTC");
// Get a new address to receive coins
let bdk_new_addr = wallet.get_address(AddressIndex::New)?.address;
// Send 10 BTC from core wallet to bdk wallet
bitcoind.client.send_to_address(
&bdk_new_addr,
Amount::from_btc(10.0)?,
None,
None,
None,
None,
None,
None,
)?;
// Confirm transaction by generating 1 block
bitcoind.client.generate_to_address(1, &core_address)?;
// Sync the BDK wallet
// This time the sync will fetch the new transaction and update it in
// wallet database
wallet.sync(&blockchain, SyncOptions::default())?;
println!(">> Received coins in BDK wallet");
println!(
"Available balance in BDK wallet: {} sats",
wallet.get_balance()?
);
println!("\n>> Sending coins: BDK --> Core, 5 BTC");
// Attempt to send back 5.0 BTC to core address by creating a transaction
//
// Transactions are created using a `TxBuilder`.
// This helps us to systematically build a transaction with all
// required customization.
// A full list of APIs offered by `TxBuilder` can be found at
// https://docs.rs/bdk/latest/bdk/wallet/tx_builder/struct.TxBuilder.html
let mut tx_builder = wallet.build_tx();
// For a regular transaction, just set the recipient and amount
tx_builder.set_recipients(vec![(core_address.script_pubkey(), 500000000)]);
// Finalize the transaction and extract the PSBT
let (mut psbt, _) = tx_builder.finish()?;
// Set signing option
let signopt = SignOptions {
assume_height: None,
..Default::default()
};
// Sign the psbt
wallet.sign(&mut psbt, signopt)?;
// Extract the signed transaction
let tx = psbt.extract_tx();
// Broadcast the transaction
blockchain.broadcast(&tx)?;
// Confirm transaction by generating some blocks
bitcoind.client.generate_to_address(1, &core_address)?;
// Sync the BDK wallet
wallet.sync(&blockchain, SyncOptions::default())?;
println!(">> Coins sent to Core wallet");
println!(
"Remaining BDK wallet balance: {} sats",
wallet.get_balance()?
);
println!("\nCongrats!! you made your first test transaction with bdk and bitcoin core.");
Ok(())
}
// Helper function demonstrating privatekey extraction using bip39 mnemonic
// The mnemonic can be shown to user to safekeeping and the same wallet
// private descriptors can be recreated from it.
fn generate_random_ext_privkey() -> Result<ExtendedPrivKey, Box<dyn Error>> {
// a Bip39 passphrase can be set optionally
let password = Some("random password".to_string());
// Generate a random mnemonic, and use that to create an Extended PrivateKey
let mnemonic: GeneratedKey<_, Segwitv0> =
Mnemonic::generate((WordCount::Words12, Language::English))
.map_err(|e| e.expect("Unknown Error"))?;
let mnemonic = mnemonic.into_key();
let xkey: ExtendedKey = (mnemonic, password).into_extended_key()?;
let xprv = xkey
.into_xprv(Network::Regtest)
.expect("Expected Private Key");
Ok(xprv)
}

View File

@ -46,12 +46,13 @@
//! ```toml //! ```toml
//! bdk = "0.17.0" //! bdk = "0.17.0"
//! ``` //! ```
//!
//! # Examples
#![cfg_attr( #![cfg_attr(
feature = "electrum", feature = "electrum",
doc = r##" doc = r##"
## Sync the balance of a descriptor ## Sync the balance of a descriptor
### Example
```no_run ```no_run
use bdk::{Wallet, SyncOptions}; use bdk::{Wallet, SyncOptions};
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
@ -80,7 +81,6 @@ fn main() -> Result<(), bdk::Error> {
//! //!
//! ## Generate a few addresses //! ## Generate a few addresses
//! //!
//! ### Example
//! ``` //! ```
//! use bdk::{Wallet}; //! use bdk::{Wallet};
//! use bdk::database::MemoryDatabase; //! use bdk::database::MemoryDatabase;
@ -106,7 +106,6 @@ fn main() -> Result<(), bdk::Error> {
doc = r##" doc = r##"
## Create a transaction ## Create a transaction
### Example
```no_run ```no_run
use bdk::{FeeRate, Wallet, SyncOptions}; use bdk::{FeeRate, Wallet, SyncOptions};
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
@ -150,7 +149,6 @@ fn main() -> Result<(), bdk::Error> {
//! //!
//! ## Sign a transaction //! ## Sign a transaction
//! //!
//! ### Example
//! ```no_run //! ```no_run
//! use std::str::FromStr; //! use std::str::FromStr;
//! //!
@ -192,7 +190,7 @@ fn main() -> Result<(), bdk::Error> {
//! * `async-interface`: async functions in bdk traits //! * `async-interface`: async functions in bdk traits
//! * `keys-bip39`: [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic codes for generating deterministic keys //! * `keys-bip39`: [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic codes for generating deterministic keys
//! //!
//! ## Internal features //! # Internal features
//! //!
//! These features do not expose any new API, but influence internal implementation aspects of //! These features do not expose any new API, but influence internal implementation aspects of
//! BDK. //! BDK.

View File

@ -140,6 +140,8 @@ pub struct AddressInfo {
pub index: u32, pub index: u32,
/// Address /// Address
pub address: Address, pub address: Address,
/// Type of keychain
pub keychain: KeychainKind,
} }
impl Deref for AddressInfo { impl Deref for AddressInfo {
@ -246,6 +248,7 @@ where
.map(|address| AddressInfo { .map(|address| AddressInfo {
address, address,
index: incremented_index, index: incremented_index,
keychain,
}) })
.map_err(|_| Error::ScriptDoesntHaveAddressForm) .map_err(|_| Error::ScriptDoesntHaveAddressForm)
} }
@ -276,6 +279,7 @@ where
.map(|address| AddressInfo { .map(|address| AddressInfo {
address, address,
index: current_index, index: current_index,
keychain,
}) })
.map_err(|_| Error::ScriptDoesntHaveAddressForm) .map_err(|_| Error::ScriptDoesntHaveAddressForm)
} }
@ -286,7 +290,11 @@ where
self.get_descriptor_for_keychain(keychain) self.get_descriptor_for_keychain(keychain)
.as_derived(index, &self.secp) .as_derived(index, &self.secp)
.address(self.network) .address(self.network)
.map(|address| AddressInfo { index, address }) .map(|address| AddressInfo {
index,
address,
keychain,
})
.map_err(|_| Error::ScriptDoesntHaveAddressForm) .map_err(|_| Error::ScriptDoesntHaveAddressForm)
} }
@ -298,7 +306,11 @@ where
self.get_descriptor_for_keychain(keychain) self.get_descriptor_for_keychain(keychain)
.as_derived(index, &self.secp) .as_derived(index, &self.secp)
.address(self.network) .address(self.network)
.map(|address| AddressInfo { index, address }) .map(|address| AddressInfo {
index,
address,
keychain,
})
.map_err(|_| Error::ScriptDoesntHaveAddressForm) .map_err(|_| Error::ScriptDoesntHaveAddressForm)
} }
@ -3964,6 +3976,7 @@ pub(crate) mod test {
AddressInfo { AddressInfo {
index: 0, index: 0,
address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a").unwrap(), address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a").unwrap(),
keychain: KeychainKind::External,
} }
); );
@ -3972,7 +3985,8 @@ pub(crate) mod test {
wallet.get_address(New).unwrap(), wallet.get_address(New).unwrap(),
AddressInfo { AddressInfo {
index: 1, index: 1,
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap() address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
keychain: KeychainKind::External,
} }
); );
@ -3981,7 +3995,8 @@ pub(crate) mod test {
wallet.get_address(Peek(25)).unwrap(), wallet.get_address(Peek(25)).unwrap(),
AddressInfo { AddressInfo {
index: 25, index: 25,
address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap() address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap(),
keychain: KeychainKind::External,
} }
); );
@ -3990,7 +4005,8 @@ pub(crate) mod test {
wallet.get_address(New).unwrap(), wallet.get_address(New).unwrap(),
AddressInfo { AddressInfo {
index: 2, index: 2,
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap() address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
keychain: KeychainKind::External,
} }
); );
@ -3999,7 +4015,8 @@ pub(crate) mod test {
wallet.get_address(Reset(1)).unwrap(), wallet.get_address(Reset(1)).unwrap(),
AddressInfo { AddressInfo {
index: 1, index: 1,
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap() address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
keychain: KeychainKind::External,
} }
); );
@ -4008,7 +4025,8 @@ pub(crate) mod test {
wallet.get_address(New).unwrap(), wallet.get_address(New).unwrap(),
AddressInfo { AddressInfo {
index: 2, index: 2,
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap() address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
keychain: KeychainKind::External,
} }
); );
} }
@ -4037,15 +4055,21 @@ pub(crate) mod test {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
wallet.get_address(AddressIndex::New).unwrap().address, wallet.get_address(AddressIndex::New).unwrap(),
Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap() AddressInfo {
index: 0,
address: Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(),
keychain: KeychainKind::External,
}
); );
assert_eq!( assert_eq!(
wallet wallet.get_internal_address(AddressIndex::New).unwrap(),
.get_internal_address(AddressIndex::New) AddressInfo {
.unwrap() index: 0,
.address, address: Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap(),
Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap() keychain: KeychainKind::Internal,
}
); );
let wallet = Wallet::new( let wallet = Wallet::new(
@ -4057,11 +4081,12 @@ pub(crate) mod test {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
wallet wallet.get_internal_address(AddressIndex::New).unwrap(),
.get_internal_address(AddressIndex::New) AddressInfo {
.unwrap() index: 0,
.address, address: Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(),
Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(), keychain: KeychainKind::Internal,
},
"when there's no internal descriptor it should just use external" "when there's no internal descriptor it should just use external"
); );
} }