From 72d2e51ec863139e271c194fb26ed2b7f8b422bf Mon Sep 17 00:00:00 2001 From: f321x Date: Thu, 4 Jul 2024 13:53:57 +0000 Subject: [PATCH] make backend synchronous again, implement simple punishment handler --- taptrade-cli-demo/coordinator/Cargo.lock | 209 ++---------------- taptrade-cli-demo/coordinator/Cargo.toml | 6 +- .../coordinator/src/coordinator/monitoring.rs | 3 +- .../coordinator/src/wallet/mod.rs | 23 +- .../coordinator/src/wallet/verify_tx.rs | 160 ++++++++++++++ 5 files changed, 202 insertions(+), 199 deletions(-) create mode 100644 taptrade-cli-demo/coordinator/src/wallet/verify_tx.rs diff --git a/taptrade-cli-demo/coordinator/Cargo.lock b/taptrade-cli-demo/coordinator/Cargo.lock index 9ee6047..78454db 100644 --- a/taptrade-cli-demo/coordinator/Cargo.lock +++ b/taptrade-cli-demo/coordinator/Cargo.lock @@ -84,10 +84,10 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "http-body-util", - "hyper 1.3.1", + "hyper", "hyper-util", "itoa", "matchit", @@ -117,8 +117,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -179,8 +179,6 @@ dependencies = [ "bitcoin", "bitcoinconsensus", "electrum-client", - "esplora-client", - "futures", "getrandom", "js-sys", "log", @@ -224,12 +222,6 @@ 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-private" version = "0.1.0" @@ -327,7 +319,7 @@ dependencies = [ "futures-util", "hex", "rand", - "reqwest 0.12.5", + "reqwest", "serde", "sqlx", "tokio", @@ -505,19 +497,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "esplora-client" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb1f7f2489cce83bc3bd92784f9ba5271eeb6e729b975895fc541f78cbfcdca" -dependencies = [ - "bitcoin", - "bitcoin-internals", - "log", - "reqwest 0.11.27", - "serde", -] - [[package]] name = "etcetera" version = "0.8.0" @@ -592,21 +571,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.30" @@ -680,7 +644,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -728,25 +691,6 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.5" @@ -758,7 +702,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.1.0", + "http", "indexmap", "slab", "tokio", @@ -839,17 +783,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -861,17 +794,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.0" @@ -879,7 +801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -890,8 +812,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "pin-project-lite", ] @@ -907,30 +829,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.3.1" @@ -940,9 +838,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", - "http 1.1.0", - "http-body 1.0.0", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -959,8 +857,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", - "http 1.1.0", - "hyper 1.3.1", + "http", + "hyper", "hyper-util", "rustls 0.23.10", "rustls-pki-types", @@ -977,7 +875,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.3.1", + "hyper", "hyper-util", "native-tls", "tokio", @@ -994,9 +892,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", @@ -1526,43 +1424,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.29", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", - "tokio", - "tokio-socks", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg 0.50.0", -] - [[package]] name = "reqwest" version = "0.12.5" @@ -1575,11 +1436,11 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.5", - "http 1.1.0", - "http-body 1.0.0", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.3.1", + "hyper", "hyper-rustls", "hyper-tls", "hyper-util", @@ -1604,7 +1465,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.52.0", + "winreg", ] [[package]] @@ -2331,18 +2192,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-socks" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" -dependencies = [ - "either", - "futures-util", - "thiserror", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.15" @@ -2800,16 +2649,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winreg" version = "0.52.0" diff --git a/taptrade-cli-demo/coordinator/Cargo.toml b/taptrade-cli-demo/coordinator/Cargo.toml index 7bd1762..f9f4556 100644 --- a/taptrade-cli-demo/coordinator/Cargo.toml +++ b/taptrade-cli-demo/coordinator/Cargo.toml @@ -6,7 +6,11 @@ edition = "2021" [dependencies] anyhow = "1.0.86" axum = { version = "0.7.5", features = ["tokio", "json"] } -bdk = { version = "0.29.0", features = ["key-value-db", "bitcoinconsensus", "verify", "use-esplora-async"] } + +# "use-esplora-async", "async-interface", for async esplora +bdk = { version = "0.29.0", default-features = false, features = ["key-value-db", "bitcoinconsensus", "std", "electrum", "verify"] } +# bitcoinconsensus = "0.106.0" + dotenv = "0.15.0" futures-util = "0.3.30" hex = "0.4.3" diff --git a/taptrade-cli-demo/coordinator/src/coordinator/monitoring.rs b/taptrade-cli-demo/coordinator/src/coordinator/monitoring.rs index c161336..2537a0c 100644 --- a/taptrade-cli-demo/coordinator/src/coordinator/monitoring.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/monitoring.rs @@ -29,8 +29,7 @@ async fn punish_trader( // publish bond coordinator .coordinator_wallet - .publish_bond_tx_hex(bond_tx_hex) - .await?; + .publish_bond_tx_hex(&bond.bond_tx_hex)?; // can be made async with esplora backend if we figure out the compilation error of bdk // remove offer from db/orderbook Ok(()) diff --git a/taptrade-cli-demo/coordinator/src/wallet/mod.rs b/taptrade-cli-demo/coordinator/src/wallet/mod.rs index 09b8e03..11d6ce2 100644 --- a/taptrade-cli-demo/coordinator/src/wallet/mod.rs +++ b/taptrade-cli-demo/coordinator/src/wallet/mod.rs @@ -1,12 +1,12 @@ mod utils; +// pub mod verify_tx; use super::*; use anyhow::Context; use bdk::{ bitcoin::{self, bip32::ExtendedPrivKey, consensus::encode::deserialize, Transaction}, - blockchain::{Blockchain, ElectrumBlockchain, EsploraBlockchain}, - electrum_client::Client, - esplora_client::AsyncClient, + blockchain::{Blockchain, ElectrumBlockchain}, + electrum_client::client::Client, sled::{self, Tree}, template::Bip86, wallet::verify::*, @@ -15,11 +15,12 @@ use bdk::{ use std::fmt; use std::str::FromStr; use utils::*; +// use verify_tx::*; #[derive(Clone)] pub struct CoordinatorWallet { pub wallet: Arc>>, - pub backend: Arc, + pub backend: Arc, } #[derive(PartialEq, Debug)] @@ -34,11 +35,11 @@ impl CoordinatorWallet { let wallet_xprv = ExtendedPrivKey::from_str( &env::var("WALLET_XPRV").context("loading WALLET_XPRV from .env failed")?, )?; - // let backend = ElectrumBlockchain::from(Client::new( - // &env::var("ELECTRUM_BACKEND") - // .context("Parsing ELECTRUM_BACKEND from .env failed, is it set?")?, - // )?); - let backend = EsploraBlockchain::new(&env::var("ESPLORA_BACKEND")?, 1000); + let backend = ElectrumBlockchain::from(Client::new( + &env::var("ELECTRUM_BACKEND") + .context("Parsing ELECTRUM_BACKEND from .env failed, is it set?")?, + )?); + // let backend = EsploraBlockchain::new(&env::var("ESPLORA_BACKEND")?, 1000); let sled_db = sled::open(env::var("BDK_DB_PATH")?)?.open_tree("default_wallet")?; let wallet = Wallet::new( Bip86(wallet_xprv, KeychainKind::External), @@ -113,11 +114,11 @@ impl CoordinatorWallet { Ok(()) } - pub async fn publish_bond_tx_hex(&self, bond: &str) -> Result<()> { + pub fn publish_bond_tx_hex(&self, bond: &str) -> Result<()> { let blockchain = &*self.backend; let tx: Transaction = deserialize(&hex::decode(bond)?)?; - blockchain.broadcast(&tx).await?; + blockchain.broadcast(&tx)?; Ok(()) } } diff --git a/taptrade-cli-demo/coordinator/src/wallet/verify_tx.rs b/taptrade-cli-demo/coordinator/src/wallet/verify_tx.rs new file mode 100644 index 0000000..5ee2402 --- /dev/null +++ b/taptrade-cli-demo/coordinator/src/wallet/verify_tx.rs @@ -0,0 +1,160 @@ +//////////////////////////////////////////////////////////////////////////// +// copied from bdk as verify_tx does not compile with async esplora backend. +// needs to be updated in case we want to use async esplora +//////////////////////////////////////////////////////////////////////////// + +//! Verify transactions against the consensus rules + +use std::collections::HashMap; +use std::fmt; + +use bdk::bitcoin::consensus::serialize; +use bdk::bitcoin::{OutPoint, Transaction, Txid}; + +use bdk::blockchain::GetTx; +use bdk::database::Database; +// use bdk::error::Error; + +/// Verify a transaction against the consensus rules +/// +/// This function uses [`bitcoinconsensus`] to verify transactions by fetching the required data +/// either from the [`Database`] or using the [`Blockchain`]. +/// +/// Depending on the [capabilities](crate::blockchain::Blockchain::get_capabilities) of the +/// [`Blockchain`] backend, the method could fail when called with old "historical" transactions or +/// with unconfirmed transactions that have been evicted from the backend's memory. +/// +/// [`Blockchain`]: crate::blockchain::Blockchain +pub fn verify_tx_mod( + tx: &Transaction, + database: &D, + blockchain: &B, +) -> Result<(), VerifyError> { + // log::debug!("Verifying {}", tx.txid()); + + let serialized_tx = serialize(tx); + let mut tx_cache = HashMap::<_, Transaction>::new(); + + for (index, input) in tx.input.iter().enumerate() { + let prev_tx = if let Some(prev_tx) = tx_cache.get(&input.previous_output.txid) { + prev_tx.clone() + } else if let Some(prev_tx) = database.get_raw_tx(&input.previous_output.txid)? { + prev_tx + } else if let Some(prev_tx) = blockchain.get_tx(&input.previous_output.txid)? { + prev_tx + } else { + return Err(VerifyError::MissingInputTx(input.previous_output.txid)); + }; + + let spent_output = prev_tx + .output + .get(input.previous_output.vout as usize) + .ok_or(VerifyError::InvalidInput(input.previous_output))?; + + let res = bitcoinconsensus::verify( + &spent_output.script_pubkey.to_bytes(), + spent_output.value, + &serialized_tx, + index, + ); + + // Since we have a local cache we might as well cache stuff from the db, as it will very + // likely decrease latency compared to reading from disk or performing an SQL query. + tx_cache.insert(prev_tx.txid(), prev_tx); + } + + Ok(()) +} + +/// Error during validation of a tx agains the consensus rules +#[derive(Debug)] +pub enum VerifyError { + /// The transaction being spent is not available in the database or the blockchain client + MissingInputTx(Txid), + /// The transaction being spent doesn't have the requested output + InvalidInput(OutPoint), + + /// Consensus error + Consensus(bitcoinconsensus::Error), + + /// Generic error + /// + /// It has to be wrapped in a `Box` since `Error` has a variant that contains this enum + Global(Box), +} + +impl fmt::Display for VerifyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MissingInputTx(txid) => write!(f, "The transaction being spent is not available in the database or the blockchain client: {}", txid), + Self::InvalidInput(outpoint) => write!(f, "The transaction being spent doesn't have the requested output: {}", outpoint), + Self::Consensus(err) => write!(f, "Consensus error: {:?}", err), + Self::Global(err) => write!(f, "Generic error: {}", err), + } + } +} + +impl std::error::Error for VerifyError {} + +impl From for VerifyError { + fn from(other: Error) -> Self { + VerifyError::Global(Box::new(other)) + } +} +// impl_error!(bitcoinconsensus::Error, Consensus, VerifyError); + +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::database::{BatchOperations, MemoryDatabase}; +// use assert_matches::assert_matches; +// use bitcoin::consensus::encode::deserialize; +// use bitcoin::hashes::hex::FromHex; +// use bitcoin::{Transaction, Txid}; + +// struct DummyBlockchain; + +// impl GetTx for DummyBlockchain { +// fn get_tx(&self, _txid: &Txid) -> Result, Error> { +// Ok(None) +// } +// } + +// #[test] +// fn test_verify_fail_unsigned_tx() { +// // https://blockstream.info/tx/95da344585fcf2e5f7d6cbf2c3df2dcce84f9196f7a7bb901a43275cd6eb7c3f +// let prev_tx: Transaction = deserialize(&Vec::::from_hex("020000000101192dea5e66d444380e106f8e53acb171703f00d43fb6b3ae88ca5644bdb7e1000000006b48304502210098328d026ce138411f957966c1cf7f7597ccbb170f5d5655ee3e9f47b18f6999022017c3526fc9147830e1340e04934476a3d1521af5b4de4e98baf49ec4c072079e01210276f847f77ec8dd66d78affd3c318a0ed26d89dab33fa143333c207402fcec352feffffff023d0ac203000000001976a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988aca4b956050000000017a91494d5543c74a3ee98e0cf8e8caef5dc813a0f34b48768cb0700").unwrap()).unwrap(); +// // https://blockstream.info/tx/aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d +// let signed_tx: Transaction = deserialize(&Vec::::from_hex("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700").unwrap()).unwrap(); + +// let mut database = MemoryDatabase::new(); +// let blockchain = DummyBlockchain; + +// let mut unsigned_tx = signed_tx.clone(); +// for input in &mut unsigned_tx.input { +// input.script_sig = Default::default(); +// input.witness = Default::default(); +// } + +// let result = verify_tx_mod(&signed_tx, &database, &blockchain); +// assert_matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid(), +// "Error should be a `MissingInputTx` error" +// ); + +// // insert the prev_tx +// database.set_raw_tx(&prev_tx).unwrap(); + +// let result = verify_tx_mod(&unsigned_tx, &database, &blockchain); +// assert_matches!( +// result, +// Err(VerifyError::Consensus(_)), +// "Error should be a `Consensus` error" +// ); + +// let result = verify_tx_mod(&signed_tx, &database, &blockchain); +// assert!( +// result.is_ok(), +// "Should work since the TX is correctly signed" +// ); +// } +// }