Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reduce evm wait_for_confirmations calls, fix endless loop in wait_for_htlc_tx_spend #1724

Merged
merged 11 commits into from
Mar 21, 2023
Merged
151 changes: 107 additions & 44 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,22 @@ cfg_wasm32! {
}

use super::{coin_conf, AsyncMutex, BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner,
CoinProtocol, CoinTransportMetrics, CoinsContext, EthValidateFeeArgs, FeeApproxStage, FoundSwapTxSpend,
HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError,
NegotiateSwapContractAddrErr, NumConversError, NumConversResult, PaymentInstructions,
PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError,
RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundError,
RefundPaymentArgs, RefundResult, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared,
SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError,
SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError,
TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails,
TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod,
ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr,
ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult,
WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput,
WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, EARLY_CONFIRMATION_ERR_LOG,
INSUFFICIENT_WATCHER_REWARD_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG,
INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG};
CoinProtocol, CoinTransportMetrics, CoinsContext, ConfirmPaymentInput, EthValidateFeeArgs, FeeApproxStage,
FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin,
MyAddressError, MyWalletAddress, NegotiateSwapContractAddrErr, NumConversError, NumConversResult,
PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed,
RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult,
RefundError, RefundPaymentArgs, RefundResult, RpcClientType, RpcTransportEventHandler,
RpcTransportEventHandlerShared, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput,
SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee,
TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction,
TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr,
UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr,
ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError,
VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput,
WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult,
EARLY_CONFIRMATION_ERR_LOG, INSUFFICIENT_WATCHER_REWARD_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG,
INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG};
pub use rlp;

#[cfg(test)] mod eth_tests;
Expand All @@ -100,7 +100,6 @@ mod web3_transport;
#[path = "eth/v2_activation.rs"] pub mod v2_activation;
#[cfg(feature = "enable-nft-integration")]
use crate::nft::WithdrawNftResult;
use crate::MyWalletAddress;
#[cfg(feature = "enable-nft-integration")]
use crate::{lp_coinfind_or_err, MmCoinEnum, TransactionType};
use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error};
Expand Down Expand Up @@ -1726,31 +1725,24 @@ impl MarketCoinOps for EthCoin {
)
}

