Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ async fn withdraw_impl(ctx: MmArc, coin: EthCoin, req: WithdrawRequest) -> Withd
coin: coin.ticker.clone(),
internal_id: vec![].into(),
timestamp: now_ms() / 1000,
kmd_rewards: None,
})
}

Expand Down Expand Up @@ -2194,6 +2195,7 @@ impl EthCoin {
tx_hex: BytesJson(rlp::encode(&raw)),
internal_id: BytesJson(internal_id.to_vec()),
timestamp: block.timestamp.into(),
kmd_rewards: None,
};

existing_history.push(details);
Expand Down Expand Up @@ -2562,6 +2564,7 @@ impl EthCoin {
tx_hex: BytesJson(rlp::encode(&raw)),
internal_id,
timestamp: block.timestamp.into(),
kmd_rewards: None,
};

existing_history.push(details);
Expand Down
20 changes: 20 additions & 0 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,21 @@ impl Into<TxFeeDetails> for Qrc20FeeDetails {
fn into(self: Qrc20FeeDetails) -> TxFeeDetails { TxFeeDetails::Qrc20(self) }
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct KmdRewardsDetails {
amount: BigDecimal,
claimed_by_me: bool,
}

impl KmdRewardsDetails {
pub fn claimed_by_me(amount: BigDecimal) -> KmdRewardsDetails {
KmdRewardsDetails {
amount,
claimed_by_me: true,
}
}
}

/// Transaction details
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct TransactionDetails {
Expand Down Expand Up @@ -459,6 +474,9 @@ pub struct TransactionDetails {
coin: String,
/// Internal MM2 id used for internal transaction identification, for some coins it might be equal to transaction hash
internal_id: BytesJson,
/// Amount of accrued rewards.
#[serde(skip_serializing_if = "Option::is_none")]
kmd_rewards: Option<KmdRewardsDetails>,
}

impl TransactionDetails {
Expand All @@ -475,6 +493,8 @@ impl TransactionDetails {
// in case of electrum returned -1 so there could be records with MAX confirmations
self.timestamp == 0
}

pub fn should_update_kmd_rewards(&self) -> bool { self.coin == "KMD" && self.kmd_rewards.is_none() }
}

#[derive(Clone, Debug, PartialEq, Serialize)]
Expand Down
23 changes: 21 additions & 2 deletions mm2src/coins/qrc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::utxo::rpc_clients::{ElectrumClient, NativeClient, UnspentInfo, UtxoRp
UtxoRpcError, UtxoRpcResult};
use crate::utxo::utxo_common::{self, big_decimal_from_sat, check_all_inputs_signed_by_pub};
use crate::utxo::{qtum, sign_tx, ActualTxFee, AdditionalTxData, FeePolicy, GenerateTxError, GenerateTxResult,
RecentlySpentOutPoints, UtxoCoinBuilder, UtxoCoinFields, UtxoCommonOps, UtxoTx,
VerboseTransactionFrom, UTXO_LOCK};
HistoryUtxoTx, HistoryUtxoTxMap, RecentlySpentOutPoints, UtxoCoinBuilder, UtxoCoinFields,
UtxoCommonOps, UtxoTx, VerboseTransactionFrom, UTXO_LOCK};
use crate::{BalanceError, BalanceFut, CoinBalance, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MarketCoinOps,
MmCoin, NegotiateSwapContractAddrErr, SwapOps, TradeFee, TradePreimageError, TradePreimageFut,
TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionFut,
Expand Down Expand Up @@ -505,6 +505,24 @@ impl UtxoCommonOps for Qrc20Coin {
utxo_common::calc_interest_if_required(self, unsigned, data, my_script_pub).await
}

async fn calc_interest_of_tx(
&self,
_tx: &UtxoTx,
_input_transactions: &mut HistoryUtxoTxMap,
) -> UtxoRpcResult<u64> {
MmError::err(UtxoRpcError::Internal(
"QRC20 coin doesn't support transaction rewards".to_owned(),
))
}

async fn get_mut_verbose_transaction_from_map_or_rpc<'a, 'b>(
&'a self,
tx_hash: H256Json,
utxo_tx_map: &'b mut HistoryUtxoTxMap,
) -> UtxoRpcResult<&'b mut HistoryUtxoTx> {
utxo_common::get_mut_verbose_transaction_from_map_or_rpc(self, tx_hash, utxo_tx_map).await
}

async fn p2sh_spending_tx(
&self,
prev_transaction: UtxoTx,
Expand Down Expand Up @@ -1270,6 +1288,7 @@ async fn qrc20_withdraw(coin: Qrc20Coin, req: WithdrawRequest) -> WithdrawResult
coin: conf.ticker.clone(),
internal_id: vec![].into(),
timestamp: now_ms() / 1000,
kmd_rewards: None,
})
}

