Skip to content

Commit

Permalink
UTXO swap watcher further improvements #431 (#1552)
Browse files Browse the repository at this point in the history
* add initial swap watcher node functionality

* broadcast watcher message immediately after taker payment is sent

* rename the WatcherData struct to TakerSwapWatcherData

* use separate SwapWatcherMsg for watchers

* make taker send the entire spending transaction to watcher nodes

* broadcast watcher message periodically in wait_for_taker_payment_spend

* check if transaction outputs is empty in p2sh_spending_tx

* fix minor details

* move utxo coin generation methods to docker_tests_common

* use Mm2TestConf for watcher node tests

* prevent running multiple watcher threads for the same swap

* remove async from spawn_taker_swap_watcher

* check if htlc spend fee is greater than the transaction output

* add empty line at the end of docker_tests_common

* discard prev_transaction mutability after modification

* check if transaction outputs is empty in utxo_common functions

* use the drop_mutability macro

* move all watcher related code to the swap_watcher module

* improve the taker_swap_watchers usage

* remove unused types and structs

* check if the transaction script has instructions

* check if the transaction has any inputs

* fix minor stuff

* make watchers validate taker payment at most once

* release taker_swap_watchers lock after inserting the uuid to the set

* fix the error type in instruction iterator

* reorder taker_swap_watchers lock/release structure

* use separate files for large json test strings

* remove watcher arguments from test helper functions

* propagate watcher messages whether the node is watcher or not

* remove swap lock related code from the swap_watcher

* remove running_swaps related code from swap_watcher

* check if a watcher with the same uuid exists before spawning a thread

* fix minor problems

* remove AtomicSwap implementation from swap_watcher

* make taker trigger a WatcherMessageSent event only if it is actually sent

* remove an unnecessary line

* log error if watcher message could not be sent

* improve mutex usage for the taker_swap_watchers

* fix formatting

* add state machines, watcher refund and ValidatePaymentError

* use DuplicateCache for taker_swap_watchers

* remove println statements

* make the watchers wait for half the locktime before spending the maker payment

* make the watchers wait for the taker before refunding

* add refund transaction confirmation for watchers

* check at watchers if the taker payment is already spent or refunded

* validate the public keys in the watcher message

* validate taker fees at watchers

* fix state implementation return types

* fix a bug in test_watcher_node

* change the name of test_watcher_node

* fix a bug in test_validate_maker_payment_malicious

* Fix ValidatePaymentError usage

* add preimage suffix to watcher-related transaction methods

* fix error handling

* separate WatcherOps from SwapOps

* fix error match usage

* reduce bchd_grpc module visibility to pub(crate)

* fix a bug in qrc20_tests

* inline WatcherOps functions

* increase dynamic fees for watcher preimages

* fix ethereum watcher preimage fee approximation

* take back unnecessary changes

* inline SwapOps functions

* use taker fee hash for logs instead of uuid

* extend TAKER_SWAP_ENTRY_TIMEOUT to 6 hours

* remove swap_unique_data field from WatcherSearchForSwapTxSpendInput struct

* fix error handling

* remove unnecessary taker swap removal in Stopped state

* improve match usage

* use WAIT_FOR_TAKER_PAYMENT_INTERVAL const

* inline DuplicateCache remove method

* add functions for wait taker/maker payment confirmation durations

* improve error handling

* rename check_all_inputs_signed_by_pub method

* add small fixes

* fix WatcherError naming

* maker watchers periodically check for maker payment spend

* remove the watcher_search_for_swap_tx_spend method

* fix payment search interval

* small fixes

* add more watcher taker fee validations

* validate taker fee lock_time with taker payment

* remove debugging leftover

* improve swap watcher integration tests

* fix small timing issues in taker swap

* adjust swap watcher timings and add more integration tests

* remove redundant taker_payment_lock field from the watcher message

* use maker payment hash instead of tx bytes

* add missing implementations for watcher_search_for_swap_tx_spend

* add test_watcher_validate_taker_fee

* add WatcherValidateTakerFeeInput struct

* use taker payment hash instead of hex in watcher message

* remove redundant taker_pub field from the watcher message

