Removed all the bitcoin dependencies (updated the rest)

This commit is contained in:
Dave
2025-06-12 15:29:44 -04:00
parent a8a5422ea8
commit 7878bb9149
13 changed files with 5 additions and 469 deletions

View File

@@ -57,24 +57,6 @@ The Crdt Relayer replicates transactions between nodes using a websocket. We aim
Later, we will aim to remove the Crdt Relayer from the architecture, by (a) moving to pure P2P transactions between Crdt Nodes, and (b) doing leader election of a Crdt Node to reach agreement on the submitted block.
## Bitcoin integration
There is a Bitcoin client integrated into the node, which can do simple coin transfers using esplora and the Mutinynet server's Signet (30 second blocktime).
The client's demo driver can be run by doing:
```
cargo run -- init dave
cargo run -- init sammy
cargo run -- btc
```
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.
## Possible uses
### DKG

View File

@@ -1,101 +0,0 @@
use crate::{bitcoin, utils};
use bdk::bitcoin::psbt::PartiallySignedTransaction;
use bdk::bitcoin::Network;
use bdk::database::MemoryDatabase;
use bdk::keys::ExtendedKey;
use bdk::template::Bip84;
use bdk::wallet::AddressIndex::{self, New};
use bdk::wallet::AddressInfo;
use bdk::{blockchain::ElectrumBlockchain, electrum_client, SyncOptions};
use bdk::{FeeRate, KeychainKind, SignOptions, TransactionDetails, Wallet};
/// 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 = create_wallet(dave_key)?;
let sammy_wallet = 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<Wallet<MemoryDatabase>> {
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<MemoryDatabase>) {
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<MemoryDatabase>,
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()?)
}

View File