Expand Down
38 changes: 24 additions & 14 deletions mm2src/coins/qrc20/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ impl Qrc20Coin {
pub async fn transfer_details_by_hash(&self, tx_hash: H256Json) -> Result<TxTransferMap, String> {
let receipts = try_s!(self.utxo.rpc_client.get_transaction_receipts(&tx_hash).compat().await);
// request Qtum transaction details to get a tx_hex, timestamp, block_height and calculate a miner_fee
let qtum_details = try_s!(utxo_common::tx_details_by_hash(self, &tx_hash.0).await);
let mut input_transactions = HistoryUtxoTxMap::new();
let qtum_details = try_s!(utxo_common::tx_details_by_hash(self, &tx_hash.0, &mut input_transactions).await);
// Deserialize the UtxoTx to get a script pubkey
let qtum_tx: UtxoTx = try_s!(deserialize(qtum_details.tx_hex.as_slice()).map_err(|e| ERRL!("{:?}", e)));

Expand Down Expand Up @@ -410,26 +411,32 @@ impl Qrc20Coin {
tx_height: u64,
transfer_map: &mut TxTransferMap,
) -> ProcessCachedTransferMapResult {
async fn tx_details_by_hash(coin: &Qrc20Coin, ctx: &MmArc, tx_hash: &H256Json) -> Option<TransactionDetails> {
mm_counter!(ctx.metrics, "tx.history.request.count", 1, "coin" => coin.utxo.conf.ticker.clone(), "method" => "tx_detail_by_hash");
match utxo_common::tx_details_by_hash(coin, &tx_hash.0).await {
async fn get_verbose_transaction(coin: &Qrc20Coin, ctx: &MmArc, tx_hash: H256Json) -> Option<RpcTransaction> {
mm_counter!(ctx.metrics, "tx.history.request.count", 1, "coin" => coin.utxo.conf.ticker.clone(), "method" => "get_verbose_transaction");
match coin
.utxo
.rpc_client
.get_verbose_transaction(tx_hash.clone())
.compat()
.await
{
Ok(d) => {
mm_counter!(ctx.metrics, "tx.history.response.count", 1, "coin" => coin.utxo.conf.ticker.clone(), "method" => "tx_detail_by_hash");
mm_counter!(ctx.metrics, "tx.history.response.count", 1, "coin" => coin.utxo.conf.ticker.clone(), "method" => "get_verbose_transaction");
Some(d)
},
Err(e) => {
ctx.log.log(
"😟",
&[&"tx_history", &coin.utxo.conf.ticker],
&ERRL!("Error {:?} on tx_details_by_hash for {:?} tx", e, tx_hash),
&ERRL!("Error {:?} on get_verbose_transaction for {:?} tx", e, tx_hash),
);
None
},
}
}

// `qtum_details` will be initialized once if it's required
let mut qtum_details = None;
// `qtum_verbose` will be initialized once if it's required
let mut qtum_verbose = None;

let mut updated = false;
for (id, tx) in transfer_map {
Expand All @@ -452,13 +459,13 @@ impl Qrc20Coin {
updated = true;
}
if tx.should_update_timestamp() {
if qtum_details.is_none() {
qtum_details = tx_details_by_hash(self, ctx, tx_hash).await;
if qtum_verbose.is_none() {
qtum_verbose = get_verbose_transaction(self, ctx, tx_hash.clone()).await;
}
if let Some(ref qtum_details) = qtum_details {
tx.timestamp = qtum_details.timestamp;
if let Some(ref qtum_verbose) = qtum_verbose {
tx.timestamp = qtum_verbose.time as u64;
updated = true;
} // else `utxo_common::tx_details_by_hash` failed for some reason
} // else `UtxoRpcClientEnum::get_verbose_transaction` failed for some reason
}
}

Expand Down Expand Up @@ -885,7 +892,10 @@ mod tests {
assert_eq!(transfer_map_zero_timestamp, transfer_map_expected);

let value: MetricsJson = json::from_value(ctx.metrics.collect_json().unwrap()).unwrap();
let found = find_metrics_in_json(value, "tx.history.request.count", &[("method", "tx_detail_by_hash")]);
let found = find_metrics_in_json(value, "tx.history.request.count", &[(
"method",
"get_verbose_transaction",
)]);
match found {
Some(MetricType::Counter { key, value, .. }) if key == "tx.history.request.count" && value == 1 => (),
found => panic!("Found metric type: {:?}", found),
Expand Down
5 changes: 5 additions & 0 deletions mm2src/coins/qrc20/qrc20_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ fn test_transfer_details_by_hash() {
)
.unwrap()
.into(),
kmd_rewards: None,
};
assert_eq!(actual, expected);

Expand All @@ -516,6 +517,7 @@ fn test_transfer_details_by_hash() {
)
.unwrap()
.into(),
kmd_rewards: None,
};
assert_eq!(actual, expected);

Expand All @@ -538,6 +540,7 @@ fn test_transfer_details_by_hash() {
)
.unwrap()
.into(),
kmd_rewards: None,
};
assert_eq!(actual, expected);

Expand All @@ -560,6 +563,7 @@ fn test_transfer_details_by_hash() {
)
.unwrap()
.into(),
kmd_rewards: None,
};
assert_eq!(actual, expected);

Expand All @@ -582,6 +586,7 @@ fn test_transfer_details_by_hash() {
)
.unwrap()
.into(),
kmd_rewards: None,
};
assert_eq!(actual, expected);
assert!(it.next().is_none());
Expand Down
53 changes: 44 additions & 9 deletions mm2src/coins/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ use self::rpc_clients::{ConcurrentRequestMap, NativeClient, NativeClientImpl};
use self::rpc_clients::{ElectrumClient, ElectrumClientImpl, ElectrumRpcRequest, EstimateFeeMethod, EstimateFeeMode,
UnspentInfo, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcResult};
use super::{BalanceError, BalanceFut, BalanceResult, CoinTransportMetrics, CoinsContext, FeeApproxStage,
FoundSwapTxSpend, HistorySyncState, MarketCoinOps, MmCoin, NumConversError, NumConversResult,
RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, TradeFee, TradePreimageError,
TradePreimageFut, TradePreimageResult, Transaction, TransactionDetails, TransactionEnum, TransactionFut,
WithdrawError, WithdrawFee, WithdrawRequest};
FoundSwapTxSpend, HistorySyncState, KmdRewardsDetails, MarketCoinOps, MmCoin, NumConversError,
NumConversResult, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, TradeFee,
TradePreimageError, TradePreimageFut, TradePreimageResult, Transaction, TransactionDetails,
TransactionEnum, TransactionFut, WithdrawError, WithdrawFee, WithdrawRequest};

#[cfg(test)] pub mod utxo_tests;
#[cfg(target_arch = "wasm32")] pub mod utxo_wasm_tests;
Expand All @@ -97,6 +97,9 @@ const UTXO_DUST_AMOUNT: u64 = 1000;
const KMD_MTP_BLOCK_COUNT: NonZeroU64 = unsafe { NonZeroU64::new_unchecked(11u64) };
const DEFAULT_DYNAMIC_FEE_VOLATILITY_PERCENT: f64 = 0.5;

pub type GenerateTxResult = Result<(TransactionInputSigner, AdditionalTxData), MmError<GenerateTxError>>;
pub type HistoryUtxoTxMap = HashMap<H256Json, HistoryUtxoTx>;

#[cfg(windows)]
#[cfg(not(target_arch = "wasm32"))]
fn get_special_folder_path() -> PathBuf {
Expand Down Expand Up @@ -168,6 +171,12 @@ impl From<UtxoRpcError> for TradePreimageError {
}
}

/// The `UtxoTx` with the block height transaction mined in.
pub struct HistoryUtxoTx {
pub height: Option<u64>,
pub tx: UtxoTx,
}

/// Additional transaction data that can't be easily got from raw transaction without calling
/// additional RPC methods, e.g. to get input amount we need to request all previous transactions
/// and check output values
Expand All @@ -177,6 +186,7 @@ pub struct AdditionalTxData {
pub spent_by_me: u64,
pub fee_amount: u64,
pub unused_change: Option<u64>,
pub kmd_rewards: Option<KmdRewardsDetails>,
}

/// The fee set from coins config
Expand Down Expand Up @@ -511,6 +521,17 @@ pub trait UtxoCommonOps {
my_script_pub: Bytes,
) -> UtxoRpcResult<(TransactionInputSigner, AdditionalTxData)>;

/// Calculates interest of the specified transaction.
/// Please note, this method has to be used for KMD transactions only.
async fn calc_interest_of_tx(&self, tx: &UtxoTx, input_transactions: &mut HistoryUtxoTxMap) -> UtxoRpcResult<u64>;

/// Try to get a `HistoryUtxoTx` transaction from `utxo_tx_map` or try to request it from Rpc client.
async fn get_mut_verbose_transaction_from_map_or_rpc<'a, 'b>(
&'a self,
tx_hash: H256Json,
utxo_tx_map: &'b mut HistoryUtxoTxMap,
) -> UtxoRpcResult<&'b mut HistoryUtxoTx>;

async fn p2sh_spending_tx(
&self,
prev_transaction: UtxoTx,
Expand All @@ -527,7 +548,7 @@ pub trait UtxoCommonOps {
address: &Address,
) -> UtxoRpcResult<(Vec<UnspentInfo>, AsyncMutexGuard<'a, RecentlySpentOutPoints>)>;

/// Try load verbose transaction from cache or try to request it from Rpc client.
/// Try to load verbose transaction from cache or try to request it from Rpc client.
fn get_verbose_transaction_from_cache_or_rpc(
&self,
txid: H256Json,
Expand Down Expand Up @@ -560,10 +581,26 @@ pub trait UtxoCommonOps {

#[async_trait]
pub trait UtxoStandardOps {
/// Gets tx details by hash requesting the coin RPC if required
async fn tx_details_by_hash(&self, hash: &[u8]) -> Result<TransactionDetails, String>;
/// Gets tx details by hash requesting the coin RPC if required.
/// If you plan to call this method for multiple transactions (for example, when loading transaction history),
/// It's recommended to use this method for transactions ordered by **ascending** height for the best performance.
Comment thread
artemii235 marked this conversation as resolved.
Outdated
/// * `input_transactions` - the cache of the already requested transactions.
async fn tx_details_by_hash(
&self,
hash: &[u8],
input_transactions: &mut HistoryUtxoTxMap,
) -> Result<TransactionDetails, String>;

async fn request_tx_history(&self, metrics: MetricsArc) -> RequestTxHistoryResult;

/// Calculate the KMD rewards and re-calculate the transaction fee
/// if the specified `tx_details` was generated without considering the KMD rewards.
/// Please note, this method has to be used for KMD transactions only.
async fn update_kmd_rewards(
&self,
tx_details: &mut TransactionDetails,
input_transactions: &mut HistoryUtxoTxMap,
) -> UtxoRpcResult<()>;
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -606,8 +643,6 @@ lazy_static! {
pub static ref UTXO_LOCK: AsyncMutex<()> = AsyncMutex::new(());
}

pub type GenerateTxResult = Result<(TransactionInputSigner, AdditionalTxData), MmError<GenerateTxError>>;

#[derive(Debug, Display)]
pub enum GenerateTxError {
#[display(
Expand Down
34 changes: 32 additions & 2 deletions mm2src/coins/utxo/qtum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,24 @@ impl UtxoCommonOps for QtumCoin {
utxo_common::calc_interest_if_required(self, unsigned, data, my_script_pub).await
}

async fn calc_interest_of_tx(
&self,
_tx: &UtxoTx,
_input_transactions: &mut HistoryUtxoTxMap,
) -> UtxoRpcResult<u64> {
MmError::err(UtxoRpcError::Internal(
"QTUM coin doesn't support transaction rewards".to_owned(),
))
}

async fn get_mut_verbose_transaction_from_map_or_rpc<'a, 'b>(
&'a self,
tx_hash: H256Json,
utxo_tx_map: &'b mut HistoryUtxoTxMap,
) -> UtxoRpcResult<&'b mut HistoryUtxoTx> {
utxo_common::get_mut_verbose_transaction_from_map_or_rpc(self, tx_hash, utxo_tx_map).await
}

async fn p2sh_spending_tx(
&self,
prev_transaction: UtxoTx,
Expand Down Expand Up @@ -259,13 +277,25 @@ impl UtxoCommonOps for QtumCoin {

#[async_trait]
impl UtxoStandardOps for QtumCoin {
async fn tx_details_by_hash(&self, hash: &[u8]) -> Result<TransactionDetails, String> {
utxo_common::tx_details_by_hash(self, hash).await
async fn tx_details_by_hash(
&self,
hash: &[u8],
input_transactions: &mut HistoryUtxoTxMap,
) -> Result<TransactionDetails, String> {
utxo_common::tx_details_by_hash(self, hash, input_transactions).await
}

async fn request_tx_history(&self, metrics: MetricsArc) -> RequestTxHistoryResult {
utxo_common::request_tx_history(self, metrics).await
}

async fn update_kmd_rewards(
&self,
tx_details: &mut TransactionDetails,
input_transactions: &mut HistoryUtxoTxMap,
) -> UtxoRpcResult<()> {
utxo_common::update_kmd_rewards(self, tx_details, input_transactions).await
}
}

impl SwapOps for QtumCoin {
Expand Down
Loading