diff --git a/taptrade-cli-demo/coordinator/src/wallet/mod.rs b/taptrade-cli-demo/coordinator/src/wallet/mod.rs index 027e592..cef6304 100644 --- a/taptrade-cli-demo/coordinator/src/wallet/mod.rs +++ b/taptrade-cli-demo/coordinator/src/wallet/mod.rs @@ -168,17 +168,54 @@ mod tests { #[tokio::test] async fn test_transaction_without_signature() { - panic!("Not implemented"); + let test_wallet = new_test_wallet("tprv8ZgxMBicQKsPdHuCSjhQuSZP1h6ZTeiRqREYS5guGPdtL7D1uNLpnJmb2oJep99Esq1NbNZKVJBNnD2ZhuXSK7G5eFmmcx73gsoa65e2U32").await; + let bond_without_signature = "02000000010127a9d96655011fca55dc2667f30b98655e46da98d0f84df676b53d7fb380140000000000fdffffff02998d0000000000002251207dd0d1650cdc22537709e35620f3b5cc3249b305bda1209ba4e5e01bc3ad2d8c50c3000000000000225120a12e5d145a4a3ab43f6cc1188435e74f253eace72bd986f1aaf780fd0c6532364f860000"; + let requirements = BondRequirements { + min_input_sum_sat: 51000, + locking_amount_sat: 50000, + bond_address: "tb1p5yh969z6fgatg0mvcyvggd08fujnat8890vcdud277q06rr9xgmqwfdkcx" + .to_string(), + }; + + let result = test_wallet + .validate_bond_tx_hex(&bond_without_signature, &requirements) + .await; + assert!(result.is_err()); } #[tokio::test] async fn test_transaction_with_invalid_signature() { - panic!("Not implemented"); + let test_wallet = new_test_wallet("tprv8ZgxMBicQKsPdHuCSjhQuSZP1h6ZTeiRqREYS5guGPdtL7D1uNLpnJmb2oJep99Esq1NbNZKVJBNnD2ZhuXSK7G5eFmmcx73gsoa65e2U32").await; + // assembled bond tx but with the signature of a different bond = invalid + let bond_with_invalid_signature = "020000000001010127a9d96655011fca55dc2667f30b98655e46da98d0f84df676b53d7fb3801400000000001900000002aa900000000000002251207dd0d1650cdc22537709e35620f3b5cc3249b305bda1209ba4e5e01bc3ad2d8c50c3000000000000225120a12e5d145a4a3ab43f6cc1188435e74f253eace72bd986f1aaf780fd0c65323601401fddcc681a1d0324c8fdeabbc08a3b06c26741872363c0ddfc82f15b6abe43d37815bcdc2ce1fb2f70cac426f7fb269d322ac6a621886208d0c625335bba670800000000"; + let requirements = BondRequirements { + min_input_sum_sat: 51000, + locking_amount_sat: 50000, + bond_address: "tb1p5yh969z6fgatg0mvcyvggd08fujnat8890vcdud277q06rr9xgmqwfdkcx" + .to_string(), + }; + + let result = test_wallet + .validate_bond_tx_hex(&bond_with_invalid_signature, &requirements) + .await; + assert!(result.is_err()); } #[tokio::test] - async fn test_transaction_with_spent_input() { - panic!("Not implemented"); + async fn test_bond_with_spent_input() { + let test_wallet = new_test_wallet("tprv8ZgxMBicQKsPdHuCSjhQuSZP1h6ZTeiRqREYS5guGPdtL7D1uNLpnJmb2oJep99Esq1NbNZKVJBNnD2ZhuXSK7G5eFmmcx73gsoa65e2U32").await; + let bond_with_spent_input = "02000000000101f7d992795b0b43227ea83e296a7c2a91771ede3ef54f1eb5664393c79b9399080100000000fdffffff0250c3000000000000225120a12e5d145a4a3ab43f6cc1188435e74f253eace72bd986f1aaf780fd0c653236abc6010000000000225120b83c64b440203fb74a0c672cd829f387b957129835dd3b5c4e33fc71a146b3ae0140afdafbae5b76217f469790b211d7fbda427e5b4379c4603e9ae08c9ef5aaae30bfecfc16e5f636c737bea8e0e27974854d1cd0d094ed737aadfc679a974074574f860000"; + let requirements = BondRequirements { + min_input_sum_sat: 51000, + locking_amount_sat: 50000, + bond_address: "tb1p5yh969z6fgatg0mvcyvggd08fujnat8890vcdud277q06rr9xgmqwfdkcx" + .to_string(), + }; + + let result = test_wallet + .validate_bond_tx_hex(&bond_with_spent_input, &requirements) + .await; + assert!(result.is_err()); } #[tokio::test] diff --git a/taptrade-cli-demo/coordinator/src/wallet/verify_tx.rs b/taptrade-cli-demo/coordinator/src/wallet/verify_tx.rs deleted file mode 100644 index 5ee2402..0000000 --- a/taptrade-cli-demo/coordinator/src/wallet/verify_tx.rs +++ /dev/null @@ -1,160 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// 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" -// ); -// } -// }