@@ -1,188 +0,0 @@
use std::{collections::BTreeSet, fs, io::Write};
use bdk::keys::bip39::Mnemonic;
use bdk_esplora::{
esplora_client::{self, AsyncClient},
EsploraAsyncExt,
};
use bdk_wallet::{
bitcoin::{Address, Amount, Network, Script},
chain::ConfirmationTimeHeightAnchor,
keys::{DerivableKey, ExtendedKey},
wallet::AddressInfo,
KeychainKind, SignOptions, 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<KeychainKind, ConfirmationTimeHeightAnchor>,
name: String,
wallet: Wallet,
}
impl EsploraWallet {
/// Builds and signs a send transaction to send coins between addresses.
///
/// Does NOT send it, you must call `broadcast` to do that.
///
/// 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<bitcoin::Transaction, anyhow::Error> {
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 finalized = self.wallet.sign(&mut psbt, SignOptions::default())?;
assert!(finalized);
let tx = psbt.extract_tx()?;
Ok(tx)
}
/// Syncs the wallet with the latest state of the Bitcoin blockchain
pub(crate) async fn sync(&mut self) -> Result<(), anyhow::Error> {
print!("Syncing...");
fn generate_inspect(
kind: KeychainKind,
) -> impl FnMut(u32, &Script) + Send + Sync + 'static {
let mut once = Some(());
let mut stdout = std::io::stdout();
move |spk_i, _| {
match once.take() {
Some(_) => print!("\nScanning keychain [{:?}]", kind),
None => print!(" {:<3}", spk_i),
};
stdout.flush().expect("must flush");
}
}
let request = self
.wallet
.start_full_scan()
.inspect_spks_for_all_keychains({
let mut once = BTreeSet::<KeychainKind>::new();
move |keychain, spk_i, _| {
match once.insert(keychain) {
true => print!("\nScanning keychain [{:?}]", keychain),
false => print!(" {:<3}", spk_i),
}
std::io::stdout().flush().expect("must flush")
}
})
.inspect_spks_for_keychain(
KeychainKind::External,
generate_inspect(KeychainKind::External),
)
.inspect_spks_for_keychain(
KeychainKind::Internal,
generate_inspect(KeychainKind::Internal),
);
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()?;
println!("Sync complete for {}", self.name);
Ok(())
}
fn persist_local(&mut self) -> Result<(), anyhow::Error> {
Ok(if let Some(changeset) = self.wallet.take_staged() {
self.db.write(&changeset)?;
})
}
/// Gets the next unused address from the wallet.
pub(crate) fn next_unused_address(&mut self) -> Result<AddressInfo, anyhow::Error> {
let address = self.wallet.next_unused_address(KeychainKind::External);
self.persist_local()?;
println!(
"Generated address: https://mutinynet.com/address/{}",
address
);
Ok(address)
}
/// Returns the balance of the wallet.
pub(crate) fn balance(&self) -> bdk_wallet::wallet::Balance {
self.wallet.balance()
}
/// Broadcasts a signed transaction to the network.
pub(crate) async fn broadcast(
&self,
tx: &bitcoin::Transaction,
) -> Result<(), esplora_client::Error> {
println!(
"{} broadcasting tx https://mutinynet.com/tx/{}",
self.name,
tx.compute_txid()
);
self.client.broadcast(tx).await
}
}
/// Creates a Bitcoin descriptor wallet with the mnemonic in the given user directory.
pub(crate) fn create_wallet(name: &str, network: Network) -> anyhow::Result<EsploraWallet> {
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");
println!("Creating 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();
println!("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)
}

View File

@@ -1,2 +0,0 @@
pub mod electrum;
pub mod esplora;

View File

@@ -1,60 +0,0 @@
use bdk_wallet::bitcoin::{Amount, Network};
use crate::bitcoin::clients;
/// Demonstrates the use of bdk with the Esplora client.
///
/// This is more complex than the bare `bdk` crate, but the esplora client works.
///
/// Also, it very handily works with the mutinynet.com esplora server, which is configured
/// with 30 second block times.
pub(crate) async fn run() -> Result<(), anyhow::Error> {
simple_transfer().await
}
async fn simple_transfer() -> Result<(), anyhow::Error> {
let mut dave = clients::esplora::create_wallet("dave", Network::Signet)?;
let mut sammy = clients::esplora::create_wallet("sammy", Network::Signet)?;
let _next_address = dave.next_unused_address()?;
let dave_balance = dave.balance();
println!(
"Dave wallet balance before syncing: {} sats",
dave_balance.total()
);
dave.sync().await?;
let dave_balance = dave.balance();
println!("Wallet balance after syncing: {} sats", dave_balance);
let sammy_address = sammy.next_unused_address()?.address;
println!("Sammy's address: {}", sammy_address);
let sammy_balance = sammy.balance();
println!(
"Sammy wallet balance before syncing: {} sats",
sammy_balance
);
sammy.sync().await?;
let sammy_balance = sammy.balance();
println!("Sammy wallet balance after syncing: {} sats", sammy_balance);
let send_amount = Amount::from_sat(500);
if dave_balance.total() < send_amount {
println!(
"Please send at least {} sats to the receiving address",
send_amount
);
std::process::exit(0);
}
let tx = dave.build_and_sign_send_tx(sammy_address, send_amount)?;
dave.broadcast(&tx).await?;
Ok(())
}

View File

@@ -1,50 +0,0 @@
use bdk::{
keys::{
bip39::{Language, Mnemonic, WordCount},
DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey,
},
miniscript,
};
use std::{
fs::{self, File},
io::Write,
path::PathBuf,
};
pub fn make_mnemonic() -> String {
let mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
Mnemonic::generate((WordCount::Words12, Language::English)).unwrap();
mnemonic.to_string()
}
/// Write the mnemonic to a file in the node's side directory
///
/// TODO: obviously spitting the mnemonic out to the console is not for production
pub(crate) fn write(mnemonic_path: &PathBuf) -> Result<(), std::io::Error> {
let mnemonic = make_mnemonic();
let mut file = File::create(mnemonic_path)?;
println!("mnemonic: {mnemonic}");
file.write(mnemonic.as_bytes())?;
Ok(())
}
/// Creates Signet Bitcoin descriptors from a mnemonic
pub fn get(mnemonic_words: String) -> anyhow::Result<ExtendedKey> {
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");
Ok(xkey)
}
pub(crate) fn load_from_file(side_dir: &PathBuf) -> anyhow::Result<ExtendedKey> {
let mnemonic_path = crate::utils::side_paths(side_dir.clone()).1; // TODO: this tuple stinks
let mnemonic_words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file");
println!("Creating wallet from mnemonic: {mnemonic_words}");
get(mnemonic_words)
}

View File

@@ -1,3 +0,0 @@
pub mod clients;
pub mod driver;
pub mod keys;

View File

@@ -7,7 +7,7 @@ pub(crate) fn parse_args() -> Args {
args
}
/// A P2P smart contract execution node
/// A P2P BFT info sharing node
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub(crate) struct Args {
@@ -17,9 +17,6 @@ pub(crate) struct Args {
#[derive(Subcommand)]
pub(crate) enum Commands {
/// Placeholder for future BTC commands
// Btc {},
/// runs the Side Node
Run { name: String },

View File

@@ -2,20 +2,17 @@ use std::path::PathBuf;
use config::SideNodeConfig;
use crate::{bft_crdt, utils}; //bitcoin
use crate::{bft_crdt, utils};
pub(crate) mod config;
pub(crate) fn init(home: PathBuf, config: SideNodeConfig) -> Result<(), std::io::Error> {
ensure_side_directory_exists(&home)?;
let (bft_crdt_key_path, bitcoin_key_path, config_path) = utils::side_paths(home.clone());
let (bft_crdt_key_path, config_path) = utils::side_paths(home.clone());
println!("Writing bft bft-crdt key to: {:?}", bft_crdt_key_path);
bft_crdt::keys::write(&bft_crdt_key_path)?;
println!("Writing bitcoin key to: {:?}", bitcoin_key_path);
// bitcoin::keys::write(&bitcoin_key_path)?;
println!("Writing config to: {:?}", config_path);
config::write_toml(&config, &config_path).expect("unable to write config file");
@@ -56,24 +53,6 @@ mod tests {
(SideNodeConfig { name: name.clone() }, name)
}
#[test]
fn creates_bitcoin_keys() {
let (config, name) = side_node_config();
let side_dir = format!("/tmp/side/{name}");
let mut bitcoin_keys_path = PathBuf::new();
bitcoin_keys_path.push(side_dir.clone());
bitcoin_keys_path.push(utils::BITCOIN_KEY_FILE);
let _ = init(PathBuf::from_str(&side_dir).unwrap(), config);
assert!(bitcoin_keys_path.exists());
// check that the pem is readable
// let data = fs::read_to_string(bitcoin_keys_path).expect("couldn't read key file");
// let keys = Ed25519KeyPair::decode_base64(&data).expect("couldn't load keypair from file");
// assert_eq!(keys.public().as_bytes().len(), 32);
}
#[test]
fn creates_side_node_directory() {
let (config, name) = side_node_config();

View File

@@ -6,7 +6,6 @@ use node::SideNode;
use tokio::{sync::mpsc, task};
pub mod bft_crdt;
// pub mod bitcoin;
pub(crate) mod cli;
pub(crate) mod init;
pub mod node;
@@ -28,9 +27,6 @@ pub async fn run() {
let mut node = setup(name).await;
node.start().await;
}
// Some(Commands::Btc {}) => {
// let _ = bitcoin::driver::run().await;
// }
None => println!("No command provided. Exiting. See --help for more information."),
}
}
@@ -40,8 +36,6 @@ 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 crdt = BaseCrdt::<TransactionList>::new(&bft_crdt_keys);
// Channels for internal communication, and a tokio task for stdin input
@@ -56,7 +50,6 @@ async fn setup(name: &String) -> SideNode {
let node = SideNode::new(
crdt,
bft_crdt_keys,
// bitcoin_wallet,
incoming_receiver,
stdin_receiver,
handle,

View File

@@ -8,7 +8,6 @@ use crate::{bft_crdt::websocket::Client, bft_crdt::TransactionList, utils};
pub struct SideNode {
crdt: BaseCrdt<TransactionList>,
bft_crdt_keys: fastcrypto::ed25519::Ed25519KeyPair,
// _bitcoin_wallet: bdk::Wallet<MemoryDatabase>, // currently not read anywhere
incoming_receiver: mpsc::Receiver<SignedOp>,
stdin_receiver: std::sync::mpsc::Receiver<String>,
handle: ezsockets::Client<Client>,
@@ -18,7 +17,6 @@ impl SideNode {
pub fn new(
crdt: BaseCrdt<TransactionList>,
bft_crdt_keys: Ed25519KeyPair,
// bitcoin_wallet: bdk::Wallet<MemoryDatabase>,
incoming_receiver: mpsc::Receiver<SignedOp>,
stdin_receiver: std::sync::mpsc::Receiver<String>,
handle: ezsockets::Client<Client>,
@@ -26,7 +24,6 @@ impl SideNode {
let node = Self {
crdt,
bft_crdt_keys,
// _bitcoin_wallet: bitcoin_wallet,
incoming_receiver,
stdin_receiver,
handle,

View File

@@ -2,22 +2,18 @@ use bft_json_crdt::json_crdt::SignedOp;
use serde_json::{json, Value};
use std::path::PathBuf;
pub(crate) const BITCOIN_KEY_FILE: &str = "bitcoin_keys.pem";
pub(crate) const BFT_CRDT_KEY_FILE: &str = "keys.pem";
pub(crate) const CONFIG_FILE: &str = "config.toml";
/// Returns the path to the key file and config for this host OS.
pub(crate) fn side_paths(prefix: PathBuf) -> (PathBuf, PathBuf, PathBuf) {
pub(crate) fn side_paths(prefix: PathBuf) -> (PathBuf, PathBuf) {
let mut bft_crdt_key_path = prefix.clone();
bft_crdt_key_path.push(BFT_CRDT_KEY_FILE);
let mut bitcoin_key_path = prefix.clone();
bitcoin_key_path.push(BITCOIN_KEY_FILE);
let mut config_path = prefix.clone();
config_path.push(CONFIG_FILE);
(bft_crdt_key_path, bitcoin_key_path, config_path)
(bft_crdt_key_path, config_path)
}
/// Returns the path to the home directory for this host OS and the given node name

View File

@@ -35,9 +35,6 @@ 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 crdt = BaseCrdt::<TransactionList>::new(&bft_crdt_keys);
// Channels for internal communication, and a tokio task for stdin input
@@ -49,7 +46,6 @@ async fn setup(_: &str) -> SideNode {
let node = SideNode::new(
crdt,
bft_crdt_keys,
// bitcoin_wallet,
incoming_receiver,
stdin_receiver,
handle,