feat!: Rework sqlite, changesets, persistence and wallet-construction
Rework sqlite: Instead of only supported one schema (defined in `bdk_sqlite`), we have a schema per changeset type for more flexiblity. * rm `bdk_sqlite` crate (as we don't need `bdk_sqlite::Store` anymore). * add `sqlite` feature on `bdk_chain` which adds methods on each changeset type for initializing tables, loading the changeset and writing. Rework changesets: Some callers may want to use `KeychainTxOutIndex` where `K` may change per descriptor on every run. So we only want to persist the last revealed indices by `DescriptorId` (which uniquely-ish identifies the descriptor). * rm `keychain_added` field from `keychain_txout`'s changeset. * Add `keychain_added` to `CombinedChangeSet` (which is renamed to `WalletChangeSet`). Rework persistence: add back some safety and convenience when persisting our types. Working with changeset directly (as we were doing before) can be cumbersome. * Intoduce `struct Persisted<T>` which wraps a type `T` which stores staged changes to it. This adds safety when creating and or loading `T` from db. * `struct Persisted<T>` methods, `create`, `load` and `persist`, are avaliable if `trait PersistWith<Db>` is implemented for `T`. `Db` represents the database connection and `PersistWith` should be implemented per database-type. * For async, we have `trait PersistedAsyncWith<Db>`. * `Wallet` has impls of `PersistedWith<rusqlite::Connection>`, `PersistedWith<rusqlite::Transaction>` and `PersistedWith<bdk_file_store::Store>` by default. Rework wallet-construction: Before, we had multiple methods for loading and creating with different input-counts so it would be unwieldly to add more parameters in the future. This also makes it difficult to impl `PersistWith` (which has a single method for `load` that takes in `PersistWith::LoadParams` and a single method for `create` that takes in `PersistWith::CreateParams`). * Introduce a builder pattern when constructing a `Wallet`. For loading from persistence or `ChangeSet`, we have `LoadParams`. For creating a new wallet, we have `CreateParams`.
This commit is contained in:
@@ -19,22 +19,26 @@ bitcoin = { version = "0.32.0", features = ["serde", "base64"], default-features
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
serde_json = { version = "^1.0" }
|
||||
bdk_chain = { path = "../chain", version = "0.16.0", features = ["miniscript", "serde"], default-features = false }
|
||||
bdk_file_store = { path = "../file_store", version = "0.13.0", optional = true }
|
||||
|
||||
# Optional dependencies
|
||||
bip39 = { version = "2.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
default = ["std", "file_store"]
|
||||
std = ["bitcoin/std", "bitcoin/rand-std", "miniscript/std", "bdk_chain/std"]
|
||||
compiler = ["miniscript/compiler"]
|
||||
all-keys = ["keys-bip39"]
|
||||
keys-bip39 = ["bip39"]
|
||||
sqlite = ["bdk_chain/sqlite"]
|
||||
file_store = ["bdk_file_store"]
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.4"
|
||||
assert_matches = "1.5.0"
|
||||
tempfile = "3"
|
||||
bdk_sqlite = { path = "../sqlite" }
|
||||
bdk_chain = { path = "../chain", features = ["sqlite"] }
|
||||
bdk_wallet = { path = ".", features = ["sqlite", "file_store"] }
|
||||
bdk_file_store = { path = "../file_store" }
|
||||
anyhow = "1"
|
||||
rand = "^0.8"
|
||||
|
||||
@@ -57,18 +57,17 @@ that the `Wallet` can use to update its view of the chain.
|
||||
|
||||
## Persistence
|
||||
|
||||
To persist `Wallet` state data use a data store crate that reads and writes [`bdk_chain::CombinedChangeSet`].
|
||||
To persist `Wallet` state data use a data store crate that reads and writes [`bdk_chain::WalletChangeSet`].
|
||||
|
||||
**Implementations**
|
||||
|
||||
* [`bdk_file_store`]: Stores wallet changes in a simple flat file.
|
||||
* [`bdk_sqlite`]: Stores wallet changes in a SQLite relational database file.
|
||||
|
||||
**Example**
|
||||
|
||||
<!-- compile_fail because outpoint and txout are fake variables -->
|
||||
```rust,no_run
|
||||
use bdk_wallet::{bitcoin::Network, KeychainKind, wallet::{ChangeSet, Wallet}};
|
||||
use bdk_wallet::{bitcoin::Network, CreateParams, LoadParams, KeychainKind, ChangeSet};
|
||||
|
||||
// Open or create a new file store for wallet data.
|
||||
let mut db =
|
||||
@@ -76,21 +75,22 @@ let mut db =
|
||||
.expect("create store");
|
||||
|
||||
// Create a wallet with initial wallet data read from the file store.
|
||||
let network = Network::Testnet;
|
||||
let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/1/*)";
|
||||
let changeset = db.aggregate_changesets().expect("changeset loaded");
|
||||
let mut wallet =
|
||||
Wallet::new_or_load(descriptor, change_descriptor, changeset, Network::Testnet)
|
||||
.expect("create or load wallet");
|
||||
let load_params = LoadParams::with_descriptors(descriptor, change_descriptor, network)
|
||||
.expect("must parse descriptors");
|
||||
let create_params = CreateParams::new(descriptor, change_descriptor, network)
|
||||
.expect("must parse descriptors");
|
||||
let mut wallet = match load_params.load_wallet(&mut db).expect("wallet") {
|
||||
Some(wallet) => wallet,
|
||||
None => create_params.create_wallet(&mut db).expect("wallet"),
|
||||
};
|
||||
|
||||
// Get a new address to receive bitcoin.
|
||||
let receive_address = wallet.reveal_next_address(KeychainKind::External);
|
||||
// Persist staged wallet data changes to the file store.
|
||||
let staged_changeset = wallet.take_staged();
|
||||
if let Some(changeset) = staged_changeset {
|
||||
db.append_changeset(&changeset)
|
||||
.expect("must commit changes to database");
|
||||
}
|
||||
wallet.persist(&mut db).expect("persist");
|
||||
println!("Your new receive address is: {}", receive_address.address);
|
||||
```
|
||||
|
||||
@@ -233,7 +233,6 @@ conditions.
|
||||
[`Wallet`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/wallet/struct.Wallet.html
|
||||
[`bdk_chain`]: https://docs.rs/bdk_chain/latest
|
||||
[`bdk_file_store`]: https://docs.rs/bdk_file_store/latest
|
||||
[`bdk_sqlite`]: https://docs.rs/bdk_sqlite/latest
|
||||
[`bdk_electrum`]: https://docs.rs/bdk_electrum/latest
|
||||
[`bdk_esplora`]: https://docs.rs/bdk_esplora/latest
|
||||
[`bdk_bitcoind_rpc`]: https://docs.rs/bdk_bitcoind_rpc/latest
|
||||
|
||||
@@ -21,7 +21,7 @@ use bitcoin::Network;
|
||||
use miniscript::policy::Concrete;
|
||||
use miniscript::Descriptor;
|
||||
|
||||
use bdk_wallet::{KeychainKind, Wallet};
|
||||
use bdk_wallet::{CreateParams, KeychainKind};
|
||||
|
||||
/// Miniscript policy is a high level abstraction of spending conditions. Defined in the
|
||||
/// rust-miniscript library here https://docs.rs/miniscript/7.0.0/miniscript/policy/index.html
|
||||
@@ -77,7 +77,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
);
|
||||
|
||||
// Create a new wallet from descriptors
|
||||
let mut wallet = Wallet::new(&descriptor, &internal_descriptor, Network::Regtest)?;
|
||||
let mut wallet = CreateParams::new(&descriptor, &internal_descriptor, Network::Regtest)?
|
||||
.create_wallet_no_persist()?;
|
||||
|
||||
println!(
|
||||
"First derived address from the descriptor: \n{}",
|
||||
|
||||
@@ -281,15 +281,10 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for `IntoWalletDescriptor` that performs additional checks on the keys contained in the
|
||||
/// descriptor
|
||||
pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
|
||||
inner: T,
|
||||
secp: &SecpCtx,
|
||||
network: Network,
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||
let (descriptor, keymap) = inner.into_wallet_descriptor(secp, network)?;
|
||||
|
||||
/// Extra checks for [`ExtendedDescriptor`].
|
||||
pub(crate) fn check_wallet_descriptor(
|
||||
descriptor: &Descriptor<DescriptorPublicKey>,
|
||||
) -> Result<(), DescriptorError> {
|
||||
// Ensure the keys don't contain any hardened derivation steps or hardened wildcards
|
||||
let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| {
|
||||
if let DescriptorPublicKey::XPub(DescriptorXKey {
|
||||
@@ -316,7 +311,7 @@ pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
|
||||
// issues
|
||||
descriptor.sanity_check()?;
|
||||
|
||||
Ok((descriptor, keymap))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@@ -855,22 +850,31 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_wallet_descriptor_checked() {
|
||||
fn test_check_wallet_descriptor() {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||
let (descriptor, _) = descriptor
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.expect("must parse");
|
||||
let result = check_wallet_descriptor(&descriptor);
|
||||
|
||||
assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
|
||||
|
||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/<0;1>/*)";
|
||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||
let (descriptor, _) = descriptor
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.expect("must parse");
|
||||
let result = check_wallet_descriptor(&descriptor);
|
||||
|
||||
assert_matches!(result, Err(DescriptorError::MultiPath));
|
||||
|
||||
// repeated pubkeys
|
||||
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
|
||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||
let (descriptor, _) = descriptor
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.expect("must parse");
|
||||
let result = check_wallet_descriptor(&descriptor);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -882,8 +886,10 @@ mod test {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let descriptor = "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))";
|
||||
let (descriptor, _) =
|
||||
into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap();
|
||||
let (descriptor, _) = descriptor
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
check_wallet_descriptor(&descriptor).expect("descriptor");
|
||||
|
||||
let descriptor = descriptor.at_derivation_index(0).unwrap();
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bdk_wallet::CreateParams;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2Pkh;
|
||||
///
|
||||
@@ -81,7 +81,8 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet = Wallet::new(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
|
||||
/// let mut wallet = CreateParams::new(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -105,7 +106,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bdk_wallet::CreateParams;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2Wpkh_P2Sh;
|
||||
///
|
||||
@@ -113,11 +114,12 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// P2Wpkh_P2Sh(key_external),
|
||||
/// P2Wpkh_P2Sh(key_internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -142,7 +144,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet};
|
||||
/// # use bdk_wallet::CreateParams;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2Wpkh;
|
||||
///
|
||||
@@ -150,7 +152,9 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet = Wallet::new(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
|
||||
/// let mut wallet =
|
||||
/// CreateParams::new(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -174,7 +178,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bdk_wallet::CreateParams;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2TR;
|
||||
///
|
||||
@@ -182,7 +186,8 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet = Wallet::new(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
|
||||
/// let mut wallet = CreateParams::new(P2TR(key_external), P2TR(key_internal), Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -211,15 +216,16 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip44;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip44(key.clone(), KeychainKind::External),
|
||||
/// Bip44(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt");
|
||||
@@ -247,16 +253,17 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip44Public;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Bip44Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
|
||||
@@ -284,15 +291,16 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip49;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip49(key.clone(), KeychainKind::External),
|
||||
/// Bip49(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e");
|
||||
@@ -320,16 +328,17 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip49Public;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Bip49Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
|
||||
@@ -357,15 +366,16 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip84;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip84(key.clone(), KeychainKind::External),
|
||||
/// Bip84(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr");
|
||||
@@ -393,16 +403,16 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip84Public;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Bip84Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?.create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
|
||||
@@ -430,15 +440,16 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip86;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip86(key.clone(), KeychainKind::External),
|
||||
/// Bip86(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
|
||||
@@ -466,16 +477,17 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip86Public;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip86Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Bip86Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
|
||||
|
||||
@@ -36,12 +36,22 @@ pub use types::*;
|
||||
pub use wallet::signer;
|
||||
pub use wallet::signer::SignOptions;
|
||||
pub use wallet::tx_builder::TxBuilder;
|
||||
pub use wallet::ChangeSet;
|
||||
pub use wallet::CreateParams;
|
||||
pub use wallet::LoadParams;
|
||||
pub use wallet::PersistedWallet;
|
||||
pub use wallet::Wallet;
|
||||
|
||||
/// Get the version of BDK at runtime
|
||||
/// Get the version of [`bdk_wallet`](crate) at runtime.
|
||||
pub fn version() -> &'static str {
|
||||
env!("CARGO_PKG_VERSION", "unknown")
|
||||
}
|
||||
|
||||
pub use bdk_chain as chain;
|
||||
pub(crate) use bdk_chain::collections;
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub use bdk_chain::rusqlite;
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub use bdk_chain::sqlite;
|
||||
|
||||
pub use chain::WalletChangeSet;
|
||||
|
||||
@@ -29,11 +29,12 @@
|
||||
//! }"#;
|
||||
//!
|
||||
//! let import = FullyNodedExport::from_str(import)?;
|
||||
//! let wallet = Wallet::new(
|
||||
//! let wallet = CreateParams::new(
|
||||
//! &import.descriptor(),
|
||||
//! &import.change_descriptor().expect("change descriptor"),
|
||||
//! Network::Testnet,
|
||||
//! )?;
|
||||
//! )?
|
||||
//! .create_wallet_no_persist()?;
|
||||
//! # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
//! ```
|
||||
//!
|
||||
@@ -42,11 +43,12 @@
|
||||
//! # use bitcoin::*;
|
||||
//! # use bdk_wallet::wallet::export::*;
|
||||
//! # use bdk_wallet::*;
|
||||
//! let wallet = Wallet::new(
|
||||
//! let wallet = CreateParams::new(
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)",
|
||||
//! Network::Testnet,
|
||||
//! )?;
|
||||
//! )?
|
||||
//! .create_wallet_no_persist()?;
|
||||
//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap();
|
||||
//!
|
||||
//! println!("Exported: {}", export.to_string());
|
||||
@@ -219,12 +221,15 @@ mod test {
|
||||
use bitcoin::{transaction, BlockHash, Network, Transaction};
|
||||
|
||||
use super::*;
|
||||
use crate::wallet::Wallet;
|
||||
use crate::wallet::{CreateParams, Wallet};
|
||||
|
||||
fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet {
|
||||
use crate::wallet::Update;
|
||||
use bdk_chain::TxGraph;
|
||||
let mut wallet = Wallet::new(descriptor, change_descriptor, network).unwrap();
|
||||
let mut wallet = CreateParams::new(descriptor, change_descriptor, network)
|
||||
.expect("must parse descriptors")
|
||||
.create_wallet_no_persist()
|
||||
.expect("must create wallet");
|
||||
let transaction = Transaction {
|
||||
input: vec![],
|
||||
output: vec![],
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! # use bdk_wallet::signer::SignerOrdering;
|
||||
//! # use bdk_wallet::wallet::hardwaresigner::HWISigner;
|
||||
//! # use bdk_wallet::wallet::AddressIndex::New;
|
||||
//! # use bdk_wallet::{KeychainKind, SignOptions, Wallet};
|
||||
//! # use bdk_wallet::{CreateParams, KeychainKind, SignOptions};
|
||||
//! # use hwi::HWIClient;
|
||||
//! # use std::sync::Arc;
|
||||
//! #
|
||||
@@ -30,11 +30,7 @@
|
||||
//! let first_device = devices.remove(0)?;
|
||||
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
|
||||
//!
|
||||
//! # let mut wallet = Wallet::new(
|
||||
//! # "",
|
||||
//! # None,
|
||||
//! # Network::Testnet,
|
||||
//! # )?;
|
||||
//! # let mut wallet = CreateParams::new("", "", Network::Testnet)?.create_wallet_no_persist()?;
|
||||
//! #
|
||||
//! // Adding the hardware signer to the BDK wallet
|
||||
//! wallet.add_signer(
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
//! Wallet
|
||||
//!
|
||||
//! This module defines the [`Wallet`].
|
||||
use crate::collections::{BTreeMap, HashMap};
|
||||
use crate::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
descriptor::check_wallet_descriptor,
|
||||
};
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
string::{String, ToString},
|
||||
@@ -28,8 +31,8 @@ use bdk_chain::{
|
||||
},
|
||||
spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
|
||||
tx_graph::{CanonicalTx, TxGraph, TxNode},
|
||||
BlockId, ChainPosition, ConfirmationBlockTime, ConfirmationTime, FullTxOut, Indexed,
|
||||
IndexedTxGraph, Merge,
|
||||
BlockId, ChainPosition, ConfirmationBlockTime, ConfirmationTime, DescriptorExt, FullTxOut,
|
||||
Indexed, IndexedTxGraph, Merge,
|
||||
};
|
||||
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
|
||||
use bitcoin::{
|
||||
@@ -38,24 +41,28 @@ use bitcoin::{
|
||||
};
|
||||
use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt};
|
||||
use bitcoin::{constants::genesis_block, Amount};
|
||||
use bitcoin::{
|
||||
secp256k1::{All, Secp256k1},
|
||||
Weight,
|
||||
};
|
||||
use bitcoin::{secp256k1::Secp256k1, Weight};
|
||||
use core::fmt;
|
||||
use core::mem;
|
||||
use core::ops::Deref;
|
||||
use rand_core::RngCore;
|
||||
|
||||
use descriptor::error::Error as DescriptorError;
|
||||
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
|
||||
use miniscript::{
|
||||
descriptor::KeyMap,
|
||||
psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier},
|
||||
};
|
||||
|
||||
use bdk_chain::tx_graph::CalculateFeeError;
|
||||
|
||||
pub mod coin_selection;
|
||||
pub mod export;
|
||||
mod params;
|
||||
pub mod signer;
|
||||
pub mod tx_builder;
|
||||
pub use params::*;
|
||||
mod persisted;
|
||||
pub use persisted::*;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub mod error;
|
||||
@@ -69,8 +76,8 @@ use utils::{check_nsequence_rbf, After, Older, SecpCtx};
|
||||
|
||||
use crate::descriptor::policy::BuildSatisfaction;
|
||||
use crate::descriptor::{
|
||||
self, calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
|
||||
ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
|
||||
self, calc_checksum, DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy,
|
||||
IntoWalletDescriptor, Policy, XKeyUtils,
|
||||
};
|
||||
use crate::psbt::PsbtUtils;
|
||||
use crate::signer::SignerError;
|
||||
@@ -149,7 +156,7 @@ impl From<SyncResult> for Update {
|
||||
}
|
||||
|
||||
/// The changes made to a wallet by applying an [`Update`].
|
||||
pub type ChangeSet = bdk_chain::CombinedChangeSet<KeychainKind, ConfirmationBlockTime>;
|
||||
pub type ChangeSet = bdk_chain::WalletChangeSet;
|
||||
|
||||
/// A derived address and the index it was found at.
|
||||
/// For convenience this automatically derefs to `Address`
|
||||
@@ -177,34 +184,7 @@ impl fmt::Display for AddressInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type when constructing a fresh [`Wallet`].
|
||||
///
|
||||
/// Methods [`new`] and [`new_with_genesis_hash`] may return this error.
|
||||
///
|
||||
/// [`new`]: Wallet::new
|
||||
/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
|
||||
#[derive(Debug)]
|
||||
pub enum NewError {
|
||||
/// There was problem with the passed-in descriptor(s).
|
||||
Descriptor(crate::descriptor::DescriptorError),
|
||||
}
|
||||
|
||||
impl fmt::Display for NewError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
NewError::Descriptor(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for NewError {}
|
||||
|
||||
/// The error type when loading a [`Wallet`] from a [`ChangeSet`].
|
||||
///
|
||||
/// Method [`load_from_changeset`] may return this error.
|
||||
///
|
||||
/// [`load_from_changeset`]: Wallet::load_from_changeset
|
||||
#[derive(Debug)]
|
||||
pub enum LoadError {
|
||||
/// There was a problem with the passed-in descriptor(s).
|
||||
@@ -215,6 +195,8 @@ pub enum LoadError {
|
||||
MissingGenesis,
|
||||
/// Data loaded from persistence is missing descriptor.
|
||||
MissingDescriptor(KeychainKind),
|
||||
/// Data loaded is unexpected.
|
||||
Mismatch(LoadMismatch),
|
||||
}
|
||||
|
||||
impl fmt::Display for LoadError {
|
||||
@@ -226,6 +208,7 @@ impl fmt::Display for LoadError {
|
||||
LoadError::MissingDescriptor(k) => {
|
||||
write!(f, "loaded data is missing descriptor for keychain {k:?}")
|
||||
}
|
||||
LoadError::Mismatch(mismatch) => write!(f, "data mismatch: {mismatch:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,63 +216,34 @@ impl fmt::Display for LoadError {
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for LoadError {}
|
||||
|
||||
/// Error type for when we try load a [`Wallet`] from persistence and creating it if non-existent.
|
||||
///
|
||||
/// Methods [`new_or_load`] and [`new_or_load_with_genesis_hash`] may return this error.
|
||||
///
|
||||
/// [`new_or_load`]: Wallet::new_or_load
|
||||
/// [`new_or_load_with_genesis_hash`]: Wallet::new_or_load_with_genesis_hash
|
||||
/// Represents a mismatch with what is loaded and what is expected from [`LoadParams`].
|
||||
#[derive(Debug)]
|
||||
pub enum NewOrLoadError {
|
||||
/// There is a problem with the passed-in descriptor.
|
||||
Descriptor(crate::descriptor::DescriptorError),
|
||||
/// The loaded genesis hash does not match what was provided.
|
||||
LoadedGenesisDoesNotMatch {
|
||||
/// The expected genesis block hash.
|
||||
expected: BlockHash,
|
||||
/// The block hash loaded from persistence.
|
||||
got: Option<BlockHash>,
|
||||
},
|
||||
/// The loaded network type does not match what was provided.
|
||||
LoadedNetworkDoesNotMatch {
|
||||
/// The expected network type.
|
||||
pub enum LoadMismatch {
|
||||
/// Network does not match.
|
||||
Network {
|
||||
/// The network that is loaded.
|
||||
loaded: Network,
|
||||
/// The expected network.
|
||||
expected: Network,
|
||||
/// The network type loaded from persistence.
|
||||
got: Option<Network>,
|
||||
},
|
||||
/// The loaded desccriptor does not match what was provided.
|
||||
LoadedDescriptorDoesNotMatch {
|
||||
/// The descriptor loaded from persistence.
|
||||
got: Option<ExtendedDescriptor>,
|
||||
/// The keychain of the descriptor not matching
|
||||
/// Genesis hash does not match.
|
||||
Genesis {
|
||||
/// The genesis hash that is loaded.
|
||||
loaded: BlockHash,
|
||||
/// The expected genesis hash.
|
||||
expected: BlockHash,
|
||||
},
|
||||
/// Descriptor's [`DescriptorId`](bdk_chain::DescriptorId) does not match.
|
||||
Descriptor {
|
||||
/// Keychain identifying the descriptor.
|
||||
keychain: KeychainKind,
|
||||
/// The loaded descriptor.
|
||||
loaded: ExtendedDescriptor,
|
||||
/// The expected descriptor.
|
||||
expected: ExtendedDescriptor,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for NewOrLoadError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
NewOrLoadError::Descriptor(e) => e.fmt(f),
|
||||
NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => {
|
||||
write!(f, "loaded genesis hash is not {}, got {:?}", expected, got)
|
||||
}
|
||||
NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => {
|
||||
write!(f, "loaded network type is not {}, got {:?}", expected, got)
|
||||
}
|
||||
NewOrLoadError::LoadedDescriptorDoesNotMatch { got, keychain } => {
|
||||
write!(
|
||||
f,
|
||||
"loaded descriptor is different from what was provided, got {:?} for keychain {:?}",
|
||||
got, keychain
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for NewOrLoadError {}
|
||||
|
||||
/// An error that may occur when applying a block to [`Wallet`].
|
||||
#[derive(Debug)]
|
||||
pub enum ApplyBlockError {
|
||||
@@ -324,39 +278,81 @@ impl fmt::Display for ApplyBlockError {
|
||||
impl std::error::Error for ApplyBlockError {}
|
||||
|
||||
impl Wallet {
|
||||
/// Initialize an empty [`Wallet`].
|
||||
pub fn new<E: IntoWalletDescriptor>(
|
||||
/// Build a new [`Wallet`].
|
||||
///
|
||||
/// If you have previously created a wallet, use [`load`](Self::load) instead.
|
||||
///
|
||||
/// # Synopsis
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bitcoin::Network;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||
/// # const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||
/// // Create a non-persisted wallet.
|
||||
/// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC, Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// // Create a wallet that is persisted to SQLite database.
|
||||
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
||||
/// # let file_path = temp_dir.path().join("store.db");
|
||||
/// use bdk_wallet::rusqlite::Connection;
|
||||
/// let mut conn = Connection::open(file_path)?;
|
||||
/// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC, Network::Testnet)?
|
||||
/// .create_wallet(&mut conn)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn create<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<Self, NewError> {
|
||||
let genesis_hash = genesis_block(network).block_hash();
|
||||
Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash)
|
||||
) -> Result<CreateParams, DescriptorError> {
|
||||
CreateParams::new(descriptor, change_descriptor, network)
|
||||
}
|
||||
|
||||
/// Initialize an empty [`Wallet`] with a custom genesis hash.
|
||||
/// Create a new [`Wallet`] with given `params`.
|
||||
///
|
||||
/// This is like [`Wallet::new`] with an additional `genesis_hash` parameter. This is useful
|
||||
/// for syncing from alternative networks.
|
||||
pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
genesis_hash: BlockHash,
|
||||
) -> Result<Self, NewError> {
|
||||
let secp = Secp256k1::new();
|
||||
/// If you have previously created a wallet, use [`load`](Self::load) instead.
|
||||
pub fn create_with_params(params: CreateParams) -> Result<Self, DescriptorError> {
|
||||
let secp = params.secp;
|
||||
let network = params.network;
|
||||
let genesis_hash = params
|
||||
.genesis_hash
|
||||
.unwrap_or(genesis_block(network).block_hash());
|
||||
|
||||
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
|
||||
let mut index = KeychainTxOutIndex::<KeychainKind>::default();
|
||||
|
||||
let (signers, change_signers) =
|
||||
create_signers(&mut index, &secp, descriptor, change_descriptor, network)
|
||||
.map_err(NewError::Descriptor)?;
|
||||
check_wallet_descriptor(¶ms.descriptor)?;
|
||||
check_wallet_descriptor(¶ms.change_descriptor)?;
|
||||
let signers = Arc::new(SignersContainer::build(
|
||||
params.descriptor_keymap,
|
||||
¶ms.descriptor,
|
||||
&secp,
|
||||
));
|
||||
let change_signers = Arc::new(SignersContainer::build(
|
||||
params.change_descriptor_keymap,
|
||||
¶ms.change_descriptor,
|
||||
&secp,
|
||||
));
|
||||
let index = create_indexer(
|
||||
params.descriptor,
|
||||
params.change_descriptor,
|
||||
params.lookahead,
|
||||
)?;
|
||||
|
||||
let descriptor = index.get_descriptor(&KeychainKind::External).cloned();
|
||||
let change_descriptor = index.get_descriptor(&KeychainKind::Internal).cloned();
|
||||
let indexed_graph = IndexedTxGraph::new(index);
|
||||
let indexed_graph_changeset = indexed_graph.initial_changeset();
|
||||
|
||||
let staged = ChangeSet {
|
||||
chain: chain_changeset,
|
||||
indexed_tx_graph: indexed_graph.initial_changeset(),
|
||||
let stage = ChangeSet {
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
local_chain: chain_changeset,
|
||||
tx_graph: indexed_graph_changeset.tx_graph,
|
||||
indexer: indexed_graph_changeset.indexer,
|
||||
network: Some(network),
|
||||
};
|
||||
|
||||
@@ -366,11 +362,79 @@ impl Wallet {
|
||||
network,
|
||||
chain,
|
||||
indexed_graph,
|
||||
stage: staged,
|
||||
stage,
|
||||
secp,
|
||||
})
|
||||
}
|
||||
|
||||
/// Build [`Wallet`] by loading from persistence or [`ChangeSet`].
|
||||
///
|
||||
/// Note that the descriptor secret keys are not persisted to the db. You can either add
|
||||
/// signers after-the-fact with [`Wallet::add_signer`] or [`Wallet::set_keymap`]. Or you can
|
||||
/// construct wallet using [`Wallet::load_with_descriptors`].
|
||||
///
|
||||
/// # Synopsis
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use bdk_wallet::{Wallet, ChangeSet, KeychainKind};
|
||||
/// # use bitcoin::{BlockHash, Network, hashes::Hash};
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||
/// # const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||
/// # let changeset = ChangeSet::default();
|
||||
/// // Load a wallet from changeset (no persistence).
|
||||
/// let wallet = Wallet::load()
|
||||
/// .load_wallet_no_persist(changeset)?
|
||||
/// .expect("must have data to load wallet");
|
||||
///
|
||||
/// // Load a wallet that is persisted to SQLite database.
|
||||
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
||||
/// # let file_path = temp_dir.path().join("store.db");
|
||||
/// # let external_keymap = Default::default();
|
||||
/// # let internal_keymap = Default::default();
|
||||
/// # let genesis_hash = BlockHash::all_zeros();
|
||||
/// let mut conn = bdk_wallet::rusqlite::Connection::open(file_path)?;
|
||||
/// let mut wallet = Wallet::load()
|
||||
/// // manually include private keys
|
||||
/// // the alternative is to use `Wallet::load_with_descriptors`
|
||||
/// .keymap(KeychainKind::External, external_keymap)
|
||||
/// .keymap(KeychainKind::Internal, internal_keymap)
|
||||
/// // set a lookahead for our indexer
|
||||
/// .lookahead(101)
|
||||
/// // ensure loaded wallet's genesis hash matches this value
|
||||
/// .genesis_hash(genesis_hash)
|
||||
/// .load_wallet(&mut conn)?
|
||||
/// .expect("must have data to load wallet");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn load() -> LoadParams {
|
||||
LoadParams::new()
|
||||
}
|
||||
|
||||
/// Build [`Wallet`] by loading from persistence or [`ChangeSet`]. This fails if the loaded
|
||||
/// wallet has a different `network`.
|
||||
///
|
||||
/// Note that the descriptor secret keys are not persisted to the db. You can either add
|
||||
/// signers after-the-fact with [`Wallet::add_signer`] or [`Wallet::set_keymap`]. Or you can
|
||||
/// construct wallet using [`Wallet::load_with_descriptors`].
|
||||
pub fn load_with_network(network: Network) -> LoadParams {
|
||||
LoadParams::with_network(network)
|
||||
}
|
||||
|
||||
/// Build [`Wallet`] by loading from persistence or [`ChangeSet`]. This fails if the loaded
|
||||
/// wallet has a different `network`, `descriptor` or `change_descriptor`.
|
||||
///
|
||||
/// If the passed-in descriptors contains secret keys, the keys will be included in the
|
||||
/// constructed wallet (which means you can sign transactions).
|
||||
pub fn load_with_descriptors<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<LoadParams, DescriptorError> {
|
||||
LoadParams::with_descriptors(descriptor, change_descriptor, network)
|
||||
}
|
||||
|
||||
/// Load [`Wallet`] from the given previously persisted [`ChangeSet`].
|
||||
///
|
||||
/// Note that the descriptor secret keys are not persisted to the db; this means that after
|
||||
@@ -382,68 +446,102 @@ impl Wallet {
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bdk_wallet::signer::{SignersContainer, SignerOrdering};
|
||||
/// # use bdk_wallet::descriptor::Descriptor;
|
||||
/// # use bitcoin::key::Secp256k1;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_sqlite::{Store, rusqlite::Connection};
|
||||
/// # use bitcoin::Network;
|
||||
/// # use bdk_wallet::{LoadParams, KeychainKind, PersistedWallet};
|
||||
/// use bdk_chain::sqlite::Connection;
|
||||
/// #
|
||||
/// # fn main() -> Result<(), anyhow::Error> {
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
||||
/// # let file_path = temp_dir.path().join("store.db");
|
||||
/// let conn = Connection::open(file_path).expect("must open connection");
|
||||
/// let mut db = Store::new(conn).expect("must create db");
|
||||
/// let secp = Secp256k1::new();
|
||||
/// const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||
/// const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||
///
|
||||
/// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap();
|
||||
/// let (internal_descriptor, internal_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)").unwrap();
|
||||
/// let mut conn = Connection::open(file_path)?;
|
||||
/// let mut wallet: PersistedWallet =
|
||||
/// LoadParams::with_descriptors(EXTERNAL_DESC, INTERNAL_DESC, Network::Testnet)?
|
||||
/// .load_wallet(&mut conn)?
|
||||
/// .expect("db should have data to load wallet");
|
||||
///
|
||||
/// let external_signer_container = SignersContainer::build(external_keymap, &external_descriptor, &secp);
|
||||
/// let internal_signer_container = SignersContainer::build(internal_keymap, &internal_descriptor, &secp);
|
||||
/// let changeset = db.read()?.expect("there must be an existing changeset");
|
||||
/// let mut wallet = Wallet::load_from_changeset(changeset)?;
|
||||
///
|
||||
/// external_signer_container.signers().into_iter()
|
||||
/// .for_each(|s| wallet.add_signer(KeychainKind::External, SignerOrdering::default(), s.clone()));
|
||||
/// internal_signer_container.signers().into_iter()
|
||||
/// .for_each(|s| wallet.add_signer(KeychainKind::Internal, SignerOrdering::default(), s.clone()));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Alternatively, you can call [`Wallet::new_or_load`], which will add the private keys of the
|
||||
/// passed-in descriptors to the [`Wallet`].
|
||||
pub fn load_from_changeset(changeset: ChangeSet) -> Result<Self, LoadError> {
|
||||
pub fn load_with_params(
|
||||
changeset: ChangeSet,
|
||||
params: LoadParams,
|
||||
) -> Result<Option<Self>, LoadError> {
|
||||
if changeset.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let secp = Secp256k1::new();
|
||||
let network = changeset.network.ok_or(LoadError::MissingNetwork)?;
|
||||
let chain =
|
||||
LocalChain::from_changeset(changeset.chain).map_err(|_| LoadError::MissingGenesis)?;
|
||||
let mut index = KeychainTxOutIndex::<KeychainKind>::default();
|
||||
let descriptor = changeset
|
||||
.indexed_tx_graph
|
||||
.indexer
|
||||
.keychains_added
|
||||
.get(&KeychainKind::External)
|
||||
.ok_or(LoadError::MissingDescriptor(KeychainKind::External))?
|
||||
.clone();
|
||||
let change_descriptor = changeset
|
||||
.indexed_tx_graph
|
||||
.indexer
|
||||
.keychains_added
|
||||
.get(&KeychainKind::Internal)
|
||||
.ok_or(LoadError::MissingDescriptor(KeychainKind::Internal))?
|
||||
.clone();
|
||||
let chain = LocalChain::from_changeset(changeset.local_chain)
|
||||
.map_err(|_| LoadError::MissingGenesis)?;
|
||||
|
||||
let (signers, change_signers) =
|
||||
create_signers(&mut index, &secp, descriptor, change_descriptor, network)
|
||||
.expect("Can't fail: we passed in valid descriptors, recovered from the changeset");
|
||||
let descriptor = changeset
|
||||
.descriptor
|
||||
.ok_or(LoadError::MissingDescriptor(KeychainKind::External))?;
|
||||
let change_descriptor = changeset
|
||||
.change_descriptor
|
||||
.ok_or(LoadError::MissingDescriptor(KeychainKind::Internal))?;
|
||||
check_wallet_descriptor(&descriptor).map_err(LoadError::Descriptor)?;
|
||||
check_wallet_descriptor(&change_descriptor).map_err(LoadError::Descriptor)?;
|
||||
|
||||
// checks
|
||||
if let Some(exp_network) = params.check_network {
|
||||
if network != exp_network {
|
||||
return Err(LoadError::Mismatch(LoadMismatch::Network {
|
||||
loaded: network,
|
||||
expected: exp_network,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if let Some(exp_genesis_hash) = params.check_genesis_hash {
|
||||
if chain.genesis_hash() != exp_genesis_hash {
|
||||
return Err(LoadError::Mismatch(LoadMismatch::Genesis {
|
||||
loaded: chain.genesis_hash(),
|
||||
expected: exp_genesis_hash,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if let Some(exp_descriptor) = params.check_descriptor {
|
||||
if descriptor.descriptor_id() != exp_descriptor.descriptor_id() {
|
||||
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
|
||||
keychain: KeychainKind::External,
|
||||
loaded: descriptor,
|
||||
expected: exp_descriptor,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if let Some(exp_change_descriptor) = params.check_change_descriptor {
|
||||
if change_descriptor.descriptor_id() != exp_change_descriptor.descriptor_id() {
|
||||
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
|
||||
keychain: KeychainKind::External,
|
||||
loaded: change_descriptor,
|
||||
expected: exp_change_descriptor,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let signers = Arc::new(SignersContainer::build(
|
||||
params.descriptor_keymap,
|
||||
&descriptor,
|
||||
&secp,
|
||||
));
|
||||
let change_signers = Arc::new(SignersContainer::build(
|
||||
params.change_descriptor_keymap,
|
||||
&change_descriptor,
|
||||
&secp,
|
||||
));
|
||||
let index = create_indexer(descriptor, change_descriptor, params.lookahead)
|
||||
.map_err(LoadError::Descriptor)?;
|
||||
|
||||
let mut indexed_graph = IndexedTxGraph::new(index);
|
||||
indexed_graph.apply_changeset(changeset.indexed_tx_graph);
|
||||
indexed_graph.apply_changeset(changeset.indexer.into());
|
||||
indexed_graph.apply_changeset(changeset.tx_graph.into());
|
||||
|
||||
let stage = ChangeSet::default();
|
||||
|
||||
Ok(Wallet {
|
||||
Ok(Some(Wallet {
|
||||
signers,
|
||||
change_signers,
|
||||
chain,
|
||||
@@ -451,146 +549,7 @@ impl Wallet {
|
||||
stage,
|
||||
network,
|
||||
secp,
|
||||
})
|
||||
}
|
||||
|
||||
/// Either loads [`Wallet`] from the given [`ChangeSet`] or initializes it if one does not exist.
|
||||
///
|
||||
/// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// use bdk_sqlite::{Store, rusqlite::Connection};
|
||||
/// # use bitcoin::Network::Testnet;
|
||||
/// let conn = Connection::open_in_memory().expect("must open connection");
|
||||
/// let mut db = Store::new(conn).expect("must create db");
|
||||
/// let changeset = db.read()?;
|
||||
///
|
||||
/// let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||
/// let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||
///
|
||||
/// let mut wallet = Wallet::new_or_load(external_descriptor, internal_descriptor, changeset, Testnet)?;
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn new_or_load<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
changeset: Option<ChangeSet>,
|
||||
network: Network,
|
||||
) -> Result<Self, NewOrLoadError> {
|
||||
let genesis_hash = genesis_block(network).block_hash();
|
||||
Self::new_or_load_with_genesis_hash(
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
changeset,
|
||||
network,
|
||||
genesis_hash,
|
||||
)
|
||||
}
|
||||
|
||||
/// Either loads [`Wallet`] from a [`ChangeSet`] or initializes it if one does not exist, using the
|
||||
/// provided descriptor, change descriptor, network, and custom genesis hash.
|
||||
///
|
||||
/// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided.
|
||||
/// This is like [`Wallet::new_or_load`] with an additional `genesis_hash` parameter. This is
|
||||
/// useful for syncing from alternative networks.
|
||||
pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
changeset: Option<ChangeSet>,
|
||||
network: Network,
|
||||
genesis_hash: BlockHash,
|
||||
) -> Result<Self, NewOrLoadError> {
|
||||
if let Some(changeset) = changeset {
|
||||
let mut wallet = Self::load_from_changeset(changeset).map_err(|e| match e {
|
||||
LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
|
||||
LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch {
|
||||
expected: network,
|
||||
got: None,
|
||||
},
|
||||
LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch {
|
||||
expected: genesis_hash,
|
||||
got: None,
|
||||
},
|
||||
LoadError::MissingDescriptor(keychain) => {
|
||||
NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
||||
got: None,
|
||||
keychain,
|
||||
}
|
||||
}
|
||||
})?;
|
||||
if wallet.network != network {
|
||||
return Err(NewOrLoadError::LoadedNetworkDoesNotMatch {
|
||||
expected: network,
|
||||
got: Some(wallet.network),
|
||||
});
|
||||
}
|
||||
if wallet.chain.genesis_hash() != genesis_hash {
|
||||
return Err(NewOrLoadError::LoadedGenesisDoesNotMatch {
|
||||
expected: genesis_hash,
|
||||
got: Some(wallet.chain.genesis_hash()),
|
||||
});
|
||||
}
|
||||
|
||||
let (expected_descriptor, expected_descriptor_keymap) = descriptor
|
||||
.into_wallet_descriptor(&wallet.secp, network)
|
||||
.map_err(NewOrLoadError::Descriptor)?;
|
||||
let wallet_descriptor = wallet.public_descriptor(KeychainKind::External);
|
||||
if wallet_descriptor != &expected_descriptor {
|
||||
return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
||||
got: Some(wallet_descriptor.clone()),
|
||||
keychain: KeychainKind::External,
|
||||
});
|
||||
}
|
||||
// if expected descriptor has private keys add them as new signers
|
||||
if !expected_descriptor_keymap.is_empty() {
|
||||
let signer_container = SignersContainer::build(
|
||||
expected_descriptor_keymap,
|
||||
&expected_descriptor,
|
||||
&wallet.secp,
|
||||
);
|
||||
signer_container.signers().into_iter().for_each(|signer| {
|
||||
wallet.add_signer(
|
||||
KeychainKind::External,
|
||||
SignerOrdering::default(),
|
||||
signer.clone(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let (expected_change_descriptor, expected_change_descriptor_keymap) = change_descriptor
|
||||
.into_wallet_descriptor(&wallet.secp, network)
|
||||
.map_err(NewOrLoadError::Descriptor)?;
|
||||
let wallet_change_descriptor = wallet.public_descriptor(KeychainKind::Internal);
|
||||
if wallet_change_descriptor != &expected_change_descriptor {
|
||||
return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
||||
got: Some(wallet_change_descriptor.clone()),
|
||||
keychain: KeychainKind::Internal,
|
||||
});
|
||||
}
|
||||
// if expected change descriptor has private keys add them as new signers
|
||||
if !expected_change_descriptor_keymap.is_empty() {
|
||||
let signer_container = SignersContainer::build(
|
||||
expected_change_descriptor_keymap,
|
||||
&expected_change_descriptor,
|
||||
&wallet.secp,
|
||||
);
|
||||
signer_container.signers().into_iter().for_each(|signer| {
|
||||
wallet.add_signer(
|
||||
KeychainKind::Internal,
|
||||
SignerOrdering::default(),
|
||||
signer.clone(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(wallet)
|
||||
} else {
|
||||
Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash)
|
||||
.map_err(|e| match e {
|
||||
NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the Bitcoin network the wallet is using.
|
||||
@@ -642,17 +601,15 @@ impl Wallet {
|
||||
/// calls to this method before closing the wallet. For example:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use bdk_wallet::wallet::{Wallet, ChangeSet};
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_sqlite::{rusqlite::Connection, Store};
|
||||
/// let conn = Connection::open_in_memory().expect("must open connection");
|
||||
/// let mut db = Store::new(conn).expect("must create store");
|
||||
/// # let changeset = ChangeSet::default();
|
||||
/// # let mut wallet = Wallet::load_from_changeset(changeset).expect("load wallet");
|
||||
/// # use bdk_wallet::{LoadParams, ChangeSet, KeychainKind};
|
||||
/// use bdk_chain::sqlite::Connection;
|
||||
/// let mut conn = Connection::open_in_memory().expect("must open connection");
|
||||
/// let mut wallet = LoadParams::new()
|
||||
/// .load_wallet(&mut conn)
|
||||
/// .expect("database is okay")
|
||||
/// .expect("database has data");
|
||||
/// let next_address = wallet.reveal_next_address(KeychainKind::External);
|
||||
/// if let Some(changeset) = wallet.take_staged() {
|
||||
/// db.write(&changeset)?;
|
||||
/// }
|
||||
/// wallet.persist(&mut conn).expect("write is okay");
|
||||
///
|
||||
/// // Now it's safe to show the user their next address!
|
||||
/// println!("Next address: {}", next_address.address);
|
||||
@@ -666,7 +623,7 @@ impl Wallet {
|
||||
.reveal_next_spk(&keychain)
|
||||
.expect("keychain must exist");
|
||||
|
||||
stage.merge(indexed_tx_graph::ChangeSet::from(index_changeset).into());
|
||||
stage.merge(index_changeset.into());
|
||||
|
||||
AddressInfo {
|
||||
index,
|
||||
@@ -1110,16 +1067,38 @@ impl Wallet {
|
||||
signers.add_external(signer.id(&self.secp), ordering, signer);
|
||||
}
|
||||
|
||||
/// Set the keymap for a given keychain.
|
||||
pub fn set_keymap(&mut self, keychain: KeychainKind, keymap: KeyMap) {
|
||||
let wallet_signers = match keychain {
|
||||
KeychainKind::External => Arc::make_mut(&mut self.signers),
|
||||
KeychainKind::Internal => Arc::make_mut(&mut self.change_signers),
|
||||
};
|
||||
let descriptor = self
|
||||
.indexed_graph
|
||||
.index
|
||||
.get_descriptor(&keychain)
|
||||
.expect("keychain must exist");
|
||||
*wallet_signers = SignersContainer::build(keymap, descriptor, &self.secp);
|
||||
}
|
||||
|
||||
/// Set the keymap for each keychain.
|
||||
pub fn set_keymaps(&mut self, keymaps: impl IntoIterator<Item = (KeychainKind, KeyMap)>) {
|
||||
for (keychain, keymap) in keymaps {
|
||||
self.set_keymap(keychain, keymap);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the signers
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// # use bdk_wallet::bitcoin::Network;
|
||||
/// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)";
|
||||
/// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)";
|
||||
/// let wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?;
|
||||
/// let wallet = CreateParams::new(descriptor, change_descriptor, Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
/// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
|
||||
/// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
|
||||
/// println!("secret_key: {}", secret_key);
|
||||
@@ -2424,25 +2403,23 @@ fn new_local_utxo(
|
||||
}
|
||||
}
|
||||
|
||||
fn create_signers<E: IntoWalletDescriptor>(
|
||||
index: &mut KeychainTxOutIndex<KeychainKind>,
|
||||
secp: &Secp256k1<All>,
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), DescriptorError> {
|
||||
let descriptor = into_wallet_descriptor_checked(descriptor, secp, network)?;
|
||||
let change_descriptor = into_wallet_descriptor_checked(change_descriptor, secp, network)?;
|
||||
let (descriptor, keymap) = descriptor;
|
||||
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
let _ = index
|
||||
.insert_descriptor(KeychainKind::External, descriptor)
|
||||
.expect("this is the first descriptor we're inserting");
|
||||
fn create_indexer(
|
||||
descriptor: ExtendedDescriptor,
|
||||
change_descriptor: ExtendedDescriptor,
|
||||
lookahead: u32,
|
||||
) -> Result<KeychainTxOutIndex<KeychainKind>, DescriptorError> {
|
||||
let mut indexer = KeychainTxOutIndex::<KeychainKind>::new(lookahead);
|
||||
|
||||
let (descriptor, keymap) = change_descriptor;
|
||||
let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
let _ = index
|
||||
.insert_descriptor(KeychainKind::Internal, descriptor)
|
||||
// let (descriptor, keymap) = descriptor;
|
||||
// let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
assert!(indexer
|
||||
.insert_descriptor(KeychainKind::External, descriptor)
|
||||
.expect("first descriptor introduced must succeed"));
|
||||
|
||||
// let (descriptor, keymap) = change_descriptor;
|
||||
// let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
assert!(indexer
|
||||
.insert_descriptor(KeychainKind::Internal, change_descriptor)
|
||||
.map_err(|e| {
|
||||
use bdk_chain::indexer::keychain_txout::InsertDescriptorError;
|
||||
match e {
|
||||
@@ -2453,9 +2430,9 @@ fn create_signers<E: IntoWalletDescriptor>(
|
||||
unreachable!("this is the first time we're assigning internal")
|
||||
}
|
||||
}
|
||||
})?;
|
||||
})?);
|
||||
|
||||
Ok((signers, change_signers))
|
||||
Ok(indexer)
|
||||
}
|
||||
|
||||
/// Transforms a [`FeeRate`] to `f64` with unit as sat/vb.
|
||||
@@ -2476,16 +2453,18 @@ macro_rules! doctest_wallet {
|
||||
() => {{
|
||||
use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash};
|
||||
use $crate::chain::{ConfirmationBlockTime, BlockId, TxGraph};
|
||||
use $crate::wallet::{Update, Wallet};
|
||||
use $crate::wallet::{Update, CreateParams};
|
||||
use $crate::KeychainKind;
|
||||
let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
|
||||
let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
|
||||
|
||||
let mut wallet = Wallet::new(
|
||||
let mut wallet = CreateParams::new(
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
Network::Regtest,
|
||||
)
|
||||
.unwrap()
|
||||
.create_wallet_no_persist()
|
||||
.unwrap();
|
||||
let address = wallet.peek_address(KeychainKind::External, 0).address;
|
||||
let tx = Transaction {
|
||||
|
||||
217
crates/wallet/src/wallet/params.rs
Normal file
217
crates/wallet/src/wallet/params.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use bdk_chain::{keychain_txout::DEFAULT_LOOKAHEAD, PersistAsyncWith, PersistWith};
|
||||
use bitcoin::{BlockHash, Network};
|
||||
use miniscript::descriptor::KeyMap;
|
||||
|
||||
use crate::{
|
||||
descriptor::{DescriptorError, ExtendedDescriptor, IntoWalletDescriptor},
|
||||
KeychainKind, Wallet,
|
||||
};
|
||||
|
||||
use super::{utils::SecpCtx, ChangeSet, LoadError, PersistedWallet};
|
||||
|
||||
/// Parameters for [`Wallet::create`] or [`PersistedWallet::create`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[must_use]
|
||||
pub struct CreateParams {
|
||||
pub(crate) descriptor: ExtendedDescriptor,
|
||||
pub(crate) descriptor_keymap: KeyMap,
|
||||
pub(crate) change_descriptor: ExtendedDescriptor,
|
||||
pub(crate) change_descriptor_keymap: KeyMap,
|
||||
pub(crate) network: Network,
|
||||
pub(crate) genesis_hash: Option<BlockHash>,
|
||||
pub(crate) lookahead: u32,
|
||||
pub(crate) secp: SecpCtx,
|
||||
}
|
||||
|
||||
impl CreateParams {
|
||||
/// Construct parameters with provided `descriptor`, `change_descriptor` and `network`.
|
||||
///
|
||||
/// Default values: `genesis_hash` = `None`, `lookahead` = [`DEFAULT_LOOKAHEAD`]
|
||||
pub fn new<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<Self, DescriptorError> {
|
||||
let secp = SecpCtx::default();
|
||||
|
||||
let (descriptor, descriptor_keymap) = descriptor.into_wallet_descriptor(&secp, network)?;
|
||||
let (change_descriptor, change_descriptor_keymap) =
|
||||
change_descriptor.into_wallet_descriptor(&secp, network)?;
|
||||
|
||||
Ok(Self {
|
||||
descriptor,
|
||||
descriptor_keymap,
|
||||
change_descriptor,
|
||||
change_descriptor_keymap,
|
||||
network,
|
||||
genesis_hash: None,
|
||||
lookahead: DEFAULT_LOOKAHEAD,
|
||||
secp,
|
||||
})
|
||||
}
|
||||
|
||||
/// Extend the given `keychain`'s `keymap`.
|
||||
pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self {
|
||||
match keychain {
|
||||
KeychainKind::External => &mut self.descriptor_keymap,
|
||||
KeychainKind::Internal => &mut self.change_descriptor_keymap,
|
||||
}
|
||||
.extend(keymap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use a custom `genesis_hash`.
|
||||
pub fn genesis_hash(mut self, genesis_hash: BlockHash) -> Self {
|
||||
self.genesis_hash = Some(genesis_hash);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use custom lookahead value.
|
||||
pub fn lookahead(mut self, lookahead: u32) -> Self {
|
||||
self.lookahead = lookahead;
|
||||
self
|
||||
}
|
||||
|
||||
/// Create [`PersistedWallet`] with the given `Db`.
|
||||
pub fn create_wallet<Db>(
|
||||
self,
|
||||
db: &mut Db,
|
||||
) -> Result<PersistedWallet, <Wallet as PersistWith<Db>>::CreateError>
|
||||
where
|
||||
Wallet: PersistWith<Db, CreateParams = Self>,
|
||||
{
|
||||
PersistedWallet::create(db, self)
|
||||
}
|
||||
|
||||
/// Create [`PersistedWallet`] with the given async `Db`.
|
||||
pub async fn create_wallet_async<Db>(
|
||||
self,
|
||||
db: &mut Db,
|
||||
) -> Result<PersistedWallet, <Wallet as PersistAsyncWith<Db>>::CreateError>
|
||||
where
|
||||
Wallet: PersistAsyncWith<Db, CreateParams = Self>,
|
||||
{
|
||||
PersistedWallet::create_async(db, self).await
|
||||
}
|
||||
|
||||
/// Create [`Wallet`] without persistence.
|
||||
pub fn create_wallet_no_persist(self) -> Result<Wallet, DescriptorError> {
|
||||
Wallet::create_with_params(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for [`Wallet::load`] or [`PersistedWallet::load`].
|
||||
#[must_use]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoadParams {
|
||||
pub(crate) descriptor_keymap: KeyMap,
|
||||
pub(crate) change_descriptor_keymap: KeyMap,
|
||||
pub(crate) lookahead: u32,
|
||||
pub(crate) check_network: Option<Network>,
|
||||
pub(crate) check_genesis_hash: Option<BlockHash>,
|
||||
pub(crate) check_descriptor: Option<ExtendedDescriptor>,
|
||||
pub(crate) check_change_descriptor: Option<ExtendedDescriptor>,
|
||||
pub(crate) secp: SecpCtx,
|
||||
}
|
||||
|
||||
impl LoadParams {
|
||||
/// Construct parameters with default values.
|
||||
///
|
||||
/// Default values: `lookahead` = [`DEFAULT_LOOKAHEAD`]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
descriptor_keymap: KeyMap::default(),
|
||||
change_descriptor_keymap: KeyMap::default(),
|
||||
lookahead: DEFAULT_LOOKAHEAD,
|
||||
check_network: None,
|
||||
check_genesis_hash: None,
|
||||
check_descriptor: None,
|
||||
check_change_descriptor: None,
|
||||
secp: SecpCtx::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct parameters with `network` check.
|
||||
pub fn with_network(network: Network) -> Self {
|
||||
Self {
|
||||
check_network: Some(network),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct parameters with descriptor checks.
|
||||
pub fn with_descriptors<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<Self, DescriptorError> {
|
||||
let mut params = Self::with_network(network);
|
||||
let secp = ¶ms.secp;
|
||||
|
||||
let (descriptor, descriptor_keymap) = descriptor.into_wallet_descriptor(secp, network)?;
|
||||
params.check_descriptor = Some(descriptor);
|
||||
params.descriptor_keymap = descriptor_keymap;
|
||||
|
||||
let (change_descriptor, change_descriptor_keymap) =
|
||||
change_descriptor.into_wallet_descriptor(secp, network)?;
|
||||
params.check_change_descriptor = Some(change_descriptor);
|
||||
params.change_descriptor_keymap = change_descriptor_keymap;
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
/// Extend the given `keychain`'s `keymap`.
|
||||
pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self {
|
||||
match keychain {
|
||||
KeychainKind::External => &mut self.descriptor_keymap,
|
||||
KeychainKind::Internal => &mut self.change_descriptor_keymap,
|
||||
}
|
||||
.extend(keymap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Check for a `genesis_hash`.
|
||||
pub fn genesis_hash(mut self, genesis_hash: BlockHash) -> Self {
|
||||
self.check_genesis_hash = Some(genesis_hash);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use custom lookahead value.
|
||||
pub fn lookahead(mut self, lookahead: u32) -> Self {
|
||||
self.lookahead = lookahead;
|
||||
self
|
||||
}
|
||||
|
||||
/// Load [`PersistedWallet`] with the given `Db`.
|
||||
pub fn load_wallet<Db>(
|
||||
self,
|
||||
db: &mut Db,
|
||||
) -> Result<Option<PersistedWallet>, <Wallet as PersistWith<Db>>::LoadError>
|
||||
where
|
||||
Wallet: PersistWith<Db, LoadParams = Self>,
|
||||
{
|
||||
PersistedWallet::load(db, self)
|
||||
}
|
||||
|
||||
/// Load [`PersistedWallet`] with the given async `Db`.
|
||||
pub async fn load_wallet_async<Db>(
|
||||
self,
|
||||
db: &mut Db,
|
||||
) -> Result<Option<PersistedWallet>, <Wallet as PersistAsyncWith<Db>>::LoadError>
|
||||
where
|
||||
Wallet: PersistAsyncWith<Db, LoadParams = Self>,
|
||||
{
|
||||
PersistedWallet::load_async(db, self).await
|
||||
}
|
||||
|
||||
/// Load [`Wallet`] without persistence.
|
||||
pub fn load_wallet_no_persist(self, changeset: ChangeSet) -> Result<Option<Wallet>, LoadError> {
|
||||
Wallet::load_with_params(changeset, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoadParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
180
crates/wallet/src/wallet/persisted.rs
Normal file
180
crates/wallet/src/wallet/persisted.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use core::fmt;
|
||||
|
||||
use crate::wallet::{ChangeSet, CreateParams, LoadError, LoadParams};
|
||||
use crate::{descriptor::DescriptorError, Wallet};
|
||||
use bdk_chain::{Merge, PersistWith};
|
||||
|
||||
/// Represents a persisted wallet.
|
||||
pub type PersistedWallet = bdk_chain::Persisted<Wallet>;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
impl<'c> PersistWith<bdk_chain::sqlite::Transaction<'c>> for Wallet {
|
||||
type CreateParams = CreateParams;
|
||||
type LoadParams = LoadParams;
|
||||
|
||||
type CreateError = CreateWithPersistError<bdk_chain::rusqlite::Error>;
|
||||
type LoadError = LoadWithPersistError<bdk_chain::rusqlite::Error>;
|
||||
type PersistError = bdk_chain::rusqlite::Error;
|
||||
|
||||
fn create(
|
||||
db: &mut bdk_chain::sqlite::Transaction<'c>,
|
||||
params: Self::CreateParams,
|
||||
) -> Result<Self, Self::CreateError> {
|
||||
let mut wallet =
|
||||
Self::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
|
||||
if let Some(changeset) = wallet.take_staged() {
|
||||
changeset
|
||||
.persist_to_sqlite(db)
|
||||
.map_err(CreateWithPersistError::Persist)?;
|
||||
}
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
fn load(
|
||||
conn: &mut bdk_chain::sqlite::Transaction<'c>,
|
||||
params: Self::LoadParams,
|
||||
) -> Result<Option<Self>, Self::LoadError> {
|
||||
let changeset = ChangeSet::from_sqlite(conn).map_err(LoadWithPersistError::Persist)?;
|
||||
if changeset.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
Self::load_with_params(changeset, params).map_err(LoadWithPersistError::InvalidChangeSet)
|
||||
}
|
||||
|
||||
fn persist(
|
||||
&mut self,
|
||||
conn: &mut bdk_chain::sqlite::Transaction,
|
||||
) -> Result<bool, Self::PersistError> {
|
||||
if let Some(changeset) = self.take_staged() {
|
||||
changeset.persist_to_sqlite(conn)?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
impl PersistWith<bdk_chain::sqlite::Connection> for Wallet {
|
||||
type CreateParams = CreateParams;
|
||||
type LoadParams = LoadParams;
|
||||
|
||||
type CreateError = CreateWithPersistError<bdk_chain::rusqlite::Error>;
|
||||
type LoadError = LoadWithPersistError<bdk_chain::rusqlite::Error>;
|
||||
type PersistError = bdk_chain::rusqlite::Error;
|
||||
|
||||
fn create(
|
||||
db: &mut bdk_chain::sqlite::Connection,
|
||||
params: Self::CreateParams,
|
||||
) -> Result<Self, Self::CreateError> {
|
||||
let mut db_tx = db.transaction().map_err(CreateWithPersistError::Persist)?;
|
||||
let wallet = PersistWith::create(&mut db_tx, params)?;
|
||||
db_tx.commit().map_err(CreateWithPersistError::Persist)?;
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
fn load(
|
||||
db: &mut bdk_chain::sqlite::Connection,
|
||||
params: Self::LoadParams,
|
||||
) -> Result<Option<Self>, Self::LoadError> {
|
||||
let mut db_tx = db.transaction().map_err(LoadWithPersistError::Persist)?;
|
||||
let wallet_opt = PersistWith::load(&mut db_tx, params)?;
|
||||
db_tx.commit().map_err(LoadWithPersistError::Persist)?;
|
||||
Ok(wallet_opt)
|
||||
}
|
||||
|
||||
fn persist(
|
||||
&mut self,
|
||||
db: &mut bdk_chain::sqlite::Connection,
|
||||
) -> Result<bool, Self::PersistError> {
|
||||
let mut db_tx = db.transaction()?;
|
||||
let has_changes = PersistWith::persist(self, &mut db_tx)?;
|
||||
db_tx.commit()?;
|
||||
Ok(has_changes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "file_store")]
|
||||
impl PersistWith<bdk_file_store::Store<ChangeSet>> for Wallet {
|
||||
type CreateParams = CreateParams;
|
||||
type LoadParams = LoadParams;
|
||||
type CreateError = CreateWithPersistError<std::io::Error>;
|
||||
type LoadError = LoadWithPersistError<bdk_file_store::AggregateChangesetsError<ChangeSet>>;
|
||||
type PersistError = std::io::Error;
|
||||
|
||||
fn create(
|
||||
db: &mut bdk_file_store::Store<ChangeSet>,
|
||||
params: Self::CreateParams,
|
||||
) -> Result<Self, Self::CreateError> {
|
||||
let mut wallet =
|
||||
Self::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
|
||||
if let Some(changeset) = wallet.take_staged() {
|
||||
db.append_changeset(&changeset)
|
||||
.map_err(CreateWithPersistError::Persist)?;
|
||||
}
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
fn load(
|
||||
db: &mut bdk_file_store::Store<ChangeSet>,
|
||||
params: Self::LoadParams,
|
||||
) -> Result<Option<Self>, Self::LoadError> {
|
||||
let changeset = db
|
||||
.aggregate_changesets()
|
||||
.map_err(LoadWithPersistError::Persist)?
|
||||
.unwrap_or_default();
|
||||
Self::load_with_params(changeset, params).map_err(LoadWithPersistError::InvalidChangeSet)
|
||||
}
|
||||
|
||||
fn persist(
|
||||
&mut self,
|
||||
db: &mut bdk_file_store::Store<ChangeSet>,
|
||||
) -> Result<bool, Self::PersistError> {
|
||||
if let Some(changeset) = self.take_staged() {
|
||||
db.append_changeset(&changeset)?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for [`PersistedWallet::load`].
|
||||
#[derive(Debug)]
|
||||
pub enum LoadWithPersistError<E> {
|
||||
/// Error from persistence.
|
||||
Persist(E),
|
||||
/// Occurs when the loaded changeset cannot construct [`Wallet`].
|
||||
InvalidChangeSet(LoadError),
|
||||
}
|
||||
|
||||
impl<E: fmt::Display> fmt::Display for LoadWithPersistError<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Persist(err) => fmt::Display::fmt(err, f),
|
||||
Self::InvalidChangeSet(err) => fmt::Display::fmt(&err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<E: fmt::Debug + fmt::Display> std::error::Error for LoadWithPersistError<E> {}
|
||||
|
||||
/// Error type for [`PersistedWallet::create`].
|
||||
#[derive(Debug)]
|
||||
pub enum CreateWithPersistError<E> {
|
||||
/// Error from persistence.
|
||||
Persist(E),
|
||||
/// Occurs when the loaded changeset cannot contruct [`Wallet`].
|
||||
Descriptor(DescriptorError),
|
||||
}
|
||||
|
||||
impl<E: fmt::Display> fmt::Display for CreateWithPersistError<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Persist(err) => fmt::Display::fmt(err, f),
|
||||
Self::Descriptor(err) => fmt::Display::fmt(&err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<E: fmt::Debug + fmt::Display> std::error::Error for CreateWithPersistError<E> {}
|
||||
@@ -69,7 +69,8 @@
|
||||
//!
|
||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)";
|
||||
//! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)";
|
||||
//! let mut wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?;
|
||||
//! let mut wallet = CreateParams::new(descriptor, change_descriptor, Network::Testnet)?
|
||||
//! .create_wallet_no_persist()?;
|
||||
//! wallet.add_signer(
|
||||
//! KeychainKind::External,
|
||||
//! SignerOrdering(200),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(unused)]
|
||||
use bdk_chain::{BlockId, ConfirmationBlockTime, ConfirmationTime, TxGraph};
|
||||
use bdk_wallet::{
|
||||
wallet::{Update, Wallet},
|
||||
wallet::{CreateParams, Update, Wallet},
|
||||
KeychainKind, LocalOutput,
|
||||
};
|
||||
use bitcoin::{
|
||||
@@ -16,7 +16,11 @@ use std::str::FromStr;
|
||||
/// to a foreign address and one returning 50_000 back to the wallet. The remaining 1000
|
||||
/// sats are the transaction fee.
|
||||
pub fn get_funded_wallet_with_change(descriptor: &str, change: &str) -> (Wallet, bitcoin::Txid) {
|
||||
let mut wallet = Wallet::new(descriptor, change, Network::Regtest).unwrap();
|
||||
let mut wallet = CreateParams::new(descriptor, change, Network::Regtest)
|
||||
.expect("must parse descriptors")
|
||||
.create_wallet_no_persist()
|
||||
.expect("descriptors must be valid");
|
||||
|
||||
let receive_address = wallet.peek_address(KeychainKind::External, 0).address;
|
||||
let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
|
||||
.expect("address")
|
||||
|
||||
@@ -3,18 +3,17 @@ extern crate alloc;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Context;
|
||||
use assert_matches::assert_matches;
|
||||
use bdk_chain::collections::BTreeMap;
|
||||
use bdk_chain::COINBASE_MATURITY;
|
||||
use bdk_chain::{BlockId, ConfirmationTime};
|
||||
use bdk_sqlite::rusqlite::Connection;
|
||||
use bdk_chain::{PersistWith, COINBASE_MATURITY};
|
||||
use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
|
||||
use bdk_wallet::psbt::PsbtUtils;
|
||||
use bdk_wallet::signer::{SignOptions, SignerError};
|
||||
use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection};
|
||||
use bdk_wallet::wallet::error::CreateTxError;
|
||||
use bdk_wallet::wallet::tx_builder::AddForeignUtxoError;
|
||||
use bdk_wallet::wallet::{AddressInfo, Balance, ChangeSet, NewError, Wallet};
|
||||
use bdk_wallet::wallet::{AddressInfo, Balance, CreateParams, LoadParams, Wallet};
|
||||
use bdk_wallet::KeychainKind;
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::key::Secp256k1;
|
||||
@@ -102,46 +101,44 @@ const P2WPKH_FAKE_WITNESS_SIZE: usize = 106;
|
||||
const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48];
|
||||
|
||||
#[test]
|
||||
fn load_recovers_wallet() -> anyhow::Result<()> {
|
||||
fn run<Db, New, Recover, Read, Write>(
|
||||
fn wallet_is_persisted() -> anyhow::Result<()> {
|
||||
fn run<Db, CreateDb, OpenDb>(
|
||||
filename: &str,
|
||||
create_new: New,
|
||||
recover: Recover,
|
||||
read: Read,
|
||||
write: Write,
|
||||
create_db: CreateDb,
|
||||
open_db: OpenDb,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
New: Fn(&Path) -> anyhow::Result<Db>,
|
||||
Recover: Fn(&Path) -> anyhow::Result<Db>,
|
||||
Read: Fn(&mut Db) -> anyhow::Result<Option<ChangeSet>>,
|
||||
Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>,
|
||||
CreateDb: Fn(&Path) -> anyhow::Result<Db>,
|
||||
OpenDb: Fn(&Path) -> anyhow::Result<Db>,
|
||||
Wallet: PersistWith<Db, CreateParams = CreateParams, LoadParams = LoadParams>,
|
||||
<Wallet as PersistWith<Db>>::CreateError: std::error::Error + Send + Sync + 'static,
|
||||
<Wallet as PersistWith<Db>>::LoadError: std::error::Error + Send + Sync + 'static,
|
||||
<Wallet as PersistWith<Db>>::PersistError: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
||||
let file_path = temp_dir.path().join(filename);
|
||||
let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
|
||||
let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_with_change_desc();
|
||||
|
||||
// create new wallet
|
||||
let wallet_spk_index = {
|
||||
let mut wallet =
|
||||
Wallet::new(desc, change_desc, Network::Testnet).expect("must init wallet");
|
||||
|
||||
let mut db = create_db(&file_path)?;
|
||||
let mut wallet = CreateParams::new(external_desc, internal_desc, Network::Testnet)?
|
||||
.create_wallet(&mut db)?;
|
||||
wallet.reveal_next_address(KeychainKind::External);
|
||||
|
||||
// persist new wallet changes
|
||||
let mut db = create_new(&file_path).expect("must create db");
|
||||
if let Some(changeset) = wallet.take_staged() {
|
||||
write(&mut db, &changeset)?;
|
||||
}
|
||||
assert!(wallet.persist(&mut db)?, "must write");
|
||||
wallet.spk_index().clone()
|
||||
};
|
||||
|
||||
// recover wallet
|
||||
{
|
||||
// load persisted wallet changes
|
||||
let db = &mut recover(&file_path).expect("must recover db");
|
||||
let changeset = read(db).expect("must recover wallet").expect("changeset");
|
||||
let mut db = open_db(&file_path).context("failed to recover db")?;
|
||||
let wallet =
|
||||
LoadParams::with_descriptors(external_desc, internal_desc, Network::Testnet)?
|
||||
.load_wallet(&mut db)?
|
||||
.expect("wallet must exist");
|
||||
|
||||
let wallet = Wallet::load_from_changeset(changeset).expect("must recover wallet");
|
||||
assert_eq!(wallet.network(), Network::Testnet);
|
||||
assert_eq!(
|
||||
wallet.spk_index().keychains().collect::<Vec<_>>(),
|
||||
@@ -154,7 +151,8 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
|
||||
let secp = Secp256k1::new();
|
||||
assert_eq!(
|
||||
*wallet.public_descriptor(KeychainKind::External),
|
||||
desc.into_wallet_descriptor(&secp, wallet.network())
|
||||
external_desc
|
||||
.into_wallet_descriptor(&secp, wallet.network())
|
||||
.unwrap()
|
||||
.0
|
||||
);
|
||||
@@ -167,166 +165,11 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
|
||||
"store.db",
|
||||
|path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?),
|
||||
|path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?),
|
||||
|db| Ok(bdk_file_store::Store::aggregate_changesets(db)?),
|
||||
|db, changeset| Ok(bdk_file_store::Store::append_changeset(db, changeset)?),
|
||||
)?;
|
||||
run(
|
||||
run::<bdk_chain::sqlite::Connection, _, _>(
|
||||
"store.sqlite",
|
||||
|path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
|
||||
|path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
|
||||
|db| Ok(bdk_sqlite::Store::read(db)?),
|
||||
|db, changeset| Ok(bdk_sqlite::Store::write(db, changeset)?),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_or_load() -> anyhow::Result<()> {
|
||||
fn run<Db, NewOrRecover, Read, Write>(
|
||||
filename: &str,
|
||||
new_or_load: NewOrRecover,
|
||||
read: Read,
|
||||
write: Write,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
NewOrRecover: Fn(&Path) -> anyhow::Result<Db>,
|
||||
Read: Fn(&mut Db) -> anyhow::Result<Option<ChangeSet>>,
|
||||
Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>,
|
||||
{
|
||||
let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
||||
let file_path = temp_dir.path().join(filename);
|
||||
let (desc, change_desc) = get_test_wpkh_with_change_desc();
|
||||
|
||||
// init wallet when non-existent
|
||||
let wallet_keychains: BTreeMap<_, _> = {
|
||||
let wallet = &mut Wallet::new_or_load(desc, change_desc, None, Network::Testnet)
|
||||
.expect("must init wallet");
|
||||
let mut db = new_or_load(&file_path).expect("must create db");
|
||||
if let Some(changeset) = wallet.take_staged() {
|
||||
write(&mut db, &changeset)?;
|
||||
}
|
||||
wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
|
||||
};
|
||||
|
||||
// wrong network
|
||||
{
|
||||
let mut db = new_or_load(&file_path).expect("must create db");
|
||||
let changeset = read(&mut db)?;
|
||||
let err = Wallet::new_or_load(desc, change_desc, changeset, Network::Bitcoin)
|
||||
.expect_err("wrong network");
|
||||
assert!(
|
||||
matches!(
|
||||
err,
|
||||
bdk_wallet::wallet::NewOrLoadError::LoadedNetworkDoesNotMatch {
|
||||
got: Some(Network::Testnet),
|
||||
expected: Network::Bitcoin
|
||||
}
|
||||
),
|
||||
"err: {}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
// wrong genesis hash
|
||||
{
|
||||
let exp_blockhash = BlockHash::all_zeros();
|
||||
let got_blockhash = bitcoin::constants::genesis_block(Network::Testnet).block_hash();
|
||||
|
||||
let db = &mut new_or_load(&file_path).expect("must open db");
|
||||
let changeset = read(db)?;
|
||||
let err = Wallet::new_or_load_with_genesis_hash(
|
||||
desc,
|
||||
change_desc,
|
||||
changeset,
|
||||
Network::Testnet,
|
||||
exp_blockhash,
|
||||
)
|
||||
.expect_err("wrong genesis hash");
|
||||
assert!(
|
||||
matches!(
|
||||
err,
|
||||
bdk_wallet::wallet::NewOrLoadError::LoadedGenesisDoesNotMatch { got, expected }
|
||||
if got == Some(got_blockhash) && expected == exp_blockhash
|
||||
),
|
||||
"err: {}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
// wrong external descriptor
|
||||
{
|
||||
let (exp_descriptor, exp_change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
|
||||
let got_descriptor = desc
|
||||
.into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
let db = &mut new_or_load(&file_path).expect("must open db");
|
||||
let changeset = read(db)?;
|
||||
let err =
|
||||
Wallet::new_or_load(exp_descriptor, exp_change_desc, changeset, Network::Testnet)
|
||||
.expect_err("wrong external descriptor");
|
||||
assert!(
|
||||
matches!(
|
||||
err,
|
||||
bdk_wallet::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
|
||||
if got == &Some(got_descriptor) && keychain == KeychainKind::External
|
||||
),
|
||||
"err: {}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
// wrong internal descriptor
|
||||
{
|
||||
let exp_descriptor = get_test_tr_single_sig();
|
||||
let got_descriptor = change_desc
|
||||
.into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
let db = &mut new_or_load(&file_path).expect("must open db");
|
||||
let changeset = read(db)?;
|
||||
let err = Wallet::new_or_load(desc, exp_descriptor, changeset, Network::Testnet)
|
||||
.expect_err("wrong internal descriptor");
|
||||
assert!(
|
||||
matches!(
|
||||
err,
|
||||
bdk_wallet::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
|
||||
if got == &Some(got_descriptor) && keychain == KeychainKind::Internal
|
||||
),
|
||||
"err: {}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
// all parameters match
|
||||
{
|
||||
let db = &mut new_or_load(&file_path).expect("must open db");
|
||||
let changeset = read(db)?;
|
||||
let wallet = Wallet::new_or_load(desc, change_desc, changeset, Network::Testnet)
|
||||
.expect("must recover wallet");
|
||||
assert_eq!(wallet.network(), Network::Testnet);
|
||||
assert!(wallet
|
||||
.keychains()
|
||||
.map(|(k, v)| (*k, v.clone()))
|
||||
.eq(wallet_keychains));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
run(
|
||||
"store.db",
|
||||
|path| Ok(bdk_file_store::Store::open_or_create_new(DB_MAGIC, path)?),
|
||||
|db| Ok(bdk_file_store::Store::aggregate_changesets(db)?),
|
||||
|db, changeset| Ok(bdk_file_store::Store::append_changeset(db, changeset)?),
|
||||
)?;
|
||||
run(
|
||||
"store.sqlite",
|
||||
|path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
|
||||
|db| Ok(bdk_sqlite::Store::read(db)?),
|
||||
|db, changeset| Ok(bdk_sqlite::Store::write(db, changeset)?),
|
||||
|path| Ok(bdk_chain::sqlite::Connection::open(path)?),
|
||||
|path| Ok(bdk_chain::sqlite::Connection::open(path)?),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -336,14 +179,11 @@ fn new_or_load() -> anyhow::Result<()> {
|
||||
fn test_error_external_and_internal_are_the_same() {
|
||||
// identical descriptors should fail to create wallet
|
||||
let desc = get_test_wpkh();
|
||||
let err = Wallet::new(desc, desc, Network::Testnet);
|
||||
let err = CreateParams::new(desc, desc, Network::Testnet)
|
||||
.unwrap()
|
||||
.create_wallet_no_persist();
|
||||
assert!(
|
||||
matches!(
|
||||
&err,
|
||||
Err(NewError::Descriptor(
|
||||
DescriptorError::ExternalAndInternalAreTheSame
|
||||
))
|
||||
),
|
||||
matches!(&err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
|
||||
"expected same descriptors error, got {:?}",
|
||||
err,
|
||||
);
|
||||
@@ -351,14 +191,11 @@ fn test_error_external_and_internal_are_the_same() {
|
||||
// public + private of same descriptor should fail to create wallet
|
||||
let desc = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
|
||||
let change_desc = "wpkh([3c31d632/84'/1'/0']tpubDCYwFkks2cg78N7eoYbBatsFEGje8vW8arSKW4rLwD1AU1s9KJMDRHE32JkvYERuiFjArrsH7qpWSpJATed5ShZbG9KsskA5Rmi6NSYgYN2/0/*)";
|
||||
let err = Wallet::new(desc, change_desc, Network::Testnet);
|
||||
let err = CreateParams::new(desc, change_desc, Network::Testnet)
|
||||
.unwrap()
|
||||
.create_wallet_no_persist();
|
||||
assert!(
|
||||
matches!(
|
||||
err,
|
||||
Err(NewError::Descriptor(
|
||||
DescriptorError::ExternalAndInternalAreTheSame
|
||||
))
|
||||
),
|
||||
matches!(err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
|
||||
"expected same descriptors error, got {:?}",
|
||||
err,
|
||||
);
|
||||
@@ -1316,8 +1153,11 @@ fn test_create_tx_policy_path_required() {
|
||||
|
||||
#[test]
|
||||
fn test_create_tx_policy_path_no_csv() {
|
||||
let (desc, change_desc) = get_test_wpkh_with_change_desc();
|
||||
let mut wallet = Wallet::new(desc, change_desc, Network::Regtest).expect("wallet");
|
||||
let (descriptor, change_descriptor) = get_test_wpkh_with_change_desc();
|
||||
let mut wallet = CreateParams::new(descriptor, change_descriptor, Network::Regtest)
|
||||
.expect("must parse")
|
||||
.create_wallet_no_persist()
|
||||
.expect("wallet");
|
||||
|
||||
let tx = Transaction {
|
||||
version: transaction::Version::non_standard(0),
|
||||
@@ -2927,9 +2767,12 @@ fn test_sign_nonstandard_sighash() {
|
||||
|
||||
#[test]
|
||||
fn test_unused_address() {
|
||||
let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
|
||||
let change_desc = get_test_wpkh();
|
||||
let mut wallet = Wallet::new(desc, change_desc, Network::Testnet).expect("wallet");
|
||||
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
|
||||
let change_descriptor = get_test_wpkh();
|
||||
let mut wallet = CreateParams::new(descriptor, change_descriptor, Network::Testnet)
|
||||
.expect("must parse descriptors")
|
||||
.create_wallet_no_persist()
|
||||
.expect("wallet");
|
||||
|
||||
// `list_unused_addresses` should be empty if we haven't revealed any
|
||||
assert!(wallet
|
||||
@@ -2956,8 +2799,11 @@ fn test_unused_address() {
|
||||
#[test]
|
||||
fn test_next_unused_address() {
|
||||
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
|
||||
let change = get_test_wpkh();
|
||||
let mut wallet = Wallet::new(descriptor, change, Network::Testnet).expect("wallet");
|
||||
let change_descriptor = get_test_wpkh();
|
||||
let mut wallet = CreateParams::new(descriptor, change_descriptor, Network::Testnet)
|
||||
.expect("must parse descriptors")
|
||||
.create_wallet_no_persist()
|
||||
.expect("wallet");
|
||||
assert_eq!(wallet.derivation_index(KeychainKind::External), None);
|
||||
|
||||
assert_eq!(
|
||||
@@ -3002,9 +2848,12 @@ fn test_next_unused_address() {
|
||||
|
||||
#[test]
|
||||
fn test_peek_address_at_index() {
|
||||
let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
|
||||
let change_desc = get_test_wpkh();
|
||||
let mut wallet = Wallet::new(desc, change_desc, Network::Testnet).unwrap();
|
||||
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
|
||||
let change_descriptor = get_test_wpkh();
|
||||
let mut wallet = CreateParams::new(descriptor, change_descriptor, Network::Testnet)
|
||||
.expect("must parse descriptors")
|
||||
.create_wallet_no_persist()
|
||||
.expect("wallet");
|
||||
|
||||
assert_eq!(
|
||||
wallet.peek_address(KeychainKind::External, 1).to_string(),
|
||||
@@ -3039,8 +2888,11 @@ fn test_peek_address_at_index() {
|
||||
|
||||
#[test]
|
||||
fn test_peek_address_at_index_not_derivable() {
|
||||
let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
|
||||
get_test_wpkh(), Network::Testnet).unwrap();
|
||||
let wallet = CreateParams::new(
|
||||
"wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
|
||||
get_test_wpkh(),
|
||||
Network::Testnet,
|
||||
).unwrap().create_wallet_no_persist().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wallet.peek_address(KeychainKind::External, 1).to_string(),
|
||||
@@ -3060,8 +2912,11 @@ fn test_peek_address_at_index_not_derivable() {
|
||||
|
||||
#[test]
|
||||
fn test_returns_index_and_address() {
|
||||
let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
get_test_wpkh(), Network::Testnet).unwrap();
|
||||
let mut wallet = CreateParams::new(
|
||||
"wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
get_test_wpkh(),
|
||||
Network::Testnet,
|
||||
).unwrap().create_wallet_no_persist().unwrap();
|
||||
|
||||
// new index 0
|
||||
assert_eq!(
|
||||
@@ -3127,11 +2982,13 @@ fn test_sending_to_bip350_bech32m_address() {
|
||||
fn test_get_address() {
|
||||
use bdk_wallet::descriptor::template::Bip84;
|
||||
let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
|
||||
let wallet = Wallet::new(
|
||||
let wallet = CreateParams::new(
|
||||
Bip84(key, KeychainKind::External),
|
||||
Bip84(key, KeychainKind::Internal),
|
||||
Network::Regtest,
|
||||
)
|
||||
.unwrap()
|
||||
.create_wallet_no_persist()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -3160,7 +3017,10 @@ fn test_get_address() {
|
||||
#[test]
|
||||
fn test_reveal_addresses() {
|
||||
let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
|
||||
let mut wallet = Wallet::new(desc, change_desc, Network::Signet).unwrap();
|
||||
let mut wallet = CreateParams::new(desc, change_desc, Network::Signet)
|
||||
.expect("must parse")
|
||||
.create_wallet_no_persist()
|
||||
.unwrap();
|
||||
let keychain = KeychainKind::External;
|
||||
|
||||
let last_revealed_addr = wallet.reveal_addresses_to(keychain, 9).last().unwrap();
|
||||
@@ -3181,11 +3041,13 @@ fn test_get_address_no_reuse() {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
|
||||
let mut wallet = Wallet::new(
|
||||
let mut wallet = CreateParams::new(
|
||||
Bip84(key, KeychainKind::External),
|
||||
Bip84(key, KeychainKind::Internal),
|
||||
Network::Regtest,
|
||||
)
|
||||
.unwrap()
|
||||
.create_wallet_no_persist()
|
||||
.unwrap();
|
||||
|
||||
let mut used_set = HashSet::new();
|
||||
@@ -3655,11 +3517,13 @@ fn test_taproot_sign_derive_index_from_psbt() {
|
||||
let mut psbt = builder.finish().unwrap();
|
||||
|
||||
// re-create the wallet with an empty db
|
||||
let wallet_empty = Wallet::new(
|
||||
let wallet_empty = CreateParams::new(
|
||||
get_test_tr_single_sig_xprv(),
|
||||
get_test_tr_single_sig(),
|
||||
Network::Regtest,
|
||||
)
|
||||
.unwrap()
|
||||
.create_wallet_no_persist()
|
||||
.unwrap();
|
||||
|
||||
// signing with an empty db means that we will only look at the psbt to infer the
|
||||
@@ -3760,7 +3624,10 @@ fn test_taproot_sign_non_default_sighash() {
|
||||
#[test]
|
||||
fn test_spend_coinbase() {
|
||||
let (desc, change_desc) = get_test_wpkh_with_change_desc();
|
||||
let mut wallet = Wallet::new(desc, change_desc, Network::Regtest).unwrap();
|
||||
let mut wallet = CreateParams::new(desc, change_desc, Network::Regtest)
|
||||
.unwrap()
|
||||
.create_wallet_no_persist()
|
||||
.unwrap();
|
||||
|
||||
let confirmation_height = 5;
|
||||
wallet
|
||||
@@ -4014,6 +3881,7 @@ fn test_taproot_load_descriptor_duplicated_keys() {
|
||||
/// [#1483]: https://github.com/bitcoindevkit/bdk/issues/1483
|
||||
/// [#1486]: https://github.com/bitcoindevkit/bdk/pull/1486
|
||||
#[test]
|
||||
#[cfg(debug_assertions)]
|
||||
#[should_panic(
|
||||
expected = "replenish lookahead: must not have existing spk: keychain=Internal, lookahead=25, next_store_index=0, next_reveal_index=0"
|
||||
)]
|
||||
|
||||
Reference in New Issue
Block a user