diff --git a/application/comit_node/src/comit_client/mod.rs b/application/comit_node/src/comit_client/mod.rs index 0d076e6b43..9bc9a27f48 100644 --- a/application/comit_node/src/comit_client/mod.rs +++ b/application/comit_node/src/comit_client/mod.rs @@ -7,7 +7,7 @@ use std::{io, net::SocketAddr, sync::Arc}; use std::{fmt::Debug, panic::RefUnwindSafe}; use swap_protocols::{bam_types, rfc003}; -pub trait Client: Send + Sync { +pub trait Client: Send + Sync + 'static { fn send_swap_request< SL: rfc003::Ledger, TL: rfc003::Ledger, diff --git a/application/comit_node/src/http_api/rfc003/swap.rs b/application/comit_node/src/http_api/rfc003/swap.rs index e932e271d6..9ea5a6ad88 100644 --- a/application/comit_node/src/http_api/rfc003/swap.rs +++ b/application/comit_node/src/http_api/rfc003/swap.rs @@ -13,8 +13,9 @@ use swap_protocols::{ ledger::{Bitcoin, Ethereum}, rfc003::{ self, bitcoin, + roles::{Alice, Bob}, state_store::{self, StateStore}, - Ledger, Secret, SecretHash, + Ledger, Secret, }, Assets, Ledgers, Metadata, MetadataStore, Roles, }; @@ -290,14 +291,16 @@ fn handle_state_for_get_swap, S: state_store::StateStor target_asset: Assets::Ether, role, }) => match role { - Roles::Alice => match state_store - .get::(id) - { - Err(e) => error!("Could not retrieve state: {:?}", e), - Ok(state) => info!("Here is the state we have retrieved: {:?}", state), - }, + Roles::Alice => { + match state_store + .get::>(id) + { + Err(e) => error!("Could not retrieve state: {:?}", e), + Ok(state) => info!("Here is the state we have retrieved: {:?}", state), + } + } Roles::Bob => match state_store - .get::(id) + .get::>(id) { Err(e) => error!("Could not retrieve state: {:?}", e), Ok(state) => info!("Here is the state we have retrieved: {:?}", state), diff --git a/application/comit_node/src/swap_protocols/rfc003/actions/alice/btc_eth.rs b/application/comit_node/src/swap_protocols/rfc003/actions/alice/btc_eth.rs index 9fca47ed13..fe79fe46af 100644 --- a/application/comit_node/src/swap_protocols/rfc003/actions/alice/btc_eth.rs +++ b/application/comit_node/src/swap_protocols/rfc003/actions/alice/btc_eth.rs @@ -8,13 +8,12 @@ use swap_protocols::{ ethereum::EtherRedeem, Action, StateActions, }, - bitcoin::{bitcoin_htlc, bitcoin_htlc_address}, + roles::Alice, state_machine::*, - Secret, }, }; -impl StateActions for SwapStates { +impl StateActions for SwapStates> { type Accept = (); type Decline = (); type Fund = BitcoinFund; @@ -26,7 +25,7 @@ impl StateActions for SwapStates vec![], SS::Accepted(Accepted { ref swap, .. }) => vec![Action::Fund(BitcoinFund { - address: bitcoin_htlc_address(swap), + address: swap.source_htlc_params().compute_address(), value: swap.source_asset, })], SS::SourceFunded { .. } => vec![], @@ -44,7 +43,7 @@ impl StateActions for SwapStates vec![Action::Refund(BitcoinRefund { outpoint: *source_htlc_location, - htlc: bitcoin_htlc(swap), + htlc: swap.source_htlc_params().into(), value: swap.source_asset, transient_keypair: swap.source_ledger_refund_identity, })], diff --git a/application/comit_node/src/swap_protocols/rfc003/actions/bob/btc_eth.rs b/application/comit_node/src/swap_protocols/rfc003/actions/bob/btc_eth.rs index 418d7e6f1e..ea9b73dd3a 100644 --- a/application/comit_node/src/swap_protocols/rfc003/actions/bob/btc_eth.rs +++ b/application/comit_node/src/swap_protocols/rfc003/actions/bob/btc_eth.rs @@ -8,14 +8,13 @@ use swap_protocols::{ ethereum::{EtherDeploy, EtherRefund}, Accept, Action, Decline, StateActions, }, - bitcoin::bitcoin_htlc, - ethereum::ethereum_htlc, + ethereum::{EtherHtlc, Htlc}, + roles::Bob, state_machine::*, - SecretHash, }, }; -impl StateActions for SwapStates { +impl StateActions for SwapStates> { type Accept = Accept; type Decline = Decline; type Fund = EtherDeploy; @@ -28,7 +27,7 @@ impl StateActions for SwapStates vec![Action::Accept(Accept), Action::Decline(Decline)], SS::Accepted { .. } => vec![], SS::SourceFunded(SourceFunded { ref swap, .. }) => { - let htlc = ethereum_htlc(swap); + let htlc: EtherHtlc = swap.target_htlc_params().into(); vec![Action::Fund(EtherDeploy { data: htlc.compile_to_hex().into(), value: swap.target_asset, @@ -68,9 +67,9 @@ impl StateActions for SwapStates vec![Action::Redeem(BitcoinRedeem { outpoint: *source_htlc_location, - htlc: bitcoin_htlc(swap), + htlc: swap.source_htlc_params().into(), value: swap.source_asset, - transient_keypair: swap.source_ledger_refund_identity, + transient_keypair: swap.source_ledger_success_identity, secret: *secret, })], SS::Error(_) => vec![], @@ -84,16 +83,14 @@ mod tests { use super::*; use bitcoin_support; - use hex; - use secp256k1_support; - use swap_protocols::rfc003::Secret; + use hex::FromHex; + use swap_protocols::rfc003::{roles::test::Bobisha, Secret}; #[test] fn given_state_instance_when_calling_actions_should_not_need_to_specify_type_arguments() { - let swap_state = SwapStates::from(Start { - source_ledger_refund_identity: secp256k1_support::KeyPair::from_secret_key_slice( - &hex::decode("18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725") - .unwrap(), + let swap_state = SwapStates::from(Start:: { + source_ledger_refund_identity: bitcoin_support::PubkeyHash::from_hex( + "875638cac0b0ae9f826575e190f2788918c354c2", ) .unwrap(), target_ledger_success_identity: "8457037fcd80a8650c4692d7fcfc1d0a96b92867" diff --git a/application/comit_node/src/swap_protocols/rfc003/alice/handler.rs b/application/comit_node/src/swap_protocols/rfc003/alice/handler.rs index 0878682f29..f7c84635d0 100644 --- a/application/comit_node/src/swap_protocols/rfc003/alice/handler.rs +++ b/application/comit_node/src/swap_protocols/rfc003/alice/handler.rs @@ -15,9 +15,10 @@ use swap_protocols::{ self, alice::SwapRequests, messages::Request, + roles::Alice, state_machine::{Start, SwapStates}, state_store::StateStore, - ExtractSecret, Ledger, Secret, + Ledger, Secret, }, }; use swaps::{alice_events, common::SwapId}; @@ -123,11 +124,9 @@ impl< fn spawn_state_machine>( id: SwapId, - start_state: Start, + start_state: Start>, state_store: &S, -) where - TL::Transaction: ExtractSecret, -{ +) { let state = SwapStates::Start(start_state); // TODO: spawn state machine from state here diff --git a/application/comit_node/src/swap_protocols/rfc003/bitcoin/extract_secret.rs b/application/comit_node/src/swap_protocols/rfc003/bitcoin/extract_secret.rs index 51f463e327..61e2c50d00 100644 --- a/application/comit_node/src/swap_protocols/rfc003/bitcoin/extract_secret.rs +++ b/application/comit_node/src/swap_protocols/rfc003/bitcoin/extract_secret.rs @@ -1,9 +1,15 @@ use bitcoin_support::Transaction; -use swap_protocols::rfc003::secret::{ExtractSecret, Secret, SecretHash}; +use swap_protocols::{ + ledger::Bitcoin, + rfc003::{ + secret::{Secret, SecretHash}, + ExtractSecret, + }, +}; -impl ExtractSecret for Transaction { - fn extract_secret(&self, secret_hash: &SecretHash) -> Option { - self.input.iter().find_map(|txin| { +impl ExtractSecret for Bitcoin { + fn extract_secret(transaction: &Transaction, secret_hash: &SecretHash) -> Option { + transaction.input.iter().find_map(|txin| { txin.witness .iter() .find_map(|script_item| match Secret::from_vec(&script_item) { @@ -50,7 +56,7 @@ mod test { let secret = Secret::from(*b"This is our favourite passphrase"); let transaction = setup(&secret); - assert_that!(transaction.extract_secret(&secret.hash())) + assert_that!(Bitcoin::extract_secret(&transaction, &secret.hash())) .is_some() .is_equal_to(&secret); } @@ -65,7 +71,7 @@ mod test { bfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbf", ) .unwrap(); - assert_that!(transaction.extract_secret(&secret_hash)).is_none(); + assert_that!(Bitcoin::extract_secret(&transaction, &secret_hash)).is_none(); } #[test] @@ -77,7 +83,7 @@ mod test { .unwrap(); let secret = Secret::from_vec(&hex_secret).unwrap(); - assert_that!(transaction.extract_secret(&secret.hash())) + assert_that!(Bitcoin::extract_secret(&transaction, &secret.hash())) .is_some() .is_equal_to(&secret); } diff --git a/application/comit_node/src/swap_protocols/rfc003/bitcoin/mod.rs b/application/comit_node/src/swap_protocols/rfc003/bitcoin/mod.rs index f555c4e2da..a8a0c93339 100644 --- a/application/comit_node/src/swap_protocols/rfc003/bitcoin/mod.rs +++ b/application/comit_node/src/swap_protocols/rfc003/bitcoin/mod.rs @@ -1,19 +1,19 @@ use bitcoin_support::{Address, BitcoinQuantity, Blocks, OutPoint}; use secp256k1_support::KeyPair; -use swap_protocols::{ledger::Bitcoin, rfc003::Ledger}; +use swap_protocols::{ + ledger::Bitcoin, + rfc003::{state_machine::HtlcParams, Ledger}, +}; mod extract_secret; mod htlc; mod queries; +mod validation; pub use self::{ htlc::{Htlc, UnlockingError}, queries::*, }; -use swap_protocols::{ - asset::Asset, - rfc003::{state_machine::OngoingSwap, IntoSecretHash}, -}; impl Ledger for Bitcoin { type LockDuration = Blocks; @@ -21,19 +21,19 @@ impl Ledger for Bitcoin { type HtlcIdentity = KeyPair; } -pub fn bitcoin_htlc( - swap: &OngoingSwap, -) -> Htlc { - Htlc::new( - swap.source_ledger_success_identity, - swap.source_ledger_refund_identity, - swap.secret.clone().into(), - swap.source_ledger_lock_duration.into(), - ) +impl From> for Htlc { + fn from(htlc_params: HtlcParams) -> Self { + Htlc::new( + htlc_params.success_identity, + htlc_params.refund_identity, + htlc_params.secret_hash, + htlc_params.lock_duration.into(), + ) + } } -pub fn bitcoin_htlc_address( - swap: &OngoingSwap, -) -> Address { - bitcoin_htlc(swap).compute_address(swap.source_ledger.network) +impl HtlcParams { + pub fn compute_address(&self) -> Address { + Htlc::from(self.clone()).compute_address(self.ledger.network) + } } diff --git a/application/comit_node/src/swap_protocols/rfc003/bitcoin/queries.rs b/application/comit_node/src/swap_protocols/rfc003/bitcoin/queries.rs index 533a24e98d..f86424f84d 100644 --- a/application/comit_node/src/swap_protocols/rfc003/bitcoin/queries.rs +++ b/application/comit_node/src/swap_protocols/rfc003/bitcoin/queries.rs @@ -1,73 +1,44 @@ use bitcoin_support::{BitcoinQuantity, OutPoint}; use ledger_query_service::BitcoinQuery; use swap_protocols::{ - asset::Asset, ledger::Bitcoin, rfc003::{ - bitcoin::bitcoin_htlc_address, - events::{ - NewSourceHtlcFundedQuery, NewSourceHtlcRedeemedQuery, NewSourceHtlcRefundedQuery, - }, - state_machine::OngoingSwap, - IntoSecretHash, Ledger, + events::{NewHtlcFundedQuery, NewHtlcRedeemedQuery, NewHtlcRefundedQuery}, + state_machine::HtlcParams, }, }; -impl NewSourceHtlcFundedQuery for BitcoinQuery -where - TL: Ledger, - TA: Asset, - S: IntoSecretHash, -{ - fn new_source_htlc_funded_query( - swap: &OngoingSwap, - ) -> Self { +impl NewHtlcFundedQuery for BitcoinQuery { + fn new_htlc_funded_query(htlc_params: &HtlcParams) -> Self { BitcoinQuery::Transaction { - to_address: Some(bitcoin_htlc_address(swap)), + to_address: Some(htlc_params.compute_address()), from_outpoint: None, unlock_script: None, } } } -impl NewSourceHtlcRefundedQuery for BitcoinQuery -where - TL: Ledger, - TA: Asset, - S: IntoSecretHash, -{ - fn new_source_htlc_refunded_query( - swap: &OngoingSwap, - source_htlc_location: &OutPoint, +impl NewHtlcRefundedQuery for BitcoinQuery { + fn new_htlc_refunded_query( + _htlc_params: &HtlcParams, + htlc_location: &OutPoint, ) -> Self { BitcoinQuery::Transaction { to_address: None, - from_outpoint: Some(source_htlc_location.clone()), - unlock_script: Some(vec![ - swap.source_ledger_refund_identity - .public_key() - .inner() - .serialize() - .to_vec(), - vec![0u8], - ]), + from_outpoint: Some(*htlc_location), + unlock_script: Some(vec![vec![0u8]]), } } } -impl NewSourceHtlcRedeemedQuery for BitcoinQuery -where - TL: Ledger, - TA: Asset, - S: IntoSecretHash, -{ - fn new_source_htlc_redeemed_query( - _swap: &OngoingSwap, - source_htlc_location: &OutPoint, +impl NewHtlcRedeemedQuery for BitcoinQuery { + fn new_htlc_redeemed_query( + _htlc_params: &HtlcParams, + htlc_location: &OutPoint, ) -> Self { BitcoinQuery::Transaction { to_address: None, - from_outpoint: Some(source_htlc_location.clone()), + from_outpoint: Some(*htlc_location), unlock_script: Some(vec![vec![1u8]]), } } diff --git a/application/comit_node/src/swap_protocols/rfc003/bitcoin/validation.rs b/application/comit_node/src/swap_protocols/rfc003/bitcoin/validation.rs new file mode 100644 index 0000000000..8439b1adcc --- /dev/null +++ b/application/comit_node/src/swap_protocols/rfc003/bitcoin/validation.rs @@ -0,0 +1,154 @@ +use bitcoin_support::{BitcoinQuantity, FindOutput, OutPoint, Transaction}; +use swap_protocols::{ + ledger::Bitcoin, + rfc003::{ + is_contained_in_transaction::{Error, IsContainedInTransaction}, + state_machine::HtlcParams, + }, +}; + +impl IsContainedInTransaction for BitcoinQuantity { + fn is_contained_in_transaction( + htlc_params: &HtlcParams, + transaction: Transaction, + ) -> Result> { + let address = htlc_params.compute_address(); + + let (vout, txout) = transaction + .find_output(&address) + .ok_or(Error::WrongTransaction)?; + + let location = OutPoint { + txid: transaction.txid(), + vout: vout as u32, + }; + + let actual_value = BitcoinQuantity::from_satoshi(txout.value); + let required_value = htlc_params.asset; + + debug!("Value of HTLC at {:?} is {}", location, actual_value); + + let has_enough_money = actual_value >= required_value; + + trace!( + "{} >= {} -> {}", + actual_value, + required_value, + has_enough_money + ); + if has_enough_money { + Ok(location) + } else { + Err(Error::UnexpectedAsset { + found: actual_value, + expected: required_value, + }) + } + } +} + +#[cfg(test)] +mod tests { + extern crate bitcoin_support; + + use super::{Error as ValidationError, *}; + use bitcoin_support::{BitcoinQuantity, Blocks, Transaction, TxOut}; + use hex::FromHex; + use spectral::prelude::*; + use swap_protocols::rfc003::{state_machine::*, Secret}; + + fn gen_htlc_params(bitcoin_amount: BitcoinQuantity) -> HtlcParams { + HtlcParams { + asset: bitcoin_amount, + ledger: Bitcoin::regtest(), + success_identity: bitcoin_support::PubkeyHash::from_hex( + "d38e554430c4035f2877a579a07a99886153f071", + ) + .unwrap(), + refund_identity: bitcoin_support::PubkeyHash::from_hex( + "d38e554430c4035f2877a579a07a99886153f072", + ) + .unwrap(), + lock_duration: Blocks::from(144), + secret_hash: Secret::from(*b"hello world, you are beautiful!!").into(), + } + } + + #[test] + fn transaction_contains_output_with_sufficient_money() { + let bitcoin_amount = BitcoinQuantity::from_bitcoin(1.0); + let htlc_params = gen_htlc_params(bitcoin_amount); + let script_pubkey = htlc_params.compute_address().script_pubkey(); + + let transaction_output = TxOut { + value: htlc_params.asset.satoshi(), + script_pubkey, + }; + + let transaction = Transaction { + version: 1, + lock_time: 42, + input: vec![], + output: vec![transaction_output], + }; + + let bitcoin_transaction: Transaction = transaction.into(); + + let result = + BitcoinQuantity::is_contained_in_transaction(&htlc_params, bitcoin_transaction.clone()); + + let txid = bitcoin_transaction.txid(); + let expected_outpoint = OutPoint { txid, vout: 0 }; + + assert_that(&result).is_ok_containing(expected_outpoint) + } + + #[test] + fn transaction_does_not_contain_output() { + let bitcoin_amount = BitcoinQuantity::from_bitcoin(1.0); + let transaction = Transaction { + version: 1, + lock_time: 42, + input: vec![], + output: vec![], + }; + + let result = BitcoinQuantity::is_contained_in_transaction( + &gen_htlc_params(bitcoin_amount), + transaction.into(), + ); + + assert_that(&result).is_err_containing(ValidationError::WrongTransaction) + } + + #[test] + fn transaction_does_not_contain_enough_money() { + let bitcoin_amount = BitcoinQuantity::from_bitcoin(1.0); + let htlc_params = gen_htlc_params(bitcoin_amount); + + let script_pubkey = htlc_params.compute_address().script_pubkey(); + + let provided_bitcoin_amount = BitcoinQuantity::from_bitcoin(0.5); + + let transaction_output = TxOut { + value: provided_bitcoin_amount.satoshi(), + script_pubkey, + }; + + let transaction = Transaction { + version: 1, + lock_time: 42, + input: vec![], + output: vec![transaction_output], + }; + + let result = BitcoinQuantity::is_contained_in_transaction(&htlc_params, transaction.into()); + + let expected_error = ValidationError::UnexpectedAsset { + found: provided_bitcoin_amount, + expected: bitcoin_amount, + }; + + assert_that(&result).is_err_containing(expected_error) + } +} diff --git a/application/comit_node/src/swap_protocols/rfc003/ethereum/extract_secret.rs b/application/comit_node/src/swap_protocols/rfc003/ethereum/extract_secret.rs index 3c8d77d997..b340d4d77c 100644 --- a/application/comit_node/src/swap_protocols/rfc003/ethereum/extract_secret.rs +++ b/application/comit_node/src/swap_protocols/rfc003/ethereum/extract_secret.rs @@ -1,12 +1,18 @@ use ethereum_support::Transaction; -use swap_protocols::rfc003::secret::{ExtractSecret, Secret, SecretHash}; +use swap_protocols::{ + ledger::Ethereum, + rfc003::{ + secret::{Secret, SecretHash}, + ExtractSecret, + }, +}; -impl ExtractSecret for Transaction { - fn extract_secret(&self, secret_hash: &SecretHash) -> Option { - let data = &self.input.0; +impl ExtractSecret for Ethereum { + fn extract_secret(transaction: &Transaction, secret_hash: &SecretHash) -> Option { + let data = &transaction.input.0; info!( "Attempting to extract secret for {:?} from transaction {:?}", - secret_hash, self.hash + secret_hash, transaction.hash ); match Secret::from_vec(&data) { Ok(secret) => match secret.hash() == *secret_hash { @@ -14,7 +20,7 @@ impl ExtractSecret for Transaction { false => { error!( "Input ({:?}) in transaction {:?} is NOT the pre-image to {:?}", - data, self.hash, secret_hash + data, transaction.hash, secret_hash ); None } @@ -57,7 +63,7 @@ mod test { let secret = Secret::from(*b"This is our favourite passphrase"); let transaction = setup(&secret); - assert_that!(transaction.extract_secret(&secret.hash())) + assert_that!(Ethereum::extract_secret(&transaction, &secret.hash())) .is_some() .is_equal_to(&secret); } @@ -72,6 +78,6 @@ mod test { bfbfbfbfbfbfbfbfbfbfbfbfbfbfbfbf", ) .unwrap(); - assert_that!(transaction.extract_secret(&secret_hash)).is_none(); + assert_that!(Ethereum::extract_secret(&transaction, &secret_hash)).is_none(); } } diff --git a/application/comit_node/src/swap_protocols/rfc003/ethereum/mod.rs b/application/comit_node/src/swap_protocols/rfc003/ethereum/mod.rs index 368df86462..8d5d0c5adf 100644 --- a/application/comit_node/src/swap_protocols/rfc003/ethereum/mod.rs +++ b/application/comit_node/src/swap_protocols/rfc003/ethereum/mod.rs @@ -1,19 +1,17 @@ use ethereum_support::Bytes; use hex; +use swap_protocols::rfc003::state_machine::HtlcParams; pub use self::{erc20_htlc::*, ether_htlc::*, queries::*}; use ethereum_support::{web3::types::Address, EtherQuantity}; use std::time::Duration; -use swap_protocols::{ - asset::Asset, - ledger::Ethereum, - rfc003::{state_machine::OngoingSwap, IntoSecretHash, Ledger}, -}; +use swap_protocols::{ledger::Ethereum, rfc003::Ledger}; mod erc20_htlc; mod ether_htlc; mod extract_secret; mod queries; +mod validation; #[derive(Deserialize, Serialize, Debug)] pub struct ByteCode(pub String); @@ -49,13 +47,13 @@ impl Ledger for Ethereum { type HtlcIdentity = Address; } -pub fn ethereum_htlc( - swap: &OngoingSwap, -) -> Box { - Box::new(EtherHtlc::new( - swap.target_ledger_lock_duration, - swap.target_ledger_refund_identity, - swap.target_ledger_success_identity, - swap.secret.clone().into(), - )) +impl From> for EtherHtlc { + fn from(htlc_params: HtlcParams) -> Self { + EtherHtlc::new( + htlc_params.lock_duration, + htlc_params.refund_identity, + htlc_params.success_identity, + htlc_params.secret_hash, + ) + } } diff --git a/application/comit_node/src/swap_protocols/rfc003/ethereum/queries.rs b/application/comit_node/src/swap_protocols/rfc003/ethereum/queries.rs index e829f075be..acd5c83709 100644 --- a/application/comit_node/src/swap_protocols/rfc003/ethereum/queries.rs +++ b/application/comit_node/src/swap_protocols/rfc003/ethereum/queries.rs @@ -1,47 +1,35 @@ use ethereum_support::{web3::types::Address, EtherQuantity}; use ledger_query_service::EthereumQuery; use swap_protocols::{ - asset::Asset, ledger::Ethereum, rfc003::{ - events::{NewTargetHtlcRedeemedQuery, NewTargetHtlcRefundedQuery}, - state_machine::OngoingSwap, - IntoSecretHash, Ledger, + events::{NewHtlcRedeemedQuery, NewHtlcRefundedQuery}, + state_machine::HtlcParams, }, }; -impl NewTargetHtlcRefundedQuery for EthereumQuery -where - SL: Ledger, - SA: Asset, - S: IntoSecretHash, -{ - fn new_target_htlc_refunded_query( - _swap: &OngoingSwap, - target_htlc_location: &Address, +impl NewHtlcRefundedQuery for EthereumQuery { + fn new_htlc_refunded_query( + _htlc_params: &HtlcParams, + htlc_location: &Address, ) -> Self { EthereumQuery::Transaction { from_address: None, - to_address: Some(target_htlc_location.clone()), + to_address: Some(htlc_location.clone()), is_contract_creation: Some(false), transaction_data: None, } } } -impl NewTargetHtlcRedeemedQuery for EthereumQuery -where - SL: Ledger, - SA: Asset, - S: IntoSecretHash, -{ - fn new_target_htlc_redeemed_query( - _swap: &OngoingSwap, - target_htlc_location: &Address, +impl NewHtlcRedeemedQuery for EthereumQuery { + fn new_htlc_redeemed_query( + _htlc_params: &HtlcParams, + htlc_location: &Address, ) -> Self { EthereumQuery::Transaction { from_address: None, - to_address: Some(target_htlc_location.clone()), + to_address: Some(htlc_location.clone()), is_contract_creation: Some(false), transaction_data: None, } diff --git a/application/comit_node/src/swap_protocols/rfc003/ethereum/validation.rs b/application/comit_node/src/swap_protocols/rfc003/ethereum/validation.rs new file mode 100644 index 0000000000..69e6c14440 --- /dev/null +++ b/application/comit_node/src/swap_protocols/rfc003/ethereum/validation.rs @@ -0,0 +1,35 @@ +use ethereum_support::{self, CalculateContractAddress, EtherQuantity}; +use swap_protocols::{ + ledger::Ethereum, + rfc003::{ + ethereum::{EtherHtlc, Htlc}, + is_contained_in_transaction::{Error, IsContainedInTransaction}, + state_machine::HtlcParams, + }, +}; + +impl IsContainedInTransaction for EtherQuantity { + fn is_contained_in_transaction( + htlc_params: &HtlcParams, + tx: ethereum_support::Transaction, + ) -> Result> { + if tx.to != None { + return Err(Error::WrongTransaction); + } + + if tx.input != EtherHtlc::from(htlc_params.clone()).compile_to_hex().into() { + return Err(Error::WrongTransaction); + } + + if tx.value < htlc_params.asset.wei() { + return Err(Error::UnexpectedAsset { + found: EtherQuantity::from_wei(tx.value), + expected: htlc_params.asset, + }); + } + + let from_address: ethereum_support::Address = tx.from; + + Ok(from_address.calculate_contract_address(&tx.nonce)) + } +} diff --git a/application/comit_node/src/swap_protocols/rfc003/events/default.rs b/application/comit_node/src/swap_protocols/rfc003/events/lqs.rs similarity index 52% rename from application/comit_node/src/swap_protocols/rfc003/events/default.rs rename to application/comit_node/src/swap_protocols/rfc003/events/lqs.rs index 52e4672919..621214c936 100644 --- a/application/comit_node/src/swap_protocols/rfc003/events/default.rs +++ b/application/comit_node/src/swap_protocols/rfc003/events/lqs.rs @@ -1,42 +1,27 @@ -use comit_client::Client; use futures::{future::Either, Future}; use ledger_query_service::{CreateQuery, FirstMatch, Query, QueryIdCache}; -use std::sync::Arc; use swap_protocols::{ asset::Asset, rfc003::{ self, events::{ - Events, Funded, NewSourceHtlcFundedQuery, NewSourceHtlcRedeemedQuery, - NewSourceHtlcRefundedQuery, NewTargetHtlcFundedQuery, NewTargetHtlcRedeemedQuery, - NewTargetHtlcRefundedQuery, RedeemedOrRefunded, RequestResponded, Response, - SourceHtlcFunded, SourceHtlcRedeemedOrRefunded, SourceHtlcRefundedTargetHtlcFunded, - SourceRefundedOrTargetFunded, TargetHtlcRedeemedOrRefunded, + Funded, LedgerEvents, NewHtlcFundedQuery, NewHtlcRedeemedQuery, NewHtlcRefundedQuery, + RedeemedOrRefunded, SourceRefundedOrTargetFunded, }, - messages::Request, - state_machine::OngoingSwap, - validation::{IsContainedInSourceLedgerTransaction, IsContainedInTargetLedgerTransaction}, - IntoSecretHash, Ledger, + is_contained_in_transaction::IsContainedInTransaction, + state_machine::HtlcParams, + Ledger, }, }; -#[derive(Debug)] -pub enum Role { - Alice { client: Arc }, - Bob, -} - #[allow(missing_debug_implementations)] -pub struct DefaultEvents { - role: Role, - +pub struct LqsEvents { create_source_ledger_query: QueryIdCache, source_ledger_first_match: FirstMatch, create_target_ledger_query: QueryIdCache, target_ledger_first_match: FirstMatch, - response: Option>>, source_htlc_funded_query: Option>>, source_htlc_refunded_target_htlc_funded_query: Option>>, @@ -44,66 +29,26 @@ pub struct DefaultEvents>>, } -impl RequestResponded - for DefaultEvents -where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - ComitClient: Client, - SLQuery: Query, - TLQuery: Query, -{ - fn request_responded( - &mut self, - request: &Request, - ) -> &mut Box> { - match self.role { - Role::Alice {ref client}=> { - let client = client.clone(); - - self.response.get_or_insert_with(|| { - Box::new( - client - .send_swap_request(request.clone()) - .map_err(rfc003::Error::SwapResponse), - ) - }) - }, - Role::Bob => { - unimplemented!("return a future that resolves once the user sent a response to the COMIT node via the API") - } - } - } -} - -impl SourceHtlcFunded - for DefaultEvents +impl LedgerEvents + for LqsEvents where SL: Ledger, TL: Ledger, - SA: Asset + IsContainedInSourceLedgerTransaction, - TA: Asset, - S: IntoSecretHash, - ComitClient: Client, + SA: Asset + IsContainedInTransaction, + TA: Asset + IsContainedInTransaction, SLQuery: Query - + NewSourceHtlcFundedQuery - + NewSourceHtlcRefundedQuery - + NewSourceHtlcRedeemedQuery, + + NewHtlcRefundedQuery + + NewHtlcFundedQuery + + NewHtlcRedeemedQuery, TLQuery: Query - + NewTargetHtlcFundedQuery - + NewTargetHtlcRefundedQuery - + NewTargetHtlcRedeemedQuery, + + NewHtlcRefundedQuery + + NewHtlcFundedQuery + + NewHtlcRedeemedQuery, { - fn source_htlc_funded<'s>( - &'s mut self, - swap: &OngoingSwap, - ) -> &'s mut Box> { - let swap = swap.clone(); + fn source_htlc_funded(&mut self, htlc_params: HtlcParams) -> &mut Funded { let source_ledger_first_match = self.source_ledger_first_match.clone(); - let query = SLQuery::new_source_htlc_funded_query(&swap); + let query = SLQuery::new_htlc_funded_query(&htlc_params); let query_id = self.create_source_ledger_query.create_query(query); self.source_htlc_funded_query.get_or_insert_with(move || { @@ -113,7 +58,7 @@ where source_ledger_first_match .first_match_of(query_id) .and_then(move |tx| { - SA::is_contained_in_source_ledger_transaction(swap, tx) + SA::is_contained_in_transaction(&htlc_params, tx) .map_err(|_| rfc003::Error::InsufficientFunding) }) }); @@ -121,43 +66,22 @@ where Box::new(funded_future) }) } -} -impl - SourceHtlcRefundedTargetHtlcFunded - for DefaultEvents -where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset + IsContainedInTargetLedgerTransaction, - S: IntoSecretHash, - ComitClient: Client, - SLQuery: Query - + NewSourceHtlcFundedQuery - + NewSourceHtlcRefundedQuery - + NewSourceHtlcRedeemedQuery, - TLQuery: Query - + NewTargetHtlcFundedQuery - + NewTargetHtlcRefundedQuery - + NewTargetHtlcRedeemedQuery, -{ fn source_htlc_refunded_target_htlc_funded( &mut self, - swap: &OngoingSwap, + source_htlc_params: HtlcParams, + target_htlc_params: HtlcParams, source_htlc_location: &SL::HtlcLocation, - ) -> &mut Box> { - let swap = swap.clone(); - + ) -> &mut SourceRefundedOrTargetFunded { let source_ledger_first_match = self.source_ledger_first_match.clone(); let source_refunded_query = - SLQuery::new_source_htlc_refunded_query(&swap, source_htlc_location); + SLQuery::new_htlc_refunded_query(&source_htlc_params, source_htlc_location); let source_refunded_query_id = self .create_source_ledger_query .create_query(source_refunded_query); let target_ledger_first_match = self.target_ledger_first_match.clone(); - let target_funded_query = TLQuery::new_target_htlc_funded_query(&swap); + let target_funded_query = TLQuery::new_htlc_funded_query(&target_htlc_params); let target_funded_query_id = self .create_target_ledger_query .create_query(target_funded_query); @@ -174,7 +98,7 @@ where target_ledger_first_match .first_match_of(query_id) .and_then(move |tx| { - TA::is_contained_in_target_ledger_transaction(swap, tx) + TA::is_contained_in_transaction(&target_htlc_params, tx) .map_err(|_| rfc003::Error::InsufficientFunding) }) }); @@ -193,43 +117,21 @@ where ) }) } -} -impl - TargetHtlcRedeemedOrRefunded - for DefaultEvents -where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: IntoSecretHash, - ComitClient: Client, - SLQuery: Query - + NewSourceHtlcFundedQuery - + NewSourceHtlcRefundedQuery - + NewSourceHtlcRedeemedQuery, - TLQuery: Query - + NewTargetHtlcFundedQuery - + NewTargetHtlcRefundedQuery - + NewTargetHtlcRedeemedQuery, -{ fn target_htlc_redeemed_or_refunded( &mut self, - swap: &OngoingSwap, + target_htlc_params: HtlcParams, target_htlc_location: &TL::HtlcLocation, - ) -> &mut Box> { - let swap = swap.clone(); - + ) -> &mut RedeemedOrRefunded { let target_ledger_first_match = self.target_ledger_first_match.clone(); let target_refunded_query = - TLQuery::new_target_htlc_refunded_query(&swap, target_htlc_location); + TLQuery::new_htlc_refunded_query(&target_htlc_params, target_htlc_location); let target_refunded_query_id = self .create_target_ledger_query .create_query(target_refunded_query); let target_redeemed_query = - TLQuery::new_target_htlc_redeemed_query(&swap, target_htlc_location); + TLQuery::new_htlc_redeemed_query(&target_htlc_params, target_htlc_location); let target_redeemed_query_id = self .create_target_ledger_query .create_query(target_redeemed_query); @@ -259,43 +161,21 @@ where ) }) } -} -impl - SourceHtlcRedeemedOrRefunded - for DefaultEvents -where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: IntoSecretHash, - ComitClient: Client, - SLQuery: Query - + NewSourceHtlcFundedQuery - + NewSourceHtlcRefundedQuery - + NewSourceHtlcRedeemedQuery, - TLQuery: Query - + NewTargetHtlcFundedQuery - + NewTargetHtlcRefundedQuery - + NewTargetHtlcRedeemedQuery, -{ fn source_htlc_redeemed_or_refunded( &mut self, - swap: &OngoingSwap, + source_htlc_params: HtlcParams, source_htlc_location: &SL::HtlcLocation, - ) -> &mut Box> { - let swap = swap.clone(); - + ) -> &mut RedeemedOrRefunded { let source_ledger_first_match = self.source_ledger_first_match.clone(); let source_refunded_query = - SLQuery::new_source_htlc_refunded_query(&swap, source_htlc_location); + SLQuery::new_htlc_refunded_query(&source_htlc_params, source_htlc_location); let source_refunded_query_id = self .create_source_ledger_query .create_query(source_refunded_query); let source_redeemed_query = - SLQuery::new_source_htlc_redeemed_query(&swap, source_htlc_location); + SLQuery::new_htlc_redeemed_query(&source_htlc_params, source_htlc_location); let source_redeemed_query_id = self .create_source_ledger_query .create_query(source_redeemed_query); @@ -326,23 +206,3 @@ where }) } } - -impl Events - for DefaultEvents -where - SL: Ledger, - TL: Ledger, - SA: Asset + IsContainedInSourceLedgerTransaction, - TA: Asset + IsContainedInTargetLedgerTransaction, - S: IntoSecretHash, - ComitClient: Client, - SLQuery: Query - + NewSourceHtlcFundedQuery - + NewSourceHtlcRefundedQuery - + NewSourceHtlcRedeemedQuery, - TLQuery: Query - + NewTargetHtlcFundedQuery - + NewTargetHtlcRefundedQuery - + NewTargetHtlcRedeemedQuery, -{ -} diff --git a/application/comit_node/src/swap_protocols/rfc003/events/mod.rs b/application/comit_node/src/swap_protocols/rfc003/events/mod.rs index 2c1c274fcf..6fa6982c84 100644 --- a/application/comit_node/src/swap_protocols/rfc003/events/mod.rs +++ b/application/comit_node/src/swap_protocols/rfc003/events/mod.rs @@ -2,28 +2,34 @@ // see: https://github.com/rust-lang/rust/issues/21903 #![allow(type_alias_bounds)] -use comit_client::SwapReject; use ledger_query_service::Query; use swap_protocols::{ asset::Asset, - rfc003::{ - self, - ledger::Ledger, - messages::{AcceptResponseBody, Request}, - state_machine::OngoingSwap, - IntoSecretHash, - }, + rfc003::{self, ledger::Ledger, messages::Request}, }; use tokio::{self, prelude::future::Either}; -pub use self::default::{DefaultEvents, Role}; -use swap_protocols::rfc003::ExtractSecret; - -mod default; +pub use self::lqs::LqsEvents; +mod lqs; +mod response; +use comit_client::SwapReject; +use swap_protocols::rfc003::{ + roles::Role, + state_machine::{HtlcParams, StateMachineResponse}, +}; type Future = tokio::prelude::Future + Send; -pub type Response = Future, SwapReject>>; +pub type StateMachineResponseFuture = + Future, SwapReject>>; + +#[allow(type_alias_bounds)] +pub type ResponseFuture = StateMachineResponseFuture< + R::SourceSuccessHtlcIdentity, + R::TargetRefundHtlcIdentity, + ::LockDuration, +>; + pub type Funded = Future; pub type Refunded = Future; pub type Redeemed = Future; @@ -31,154 +37,59 @@ pub type SourceRefundedOrTargetFunded = Future>; pub type RedeemedOrRefunded = Future>; -pub trait RequestResponded: Send { - fn request_responded( - &mut self, - request: &Request, - ) -> &mut Box>; -} +pub trait LedgerEvents: Send { + fn source_htlc_funded(&mut self, htlc_params: HtlcParams) -> &mut Funded; -pub trait SourceHtlcFunded: - Send -{ - fn source_htlc_funded(&mut self, swap: &OngoingSwap) - -> &mut Box>; -} - -pub trait SourceHtlcRefundedTargetHtlcFunded< - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: IntoSecretHash, ->: Send -{ fn source_htlc_refunded_target_htlc_funded( &mut self, - swap: &OngoingSwap, + source_htlc_params: HtlcParams, + target_htlc_params: HtlcParams, source_htlc_location: &SL::HtlcLocation, - ) -> &mut Box>; -} + ) -> &mut SourceRefundedOrTargetFunded; -pub trait TargetHtlcRedeemedOrRefunded< - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: IntoSecretHash, ->: Send -{ - fn target_htlc_redeemed_or_refunded( + fn source_htlc_redeemed_or_refunded( &mut self, - swap: &OngoingSwap, - target_htlc_location: &TL::HtlcLocation, - ) -> &mut Box> - where - TL::Transaction: ExtractSecret; -} + source_htlc_params: HtlcParams, + htlc_location: &SL::HtlcLocation, + ) -> &mut RedeemedOrRefunded; -pub trait SourceHtlcRedeemedOrRefunded< - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: IntoSecretHash, ->: Send -{ - fn source_htlc_redeemed_or_refunded( + fn target_htlc_redeemed_or_refunded( &mut self, - swap: &OngoingSwap, - source_htlc_location: &SL::HtlcLocation, - ) -> &mut Box>; + target_htlc_params: HtlcParams, + htlc_location: &TL::HtlcLocation, + ) -> &mut RedeemedOrRefunded; } -pub trait Events: - RequestResponded - + SourceHtlcFunded - + SourceHtlcRefundedTargetHtlcFunded - + TargetHtlcRedeemedOrRefunded - + SourceHtlcRedeemedOrRefunded -{ +pub trait CommunicationEvents { + fn request_responded( + &mut self, + request: &Request, + ) -> &mut ResponseFuture; } -pub trait NewSourceHtlcFundedQuery: Send + Sync +pub trait NewHtlcFundedQuery: Send + Sync where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: Clone, Self: Query, { - fn new_source_htlc_funded_query(swap: &OngoingSwap) -> Self; + fn new_htlc_funded_query(htlc_params: &HtlcParams) -> Self; } -pub trait NewSourceHtlcRedeemedQuery: Send + Sync +pub trait NewHtlcRedeemedQuery: Send + Sync where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: Clone, Self: Query, { - fn new_source_htlc_redeemed_query( - swap: &OngoingSwap, - source_htlc_location: &SL::HtlcLocation, - ) -> Self; -} -pub trait NewSourceHtlcRefundedQuery: Send + Sync -where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: Clone, - Self: Query, -{ - fn new_source_htlc_refunded_query( - swap: &OngoingSwap, - source_htlc_location: &SL::HtlcLocation, + fn new_htlc_redeemed_query( + htlc_params: &HtlcParams, + htlc_location: &L::HtlcLocation, ) -> Self; } -pub trait NewTargetHtlcFundedQuery: Send + Sync -where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: Clone, - Self: Query, -{ - fn new_target_htlc_funded_query(swap: &OngoingSwap) -> Self; -} - -pub trait NewTargetHtlcRedeemedQuery: Send + Sync -where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: Clone, - Self: Query, -{ - fn new_target_htlc_redeemed_query( - swap: &OngoingSwap, - target_htlc_location: &TL::HtlcLocation, - ) -> Self; -} -pub trait NewTargetHtlcRefundedQuery: Send + Sync +pub trait NewHtlcRefundedQuery: Send + Sync where - SL: Ledger, - TL: Ledger, - SA: Asset, - TA: Asset, - S: Clone, Self: Query, { - fn new_target_htlc_refunded_query( - swap: &OngoingSwap, - target_htlc_location: &TL::HtlcLocation, + fn new_htlc_refunded_query( + htlc_params: &HtlcParams, + htlc_location: &L::HtlcLocation, ) -> Self; } diff --git a/application/comit_node/src/swap_protocols/rfc003/events/response.rs b/application/comit_node/src/swap_protocols/rfc003/events/response.rs new file mode 100644 index 0000000000..a3f49465d3 --- /dev/null +++ b/application/comit_node/src/swap_protocols/rfc003/events/response.rs @@ -0,0 +1,40 @@ +use comit_client; +use futures::Future; +use std::sync::Arc; +use swap_protocols::rfc003::roles::Alice; + +use swap_protocols::{ + asset::Asset, + rfc003::{ + self, + events::{CommunicationEvents, ResponseFuture, StateMachineResponseFuture}, + ledger::Ledger, + messages::Request, + }, +}; + +struct AliceComitClient { + #[allow(clippy::type_complexity)] + response_future: + Option>>, + client: Arc, +} + +impl + CommunicationEvents> for AliceComitClient +{ + fn request_responded( + &mut self, + request: &Request, + ) -> &mut ResponseFuture> { + let client = Arc::clone(&self.client); + self.response_future.get_or_insert_with(|| { + Box::new( + client + .send_swap_request(request.clone()) + .map_err(rfc003::Error::SwapResponse) + .map(|result| result.map(Into::into)), + ) + }) + } +} diff --git a/application/comit_node/src/swap_protocols/rfc003/is_contained_in_transaction.rs b/application/comit_node/src/swap_protocols/rfc003/is_contained_in_transaction.rs new file mode 100644 index 0000000000..c05dc45118 --- /dev/null +++ b/application/comit_node/src/swap_protocols/rfc003/is_contained_in_transaction.rs @@ -0,0 +1,22 @@ +use swap_protocols::{ + self, + asset::Asset, + rfc003::{state_machine::HtlcParams, Ledger}, +}; + +#[derive(Debug, PartialEq)] +pub enum Error { + UnexpectedAsset { found: A, expected: A }, + WrongTransaction, +} + +pub trait IsContainedInTransaction: Send + Sync +where + L: Ledger, + Self: Asset, +{ + fn is_contained_in_transaction( + htlc_params: &HtlcParams, + transaction: ::Transaction, + ) -> Result>; +} diff --git a/application/comit_node/src/swap_protocols/rfc003/ledger.rs b/application/comit_node/src/swap_protocols/rfc003/ledger.rs index 92ccfbf374..dedec236a4 100644 --- a/application/comit_node/src/swap_protocols/rfc003/ledger.rs +++ b/application/comit_node/src/swap_protocols/rfc003/ledger.rs @@ -1,8 +1,11 @@ use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; -use swap_protocols; +use swap_protocols::{ + self, + rfc003::secret::{Secret, SecretHash}, +}; -pub trait Ledger: swap_protocols::ledger::Ledger { +pub trait Ledger: swap_protocols::Ledger + ExtractSecret { type LockDuration: PartialEq + Debug + Clone @@ -19,3 +22,7 @@ pub trait Ledger: swap_protocols::ledger::Ledger { + Debug + Into<::Identity>; } + +pub trait ExtractSecret: swap_protocols::ledger::Ledger { + fn extract_secret(txn: &Self::Transaction, secret_hash: &SecretHash) -> Option; +} diff --git a/application/comit_node/src/swap_protocols/rfc003/mod.rs b/application/comit_node/src/swap_protocols/rfc003/mod.rs index 383e148c2b..c10f5520cb 100644 --- a/application/comit_node/src/swap_protocols/rfc003/mod.rs +++ b/application/comit_node/src/swap_protocols/rfc003/mod.rs @@ -7,23 +7,28 @@ pub mod alice_ledger_actor; pub mod bitcoin; pub mod ethereum; pub mod events; +pub mod is_contained_in_transaction; pub mod ledger_htlc_service; +pub mod roles; pub mod state_machine; pub mod state_store; -pub mod validation; mod error; + mod ledger; mod messages; mod outcome; mod save_state; mod secret; +#[cfg(test)] +mod state_machine_test; + pub use self::{ error::Error, - ledger::Ledger, + ledger::{ExtractSecret, Ledger}, messages::*, outcome::SwapOutcome, save_state::SaveState, - secret::{ExtractSecret, IntoSecretHash, RandomnessSource, Secret, SecretFromErr, SecretHash}, + secret::{RandomnessSource, Secret, SecretFromErr, SecretHash}, }; diff --git a/application/comit_node/src/swap_protocols/rfc003/roles.rs b/application/comit_node/src/swap_protocols/rfc003/roles.rs new file mode 100644 index 0000000000..d20d234606 --- /dev/null +++ b/application/comit_node/src/swap_protocols/rfc003/roles.rs @@ -0,0 +1,125 @@ +use std::{fmt::Debug, marker::PhantomData}; + +use swap_protocols::{ + self, + asset::Asset, + rfc003::{ledger::Ledger, Secret, SecretHash}, +}; + +pub trait Role: Send + Clone + 'static { + type SourceLedger: Ledger; + type TargetLedger: Ledger; + type SourceAsset: Asset; + type TargetAsset: Asset; + type SourceSuccessHtlcIdentity: Send + + Sync + + Clone + + Debug + + PartialEq + + Into<::Identity>; + + type SourceRefundHtlcIdentity: Send + + Sync + + Clone + + Debug + + PartialEq + + Into<::Identity>; + + type TargetSuccessHtlcIdentity: Send + + Sync + + Clone + + Debug + + PartialEq + + Into<::Identity>; + + type TargetRefundHtlcIdentity: Send + + Sync + + Clone + + Debug + + PartialEq + + Into<::Identity>; + + type Secret: Send + Sync + Clone + Into + Debug + PartialEq; +} + +#[derive(Clone, Debug)] +pub struct Alice { + phantom_data: PhantomData<(SL, TL, SA, TA)>, +} + +impl Role for Alice { + type SourceLedger = SL; + type TargetLedger = TL; + type SourceAsset = SA; + type TargetAsset = TA; + type SourceSuccessHtlcIdentity = SL::Identity; + type SourceRefundHtlcIdentity = SL::HtlcIdentity; + type TargetSuccessHtlcIdentity = TL::HtlcIdentity; + type TargetRefundHtlcIdentity = TL::Identity; + type Secret = Secret; +} + +#[derive(Debug, Clone)] +pub struct Bob { + phantom_data: PhantomData<(SL, TL, SA, TA)>, +} + +impl Role for Bob { + type SourceLedger = SL; + type TargetLedger = TL; + type SourceAsset = SA; + type TargetAsset = TA; + type SourceSuccessHtlcIdentity = SL::HtlcIdentity; + type SourceRefundHtlcIdentity = SL::Identity; + type TargetSuccessHtlcIdentity = TL::Identity; + type TargetRefundHtlcIdentity = TL::HtlcIdentity; + type Secret = SecretHash; +} + +#[cfg(test)] +pub mod test { + use super::*; + use bitcoin_support::BitcoinQuantity; + use ethereum_support::EtherQuantity; + use swap_protocols::{ + ledger::{Bitcoin, Ethereum}, + rfc003::{ + events::{CommunicationEvents, ResponseFuture}, + messages::Request, + }, + }; + + pub type Alisha = Alice; + pub type Bobisha = Bob; + + impl PartialEq for Alisha { + fn eq(&self, _: &Alisha) -> bool { + unreachable!( + "Rust erroneously forces me to be PartialEq even though I'm never instantiated" + ) + } + } + + impl PartialEq for Bobisha { + fn eq(&self, _: &Bobisha) -> bool { + unreachable!( + "Rust erroneously forces me to be PartialEq even though I'm never instantiated" + ) + } + } + + #[allow(missing_debug_implementations)] + pub struct FakeCommunicationEvents { + pub response: Option>>, + } + + impl CommunicationEvents for FakeCommunicationEvents { + fn request_responded( + &mut self, + _request: &Request, + ) -> &mut ResponseFuture { + self.response.as_mut().unwrap() + } + } + +} diff --git a/application/comit_node/src/swap_protocols/rfc003/save_state.rs b/application/comit_node/src/swap_protocols/rfc003/save_state.rs index d5e776043a..e7e267fc35 100644 --- a/application/comit_node/src/swap_protocols/rfc003/save_state.rs +++ b/application/comit_node/src/swap_protocols/rfc003/save_state.rs @@ -1,35 +1,20 @@ use futures::sync::mpsc; -use std::{fmt::Debug, sync::RwLock}; -use swap_protocols::{ - asset::Asset, - rfc003::{state_machine::SwapStates, ExtractSecret, IntoSecretHash, Ledger}, -}; +use std::sync::RwLock; +use swap_protocols::rfc003::{roles::Role, state_machine::SwapStates}; -pub trait SaveState: - Send + Sync + Debug -where - TL::Transaction: ExtractSecret, -{ - fn save(&self, state: SwapStates); +pub trait SaveState: Send + Sync { + fn save(&self, state: SwapStates); } -impl SaveState - for RwLock> -where - TL::Transaction: ExtractSecret, -{ - fn save(&self, state: SwapStates) { +impl SaveState for RwLock> { + fn save(&self, state: SwapStates) { let _self = &mut *self.write().unwrap(); *_self = state; } } -impl SaveState - for mpsc::UnboundedSender> -where - TL::Transaction: ExtractSecret, -{ - fn save(&self, state: SwapStates) { +impl SaveState for mpsc::UnboundedSender> { + fn save(&self, state: SwapStates) { // ignore error the subscriber is no longer interested in state updates let _ = self.unbounded_send(state); } diff --git a/application/comit_node/src/swap_protocols/rfc003/secret.rs b/application/comit_node/src/swap_protocols/rfc003/secret.rs index b8ede97b14..8973d1e847 100644 --- a/application/comit_node/src/swap_protocols/rfc003/secret.rs +++ b/application/comit_node/src/swap_protocols/rfc003/secret.rs @@ -81,14 +81,6 @@ impl FromStr for SecretHash { } } -pub trait IntoSecretHash: - PartialEq + Debug + Clone + Send + Sync + Into + 'static -{ -} - -impl IntoSecretHash for Secret {} -impl IntoSecretHash for SecretHash {} - #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct Secret([u8; SHA256_DIGEST_LENGTH]); @@ -215,10 +207,6 @@ impl RandomnessSource for ThreadRng { } } -pub trait ExtractSecret { - fn extract_secret(&self, secret_hash: &SecretHash) -> Option; -} - #[cfg(test)] mod tests { use super::*; diff --git a/application/comit_node/src/swap_protocols/rfc003/state_machine.rs b/application/comit_node/src/swap_protocols/rfc003/state_machine.rs index 5286016016..ef5d6cc4b9 100755 --- a/application/comit_node/src/swap_protocols/rfc003/state_machine.rs +++ b/application/comit_node/src/swap_protocols/rfc003/state_machine.rs @@ -1,34 +1,70 @@ -use futures::{future::Either, Async, Future}; +use futures::{future::Either, Async}; use state_machine_future::{RentToOwn, StateMachineFuture}; use std::sync::Arc; +use swap_protocols::rfc003::ExtractSecret; + use swap_protocols::{ + self, asset::Asset, rfc003::{ - self, events, ledger::Ledger, messages::Request, AcceptResponseBody, ExtractSecret, - IntoSecretHash, SaveState, Secret, SwapOutcome, + self, events, ledger::Ledger, messages::Request, roles::Role, AcceptResponseBody, + SaveState, Secret, SecretHash, SwapOutcome, }, }; -#[derive(Debug, Clone, PartialEq)] -pub struct OngoingSwap { - pub source_ledger: SL, - pub target_ledger: TL, - pub source_asset: SA, - pub target_asset: TA, - pub source_ledger_success_identity: SL::Identity, - pub source_ledger_refund_identity: SL::HtlcIdentity, - pub target_ledger_success_identity: TL::HtlcIdentity, - pub target_ledger_refund_identity: TL::Identity, - pub source_ledger_lock_duration: SL::LockDuration, - pub target_ledger_lock_duration: TL::LockDuration, - pub secret: S, +#[derive(Debug, Clone)] +pub struct StateMachineResponse { + pub source_ledger_success_identity: SLSI, + pub target_ledger_refund_identity: TLRI, + pub target_ledger_lock_duration: TLLD, } -impl OngoingSwap -where - TL::Transaction: ExtractSecret, +impl From> + for StateMachineResponse { - pub fn new(start: Start, response: AcceptResponseBody) -> Self { + fn from(accept_response: AcceptResponseBody) -> Self { + Self { + source_ledger_success_identity: accept_response.source_ledger_success_identity, + target_ledger_refund_identity: accept_response.target_ledger_refund_identity, + target_ledger_lock_duration: accept_response.target_ledger_lock_duration, + } + } +} + +#[derive(Clone, Debug)] +pub struct HtlcParams { + pub asset: A, + pub ledger: L, + pub success_identity: L::Identity, + pub refund_identity: L::Identity, + pub lock_duration: L::LockDuration, + pub secret_hash: SecretHash, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct OngoingSwap { + pub source_ledger: R::SourceLedger, + pub target_ledger: R::TargetLedger, + pub source_asset: R::SourceAsset, + pub target_asset: R::TargetAsset, + pub source_ledger_success_identity: R::SourceSuccessHtlcIdentity, + pub source_ledger_refund_identity: R::SourceRefundHtlcIdentity, + pub target_ledger_success_identity: R::TargetSuccessHtlcIdentity, + pub target_ledger_refund_identity: R::TargetRefundHtlcIdentity, + pub source_ledger_lock_duration: ::LockDuration, + pub target_ledger_lock_duration: ::LockDuration, + pub secret: R::Secret, +} + +impl OngoingSwap { + pub fn new( + start: Start, + response: StateMachineResponse< + R::SourceSuccessHtlcIdentity, + R::TargetRefundHtlcIdentity, + ::LockDuration, + >, + ) -> Self { OngoingSwap { source_ledger: start.source_ledger, target_ledger: start.target_ledger, @@ -43,42 +79,61 @@ where secret: start.secret, } } + + pub fn source_htlc_params(&self) -> HtlcParams { + HtlcParams { + asset: self.source_asset.clone(), + ledger: self.source_ledger.clone(), + success_identity: self.source_ledger_success_identity.clone().into(), + refund_identity: self.source_ledger_refund_identity.clone().into(), + lock_duration: self.source_ledger_lock_duration.clone(), + secret_hash: self.secret.clone().into(), + } + } + + pub fn target_htlc_params(&self) -> HtlcParams { + HtlcParams { + asset: self.target_asset.clone(), + ledger: self.target_ledger.clone(), + success_identity: self.target_ledger_success_identity.clone().into(), + refund_identity: self.target_ledger_refund_identity.clone().into(), + lock_duration: self.target_ledger_lock_duration.clone(), + secret_hash: self.secret.clone().into(), + } + } } #[allow(missing_debug_implementations)] -pub struct Context { - pub events: Box>, - pub state_repo: Arc>, +pub struct Context { + pub ledger_events: + Box>, + pub state_repo: Arc>, + pub response_event: Box + Send>, } #[derive(StateMachineFuture)] #[state_machine_future(context = "Context", derive(Clone, Debug, PartialEq))] -#[allow(missing_debug_implementations)] -pub enum Swap -where - TL::Transaction: ExtractSecret, -{ +#[allow(missing_debug_implementations, clippy::too_many_arguments)] +pub enum Swap { #[state_machine_future(start, transitions(Accepted, Final))] Start { - source_ledger_refund_identity: SL::HtlcIdentity, - target_ledger_success_identity: TL::HtlcIdentity, - source_ledger: SL, - target_ledger: TL, - source_asset: SA, - target_asset: TA, - source_ledger_lock_duration: SL::LockDuration, - secret: S, + source_ledger_refund_identity: R::SourceRefundHtlcIdentity, + target_ledger_success_identity: R::TargetSuccessHtlcIdentity, + source_ledger: R::SourceLedger, + target_ledger: R::TargetLedger, + source_asset: R::SourceAsset, + target_asset: R::TargetAsset, + source_ledger_lock_duration: ::LockDuration, + secret: R::Secret, }, #[state_machine_future(transitions(SourceFunded))] - Accepted { - swap: OngoingSwap, - }, + Accepted { swap: OngoingSwap }, #[state_machine_future(transitions(BothFunded, Final))] SourceFunded { - swap: OngoingSwap, - source_htlc_location: SL::HtlcLocation, + swap: OngoingSwap, + source_htlc_location: ::HtlcLocation, }, #[state_machine_future(transitions( @@ -88,34 +143,34 @@ where SourceRedeemedTargetFunded, ))] BothFunded { - swap: OngoingSwap, - target_htlc_location: TL::HtlcLocation, - source_htlc_location: SL::HtlcLocation, + swap: OngoingSwap, + target_htlc_location: ::HtlcLocation, + source_htlc_location: ::HtlcLocation, }, #[state_machine_future(transitions(Final))] SourceFundedTargetRefunded { - swap: OngoingSwap, - source_htlc_location: SL::HtlcLocation, + swap: OngoingSwap, + source_htlc_location: ::HtlcLocation, }, #[state_machine_future(transitions(Final))] SourceRefundedTargetFunded { - swap: OngoingSwap, - target_htlc_location: TL::HtlcLocation, + swap: OngoingSwap, + target_htlc_location: ::HtlcLocation, }, #[state_machine_future(transitions(Final))] SourceRedeemedTargetFunded { - swap: OngoingSwap, - target_htlc_location: TL::HtlcLocation, + swap: OngoingSwap, + target_htlc_location: ::HtlcLocation, }, #[state_machine_future(transitions(Final))] SourceFundedTargetRedeemed { - swap: OngoingSwap, - target_redeemed_tx: TL::Transaction, - source_htlc_location: SL::HtlcLocation, + swap: OngoingSwap, + target_redeemed_tx: ::Transaction, + source_htlc_location: ::HtlcLocation, secret: Secret, }, @@ -126,15 +181,11 @@ where Error(rfc003::Error), } -impl PollSwap - for Swap -where - TL::Transaction: ExtractSecret, -{ +impl PollSwap for Swap { fn poll_start<'s, 'c>( - state: &'s mut RentToOwn<'s, Start>, - context: &'c mut RentToOwn<'c, Context>, - ) -> Result>, rfc003::Error> { + state: &'s mut RentToOwn<'s, Start>, + context: &'c mut RentToOwn<'c, Context>, + ) -> Result>, rfc003::Error> { let request = Request { source_asset: state.source_asset.clone(), target_asset: state.target_asset.clone(), @@ -146,7 +197,7 @@ where secret_hash: state.secret.clone().into(), }; - let response = try_ready!(context.events.request_responded(&request).poll()); + let response = try_ready!(context.response_event.request_responded(&request).poll()); let state = state.take(); @@ -162,11 +213,13 @@ where } fn poll_accepted<'s, 'c>( - state: &'s mut RentToOwn<'s, Accepted>, - context: &'c mut RentToOwn<'c, Context>, - ) -> Result>, rfc003::Error> { - let source_htlc_location = - try_ready!(context.events.source_htlc_funded(&state.swap).poll()); + state: &'s mut RentToOwn<'s, Accepted>, + context: &'c mut RentToOwn<'c, Context>, + ) -> Result>, rfc003::Error> { + let source_htlc_location = try_ready!(context + .ledger_events + .source_htlc_funded(state.swap.source_htlc_params()) + .poll()); let state = state.take(); @@ -180,12 +233,16 @@ where } fn poll_source_funded<'s, 'c>( - state: &'s mut RentToOwn<'s, SourceFunded>, - context: &'c mut RentToOwn<'c, Context>, - ) -> Result>, rfc003::Error> { + state: &'s mut RentToOwn<'s, SourceFunded>, + context: &'c mut RentToOwn<'c, Context>, + ) -> Result>, rfc003::Error> { match try_ready!(context - .events - .source_htlc_refunded_target_htlc_funded(&state.swap, &state.source_htlc_location) + .ledger_events + .source_htlc_refunded_target_htlc_funded( + state.swap.source_htlc_params(), + state.swap.target_htlc_params(), + &state.source_htlc_location + ) .poll()) { Either::A(_source_refunded_txid) => { @@ -206,19 +263,22 @@ where } fn poll_both_funded<'s, 'c>( - state: &'s mut RentToOwn<'s, BothFunded>, - context: &'c mut RentToOwn<'c, Context>, - ) -> Result>, rfc003::Error> { + state: &'s mut RentToOwn<'s, BothFunded>, + context: &'c mut RentToOwn<'c, Context>, + ) -> Result>, rfc003::Error> { if let Async::Ready(redeemed_or_refunded) = context - .events - .target_htlc_redeemed_or_refunded(&state.swap, &state.target_htlc_location) + .ledger_events + .target_htlc_redeemed_or_refunded( + state.swap.target_htlc_params(), + &state.target_htlc_location, + ) .poll()? { let state = state.take(); let secret_hash = state.swap.secret.clone().into(); match redeemed_or_refunded { Either::A(target_redeemed_tx) => { - match target_redeemed_tx.extract_secret(&secret_hash) { + match R::TargetLedger::extract_secret(&target_redeemed_tx, &secret_hash) { Some(secret) => transition_save!( context.state_repo, SourceFundedTargetRedeemed { @@ -244,8 +304,11 @@ where } match try_ready!(context - .events - .source_htlc_redeemed_or_refunded(&state.swap, &state.source_htlc_location) + .ledger_events + .source_htlc_redeemed_or_refunded( + state.swap.source_htlc_params(), + &state.source_htlc_location + ) .poll()) { Either::A(_source_redeemed_tx) => { @@ -272,12 +335,15 @@ where } fn poll_source_funded_target_refunded<'s, 'c>( - state: &'s mut RentToOwn<'s, SourceFundedTargetRefunded>, - context: &'c mut RentToOwn<'c, Context>, + state: &'s mut RentToOwn<'s, SourceFundedTargetRefunded>, + context: &'c mut RentToOwn<'c, Context>, ) -> Result, rfc003::Error> { match try_ready!(context - .events - .source_htlc_redeemed_or_refunded(&state.swap, &state.source_htlc_location) + .ledger_events + .source_htlc_redeemed_or_refunded( + state.swap.source_htlc_params(), + &state.source_htlc_location + ) .poll()) { Either::A(_source_redeemed_txid) => transition_save!( @@ -291,12 +357,15 @@ where } fn poll_source_refunded_target_funded<'s, 'c>( - state: &'s mut RentToOwn<'s, SourceRefundedTargetFunded>, - context: &'c mut RentToOwn<'c, Context>, + state: &'s mut RentToOwn<'s, SourceRefundedTargetFunded>, + context: &'c mut RentToOwn<'c, Context>, ) -> Result, rfc003::Error> { match try_ready!(context - .events - .target_htlc_redeemed_or_refunded(&state.swap, &state.target_htlc_location) + .ledger_events + .target_htlc_redeemed_or_refunded( + state.swap.target_htlc_params(), + &state.target_htlc_location + ) .poll()) { Either::A(_target_redeemed_txid) => transition_save!( @@ -310,12 +379,15 @@ where } fn poll_source_redeemed_target_funded<'s, 'c>( - state: &'s mut RentToOwn<'s, SourceRedeemedTargetFunded>, - context: &'c mut RentToOwn<'c, Context>, + state: &'s mut RentToOwn<'s, SourceRedeemedTargetFunded>, + context: &'c mut RentToOwn<'c, Context>, ) -> Result, rfc003::Error> { match try_ready!(context - .events - .target_htlc_redeemed_or_refunded(&state.swap, &state.target_htlc_location) + .ledger_events + .target_htlc_redeemed_or_refunded( + state.swap.target_htlc_params(), + &state.target_htlc_location + ) .poll()) { Either::A(_target_redeemed_txid) => { @@ -329,12 +401,15 @@ where } fn poll_source_funded_target_redeemed<'s, 'c>( - state: &'s mut RentToOwn<'s, SourceFundedTargetRedeemed>, - context: &'c mut RentToOwn<'c, Context>, + state: &'s mut RentToOwn<'s, SourceFundedTargetRedeemed>, + context: &'c mut RentToOwn<'c, Context>, ) -> Result, rfc003::Error> { match try_ready!(context - .events - .source_htlc_redeemed_or_refunded(&state.swap, &state.source_htlc_location) + .ledger_events + .source_htlc_redeemed_or_refunded( + state.swap.source_htlc_params(), + &state.source_htlc_location + ) .poll()) { Either::A(_target_redeemed_txid) => { diff --git a/application/comit_node/src/swap_protocols/rfc003/state_machine_test.rs b/application/comit_node/src/swap_protocols/rfc003/state_machine_test.rs new file mode 100644 index 0000000000..ae6487d19f --- /dev/null +++ b/application/comit_node/src/swap_protocols/rfc003/state_machine_test.rs @@ -0,0 +1,271 @@ +use bitcoin_support::{BitcoinQuantity, Blocks, OutPoint, Sha256dHash}; +use comit_client::SwapReject; +use swap_protocols::{ + ledger::{Bitcoin, Ethereum}, + rfc003::{ + ethereum::Seconds, + events::{self, LedgerEvents}, + roles::test::{Alisha, Bobisha, FakeCommunicationEvents}, + state_machine::*, + Secret, + }, +}; + +use ethereum_support::EtherQuantity; +use futures::{ + future::{self, Either}, + sync::mpsc, + Stream, +}; +use hex::FromHex; +use std::{str::FromStr, sync::Arc}; + +#[derive(Default)] +struct FakeLedgerEvents { + pub source_htlc_funded: Option>>, + pub source_htlc_refunded_target_htlc_funded: + Option>>, +} + +impl LedgerEvents for FakeLedgerEvents { + fn source_htlc_funded( + &mut self, + _htlc_params: HtlcParams, + ) -> &mut events::Funded { + self.source_htlc_funded.as_mut().unwrap() + } + + fn source_htlc_refunded_target_htlc_funded( + &mut self, + _source_htlc_params: HtlcParams, + _target_htlc_params: HtlcParams, + _source_htlc_location: &bitcoin_support::OutPoint, + ) -> &mut events::SourceRefundedOrTargetFunded { + self.source_htlc_refunded_target_htlc_funded + .as_mut() + .unwrap() + } + + fn target_htlc_redeemed_or_refunded( + &mut self, + _target_htlc_params: HtlcParams, + _target_htlc_location: ðereum_support::Address, + ) -> &mut events::RedeemedOrRefunded { + unimplemented!() + } + + fn source_htlc_redeemed_or_refunded( + &mut self, + _source_htlc_params: HtlcParams, + _target_htlc_location: &bitcoin_support::OutPoint, + ) -> &mut events::RedeemedOrRefunded { + unimplemented!() + } +} + +fn gen_start_state() -> Start { + Start { + source_ledger_refund_identity: secp256k1_support::KeyPair::from_secret_key_slice( + &hex::decode("18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725") + .unwrap(), + ) + .unwrap(), + target_ledger_success_identity: ethereum_support::Address::from_str( + "8457037fcd80a8650c4692d7fcfc1d0a96b92867", + ) + .unwrap(), + source_ledger: Bitcoin::regtest(), + target_ledger: Ethereum::default(), + source_asset: BitcoinQuantity::from_bitcoin(1.0), + target_asset: EtherQuantity::from_eth(10.0), + source_ledger_lock_duration: Blocks::from(144), + secret: Secret::from(*b"hello world, you are beautiful!!"), + } +} + +macro_rules! init { + ($role:ty, $response_event:expr, $state:expr, $events:expr) => {{ + let (state_sender, state_receiver) = mpsc::unbounded(); + let context = Context { + ledger_events: Box::new($events), + state_repo: Arc::new(state_sender), + response_event: Box::new($response_event), + }; + let state: SwapStates<$role> = $state; + let final_state_future = Swap::start_in(state, context); + (final_state_future, state_receiver.map_err(|_| ())) + }}; +} + +macro_rules! run_state_machine { + ($state_machine:ident, $states:ident, $( $expected_state:expr ) , * ) => { + { + let mut expected_states = Vec::new(); + + $( + let state = $expected_state; + expected_states.push(SwapStates::from(state)); + ) + * + + let number_of_expected_states = expected_states.len() + 1; + + let mut runtime = tokio::runtime::Runtime::new().unwrap(); + + let state_machine_result = runtime.block_on($state_machine).unwrap(); + let actual_states = runtime.block_on($states.take(number_of_expected_states as u64).collect()).unwrap(); + + expected_states.push(SwapStates::from(Final(state_machine_result))); + + assert_eq!(actual_states, expected_states); + } + }; + + ($state_machine:ident, $states:ident) => { + run_state_machine!($state_machine, $states, ); + }; +} + +#[test] +fn when_swap_is_rejected_go_to_final_reject() { + let start = gen_start_state(); + + let (state_machine, states) = init!( + Alisha, + FakeCommunicationEvents:: { + response: Some(Box::new(future::ok(Err(SwapReject::Rejected)))), + }, + start.clone().into(), + FakeLedgerEvents { + ..Default::default() + } + ); + + run_state_machine!(state_machine, states); +} + +#[test] +fn source_refunded() { + let bob_response = StateMachineResponse { + target_ledger_refund_identity: ethereum_support::Address::from_str( + "71b9f69dcabb340a3fe229c3f94f1662ad85e5e8", + ) + .unwrap(), + source_ledger_success_identity: bitcoin_support::PubkeyHash::from_hex( + "d38e554430c4035f2877a579a07a99886153f071", + ) + .unwrap(), + target_ledger_lock_duration: Seconds(42), + }; + + let start = gen_start_state(); + + let (state_machine, states) = init!( + Alisha, + FakeCommunicationEvents:: { + response: Some(Box::new(future::ok(Ok(bob_response.clone())))), + }, + start.clone().into(), + FakeLedgerEvents { + source_htlc_funded: Some(Box::new(future::ok(OutPoint { + txid: Sha256dHash::from_data(b"funding"), + vout: 0, + }))), + source_htlc_refunded_target_htlc_funded: Some(Box::new(future::ok(Either::A( + bitcoin_support::Transaction { + version: 1, + lock_time: 42, + input: vec![], + output: vec![], + } + )))), + ..Default::default() + } + ); + + run_state_machine!( + state_machine, + states, + Accepted { + swap: OngoingSwap::new(start.clone(), bob_response.clone().into()), + }, + SourceFunded { + swap: OngoingSwap::new(start.clone(), bob_response.clone().into()), + source_htlc_location: OutPoint { + txid: Sha256dHash::from_data(b"funding"), + vout: 0 + } + } + ); +} + +#[test] +fn bob_transition_source_refunded() { + let start = Start { + source_ledger_refund_identity: bitcoin_support::PubkeyHash::from_hex( + "d38e554430c4035f2877a579a07a99886153f071", + ) + .unwrap(), + target_ledger_success_identity: ethereum_support::Address::from_str( + "8457037fcd80a8650c4692d7fcfc1d0a96b92867", + ) + .unwrap(), + source_ledger: Bitcoin::regtest(), + target_ledger: Ethereum::default(), + source_asset: BitcoinQuantity::from_bitcoin(1.0), + target_asset: EtherQuantity::from_eth(10.0), + source_ledger_lock_duration: Blocks::from(144), + secret: Secret::from(*b"hello world, you are beautiful!!").hash(), + }; + + let response = StateMachineResponse { + source_ledger_success_identity: secp256k1_support::KeyPair::from_secret_key_slice( + &hex::decode("18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725") + .unwrap(), + ) + .unwrap(), + target_ledger_refund_identity: ethereum_support::Address::from_str( + "8457037fcd80a8650c4692d7fcfc1d0a96b92867", + ) + .unwrap(), + target_ledger_lock_duration: Seconds(42), + }; + + let (state_machine, states) = init!( + Bobisha, + FakeCommunicationEvents:: { + response: Some(Box::new(future::ok(Ok(response.clone())))) + }, + start.clone().into(), + FakeLedgerEvents { + source_htlc_funded: Some(Box::new(future::ok(OutPoint { + txid: Sha256dHash::from_data(b"funding"), + vout: 0, + }))), + source_htlc_refunded_target_htlc_funded: Some(Box::new(future::ok(Either::A( + bitcoin_support::Transaction { + version: 1, + lock_time: 42, + input: vec![], + output: vec![], + } + )))), + ..Default::default() + } + ); + + run_state_machine!( + state_machine, + states, + Accepted { + swap: OngoingSwap::new(start.clone(), response.clone().into()) + }, + SourceFunded { + swap: OngoingSwap::new(start.clone(), response.clone().into()), + source_htlc_location: OutPoint { + txid: Sha256dHash::from_data(b"funding"), + vout: 0 + } + } + ); +} diff --git a/application/comit_node/src/swap_protocols/rfc003/state_store.rs b/application/comit_node/src/swap_protocols/rfc003/state_store.rs index 84e1acf119..4ede323e5e 100644 --- a/application/comit_node/src/swap_protocols/rfc003/state_store.rs +++ b/application/comit_node/src/swap_protocols/rfc003/state_store.rs @@ -4,10 +4,7 @@ use std::{ hash::Hash, sync::{Arc, Mutex, RwLock}, }; -use swap_protocols::{ - asset::Asset, - rfc003::{state_machine::SwapStates, ExtractSecret, IntoSecretHash, Ledger, SaveState}, -}; +use swap_protocols::rfc003::{roles::Role, state_machine::SwapStates, SaveState}; #[derive(Debug)] pub enum Error { @@ -16,28 +13,12 @@ pub enum Error { } pub trait StateStore: Send + Sync + 'static { - fn insert( - &self, - key: K, - state: SwapStates, - ) -> Result>, Error> - where - TL::Transaction: ExtractSecret; - - fn get( - &self, - key: &K, - ) -> Result, Error> - where - TL::Transaction: ExtractSecret; + fn insert(&self, key: K, state: SwapStates) -> Result>, Error>; + + fn get(&self, key: &K) -> Result, Error>; #[allow(clippy::type_complexity)] - fn save_state_for_key( - &self, - key: &K, - ) -> Result>, Error> - where - TL::Transaction: ExtractSecret; + fn save_state_for_key(&self, key: &K) -> Result>, Error>; } #[derive(Default, Debug)] @@ -46,14 +27,7 @@ pub struct InMemoryStateStore { } impl StateStore for InMemoryStateStore { - fn insert( - &self, - key: K, - state: SwapStates, - ) -> Result>, Error> - where - TL::Transaction: ExtractSecret, - { + fn insert(&self, key: K, state: SwapStates) -> Result>, Error> { let mut states = self.states.lock().unwrap(); if states.contains_key(&key) { @@ -68,40 +42,24 @@ impl StateStore for InMemorySta Ok(state) } - fn get( - &self, - key: &K, - ) -> Result, Error> - where - TL::Transaction: ExtractSecret, - { + fn get(&self, key: &K) -> Result, Error> { let states = self.states.lock().unwrap(); states .get(key) .map(|state| { - let state = state - .downcast_ref::>>>() - .unwrap(); + let state = state.downcast_ref::>>>().unwrap(); let state = state.read().unwrap(); state.clone() }) .ok_or(Error::NotFound) } - fn save_state_for_key( - &self, - key: &K, - ) -> Result>, Error> - where - TL::Transaction: ExtractSecret, - { + fn save_state_for_key(&self, key: &K) -> Result>, Error> { let states = self.states.lock().unwrap(); states .get(key) - .map(|state| -> Arc> { - let state = state - .downcast_ref::>>>() - .unwrap(); + .map(|state| -> Arc> { + let state = state.downcast_ref::>>>().unwrap(); state.clone() }) .ok_or(Error::NotFound) @@ -118,13 +76,13 @@ mod tests { use spectral::prelude::*; use swap_protocols::{ ledger::{Bitcoin, Ethereum}, - rfc003::{state_machine::Start, Secret}, + rfc003::{roles::test::Alisha, state_machine::Start, Secret}, }; #[test] fn store_get_and_save_state() { let state_store = InMemoryStateStore::default(); - let start_state = Start { + let start_state = Start:: { source_ledger_refund_identity: secp256k1_support::KeyPair::from_secret_key_slice( &hex::decode("18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725") .unwrap(), @@ -145,10 +103,10 @@ mod tests { let id = 1; let res = state_store.insert(id, state.clone()); - assert_that(&res).is_ok(); + assert!(res.is_ok()); let res = state_store.get(&id); - assert_that(&res).is_ok().is_equal_to(state); + assert_that(&res).is_ok_containing(state); let save_state = state_store.save_state_for_key(&id).unwrap(); diff --git a/application/comit_node/src/swap_protocols/rfc003/validation.rs b/application/comit_node/src/swap_protocols/rfc003/validation.rs deleted file mode 100644 index e4028315e3..0000000000 --- a/application/comit_node/src/swap_protocols/rfc003/validation.rs +++ /dev/null @@ -1,452 +0,0 @@ -use bitcoin_support::{BitcoinQuantity, FindOutput, OutPoint}; -use ethereum_support::{self, CalculateContractAddress, EtherQuantity}; -use swap_protocols::{ - asset::Asset, - ledger::{Bitcoin, Ethereum}, - rfc003::{ - self, bitcoin::bitcoin_htlc_address, ethereum::ethereum_htlc, state_machine::OngoingSwap, - IntoSecretHash, - }, - Ledger, -}; - -#[derive(Debug, PartialEq)] -pub enum Error { - UnexpectedAsset { found: A, expected: A }, - WrongTransaction, -} - -pub trait IsContainedInSourceLedgerTransaction: Send + Sync -where - SL: rfc003::Ledger, - TL: rfc003::Ledger, - Self: Asset, - TA: Asset, - S: IntoSecretHash, -{ - fn is_contained_in_source_ledger_transaction( - swap: OngoingSwap, - transaction: SL::Transaction, - ) -> Result>; -} - -pub trait IsContainedInTargetLedgerTransaction: Send + Sync -where - SL: rfc003::Ledger, - TL: rfc003::Ledger, - SA: Asset, - Self: Asset, - S: IntoSecretHash, -{ - fn is_contained_in_target_ledger_transaction( - swap: OngoingSwap, - tx: TL::Transaction, - ) -> Result>; -} - -impl IsContainedInSourceLedgerTransaction for BitcoinQuantity -where - TL: rfc003::Ledger, - TA: Asset, - S: IntoSecretHash, -{ - fn is_contained_in_source_ledger_transaction( - swap: OngoingSwap, - transaction: ::Transaction, - ) -> Result> { - let address = bitcoin_htlc_address(&swap); - - let (vout, txout) = transaction - .find_output(&address) - .ok_or(Error::WrongTransaction)?; - - let location = OutPoint { - txid: transaction.txid(), - vout: vout as u32, - }; - - let actual_value = BitcoinQuantity::from_satoshi(txout.value); - let required_value = swap.source_asset; - - debug!("Value of HTLC at {:?} is {}", location, actual_value); - - let has_enough_money = actual_value >= required_value; - - trace!( - "{} >= {} -> {}", - actual_value, - required_value, - has_enough_money - ); - if has_enough_money { - Ok(location) - } else { - Err(Error::UnexpectedAsset { - found: actual_value, - expected: required_value, - }) - } - } -} - -impl IsContainedInTargetLedgerTransaction for EtherQuantity -where - SL: rfc003::Ledger, - SA: Asset, - S: IntoSecretHash, -{ - fn is_contained_in_target_ledger_transaction( - swap: OngoingSwap, - tx: ethereum_support::Transaction, - ) -> Result> { - if tx.to != None { - return Err(Error::WrongTransaction); - } - - if tx.input != ethereum_htlc(&swap).compile_to_hex().into() { - return Err(Error::WrongTransaction); - } - - if tx.value < swap.target_asset.wei() { - return Err(Error::UnexpectedAsset { - found: EtherQuantity::from_wei(tx.value), - expected: swap.target_asset, - }); - } - - let from_address: ethereum_support::Address = tx.from; - - Ok(from_address.calculate_contract_address(&tx.nonce)) - } -} - -#[cfg(test)] -mod tests { - extern crate bitcoin_support; - extern crate ethereum_support; - extern crate hex; - extern crate secp256k1_support; - - use super::{Error as ValidationError, *}; - use bitcoin_rpc_client::rpc::{ - ScriptPubKey, ScriptType, SerializedRawTransaction, TransactionOutput, - VerboseRawTransaction, - }; - use bitcoin_support::{BitcoinQuantity, Blocks, Sha256dHash, Transaction}; - use ethereum_support::{ - web3::types::{Bytes, H256, U256}, - EtherQuantity, - }; - use hex::FromHex; - use spectral::prelude::*; - use std::str::FromStr; - use swap_protocols::{ - ledger::Ethereum, - rfc003::{ - ethereum::{ethereum_htlc, Seconds}, - state_machine::*, - AcceptResponseBody, Secret, - }, - }; - - fn gen_start_state( - bitcoin_amount: f64, - ether_amount: U256, - ) -> Start { - Start { - source_ledger_refund_identity: secp256k1_support::KeyPair::from_secret_key_slice( - &hex::decode("18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725") - .unwrap(), - ) - .unwrap(), - target_ledger_success_identity: ethereum_support::Address::from_str( - "8457037fcd80a8650c4692d7fcfc1d0a96b92867", - ) - .unwrap(), - source_ledger: Bitcoin::regtest(), - target_ledger: Ethereum::default(), - source_asset: BitcoinQuantity::from_bitcoin(bitcoin_amount), - target_asset: EtherQuantity::from_wei(ether_amount), - source_ledger_lock_duration: Blocks::from(144), - secret: Secret::from(*b"hello world, you are beautiful!!"), - } - } - - fn gen_response() -> AcceptResponseBody { - AcceptResponseBody { - target_ledger_refund_identity: ethereum_support::Address::from_str( - "71b9f69dcabb340a3fe229c3f94f1662ad85e5e8", - ) - .unwrap(), - source_ledger_success_identity: bitcoin_support::PubkeyHash::from_hex( - "d38e554430c4035f2877a579a07a99886153f071", - ) - .unwrap(), - target_ledger_lock_duration: Seconds(42), - } - } - - #[test] - fn bitcoin_transaction_contains_output_with_sufficient_money() { - let bitcoin_amount = 1.0; - - let start = gen_start_state(bitcoin_amount, U256::from(10)); - let response = gen_response(); - let swap = OngoingSwap::new(start, response); - - let script = bitcoin_htlc_address(&swap).script_pubkey(); - - let script_pub_key = ScriptPubKey { - asm: String::from(""), - hex: script.clone(), - req_sigs: None, - script_type: ScriptType::NullData, - addresses: None, - }; - - let transaction_output = TransactionOutput { - value: swap.clone().source_asset.bitcoin(), - n: 1, - script_pub_key, - }; - - let transaction = VerboseRawTransaction { - txid: Sha256dHash::from_data(b"a"), - hash: String::from(""), - size: 0, - vsize: 0, - version: 1, - locktime: 42, - vin: Vec::new(), - vout: vec![transaction_output], - hex: SerializedRawTransaction(String::from("")), - blockhash: Sha256dHash::from_data(b"blockhash"), - confirmations: 0, - time: 0, - blocktime: 0, - }; - - let bitcoin_transaction: Transaction = transaction.into(); - - let result = BitcoinQuantity::is_contained_in_source_ledger_transaction( - swap.clone(), - bitcoin_transaction.clone(), - ); - - let txid = bitcoin_transaction.txid(); - let expected_outpoint = OutPoint { txid, vout: 0 }; - - assert_that(&result).is_ok_containing(expected_outpoint) - } - - #[test] - fn bitcoin_transaction_does_not_contain_output() { - let bitcoin_amount = 1.0; - - let start = gen_start_state(bitcoin_amount, U256::from(10)); - let response = gen_response(); - let swap = OngoingSwap::new(start, response); - - let transaction = VerboseRawTransaction { - txid: Sha256dHash::from_data(b"refunded"), - hash: String::from(""), - size: 0, - vsize: 0, - version: 1, - locktime: 42, - vin: Vec::new(), - vout: Vec::new(), - hex: SerializedRawTransaction(String::from("")), - blockhash: Sha256dHash::from_data(b"blockhash"), - confirmations: 0, - time: 0, - blocktime: 0, - }; - - let result = - BitcoinQuantity::is_contained_in_source_ledger_transaction(swap, transaction.into()); - - assert_that(&result).is_err_containing(ValidationError::WrongTransaction) - } - - #[test] - fn bitcoin_transaction_does_not_contain_enough_money() { - let bitcoin_amount = 1.0; - - let start = gen_start_state(bitcoin_amount, U256::from(10)); - let response = gen_response(); - let swap = OngoingSwap::new(start, response); - - let script = bitcoin_htlc_address(&swap).script_pubkey(); - let script_pub_key = ScriptPubKey { - asm: String::from(""), - hex: script.clone(), - req_sigs: None, - script_type: ScriptType::NullData, - addresses: None, - }; - - let provided_bitcoin_amount = 0.5; - - let transaction_output = TransactionOutput { - value: provided_bitcoin_amount, - n: 1, - script_pub_key, - }; - - let transaction = VerboseRawTransaction { - txid: Sha256dHash::from_data(b"a"), - hash: String::from(""), - size: 0, - vsize: 0, - version: 1, - locktime: 42, - vin: Vec::new(), - vout: vec![transaction_output], - hex: SerializedRawTransaction(String::from("")), - blockhash: Sha256dHash::from_data(b"blockhash"), - confirmations: 0, - time: 0, - blocktime: 0, - }; - - let result = - BitcoinQuantity::is_contained_in_source_ledger_transaction(swap, transaction.into()); - - let expected_error = ValidationError::UnexpectedAsset { - found: BitcoinQuantity::from_bitcoin(provided_bitcoin_amount), - expected: BitcoinQuantity::from_bitcoin(bitcoin_amount), - }; - - assert_that(&result).is_err_containing(expected_error) - } - - #[test] - pub fn ethereum_tx_has_correct_funding_and_correct_data_should_return_contract_address() { - let ether_amount = U256::from(10); - - let start = gen_start_state(1.0, ether_amount); - let response = gen_response(); - let swap = OngoingSwap::new(start, response); - - let provided_ether_amount = U256::from(10); - let ethereum_transaction = ethereum_support::Transaction { - hash: H256::from(123), - nonce: U256::from(1), - block_hash: None, - block_number: None, - transaction_index: None, - from: "0a81e8be41b21f651a71aab1a85c6813b8bbccf8".parse().unwrap(), - to: None, - value: provided_ether_amount, - gas_price: U256::from(0), - gas: U256::from(0), - input: ethereum_htlc(&swap).compile_to_hex().into(), - }; - - let expected_address = - ethereum_support::Address::from_str("994a1e7928556ba81b85bf3c665a3f4a0f0d4cd9") - .unwrap(); - - let result = - EtherQuantity::is_contained_in_target_ledger_transaction(swap, ethereum_transaction); - - assert_that(&result).is_ok_containing(expected_address) - } - - #[test] - pub fn ethereum_tx_has_incorrect_funding_and_correct_data_should_return_error() { - let ether_amount = U256::from(10); - - let start = gen_start_state(1.0, ether_amount); - let response = gen_response(); - let swap = OngoingSwap::new(start, response); - - let provided_ether_amount = U256::from(9); - let ethereum_transaction = ethereum_support::Transaction { - hash: H256::from(123), - nonce: U256::from(1), - block_hash: None, - block_number: None, - transaction_index: None, - from: "0a81e8be41b21f651a71aab1a85c6813b8bbccf8".parse().unwrap(), - to: None, - value: provided_ether_amount, - gas_price: U256::from(0), - gas: U256::from(0), - input: ethereum_htlc(&swap).compile_to_hex().into(), - }; - - let result = - EtherQuantity::is_contained_in_target_ledger_transaction(swap, ethereum_transaction); - - let expected_error = ValidationError::UnexpectedAsset { - found: EtherQuantity::from_wei(provided_ether_amount), - expected: EtherQuantity::from_wei(ether_amount), - }; - - assert_that(&result).is_err_containing(expected_error) - } - - #[test] - pub fn ethereum_tx_has_correct_funding_but_incorrect_data_should_return_error() { - let ether_amount = U256::from(10); - - let start = gen_start_state(1.0, ether_amount); - let response = gen_response(); - let swap = OngoingSwap::new(start, response); - - let provided_ether_amount = U256::from(9); - let ethereum_transaction = ethereum_support::Transaction { - hash: H256::from(123), - nonce: U256::from(1), - block_hash: None, - block_number: None, - transaction_index: None, - from: "0a81e8be41b21f651a71aab1a85c6813b8bbccf8".parse().unwrap(), - to: None, - value: provided_ether_amount, - gas_price: U256::from(0), - gas: U256::from(0), - input: Bytes::from(vec![1, 2, 3]), - }; - - let result = - EtherQuantity::is_contained_in_target_ledger_transaction(swap, ethereum_transaction); - - let expected_error = ValidationError::WrongTransaction; - - assert_that(&result).is_err_containing(expected_error) - } - - #[test] - pub fn ethereum_tx_has_correct_funding_but_not_sending_to_0_should_return_error() { - let ether_amount = U256::from(10); - - let start = gen_start_state(1.0, ether_amount); - let response = gen_response(); - let swap = OngoingSwap::new(start, response); - - let provided_ether_amount = U256::from(9); - let ethereum_transaction = ethereum_support::Transaction { - hash: H256::from(123), - nonce: U256::from(1), - block_hash: None, - block_number: None, - transaction_index: None, - from: "0a81e8be41b21f651a71aab1a85c6813b8bbccf8".parse().unwrap(), - to: Some("0000000000000000000000000000000000000001".parse().unwrap()), - value: provided_ether_amount, - gas_price: U256::from(0), - gas: U256::from(0), - input: ethereum_htlc(&swap).compile_to_hex().into(), - }; - - let result = - EtherQuantity::is_contained_in_target_ledger_transaction(swap, ethereum_transaction); - - let expected_error = ValidationError::WrongTransaction; - - assert_that(&result).is_err_containing(expected_error) - } -} diff --git a/application/comit_node/tests/rfc003_states.rs b/application/comit_node/tests/rfc003_states.rs deleted file mode 100644 index fb2c73b73f..0000000000 --- a/application/comit_node/tests/rfc003_states.rs +++ /dev/null @@ -1,245 +0,0 @@ -extern crate bitcoin_rpc_client; -extern crate bitcoin_support; -extern crate comit_node; -extern crate ethereum_support; -extern crate futures; -extern crate hex; -extern crate secp256k1_support; -extern crate tokio; -extern crate tokio_timer; -use bitcoin_rpc_client::rpc::{SerializedRawTransaction, VerboseRawTransaction}; -use bitcoin_support::{BitcoinQuantity, Blocks, OutPoint, Sha256dHash}; -use comit_node::{ - comit_client::SwapReject, - swap_protocols::{ - ledger::{Bitcoin, Ethereum}, - rfc003::{ - ethereum::Seconds, - events::{ - self, Events, RequestResponded, SourceHtlcFunded, SourceHtlcRedeemedOrRefunded, - SourceHtlcRefundedTargetHtlcFunded, TargetHtlcRedeemedOrRefunded, - }, - state_machine::*, - AcceptResponseBody, Request, Secret, - }, - }, -}; -use ethereum_support::EtherQuantity; -use futures::{ - future::{self, Either}, - sync::mpsc, - Stream, -}; -use hex::FromHex; -use std::{str::FromStr, sync::Arc}; - -#[derive(Default)] -struct FakeEvents { - pub response: Option>>, - pub source_htlc_funded: Option>>, - pub source_htlc_refunded_target_htlc_funded: - Option>>, -} - -impl RequestResponded for FakeEvents { - fn request_responded( - &mut self, - _request: &Request, - ) -> &mut Box> { - self.response.as_mut().unwrap() - } -} - -impl SourceHtlcFunded for FakeEvents { - fn source_htlc_funded( - &mut self, - _swap: &OngoingSwap, - ) -> &mut Box> { - self.source_htlc_funded.as_mut().unwrap() - } -} - -impl SourceHtlcRefundedTargetHtlcFunded - for FakeEvents -{ - fn source_htlc_refunded_target_htlc_funded( - &mut self, - _swap: &OngoingSwap, - _source_htlc_location: &bitcoin_support::OutPoint, - ) -> &mut Box> { - self.source_htlc_refunded_target_htlc_funded - .as_mut() - .unwrap() - } -} - -impl TargetHtlcRedeemedOrRefunded - for FakeEvents -{ - fn target_htlc_redeemed_or_refunded( - &mut self, - _swap: &OngoingSwap, - _target_htlc_location: ðereum_support::Address, - ) -> &mut Box> { - unimplemented!() - } -} - -impl SourceHtlcRedeemedOrRefunded - for FakeEvents -{ - fn source_htlc_redeemed_or_refunded( - &mut self, - _swap: &OngoingSwap, - _target_htlc_location: &bitcoin_support::OutPoint, - ) -> &mut Box> { - unimplemented!() - } -} - -impl Events for FakeEvents {} - -fn gen_start_state() -> Start { - Start { - source_ledger_refund_identity: secp256k1_support::KeyPair::from_secret_key_slice( - &hex::decode("18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725") - .unwrap(), - ) - .unwrap(), - target_ledger_success_identity: ethereum_support::Address::from_str( - "8457037fcd80a8650c4692d7fcfc1d0a96b92867", - ) - .unwrap(), - source_ledger: Bitcoin::regtest(), - target_ledger: Ethereum::default(), - source_asset: BitcoinQuantity::from_bitcoin(1.0), - target_asset: EtherQuantity::from_eth(10.0), - source_ledger_lock_duration: Blocks::from(144), - secret: Secret::from(*b"hello world, you are beautiful!!"), - } -} - -fn init( - state: SwapStates, - events: FakeEvents, -) -> ( - SwapFuture, - impl Stream< - Item = SwapStates, - Error = (), - >, -) { - let (state_sender, state_receiver) = mpsc::unbounded(); - let context = Context { - events: Box::new(events), - state_repo: Arc::new(state_sender), - }; - let final_state_future = Swap::start_in(state, context); - (final_state_future, state_receiver.map_err(|_| ())) -} - -macro_rules! run_state_machine { - ($state_machine:ident, $states:ident, $( $expected_state:expr ) , * ) => { - { - let mut expected_states = Vec::new(); - - $( - let state = $expected_state; - expected_states.push(SwapStates::from(state)); - ) - * - - let number_of_expected_states = expected_states.len() + 1; - - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - - let state_machine_result = runtime.block_on($state_machine).unwrap(); - let actual_states = runtime.block_on($states.take(number_of_expected_states as u64).collect()).unwrap(); - - expected_states.push(SwapStates::from(Final(state_machine_result))); - - assert_eq!(actual_states, expected_states); - } - }; - - ($state_machine:ident, $states:ident) => { - run_state_machine!($state_machine, $states, ); - }; -} - -#[test] -fn when_swap_is_rejected_go_to_final_reject() { - let start = gen_start_state(); - - let (state_machine, states) = init( - start.clone().into(), - FakeEvents { - response: Some(Box::new(future::ok(Err(SwapReject::Rejected)))), - ..Default::default() - }, - ); - - run_state_machine!(state_machine, states); -} - -#[test] -fn source_refunded() { - let bob_response = AcceptResponseBody { - target_ledger_refund_identity: ethereum_support::Address::from_str( - "71b9f69dcabb340a3fe229c3f94f1662ad85e5e8", - ) - .unwrap(), - source_ledger_success_identity: bitcoin_support::PubkeyHash::from_hex( - "d38e554430c4035f2877a579a07a99886153f071", - ) - .unwrap(), - target_ledger_lock_duration: Seconds(42), - }; - - let start = gen_start_state(); - - let (state_machine, states) = init( - start.clone().into(), - FakeEvents { - response: Some(Box::new(future::ok(Ok(bob_response.clone())))), - source_htlc_funded: Some(Box::new(future::ok(OutPoint { - txid: Sha256dHash::from_data(b"funding"), - vout: 0, - }))), - source_htlc_refunded_target_htlc_funded: Some(Box::new(future::ok(Either::A( - VerboseRawTransaction { - txid: Sha256dHash::from_data(b"refunded"), - hash: String::from(""), - size: 0, - vsize: 0, - version: 1, - locktime: 42, - vin: Vec::new(), - vout: Vec::new(), - hex: SerializedRawTransaction(String::from("")), - blockhash: Sha256dHash::from_data(b"blockhash"), - confirmations: 0, - time: 0, - blocktime: 0, - } - .into(), - )))), - ..Default::default() - }, - ); - - run_state_machine!( - state_machine, - states, - Accepted { - swap: OngoingSwap::new(start.clone(), bob_response.clone()), - }, - SourceFunded { - swap: OngoingSwap::new(start.clone(), bob_response.clone()), - source_htlc_location: OutPoint { - txid: Sha256dHash::from_data(b"funding"), - vout: 0 - } - } - ); -}