fn wait_for_confirmations(
&self,
tx: &[u8],
confirmations: u64,
_requires_nota: bool,
wait_until: u64,
check_every: u64,
) -> Box<dyn Future<Item = (), Error = String> + Send> {
fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box<dyn Future<Item = (), Error = String> + Send> {
let ctx = try_fus!(MmArc::from_weak(&self.ctx).ok_or("No context"));
let mut status = ctx.log.status_handle();
status.status(&[&self.ticker], "Waiting for confirmations…");
status.deadline(wait_until * 1000);
status.deadline(input.wait_until * 1000);

let unsigned: UnverifiedTransaction = try_fus!(rlp::decode(tx));
let unsigned: UnverifiedTransaction = try_fus!(rlp::decode(&input.payment_tx));
let tx = try_fus!(SignedEthTx::new(unsigned));

let required_confirms = U64::from(confirmations);
let required_confirms = U64::from(input.confirmations);
let selfi = self.clone();
let fut = async move {
loop {
if status.ms2deadline().unwrap() < 0 {
status.append(" Timed out.");
return ERR!(
"Waited too long until {} for transaction {:?} confirmation ",
wait_until,
input.wait_until,
tx
);
}
Expand All @@ -1764,7 +1756,7 @@ impl MarketCoinOps for EthCoin {
selfi.ticker(),
tx.tx_hash()
);
Timer::sleep(check_every as f64).await;
Timer::sleep(input.check_every as f64).await;
continue;
},
};
Expand All @@ -1788,7 +1780,7 @@ impl MarketCoinOps for EthCoin {
e,
selfi.ticker()
);
Timer::sleep(check_every as f64).await;
Timer::sleep(input.check_every as f64).await;
continue;
},
};
Expand All @@ -1799,7 +1791,7 @@ impl MarketCoinOps for EthCoin {
status.append(" Timed out.");
return ERR!(
"Waited too long until {} for transaction {:?} confirmation ",
wait_until,
input.wait_until,
tx
);
}
Expand All @@ -1808,30 +1800,50 @@ impl MarketCoinOps for EthCoin {
// https://github.com/KomodoPlatform/atomicDEX-API/issues/1630#issuecomment-1401736168
match selfi.web3.eth().transaction(TransactionId::Hash(tx.hash)).await {
Ok(Some(_)) => {
if let Some(contract) = input.swap_contract_address.clone() {
let swap_contract_address = match contract.try_to_address() {
Ok(addr) => addr,
Err(e) => return ERR!("{}", e),
};
let swap_id = selfi.etomic_swap_id(input.time_lock, &input.secret_hash);
if let Err(e) = selfi
.wait_for_payment_state_initialization(
tx.hash,
swap_contract_address,
Token::FixedBytes(swap_id.clone()),
input.wait_until,
input.check_every,
)
.compat()
.await
{
return ERR!("{}", e);
}
}
status.append(" Confirmed.");
return Ok(());
},
Ok(None) => error!(
"Didn't find tx: {:02x} for coin: {} on RPC node using eth_getTransactionByHash. Retrying in {} seconds",
tx.hash,
selfi.ticker(),
check_every
input.check_every
),
Err(e) => error!(
"Error {} calling eth_getTransactionByHash for coin: {}, tx: {:02x}. Retrying in {} seconds",
e,
selfi.ticker(),
tx.hash,
check_every
input.check_every
),
}

Timer::sleep(check_every as f64).await;
Timer::sleep(input.check_every as f64).await;
}
}
}
}
Timer::sleep(check_every as f64).await;
Timer::sleep(input.check_every as f64).await;
}
};
Box::new(fut.boxed().compat())
Expand Down Expand Up @@ -1881,6 +1893,14 @@ impl MarketCoinOps for EthCoin {

let fut = async move {
loop {
if now_ms() / 1000 > wait_until {
return TX_PLAIN_ERR!(
"Waited too long until {} for transaction {:?} to be spent ",
wait_until,
tx,
);
}

let current_block = match selfi.current_block().compat().await {
Ok(b) => b,
Err(e) => {
Expand Down Expand Up @@ -1925,15 +1945,7 @@ impl MarketCoinOps for EthCoin {
}
}

if now_ms() / 1000 > wait_until {
return TX_PLAIN_ERR!(
"Waited too long until {} for transaction {:?} to be spent ",
wait_until,
tx,
);
}
Timer::sleep(5.).await;
continue;
}
};
Box::new(fut.boxed().compat())
Expand Down Expand Up @@ -3862,6 +3874,57 @@ impl EthCoin {
}))
}

fn wait_for_payment_state_initialization(
&self,
payment_hash: H256,
swap_contract_address: H160,
token: Token,
wait_until: u64,
check_every: u64,
) -> Box<dyn Future<Item = U256, Error = String> + Send + 'static> {
let selfi = self.clone();
let fut = async move {
loop {
if now_ms() / 1000 > wait_until {
ERRL!("Waited too long until {} for payment state to initialize!", wait_until);
}

let status = match selfi
.payment_status(swap_contract_address, token.clone())
.compat()
.await
{
Ok(status) => status,
Err(e) => {
error!(
"Error {} getting payment status tx: {:02x} from swap contract: {} for coin: {}. Retrying in {} seconds",
e,
payment_hash,
swap_contract_address,
selfi.ticker(),
check_every
);
Timer::sleep(check_every as f64).await;
continue;
},
};
if status == PAYMENT_STATE_UNINITIALIZED.into() {
error!(
"Payment tx: {:02x} is in `uninitialized` state in swap contract: {} for coin: {}. Retrying in {} seconds",
payment_hash,
swap_contract_address,
selfi.ticker(),
check_every
);
} else {
break Ok(status);
}
Timer::sleep(check_every as f64).await;
}
};
Box::new(fut.boxed().compat())
}