* use derive_htlc_key_pair to sign watcher messages

* apply minor fixes

* fix a bug in test_watcher_validate_taker_fee

* remove the taker_amount field and its usages

* remove the wait_for_confirmations_by_hash method

* implement helper methods for WatcherContext

* remove wait_for_confirmations from RefundTakerPayment state

* increase wait_for_taker_refund_deadline

* pass the secret_hash to the wait_for_htlc_tx_spend method

* make watchers sleep before calling can_refund_htlc

* fix a clippy warning

* add more helper functions for WatcherContext

* use debug macro instead of log

* fix formatting

* retry watcher_validate_taker_fee in case of error

* define TAKER_PAYMENT_SPEND_SEARCH_INTERVAL separately in different modules

* implement get_tx_hex_by_hash for the missing coins

* validate taker payment locking script

* fix a bug in docker_tests_inner

* fix formatting

* remove an unused test case

* add DEFAULT_SWAP_VIN constant

* remove loop to search for taker payment

* fix taker payment locking script validation

* move the loop out from watcher_search_for_swap_tx_spend

* use helper functions for mycoin, mycoin1 and kmd configuration

* remove logs before errors

* fix a mistake in test_get_max_taker_vol_dust_threshold

* use or_mm_err instead of ok_or_else

* add as_slice method for Script and Bytes structs

* use configurations for watcher variables

* improve error handling

* refactor the WaitForTakerPaymentSpend state

* reduce TAKER_PAYMENT_SPEND_SEARCH_INTERVAL to 1 for tests

* refactor get_tx_hex_by_hash_impl and get_raw_transaction_impl methods

* change watcher log messages

* use try_s macro instead of map_err

* use constants for logs used in watcher tests

* add new struct WatcherConf

* fix a success case name

* implement Default for WatcherConf

* fix the name of wait_for_taker_payment_conf methods
  • Loading branch information
caglaryucekaya authored Dec 12, 2022
1 parent c88ac74 commit 2e72027
Show file tree
Hide file tree
Showing 31 changed files with 1,345 additions and 566 deletions.
49 changes: 37 additions & 12 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ use super::{coin_conf, AsyncMutex, BalanceError, BalanceFut, CheckIfMyPaymentSen
TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod,
ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr,
ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult,
WatcherOps, WatcherValidatePaymentInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest,
WithdrawResult};
WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput,
WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult};
pub use rlp;

#[cfg(test)] mod eth_tests;
Expand Down Expand Up @@ -646,8 +646,17 @@ async fn get_raw_transaction_impl(coin: EthCoin, req: RawTransactionRequest) ->
None => &req.tx_hash,
};
let hash = H256::from_str(tx).map_to_mm(|e| RawTransactionError::InvalidHashError(e.to_string()))?;
let web3_tx = coin.web3.eth().transaction(TransactionId::Hash(hash)).compat().await?;
let web3_tx = web3_tx.or_mm_err(|| RawTransactionError::HashNotExist(req.tx_hash))?;
get_tx_hex_by_hash_impl(coin, hash).await
}

