diff --git a/Cargo.lock b/Cargo.lock index 6481f4f..26020dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,16 +347,10 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin_hashes 0.14.0", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.1" @@ -392,14 +386,15 @@ dependencies = [ "bip39", "bitcoin 0.30.2", "electrum-client 0.18.0", + "esplora-client", + "futures", "getrandom 0.2.15", "js-sys", "log", - "miniscript 10.0.0", + "miniscript", "rand 0.8.5", "serde", "serde_json", - "sled", "tokio", ] @@ -414,59 +409,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "bdk_chain" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "163b064557cee078e8ee5dd2c88944204506f7b2b1524f78e8fcba38c346da7b" -dependencies = [ - "bitcoin 0.32.2", - "miniscript 12.0.0", - "serde", -] - -[[package]] -name = "bdk_esplora" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "089babab213bbb32518bad79a7313ebb4c85a52c18c8b558402dfa810c27de3f" -dependencies = [ - "async-trait", - "bdk_chain", - "esplora-client", - "futures", - "miniscript 12.0.0", -] - -[[package]] -name = "bdk_sqlite" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0926dc5778fb3c5afaf7def9ed9b9c9d9fe9e3ba499e09cb816d3de43211be71" -dependencies = [ - "bdk_chain", - "rusqlite", - "serde", - "serde_json", -] - -[[package]] -name = "bdk_wallet" -version = "1.0.0-alpha.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2926afdbfc54ebf7df2caa51af5be4435b90c01c6fbe5578b51b7c2c0a264bd9" -dependencies = [ - "bdk_chain", - "bip39", - "bitcoin 0.32.2", - "getrandom 0.2.15", - "js-sys", - "miniscript 12.0.0", - "rand 0.8.5", - "serde", - "serde_json", -] - [[package]] name = "bech32" version = "0.9.1" @@ -573,9 +515,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea507acc1cd80fc084ace38544bbcf7ced7c2aa65b653b102de0ce718df668f6" dependencies = [ "base58ck", - "base64 0.21.7", "bech32 0.11.0", - "bitcoin-internals", + "bitcoin-internals 0.3.0", "bitcoin-io", "bitcoin-units", "bitcoin_hashes 0.14.0", @@ -585,6 +526,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f9997f8650dd818369931b5672a18dbef95324d0513aa99aae758de8ce86e5b" + [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -612,7 +559,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb54da0b28892f3c52203a7191534033e051b6f4b52bc15480681b57b7e036f5" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.3.0", "serde", ] @@ -942,15 +889,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - [[package]] name = "criterion" version = "0.4.0" @@ -1336,14 +1274,13 @@ dependencies = [ [[package]] name = "esplora-client" -version = "0.8.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69c6d27ef4ff21019edd98aa92199757e10a88065bbfcef6bb750ca6ec5e4a45" +checksum = "0cb1f7f2489cce83bc3bd92784f9ba5271eeb6e729b975895fc541f78cbfcdca" dependencies = [ - "bitcoin 0.32.2", - "hex-conservative", + "bitcoin 0.30.2", + "bitcoin-internals 0.1.0", "log", - "minreq", "reqwest", "serde", ] @@ -1390,18 +1327,6 @@ dependencies = [ "wasmtimer", ] -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fastcrypto" version = "0.1.8" @@ -1512,16 +1437,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "fs_extra" version = "1.3.0" @@ -1617,15 +1532,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1734,18 +1640,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown 0.14.5", -] [[package]] name = "heck" @@ -1960,15 +1854,6 @@ dependencies = [ "serde", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" version = "2.9.0" @@ -2070,17 +1955,6 @@ dependencies = [ "libc", ] -[[package]] -name = "libsqlite3-sys" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2141,17 +2015,6 @@ dependencies = [ "serde", ] -[[package]] -name = "miniscript" -version = "12.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b59c67956fd276ceec0cf194fbf80754ef4d88a496d5cf5e4fdf33561466183d" -dependencies = [ - "bech32 0.11.0", - "bitcoin 0.32.2", - "serde", -] - [[package]] name = "miniz_oxide" version = "0.7.4" @@ -2161,22 +2024,6 @@ dependencies = [ "adler", ] -[[package]] -name = "minreq" -version = "2.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fdef521c74c2884a4f3570bcdb6d2a77b3c533feb6b27ac2ae72673cc221c64" -dependencies = [ - "base64 0.12.3", - "log", - "once_cell", - "rustls 0.21.12", - "rustls-webpki 0.101.7", - "serde", - "serde_json", - "webpki-roots 0.25.4", -] - [[package]] name = "mio" version = "0.8.11" @@ -2405,17 +2252,6 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -2423,21 +2259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -2448,7 +2270,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall", "smallvec", "windows-targets 0.52.5", ] @@ -2741,15 +2563,6 @@ dependencies = [ "syn 2.0.67", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.2" @@ -2901,20 +2714,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rusqlite" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" -dependencies = [ - "bitflags 2.5.0", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3364,9 +3163,6 @@ dependencies = [ "anyhow", "async-trait", "bdk", - "bdk_esplora", - "bdk_sqlite", - "bdk_wallet", "bft-crdt-derive", "bft-json-crdt", "bitcoin 0.32.2", @@ -3427,22 +3223,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log", - "parking_lot 0.11.2", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -3699,7 +3479,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.3", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4120,7 +3900,7 @@ checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" dependencies = [ "futures", "js-sys", - "parking_lot 0.12.3", + "parking_lot", "pin-utils", "slab", "wasm-bindgen", diff --git a/README.md b/README.md index bb3ad5a..1ce466e 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,7 @@ cargo run -- btc-transfer You'll need to have funded the "dave" address prior to running the `btc` command - otherwise the transfer will fail gracefully. -I was using this primarily as a way to experiment with constructing and broadcasting Bitcoin transactions, with the hope that it would be possible to move on to more advanced constructions (e.g. state channels). However, now that I look at all the options, it seems that multi-party state channels in Bitcoin are (probably) impossible to construct. - -There is a second, unused Bitcoin client in place which uses Blockstream's Electrum server, but this didn't seem to be working properly with respect to Signet Bitcoin network during my testing, so I went with the esplora / Mutiny version instead. +I have been using this primarily as a way to experiment with constructing and broadcasting Bitcoin transactions, with the hope that it would be possible to move on to more advanced constructions (e.g. state channels). ### HTLCs (in progress) diff --git a/side-node/Cargo.toml b/side-node/Cargo.toml index 87cf69d..6e68037 100644 --- a/side-node/Cargo.toml +++ b/side-node/Cargo.toml @@ -8,10 +8,14 @@ edition = "2021" [dependencies] anyhow = "1.0.86" async-trait = "0.1.52" -bdk = { version = "0.29.0", default-feature = false, features = ["all-keys"] } -bdk_esplora = "0.15.0" -bdk_sqlite = "0.2.0" -bdk_wallet = { version = "1.0.0-alpha.13", features = ["all-keys"] } +bdk = { version = "0.29.0", features = [ + "compiler", + "use-esplora-async", + "electrum", + "std", + "keys-bip39", + "reqwest-default-tls", +], default-features = false } bft-json-crdt = { path = "../crates/bft-json-crdt" } bft-crdt-derive = { path = "../crates/bft-json-crdt/bft-crdt-derive" } bitcoin = { version = "0.32.2", features = ["rand"] } @@ -29,7 +33,7 @@ sha256 = "1.5.0" tokio = { version = "1.37.0", features = ["full"] } toml = "0.8.14" tracing = "0.1" -tracing-subscriber = {version = "0.3", features = ["std", "env-filter"]} +tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } [dev-dependencies] diff --git a/side-node/src/bitcoin/clients/electrum.rs b/side-node/src/bitcoin/clients/electrum.rs deleted file mode 100644 index 9e02122..0000000 --- a/side-node/src/bitcoin/clients/electrum.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::{bitcoin, utils}; -use bdk::bitcoin::psbt::PartiallySignedTransaction; -use bdk::wallet::AddressIndex::New; -use bdk::wallet::{AddressIndex, AddressInfo}; -use bdk::{ - bitcoin::Network, database::MemoryDatabase, keys::ExtendedKey, template::Bip84, KeychainKind, - Wallet, -}; -use bdk::{blockchain::ElectrumBlockchain, electrum_client, SyncOptions}; -use bdk::{FeeRate, SignOptions, TransactionDetails}; - -/// DEPRECATED -/// -/// This is a bdk example that uses the Electrum client to interact with the Bitcoin network. -/// Electrum is a light client that connects to a server to get information about the Bitcoin network. -/// The BDK itself does not have the ability to connect to e.g. esplora servers. As the Blockstream Electrum Signet -/// server does not appear to be picking up transactions properly at the moment, I've shifted over to using -/// the (more complex) `bdk_wallet` crate and the esplora client there (see the other bitcoin client). -/// -/// Note:the types below are all completely different than the types in `bdk_wallet`. -pub async fn run() -> Result<(), anyhow::Error> { - let dave = utils::home(&"dave".to_string()); - let sammy = utils::home(&"sammy".to_string()); - let dave_key = bitcoin::keys::load_from_file(&dave).unwrap(); - let sammy_key = bitcoin::keys::load_from_file(&sammy).unwrap(); - - let dave_wallet = bitcoin::clients::electrum::create_wallet(dave_key)?; - let sammy_wallet = bitcoin::clients::electrum::create_wallet(sammy_key)?; - - let dave_address = dave_wallet.get_address(AddressIndex::Peek(0))?.to_string(); - let sammy_address = sammy_wallet.get_address(AddressIndex::Peek(0))?.to_string(); - - println!("Dave's address: {}", dave_address); - println!("Sammy's address: {}", sammy_address); - - let blockchain = ElectrumBlockchain::from(electrum_client::Client::new( - "ssl://electrum.blockstream.info:60002", - )?); - - println!("Syncing..."); - dave_wallet.sync(&blockchain, SyncOptions::default())?; - - display_balance(&dave_wallet); - display_balance(&sammy_wallet); - - let (mut psbt, details) = - build_sending_tx(&dave_wallet, sammy_wallet.get_address(New)?).expect("psbt build error"); - - println!("About to sign the transaction: {:?}", details); - - dave_wallet.sign(&mut psbt, SignOptions::default())?; - let _signed_tx = psbt.extract_tx(); - - // println!("Broadcasting..."); - // blockchain.broadcast(&signed_tx).expect("broadcast error"); - // println!("Transaction ID: {:?}", signed_tx.txid()); - Ok(()) -} - -/// Create a BDK wallet using BIP 84 descriptor ("m/84h/1h/0h/0" and "m/84h/1h/0h/1") -pub fn create_wallet(xkey: ExtendedKey) -> anyhow::Result> { - let xprv = xkey - .into_xprv(Network::Testnet) - .expect("couldn't turn xkey into xprv"); - - let external_descriptor = Bip84(xprv, KeychainKind::External); - let internal_descriptor = Some(Bip84(xprv, KeychainKind::Internal)); - - let wallet = Wallet::new( - external_descriptor, - internal_descriptor, - Network::Testnet, - MemoryDatabase::default(), - )?; - - Ok(wallet) -} - -fn display_balance(wallet: &Wallet) { - println!( - "Wallet balance for {} after syncing: {:?} sats on network {}", - wallet - .get_address(bdk::wallet::AddressIndex::Peek(0)) - .expect("couldn't get address"), - wallet.get_balance().expect("couldn't show balance"), - wallet.network(), - ); -} - -fn build_sending_tx( - wallet: &Wallet, - recipient: AddressInfo, -) -> anyhow::Result<(PartiallySignedTransaction, TransactionDetails), anyhow::Error> { - let mut builder = wallet.build_tx(); - builder - .add_recipient(recipient.script_pubkey(), 1000) - .enable_rbf() - .do_not_spend_change() - .fee_rate(FeeRate::from_sat_per_vb(7.0)); - Ok(builder.finish()?) -} diff --git a/side-node/src/bitcoin/clients/esplora.rs b/side-node/src/bitcoin/clients/esplora.rs index a0e3b99..b25dd28 100644 --- a/side-node/src/bitcoin/clients/esplora.rs +++ b/side-node/src/bitcoin/clients/esplora.rs @@ -1,34 +1,35 @@ use std::fs; -use bdk::keys::bip39::Mnemonic; -use bdk_esplora::{ - esplora_client::{self, AsyncClient}, - EsploraAsyncExt, -}; -use bdk_wallet::{ - bitcoin::{Address, Amount, Network}, - chain::ConfirmationTimeHeightAnchor, - keys::{DerivableKey, ExtendedKey}, +use bdk::{ + bitcoin::{psbt::PartiallySignedTransaction, Network, Transaction}, + blockchain::EsploraBlockchain, + database::MemoryDatabase, + keys::{bip39::Mnemonic, DerivableKey, ExtendedKey}, + template::Bip84, wallet::AddressInfo, - KeychainKind, SignOptions, Wallet, + KeychainKind, SignOptions, SyncOptions, Wallet, }; -use bdk_sqlite::{rusqlite::Connection, Store}; - use crate::utils; -const STOP_GAP: usize = 50; -const PARALLEL_REQUESTS: usize = 5; - /// A wallet that uses the Esplora client to interact with the Bitcoin network. pub struct EsploraWallet { - client: AsyncClient, - db: Store, + pub(crate) blockchain: bdk::blockchain::EsploraBlockchain, name: String, - wallet: Wallet, + pub(crate) wallet: Wallet, } impl EsploraWallet { + pub(crate) fn sync(&self) -> anyhow::Result<()> { + self.wallet.sync(&self.blockchain, SyncOptions::default())?; + Ok(()) + } + + pub(crate) fn broadcast(&self, tx: &Transaction) -> anyhow::Result<()> { + let _ = self.blockchain.broadcast(&tx); + Ok(()) + } + /// Builds and signs a send transaction to send coins between addresses. /// /// Does NOT send it, you must call `broadcast` to do that. @@ -36,32 +37,23 @@ impl EsploraWallet { /// We could split the creation and signing easily if needed. pub(crate) fn build_and_sign_send_tx( &mut self, - recipient: Address, - amount: Amount, - ) -> Result { - let mut tx_builder = self.build_tx()?; + recipient: AddressInfo, + amount: u64, + ) -> Result { + let mut tx_builder = self.wallet.build_tx(); tx_builder .add_recipient(recipient.script_pubkey(), amount) .enable_rbf(); - let mut psbt = tx_builder.finish()?; - let tx = self.sign(&mut psbt, true)?.extract_tx()?; + let (mut psbt, _) = tx_builder.finish()?; + let tx = self.sign(&mut psbt, true)?.extract_tx(); Ok(tx) } - pub(crate) fn build_tx( - &mut self, - ) -> Result< - bdk_wallet::TxBuilder, - anyhow::Error, - > { - Ok(self.wallet.build_tx()) - } - pub(crate) fn sign( &self, - psbt: &mut bitcoin::Psbt, + psbt: &mut PartiallySignedTransaction, finalize: bool, - ) -> Result { + ) -> Result { tracing::info!("{} signing PSBT", self.name); let options = SignOptions { @@ -78,114 +70,41 @@ impl EsploraWallet { Ok(psbt.to_owned()) } - /// Syncs the wallet with the latest state of the Bitcoin blockchain - pub(crate) async fn sync(&mut self) -> Result<(), anyhow::Error> { - tracing::info!("{} full scan sync start", self.name); - let request = self.wallet.start_full_scan(); - let mut update = self - .client - .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) - .await?; - let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); - let _ = update.graph_update.update_last_seen_unconfirmed(now); - self.wallet.apply_update(update)?; - self.persist_local()?; - tracing::info!("{} sync complete", self.name); - Ok(()) - } + /// Creates a Bitcoin descriptor wallet with the mnemonic in the given user directory. + pub(crate) fn create_wallet(name: &str, network: Network) -> anyhow::Result { + let keys_dir = utils::home(name); - fn persist_local(&mut self) -> Result<(), anyhow::Error> { - Ok(if let Some(changeset) = self.wallet.take_staged() { - self.db.write(&changeset)?; - }) - } + let mnemonic_path = crate::utils::side_paths(keys_dir).1; // TODO: this tuple stinks + let words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file"); - /// Gets the next unused address from the wallet. - pub(crate) fn next_unused_address(&mut self) -> Result { - let address = self.wallet.next_unused_address(KeychainKind::External); - self.persist_local()?; - tracing::info!( - "Generated address: https://mutinynet.com/address/{}", - address - ); - Ok(address) - } + tracing::info!("Creating {name}'s wallet from mnemonic: {words}"); + let xkey: ExtendedKey = Mnemonic::parse(words) + .expect("couldn't parse mnemonic words") + .into_extended_key() + .expect("couldn't turn mnemonic into extended key"); - /// Returns the balance of the wallet. - pub(crate) fn balance(&self) -> bdk_wallet::wallet::Balance { - tracing::info!( - "{}'s balance is {}", - self.name, - self.wallet.balance().total() - ); - self.wallet.balance() - } + let xprv = xkey + .into_xprv(Network::Testnet) + .expect("couldn't turn xkey into xprv"); - /// Broadcasts a signed transaction to the network. - pub(crate) async fn broadcast( - &self, - tx: &bitcoin::Transaction, - ) -> Result<(), esplora_client::Error> { - tracing::info!( - "{} broadcasting tx https://mutinynet.com/tx/{}", - self.name, - tx.compute_txid() - ); - self.client.broadcast(tx).await - } + let external_descriptor = Bip84(xprv, KeychainKind::External); + let internal_descriptor = Some(Bip84(xprv, KeychainKind::Internal)); - pub(crate) fn wallet(&mut self) -> &Wallet { - &self.wallet + let wallet = Wallet::new( + external_descriptor, + internal_descriptor, + network, + MemoryDatabase::default(), + )?; + + let blockchain = EsploraBlockchain::new("https://mutinynet.com/api", 20); + + let esplora = EsploraWallet { + name: name.to_string(), + wallet, + blockchain, + }; + + Ok(esplora) } } - -/// Creates a Bitcoin descriptor wallet with the mnemonic in the given user directory. -pub(crate) fn create_wallet(name: &str, network: Network) -> anyhow::Result { - let keys_dir = utils::home(name); - - let mnemonic_path = crate::utils::side_paths(keys_dir).1; // TODO: this tuple stinks - let mnemonic_words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file"); - - tracing::info!("Creating {name}'s wallet from mnemonic: {mnemonic_words}"); - let mnemonic = Mnemonic::parse(mnemonic_words).unwrap(); - - // Generate the extended key - let xkey: ExtendedKey = mnemonic - .into_extended_key() - .expect("couldn't turn mnemonic into xkey"); - - let xprv = xkey - .into_xprv(Network::Signet) - .expect("problem converting xkey to xprv") - .to_string(); - - tracing::info!("Setting up esplora database for {name}"); - - let db_path = format!("/tmp/{name}-bdk-esplora-async-example.sqlite"); - let conn = Connection::open(db_path)?; - let mut db = Store::new(conn)?; - let external_descriptor = format!("wpkh({xprv}/84'/1'/0'/0/*)"); - let internal_descriptor = format!("wpkh({xprv}/84'/1'/0'/1/*)"); - let changeset = db.read().expect("couldn't read esplora database"); - - let wallet = Wallet::new_or_load( - &external_descriptor, - &internal_descriptor, - changeset, - network, - ) - .expect("problem setting up wallet"); - - let client = esplora_client::Builder::new("https://mutinynet.com/api") - .build_async() - .expect("couldn't build esplora client"); - - let esplora = EsploraWallet { - name: name.to_string(), - wallet, - db, - client, - }; - - Ok(esplora) -} diff --git a/side-node/src/bitcoin/clients/mod.rs b/side-node/src/bitcoin/clients/mod.rs index 21a4a4c..9dba5bd 100644 --- a/side-node/src/bitcoin/clients/mod.rs +++ b/side-node/src/bitcoin/clients/mod.rs @@ -1,3 +1 @@ -// Re-enable this if we want to use alongside a fullnode. -pub mod electrum; pub mod esplora; diff --git a/side-node/src/bitcoin/driver.rs b/side-node/src/bitcoin/driver.rs index 5171b2a..2635179 100644 --- a/side-node/src/bitcoin/driver.rs +++ b/side-node/src/bitcoin/driver.rs @@ -1,5 +1,6 @@ -use crate::bitcoin::clients; -use bdk_wallet::bitcoin::{Amount, Network}; +use bdk::bitcoin::Network; +use bdk::wallet::AddressIndex; +use bdk::SignOptions; use tracing_subscriber::filter::EnvFilter; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{fmt, layer::SubscriberExt}; @@ -7,52 +8,53 @@ use tracing_subscriber::{fmt, layer::SubscriberExt}; use super::clients::esplora::EsploraWallet; pub(crate) async fn simple_transfer() -> Result<(), anyhow::Error> { - let (mut dave, mut sammy) = setup().await?; + let (mut dave, sammy) = setup().await?; - let send_amount = Amount::from_sat(500); - ensure_enough_sats(&dave, send_amount); + let send_amount = 500; + let _ = ensure_enough_sats(&dave, send_amount); - let sammy_address = sammy.next_unused_address()?.address; + let sammy_address = sammy.wallet.get_address(AddressIndex::New)?; let tx = dave.build_and_sign_send_tx(sammy_address, send_amount)?; - dave.broadcast(&tx).await?; + dave.broadcast(&tx)?; Ok(()) } async fn setup() -> Result<(EsploraWallet, EsploraWallet), anyhow::Error> { tracing_setup(); - let mut dave = clients::esplora::create_wallet("dave", Network::Signet)?; - let mut sammy = clients::esplora::create_wallet("sammy", Network::Signet)?; - let _ = dave.balance(); - dave.sync().await?; - let _ = dave.balance(); - let _sammy = sammy.balance(); - sammy.sync().await?; - let _ = sammy.balance(); + let dave = EsploraWallet::create_wallet("dave", Network::Signet)?; + let sammy = EsploraWallet::create_wallet("sammy", Network::Signet)?; + let _ = dave.wallet.get_balance(); + dave.sync()?; + let _ = dave.wallet.get_balance(); + let _sammy = sammy.wallet.get_balance(); + sammy.sync()?; + let _ = sammy.wallet.get_balance(); Ok((dave, sammy)) } /// Exit if the wallet does not have enough sats to send. -fn ensure_enough_sats(wallet: &EsploraWallet, send_amount: bitcoin::Amount) { - if wallet.balance().total() < send_amount { +fn ensure_enough_sats(wallet: &EsploraWallet, send_amount: u64) -> anyhow::Result<()> { + if wallet.wallet.get_balance()?.get_total() < send_amount { tracing::error!( "Please send at least {} sats to the receiving address. Exiting.", send_amount ); std::process::exit(0); } + Ok(()) } pub(crate) async fn htlc() -> anyhow::Result<()> { - let (mut dave, mut sammy) = setup().await?; + let (dave, sammy) = setup().await?; // format a new commitment transaction like in Lightning - let mut commitment_builder = dave.build_tx()?; - let amount = Amount::from_sat(500); - let recipient = sammy.next_unused_address()?.script_pubkey(); + let mut commitment_builder = dave.wallet.build_tx(); + let amount = 500; + let recipient = sammy.wallet.get_address(AddressIndex::New)?.script_pubkey(); commitment_builder.add_recipient(recipient, amount); commitment_builder.enable_rbf(); - let psbt = commitment_builder + let (psbt, _) = commitment_builder .finish() .expect("unable to build commitment"); @@ -65,17 +67,17 @@ pub(crate) async fn htlc() -> anyhow::Result<()> { .expect("problem combining bitcoin PSBTs"); // these guys love mutability let finalized = dave - .wallet() - .finalize_psbt(&mut dave_psbt, bdk_wallet::SignOptions::default()) + .wallet + .finalize_psbt(&mut dave_psbt, SignOptions::default()) .expect("couldn't finalize"); assert!(finalized); - let tx = dave_psbt.extract_tx()?; + let tx = dave_psbt.extract_tx(); - let _ = dave.broadcast(&tx).await.expect("couldn't broadcast"); + let _ = dave.broadcast(&tx)?; let _ = sammy.sync(); - sammy.balance(); + let _ = sammy.wallet.get_balance(); Ok(()) } diff --git a/side-node/src/bitcoin/htlc.rs b/side-node/src/bitcoin/htlc.rs index 8feece7..8f18f0b 100644 --- a/side-node/src/bitcoin/htlc.rs +++ b/side-node/src/bitcoin/htlc.rs @@ -1,7 +1,6 @@ use std::str::FromStr; -use bdk_wallet::miniscript::descriptor::Wsh; -use bdk_wallet::miniscript::policy::{self, Concrete, Liftable}; +use bdk::miniscript::{descriptor::Wsh, policy::Concrete}; use bitcoin::Address; /// A hash time locked contract between two parties. @@ -35,8 +34,8 @@ impl Htlc { } } - pub(crate) fn to_miniscript_policy(&self) -> policy::Concrete { - Concrete::::from_str(&format!( + pub(crate) fn to_miniscript_policy(&self) -> Concrete { + Concrete::::from_str(&format!( "or(10@and(sha256({secret_hash}),pk({redeem_identity})),1@and(older({expiry}),pk({refund_identity})))", secret_hash = self.hashlock, redeem_identity = self.redeem_identity, @@ -45,7 +44,7 @@ impl Htlc { )).expect("Policy compilation only fails on resource limits or mixed timelocks") } - pub(crate) fn to_miniscript_descriptor(&self) -> Wsh { + pub(crate) fn to_miniscript_descriptor(&self) -> Wsh { Wsh::new( self.to_miniscript_policy() .compile() diff --git a/side-node/src/lib.rs b/side-node/src/lib.rs index 7fbf13f..3451d02 100644 --- a/side-node/src/lib.rs +++ b/side-node/src/lib.rs @@ -45,8 +45,9 @@ async fn setup(name: &String) -> SideNode { // First, load up the keys and create a bft-bft-crdt let side_dir = utils::home(name); let bft_crdt_keys = bft_crdt::keys::load_from_file(&side_dir); - let keys = bitcoin::keys::load_from_file(&side_dir).unwrap(); - let bitcoin_wallet = bitcoin::clients::electrum::create_wallet(keys).unwrap(); + // let keys = bitcoin::keys::load_from_file(&side_dir).unwrap(); + // let bitcoin_wallet = + // bitcoin::clients::esplora::EsploraWallet::create_wallet(name, keys).unwrap(); let crdt = BaseCrdt::::new(&bft_crdt_keys); // Channels for internal communication, and a tokio task for stdin input @@ -61,7 +62,7 @@ async fn setup(name: &String) -> SideNode { let node = SideNode::new( crdt, bft_crdt_keys, - bitcoin_wallet, + // bitcoin_wallet, incoming_receiver, stdin_receiver, handle, diff --git a/side-node/src/node.rs b/side-node/src/node.rs index 5f15105..dbce40a 100644 --- a/side-node/src/node.rs +++ b/side-node/src/node.rs @@ -1,4 +1,3 @@ -use bdk::database::MemoryDatabase; use bft_json_crdt::json_crdt::{BaseCrdt, SignedOp}; use fastcrypto::ed25519::Ed25519KeyPair; use tokio::sync::mpsc; @@ -8,7 +7,7 @@ use crate::{bft_crdt::websocket::Client, bft_crdt::TransactionList, utils}; pub struct SideNode { crdt: BaseCrdt, bft_crdt_keys: fastcrypto::ed25519::Ed25519KeyPair, - _bitcoin_wallet: bdk::Wallet, // currently not read anywhere + // _bitcoin_wallet: bdk::Wallet, // currently not read anywhere incoming_receiver: mpsc::Receiver, stdin_receiver: std::sync::mpsc::Receiver, handle: ezsockets::Client, @@ -18,7 +17,7 @@ impl SideNode { pub fn new( crdt: BaseCrdt, bft_crdt_keys: Ed25519KeyPair, - bitcoin_wallet: bdk::Wallet, + // bitcoin_wallet: bdk::Wallet, incoming_receiver: mpsc::Receiver, stdin_receiver: std::sync::mpsc::Receiver, handle: ezsockets::Client, @@ -26,7 +25,7 @@ impl SideNode { let node = Self { crdt, bft_crdt_keys, - _bitcoin_wallet: bitcoin_wallet, + // _bitcoin_wallet: bitcoin_wallet, incoming_receiver, stdin_receiver, handle, diff --git a/side-node/tests/side_node.rs b/side-node/tests/side_node.rs index 18f597e..5731d5e 100644 --- a/side-node/tests/side_node.rs +++ b/side-node/tests/side_node.rs @@ -2,9 +2,7 @@ use bft_json_crdt::{ json_crdt::{BaseCrdt, SignedOp}, keypair::make_keypair, }; -use side_node::{ - bft_crdt::websocket::Client, bft_crdt::TransactionList, bitcoin, node::SideNode, utils, -}; +use side_node::{bft_crdt::websocket::Client, bft_crdt::TransactionList, node::SideNode, utils}; use tokio::sync::mpsc; #[tokio::test] @@ -37,9 +35,9 @@ async fn test_distribute_via_websockets() { async fn setup(_: &str) -> SideNode { // First, load up the keys and create a bft-bft-crdt let bft_crdt_keys = make_keypair(); - let mnemonic_words = bitcoin::keys::make_mnemonic(); - let keys = bitcoin::keys::get(mnemonic_words).unwrap(); - let bitcoin_wallet = bitcoin::clients::electrum::create_wallet(keys).unwrap(); + // let mnemonic_words = bitcoin::keys::make_mnemonic(); + // let keys = bitcoin::keys::get(mnemonic_words).unwrap(); + // let bitcoin_wallet = bitcoin::clients::electrum::create_wallet(keys).unwrap(); let crdt = BaseCrdt::::new(&bft_crdt_keys); // Channels for internal communication, and a tokio task for stdin input @@ -51,7 +49,7 @@ async fn setup(_: &str) -> SideNode { let node = SideNode::new( crdt, bft_crdt_keys, - bitcoin_wallet, + // bitcoin_wallet, incoming_receiver, stdin_receiver, handle,