From 364ad95e85bd09a01cb4cbf3ceb9e9fcc074c8b4 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 24 Feb 2022 10:48:47 -0500 Subject: [PATCH 1/4] Fix hierarchy of headers on docs landing page --- src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0423dc4f..436db56f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,12 +46,13 @@ //! ```toml //! bdk = "0.16.1" //! ``` +//! +//! # Examples #![cfg_attr( feature = "electrum", doc = r##" ## Sync the balance of a descriptor -### Example ```no_run use bdk::Wallet; use bdk::database::MemoryDatabase; @@ -80,7 +81,6 @@ fn main() -> Result<(), bdk::Error> { //! //! ## Generate a few addresses //! -//! ### Example //! ``` //! use bdk::{Wallet}; //! use bdk::database::MemoryDatabase; @@ -106,7 +106,6 @@ fn main() -> Result<(), bdk::Error> { doc = r##" ## Create a transaction -### Example ```no_run use bdk::{FeeRate, Wallet}; use bdk::database::MemoryDatabase; @@ -150,7 +149,6 @@ fn main() -> Result<(), bdk::Error> { //! //! ## Sign a transaction //! -//! ### Example //! ```no_run //! use std::str::FromStr; //! @@ -192,7 +190,7 @@ fn main() -> Result<(), bdk::Error> { //! * `async-interface`: async functions in bdk traits //! * `keys-bip39`: [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic codes for generating deterministic keys //! -//! ## Internal features +//! # Internal features //! //! These features do not expose any new API, but influence internal implementation aspects of //! BDK. From c6eeb7b989f149a4632e740302787802cbdf4e6d Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 11 Mar 2022 19:44:31 -0600 Subject: [PATCH 2/4] Add sqlite-bundled feature --- .github/workflows/cont_integration.yml | 3 ++- CHANGELOG.md | 2 ++ Cargo.toml | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 5bf54f07..badcd2e6 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -28,6 +28,7 @@ jobs: - async-interface - use-esplora-reqwest - sqlite + - sqlite-bundled steps: - name: checkout uses: actions/checkout@v2 @@ -114,7 +115,7 @@ jobs: override: true - name: Test run: cargo test --no-default-features --features ${{ matrix.blockchain.features }} ${{ matrix.blockchain.name }}::bdk_blockchain_tests - + check-wasm: name: Check WASM runs-on: ubuntu-20.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index 44e6ed4a..1dabc409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Add `sqlite-bundled` feature for deployments that need a bundled version of sqlite, ie. for mobile platforms. + ## [v0.17.0] - [v0.16.1] - Removed default verification from `wallet::sync`. sync-time verification is added in `script_sync` and is activated by `verify` feature flag. diff --git a/Cargo.toml b/Cargo.toml index 49fc8a22..412ac865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ compiler = ["miniscript/compiler"] verify = ["bitcoinconsensus"] default = ["key-value-db", "electrum"] sqlite = ["rusqlite", "ahash"] +sqlite-bundled = ["sqlite", "rusqlite/bundled"] compact_filters = ["rocksdb", "socks", "lazy_static", "cc"] key-value-db = ["sled"] all-keys = ["keys-bip39"] From 8a98e69e788dd79d030798465fbc0dec044d86f1 Mon Sep 17 00:00:00 2001 From: rajarshimaitra Date: Tue, 22 Mar 2022 12:25:39 +0530 Subject: [PATCH 3/4] Add rpc wallet creation example This adds an example wallet creation code with sled DB and RPC Blockchain. The backend RPC is managed using electrsd::bitcoind --- Cargo.toml | 5 + examples/rpcwallet.rs | 233 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 examples/rpcwallet.rs diff --git a/Cargo.toml b/Cargo.toml index 49fc8a22..9763e98a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,11 @@ name = "miniscriptc" path = "examples/compiler.rs" required-features = ["compiler"] +[[example]] +name = "rpcwallet" +path = "examples/rpcwallet.rs" +required-features = ["keys-bip39", "key-value-db", "rpc"] + [workspace] members = ["macros"] [package.metadata.docs.rs] diff --git a/examples/rpcwallet.rs b/examples/rpcwallet.rs new file mode 100644 index 00000000..0263ef78 --- /dev/null +++ b/examples/rpcwallet.rs @@ -0,0 +1,233 @@ +// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , 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> { + // -- 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> { + // 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) +} From 2698fc0219eb360e84a89ceabba4793777bf550b Mon Sep 17 00:00:00 2001 From: eunoia_1729 Date: Thu, 10 Mar 2022 06:22:02 +0530 Subject: [PATCH 4/4] update AddressInfo struct --- src/wallet/mod.rs | 63 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 6986cf01..1eb697dd 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -140,6 +140,8 @@ pub struct AddressInfo { pub index: u32, /// Address pub address: Address, + /// Type of keychain + pub keychain: KeychainKind, } impl Deref for AddressInfo { @@ -246,6 +248,7 @@ where .map(|address| AddressInfo { address, index: incremented_index, + keychain, }) .map_err(|_| Error::ScriptDoesntHaveAddressForm) } @@ -276,6 +279,7 @@ where .map(|address| AddressInfo { address, index: current_index, + keychain, }) .map_err(|_| Error::ScriptDoesntHaveAddressForm) } @@ -286,7 +290,11 @@ where self.get_descriptor_for_keychain(keychain) .as_derived(index, &self.secp) .address(self.network) - .map(|address| AddressInfo { index, address }) + .map(|address| AddressInfo { + index, + address, + keychain, + }) .map_err(|_| Error::ScriptDoesntHaveAddressForm) } @@ -298,7 +306,11 @@ where self.get_descriptor_for_keychain(keychain) .as_derived(index, &self.secp) .address(self.network) - .map(|address| AddressInfo { index, address }) + .map(|address| AddressInfo { + index, + address, + keychain, + }) .map_err(|_| Error::ScriptDoesntHaveAddressForm) } @@ -3924,6 +3936,7 @@ pub(crate) mod test { AddressInfo { index: 0, address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a").unwrap(), + keychain: KeychainKind::External, } ); @@ -3932,7 +3945,8 @@ pub(crate) mod test { wallet.get_address(New).unwrap(), AddressInfo { index: 1, - address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap() + address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(), + keychain: KeychainKind::External, } ); @@ -3941,7 +3955,8 @@ pub(crate) mod test { wallet.get_address(Peek(25)).unwrap(), AddressInfo { index: 25, - address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap() + address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap(), + keychain: KeychainKind::External, } ); @@ -3950,7 +3965,8 @@ pub(crate) mod test { wallet.get_address(New).unwrap(), AddressInfo { index: 2, - address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap() + address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(), + keychain: KeychainKind::External, } ); @@ -3959,7 +3975,8 @@ pub(crate) mod test { wallet.get_address(Reset(1)).unwrap(), AddressInfo { index: 1, - address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap() + address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(), + keychain: KeychainKind::External, } ); @@ -3968,7 +3985,8 @@ pub(crate) mod test { wallet.get_address(New).unwrap(), AddressInfo { index: 2, - address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap() + address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(), + keychain: KeychainKind::External, } ); } @@ -3997,15 +4015,21 @@ pub(crate) mod test { .unwrap(); assert_eq!( - wallet.get_address(AddressIndex::New).unwrap().address, - Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap() + wallet.get_address(AddressIndex::New).unwrap(), + AddressInfo { + index: 0, + address: Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(), + keychain: KeychainKind::External, + } ); + assert_eq!( - wallet - .get_internal_address(AddressIndex::New) - .unwrap() - .address, - Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap() + wallet.get_internal_address(AddressIndex::New).unwrap(), + AddressInfo { + index: 0, + address: Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap(), + keychain: KeychainKind::Internal, + } ); let wallet = Wallet::new( @@ -4017,11 +4041,12 @@ pub(crate) mod test { .unwrap(); assert_eq!( - wallet - .get_internal_address(AddressIndex::New) - .unwrap() - .address, - Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(), + wallet.get_internal_address(AddressIndex::New).unwrap(), + AddressInfo { + index: 0, + address: Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(), + keychain: KeychainKind::Internal, + }, "when there's no internal descriptor it should just use external" ); }