async fn search_for_swap_tx_spend(
&self,
tx: &[u8],
Expand Down
39 changes: 16 additions & 23 deletions mm2src/coins/lightning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ use crate::lightning::ln_utils::{filter_channels, pay_invoice_with_max_total_clt
use crate::utxo::rpc_clients::UtxoRpcClientEnum;
use crate::utxo::utxo_common::{big_decimal_from_sat, big_decimal_from_sat_unsigned};
use crate::utxo::{sat_from_big_decimal, utxo_common, BlockchainNetwork};
use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend,
HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr,
PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut,
RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput,
SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs,
SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue,
Transaction, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod,
UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr,
ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult,
WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput,
WithdrawError, WithdrawFut, WithdrawRequest};
use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage,
FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin,
NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionError,
RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult,
SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError,
SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut,
TradePreimageResult, TradePreimageValue, Transaction, TransactionEnum, TransactionErr, TransactionFut,
TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs,
ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut,
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 @@ -1071,24 +1071,17 @@ impl MarketCoinOps for LightningCoin {

// Todo: Add waiting for confirmations logic for the case of if the channel is closed and the htlc can be claimed on-chain
// Todo: The above is postponed and might not be needed after this issue is resolved https://github.com/lightningdevkit/rust-lightning/issues/2017
fn wait_for_confirmations(
&self,
tx: &[u8],
_confirmations: u64,
_requires_nota: bool,
wait_until: u64,
check_every: u64,
) -> Box<dyn Future<Item = (), Error = String> + Send> {
let payment_hash = try_f!(payment_hash_from_slice(tx).map_err(|e| e.to_string()));
fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box<dyn Future<Item = (), Error = String> + Send> {
let payment_hash = try_f!(payment_hash_from_slice(&input.payment_tx).map_err(|e| e.to_string()));
let payment_hex = hex::encode(payment_hash.0);

let coin = self.clone();
let fut = async move {
loop {
if now_ms() / 1000 > wait_until {
if now_ms() / 1000 > input.wait_until {
return ERR!(
"Waited too long until {} for payment {} to be received",
wait_until,
input.wait_until,
payment_hex
);
}
Expand Down Expand Up @@ -1124,7 +1117,7 @@ impl MarketCoinOps for LightningCoin {
// note: When sleeping for only 1 second the test_send_payment_and_swaps unit test took 20 seconds to complete instead of 37 seconds when WAIT_CONFIRM_INTERVAL (15 seconds) is used
// Todo: In next sprints, should add a mutex for lightning swap payments to avoid overloading the shared db connection with requests when the sleep time is reduced and multiple swaps are ran together
// Todo: The aim is to make lightning swap payments as fast as possible. Running swap payments statuses should be loaded from db on restarts in this case.
Timer::sleep(check_every as f64).await;
Timer::sleep(input.check_every as f64).await;
}
};
Box::new(fut.boxed().compat())
Expand Down
21 changes: 13 additions & 8 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,18 @@ pub enum ValidateOtherPubKeyErr {
InvalidPubKey(String),
}

#[derive(Clone, Debug)]
pub struct ConfirmPaymentInput {
pub payment_tx: Vec<u8>,
pub secret_hash: Vec<u8>,
pub swap_contract_address: Option<BytesJson>,
pub time_lock: u32,
pub confirmations: u64,
pub requires_nota: bool,
pub wait_until: u64,
pub check_every: u64,
}

#[derive(Clone, Debug)]
pub struct WatcherValidateTakerFeeInput {
pub taker_fee_hash: Vec<u8>,
Expand Down Expand Up @@ -941,14 +953,7 @@ pub trait MarketCoinOps {
/// Receives raw transaction bytes as input and returns tx hash in hexadecimal format
fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box<dyn Future<Item = String, Error = String> + Send>;

fn wait_for_confirmations(
&self,
tx: &[u8],
confirmations: u64,
requires_nota: bool,
wait_until: u64,
check_every: u64,
) -> Box<dyn Future<Item = (), Error = String> + Send>;
fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box<dyn Future<Item = (), Error = String> + Send>;

fn wait_for_htlc_tx_spend(
&self,
Expand Down
Loading