async fn get_tx_hex_by_hash_impl(coin: EthCoin, tx_hash: H256) -> RawTransactionResult {
let web3_tx = coin
.web3
.eth()
.transaction(TransactionId::Hash(tx_hash))
.compat()
.await?
.or_mm_err(|| RawTransactionError::HashNotExist(tx_hash.to_string()))?;
let raw = signed_tx_from_web3_tx(web3_tx).map_to_mm(RawTransactionError::InternalError)?;
Ok(RawTransactionRes {
tx_hex: BytesJson(rlp::encode(&raw)),
Expand Down Expand Up @@ -1119,7 +1128,7 @@ impl SwapOps for EthCoin {
.await
}

fn check_tx_signed_by_pub(&self, _tx: &[u8], _expected_pub: &[u8]) -> Result<bool, String> {
fn check_tx_signed_by_pub(&self, _tx: &[u8], _expected_pub: &[u8]) -> Result<bool, MmError<ValidatePaymentError>> {
unimplemented!();
}

Expand Down Expand Up @@ -1226,11 +1235,11 @@ impl SwapOps for EthCoin {

#[async_trait]
impl WatcherOps for EthCoin {
fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut {
fn send_maker_payment_spend_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut {
unimplemented!();
}

fn create_taker_spends_maker_payment_preimage(
fn create_maker_payment_spend_preimage(
&self,
_maker_payment_tx: &[u8],
_time_lock: u32,
Expand All @@ -1241,7 +1250,7 @@ impl WatcherOps for EthCoin {
unimplemented!();
}

fn create_taker_refunds_payment_preimage(
fn create_taker_payment_refund_preimage(
&self,
_taker_payment_tx: &[u8],
_time_lock: u32,
Expand All @@ -1253,17 +1262,24 @@ impl WatcherOps for EthCoin {
unimplemented!();
}

fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut {
fn send_taker_payment_refund_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut {
unimplemented!();
}

fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec<u8>, _verified_pub: Vec<u8>) -> ValidatePaymentFut<()> {
fn watcher_validate_taker_fee(&self, _input: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()> {
unimplemented!();
}

fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> {
unimplemented!();
}

async fn watcher_search_for_swap_tx_spend(
&self,
_input: WatcherSearchForSwapTxSpendInput<'_>,
) -> Result<Option<FoundSwapTxSpend>, String> {
unimplemented!();
}
}

#[cfg_attr(test, mockable)]
Expand Down Expand Up @@ -1456,6 +1472,7 @@ impl MarketCoinOps for EthCoin {
wait_until: u64,
from_block: u64,
swap_contract_address: &Option<BytesJson>,
check_every: f64,
) -> TransactionFut {
let unverified: UnverifiedTransaction = try_tx_fus!(rlp::decode(tx_bytes));
let tx = try_tx_fus!(SignedEthTx::new(unverified));
Expand Down Expand Up @@ -1517,12 +1534,12 @@ impl MarketCoinOps for EthCoin {
Ok(Some(t)) => t,
Ok(None) => {
info!("Tx {} not found yet", tx_hash);
Timer::sleep(5.).await;
Timer::sleep(check_every).await;
continue;
},
Err(e) => {
error!("Get tx {} error: {}", tx_hash, e);
Timer::sleep(5.).await;
Timer::sleep(check_every).await;
continue;
},
};
Expand Down Expand Up @@ -3244,6 +3261,14 @@ impl MmCoin for EthCoin {
Box::new(get_raw_transaction_impl(self.clone(), req).boxed().compat())
}

fn get_tx_hex_by_hash(&self, tx_hash: Vec<u8>) -> RawTransactionFut {
Box::new(
get_tx_hex_by_hash_impl(self.clone(), H256::from(tx_hash.as_slice()))
.boxed()
.compat(),
)
}

fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut {
Box::new(Box::pin(withdraw_impl(self.clone(), req)).compat())
}
Expand Down
11 changes: 10 additions & 1 deletion mm2src/coins/eth/eth_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE: u64 = 52_500_000_000;
// `GAS_PRICE` increased by 7%
const GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE: u64 = 53_500_000_000;

const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 1.;

fn check_sum(addr: &str, expected: &str) {
let actual = checksum_address(addr);
assert_eq!(expected, actual);
Expand Down Expand Up @@ -470,7 +472,14 @@ fn test_wait_for_payment_spend_timeout() {
];

assert!(coin
.wait_for_htlc_tx_spend(&tx_bytes, &[], wait_until, from_block, &coin.swap_contract_address())
.wait_for_htlc_tx_spend(
&tx_bytes,
&[],
wait_until,
from_block,
&coin.swap_contract_address(),
TAKER_PAYMENT_SPEND_SEARCH_INTERVAL
)
.wait()
.is_err());
}
Expand Down
33 changes: 25 additions & 8 deletions mm2src/coins/lightning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, F
TradePreimageResult, TradePreimageValue, Transaction, TransactionEnum, TransactionErr, TransactionFut,
TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs,
ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut,
ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, WatcherValidatePaymentInput,
WithdrawError, WithdrawFut, WithdrawRequest};
ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput,
WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest};
use async_trait::async_trait;
use bitcoin::bech32::ToBase32;
use bitcoin::hashes::Hash;
Expand Down Expand Up @@ -719,7 +719,7 @@ impl SwapOps for LightningCoin {
unimplemented!()
}

fn check_tx_signed_by_pub(&self, _tx: &[u8], _expected_pub: &[u8]) -> Result<bool, String> {
fn check_tx_signed_by_pub(&self, _tx: &[u8], _expected_pub: &[u8]) -> Result<bool, MmError<ValidatePaymentError>> {
unimplemented!();
}

Expand Down Expand Up @@ -831,7 +831,7 @@ fn payment_hash_from_slice(data: &[u8]) -> Result<PaymentHash, PaymentHashFromSl

#[async_trait]
impl WatcherOps for LightningCoin {
fn create_taker_spends_maker_payment_preimage(
fn create_maker_payment_spend_preimage(
&self,
_maker_payment_tx: &[u8],
_time_lock: u32,
Expand All @@ -842,11 +842,11 @@ impl WatcherOps for LightningCoin {
unimplemented!();
}

fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut {
fn send_maker_payment_spend_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut {
unimplemented!();
}

fn create_taker_refunds_payment_preimage(
fn create_taker_payment_refund_preimage(
&self,
_taker_payment_tx: &[u8],
_time_lock: u32,
Expand All @@ -858,17 +858,24 @@ impl WatcherOps for LightningCoin {
unimplemented!();
}

fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut {
fn send_taker_payment_refund_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut {
unimplemented!();
}

fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec<u8>, _verified_pub: Vec<u8>) -> ValidatePaymentFut<()> {
fn watcher_validate_taker_fee(&self, _input: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()> {
unimplemented!();
}

fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> {
unimplemented!();
}

async fn watcher_search_for_swap_tx_spend(
&self,
_input: WatcherSearchForSwapTxSpendInput<'_>,
) -> Result<Option<FoundSwapTxSpend>, String> {
unimplemented!();
}
}

impl MarketCoinOps for LightningCoin {
Expand Down Expand Up @@ -1018,6 +1025,7 @@ impl MarketCoinOps for LightningCoin {
wait_until: u64,
_from_block: u64,
_swap_contract_address: &Option<BytesJson>,
_check_every: f64,
) -> TransactionFut {
let payment_hash = try_tx_fus!(payment_hash_from_slice(transaction));
let payment_hex = hex::encode(payment_hash.0);
Expand Down Expand Up @@ -1112,6 +1120,15 @@ impl MmCoin for LightningCoin {
Box::new(fut.boxed().compat())
}

fn get_tx_hex_by_hash(&self, _tx_hash: Vec<u8>) -> RawTransactionFut {
let fut = async move {
MmError::err(RawTransactionError::InternalError(
"get_tx_hex_by_hash method is not supported for lightning.".into(),
))
};
Box::new(fut.boxed().compat())
}

fn withdraw(&self, _req: WithdrawRequest) -> WithdrawFut {
let fut = async move {
MmError::err(WithdrawError::InternalError(
Expand Down
2 changes: 2 additions & 0 deletions mm2src/coins/lightning/ln_platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering, Ordering};

const CHECK_FOR_NEW_BEST_BLOCK_INTERVAL: f64 = 60.;
const TRY_LOOP_INTERVAL: f64 = 60.;
const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 10.;

#[inline]
pub fn h256_json_from_txid(txid: Txid) -> H256Json { H256Json::from(txid.as_hash().into_inner()).reversed() }
Expand Down Expand Up @@ -524,6 +525,7 @@ impl Platform {
(now_ms() / 1000) + 3600,
from_block.try_into()?,
&None,
TAKER_PAYMENT_SPEND_SEARCH_INTERVAL,
)
.compat()
.await
Expand Down
45 changes: 36 additions & 9 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,14 +486,23 @@ pub enum ValidateOtherPubKeyErr {
InvalidPubKey(String),
}

#[derive(Clone, Debug)]
pub struct WatcherValidateTakerFeeInput {
pub taker_fee_hash: Vec<u8>,
pub sender_pubkey: Vec<u8>,
pub min_block_number: u64,
pub fee_addr: Vec<u8>,
pub lock_duration: u64,
}

#[derive(Clone, Debug)]
pub struct WatcherValidatePaymentInput {
pub payment_tx: Vec<u8>,
pub taker_payment_refund_preimage: Vec<u8>,
pub time_lock: u32,
pub taker_pub: Vec<u8>,
pub maker_pub: Vec<u8>,
pub secret_hash: Vec<u8>,
pub amount: BigDecimal,
pub try_spv_proof_until: u64,
pub confirmations: u64,
}
Expand All @@ -512,6 +521,16 @@ pub struct ValidatePaymentInput {
pub unique_swap_data: Vec<u8>,
}

#[derive(Clone, Debug)]
pub struct WatcherSearchForSwapTxSpendInput<'a> {
pub time_lock: u32,
pub taker_pub: &'a [u8],
pub maker_pub: &'a [u8],
pub secret_hash: &'a [u8],
pub tx: &'a [u8],
pub search_from_block: u64,
}

pub struct SearchForSwapTxSpendInput<'a> {
pub time_lock: u32,
pub other_pub: &'a [u8],
Expand Down Expand Up @@ -664,7 +683,7 @@ pub trait SwapOps {

async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result<Vec<u8>, String>;

fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result<bool, String>;
fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result<bool, MmError<ValidatePaymentError>>;

/// Whether the refund transaction can be sent now
/// For example: there are no additional conditions for ETH, but for some UTXO coins we should wait for
Expand Down Expand Up @@ -727,11 +746,11 @@ pub trait SwapOps {

#[async_trait]
pub trait WatcherOps {
fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut;
fn send_maker_payment_spend_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut;

fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut;
fn send_taker_payment_refund_preimage(&self, preimage: &[u8]) -> TransactionFut;

fn create_taker_refunds_payment_preimage(
fn create_taker_payment_refund_preimage(
&self,
_taker_payment_tx: &[u8],
_time_lock: u32,
Expand All @@ -741,7 +760,7 @@ pub trait WatcherOps {
_swap_unique_data: &[u8],
) -> TransactionFut;

fn create_taker_spends_maker_payment_preimage(
fn create_maker_payment_spend_preimage(
&self,
_maker_payment_tx: &[u8],
_time_lock: u32,
Expand All @@ -750,9 +769,14 @@ pub trait WatcherOps {
_swap_unique_data: &[u8],
) -> TransactionFut;

fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec<u8>, _verified_pub: Vec<u8>) -> ValidatePaymentFut<()>;
fn watcher_validate_taker_fee(&self, input: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()>;

fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()>;

async fn watcher_search_for_swap_tx_spend(
&self,
input: WatcherSearchForSwapTxSpendInput<'_>,
) -> Result<Option<FoundSwapTxSpend>, String>;
}

/// Operations that coins have independently from the MarketMaker.
Expand Down Expand Up @@ -813,6 +837,7 @@ pub trait MarketCoinOps {
wait_until: u64,
from_block: u64,
swap_contract_address: &Option<BytesJson>,
check_every: f64,
) -> TransactionFut;

fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result<TransactionEnum, MmError<TxMarshalingErr>>;
Expand Down Expand Up @@ -1195,12 +1220,12 @@ pub enum FeeApproxStage {
WithoutApprox,
/// Increase the trade fee slightly.
StartSwap,
/// Increase the trade fee slightly
WatcherPreimage,
/// Increase the trade fee significantly.
OrderIssue,
/// Increase the trade fee largely.
TradePreimage,
/// Increase the trade fee very largely
WatcherPreimage,
}

#[derive(Debug)]
Expand Down Expand Up @@ -1869,6 +1894,8 @@ pub trait MmCoin: SwapOps + WatcherOps + MarketCoinOps + Send + Sync + 'static {

fn get_raw_transaction(&self, req: RawTransactionRequest) -> RawTransactionFut;

fn get_tx_hex_by_hash(&self, tx_hash: Vec<u8>) -> RawTransactionFut;

/// Maximum number of digits after decimal point used to denominate integer coin units (satoshis, wei, etc.)
fn decimals(&self) -> u8;

Expand Down
Loading

0 comments on commit 2e72027

Please sign in to comment.