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

[r2r] broadcast tx to txhlp / refactor tx errors #1245

Merged
merged 50 commits into from
May 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
daeafc8
send p2p transaction message to `txhlp/$coin.ticker` topic
onur-ozkan Apr 5, 2022
860163c
propogate the message if coin inside of the topic is activated
onur-ozkan Apr 5, 2022
43862b6
impl `send_raw_tx_bytes` for `MarketCoinOps`
onur-ozkan Apr 5, 2022
b02ce08
update transaction broadcasting
onur-ozkan Apr 5, 2022
f3747f7
create and use dedicated func for sending transaction messages
onur-ozkan Apr 5, 2022
7acdd35
remove unused field from `SwapMsgStore`
onur-ozkan Apr 5, 2022
06ecd56
fix linting
onur-ozkan Apr 6, 2022
5a73d5d
log the error on transaction broadcasting
onur-ozkan Apr 6, 2022
9a4c496
fix `send_raw_tx_bytes` of solana
onur-ozkan Apr 6, 2022
4a5d9c5
fix `send_raw_tx_bytes`
onur-ozkan Apr 6, 2022
a9f2e6b
move use statement
onur-ozkan Apr 6, 2022
840d022
save development state
onur-ozkan Apr 6, 2022
658ff56
send transactions as p2p message
onur-ozkan Apr 6, 2022
f8ed4cc
update exptected error result in test fn `construct_and_send_invalid_…
onur-ozkan Apr 7, 2022
fedbc32
[opt] inline wrapper functions
onur-ozkan Apr 7, 2022
78d64e3
fix `send_raw_tx` and test `send_raw_tx_bytes`
onur-ozkan Apr 7, 2022
e9d16a9
un-inline `send_raw_tx`
onur-ozkan Apr 7, 2022
8545045
Updates after code-review
onur-ozkan Apr 10, 2022
5c24769
change log level of tx broadcasting
onur-ozkan Apr 11, 2022
1c3d7bd
save development state
onur-ozkan Apr 13, 2022
b11fe2c
implement fail-safe returns for `send_taker_refunds_payment`
onur-ozkan Apr 13, 2022
98df7cf
save development state
onur-ozkan Apr 13, 2022
b1f9745
optimize code-base
onur-ozkan Apr 13, 2022
f136301
fix fmt
onur-ozkan Apr 13, 2022
9a203e7
create wrapper macro `FSTX_ERR` that wraps `ERR`
onur-ozkan Apr 13, 2022
b6c8ecb
fix indendation of `FSTX_ERR` macro
onur-ozkan Apr 13, 2022
d10d4a9
update namings
onur-ozkan Apr 13, 2022
3a2b0e9
replace `format!` with `ERRL!` in `FailSafeTxErr::RpcCallFailed`
onur-ozkan Apr 13, 2022
3299524
optimize PR diffs
onur-ozkan Apr 13, 2022
d2cbe7b
doc comment `send_raw_tx_bytes`
onur-ozkan Apr 13, 2022
6534913
optimize `mm2src/coins/utxo/utxo_common.rs` diffs
onur-ozkan Apr 13, 2022
1778350
use full path for tx macros
onur-ozkan Apr 14, 2022
903526f
Merge remote-tracking branch 'origin/dev' into p2p-transaction-helper…
onur-ozkan Apr 14, 2022
6b9e579
save development state
onur-ozkan Apr 18, 2022
19a493d
add test fn for `send_maker_spends_taker_payment`
onur-ozkan Apr 18, 2022
aa43384
update `test_send_contract_calls_recoverable_tx`
onur-ozkan Apr 18, 2022
21f2b6a
fix typo
onur-ozkan Apr 18, 2022
7811106
impl helper functions for `enum TransactionErr`
onur-ozkan Apr 19, 2022
9c9db1c
refactor `send_raw_tx_bytes`
onur-ozkan Apr 20, 2022
f461dcc
sub to `txhlp/$coin` when utxo coin enabled in native mode
onur-ozkan Apr 20, 2022
bad90b6
unbox `TransactionEnum` in `TxRecoverableError`
onur-ozkan Apr 20, 2022
8ca638f
add more spesific conditions on `txhlp` subscription
onur-ozkan Apr 21, 2022
ed574a5
rollback unnecessary broadcastings
onur-ozkan Apr 21, 2022
46ad808
fix PR notes
onur-ozkan Apr 29, 2022
d456326
fix auto-format's bug
onur-ozkan Apr 29, 2022
7e6c215
sync dev
onur-ozkan Apr 29, 2022
9444eda
remove unnecessary type casting
onur-ozkan Apr 29, 2022
5ba0dd1
resolve pr notes
onur-ozkan Apr 30, 2022
f87cef3
resolve pr notes
onur-ozkan Apr 30, 2022
21f9684
resolve pr notes
onur-ozkan May 4, 2022
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
340 changes: 185 additions & 155 deletions mm2src/coins/eth.rs

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions mm2src/coins/lightning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,15 @@ impl MarketCoinOps for LightningCoin {
))
}

fn send_raw_tx_bytes(&self, _tx: &[u8]) -> Box<dyn Future<Item = String, Error = String> + Send> {
Box::new(futures01::future::err(
MmError::new(
"send_raw_tx is not supported for lightning, please use send_payment method instead.".to_string(),
)
.to_string(),
))
}

// Todo: Implement this when implementing swaps for lightning as it's is used mainly for swaps
fn wait_for_confirmations(
&self,
Expand Down
2 changes: 1 addition & 1 deletion mm2src/coins/lightning/ln_platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ impl Platform {
)
.compat()
.await
.map_to_mm(SaveChannelClosingError::WaitForFundingTxSpendError)?;
.map_to_mm(|e| SaveChannelClosingError::WaitForFundingTxSpendError(e.get_plain_text_format()))?;

let closing_tx_hash = format!("{:02x}", closing_tx.tx_hash());

Expand Down
117 changes: 115 additions & 2 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,72 @@ macro_rules! try_f {
};
}

/// `TransactionErr` compatible `try_fus` macro.
macro_rules! try_tx_fus {
($e: expr) => {
match $e {
Ok(ok) => ok,
Err(err) => return Box::new(futures01::future::err(crate::TransactionErr::Plain(ERRL!("{:?}", err)))),
}
};
($e: expr, $tx: expr) => {
match $e {
Ok(ok) => ok,
Err(err) => {
return Box::new(futures01::future::err(crate::TransactionErr::TxRecoverable(
TransactionEnum::from($tx),
ERRL!("{:?}", err),
)))
},
}
};
}

/// `TransactionErr` compatible `try_s` macro.
macro_rules! try_tx_s {
($e: expr) => {
match $e {
Ok(ok) => ok,
Err(err) => {
return Err(crate::TransactionErr::Plain(format!(
"{}:{}] {:?}",
file!(),
line!(),
err
)))
},
}
};
($e: expr, $tx: expr) => {
match $e {
Ok(ok) => ok,
Err(err) => {
return Err(crate::TransactionErr::TxRecoverable(
TransactionEnum::from($tx),
format!("{}:{}] {:?}", file!(), line!(), err),
))
},
}
};
}

/// `TransactionErr:Plain` compatible `ERR` macro.
macro_rules! TX_PLAIN_ERR {
($format: expr, $($args: tt)+) => { Err(crate::TransactionErr::Plain((ERRL!($format, $($args)+)))) };
($format: expr) => { Err(crate::TransactionErr::Plain(ERRL!($format))) }
}

/// `TransactionErr:TxRecoverable` compatible `ERR` macro.
#[allow(unused_macros)]
macro_rules! TX_RECOVERABLE_ERR {
($tx: expr, $format: expr, $($args: tt)+) => {
Err(crate::TransactionErr::TxRecoverable(TransactionEnum::from($tx), ERRL!($format, $($args)+)))
};
($tx: expr, $format: expr) => {
Err(crate::TransactionErr::TxRecoverable(TransactionEnum::from($tx), ERRL!($format)))
};
}

macro_rules! ok_or_continue_after_sleep {
($e:expr, $delay: ident) => {
match $e {
Expand Down Expand Up @@ -318,7 +384,36 @@ impl Deref for TransactionEnum {
}
}

pub type TransactionFut = Box<dyn Future<Item = TransactionEnum, Error = String> + Send>;
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum TransactionErr {
/// Keeps transactions while throwing errors.
TxRecoverable(TransactionEnum, String),
/// Simply for plain error messages.
Plain(String),
}

impl TransactionErr {
/// Returns transaction if the error includes it.
#[inline]
pub fn get_tx(&self) -> Option<TransactionEnum> {
match self {
TransactionErr::TxRecoverable(tx, _) => Some(tx.clone()),
_ => None,
}
}

#[inline]
/// Returns plain text part of error.
pub fn get_plain_text_format(&self) -> String {
match self {
TransactionErr::TxRecoverable(_, err) => err.to_string(),
TransactionErr::Plain(err) => err.to_string(),
}
}
}

pub type TransactionFut = Box<dyn Future<Item = TransactionEnum, Error = TransactionErr> + Send>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please consider this comment? #1258 (comment)
If you have a different point of view, I'll accept it 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both ways are fine. Declaring on top of the module or just keep them close to it's couples. But, have to apply only one of them in the project, otherwise it will complicate things more.

@artemii235 which way we should follow? The choice will effect all the project (either all the types will move to the top, or will move to it's couple structs/functions).


#[derive(Debug, PartialEq)]
pub enum FoundSwapTxSpend {
Expand Down Expand Up @@ -516,6 +611,9 @@ pub trait MarketCoinOps {
/// Receives raw transaction bytes in hexadecimal format as input and returns tx hash in hexadecimal format
fn send_raw_tx(&self, tx: &str) -> Box<dyn Future<Item = String, Error = String> + Send>;

/// 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],
Expand Down Expand Up @@ -778,7 +876,7 @@ impl Default for TransactionType {
/// Transaction details
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct TransactionDetails {
/// Raw bytes of signed transaction in hexadecimal string, this should be sent as is to send_raw_transaction RPC to broadcast the transaction
/// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction
pub tx_hex: BytesJson,
/// Transaction hash in hexadecimal format
tx_hash: String,
Expand Down Expand Up @@ -1631,6 +1729,21 @@ impl Deref for MmCoinEnum {
}
}

impl MmCoinEnum {
pub fn is_utxo_in_native_mode(&self) -> bool {
match self {
MmCoinEnum::UtxoCoin(ref c) => c.as_ref().rpc_client.is_native(),
MmCoinEnum::QtumCoin(ref c) => c.as_ref().rpc_client.is_native(),
MmCoinEnum::Qrc20Coin(ref c) => c.as_ref().rpc_client.is_native(),
MmCoinEnum::Bch(ref c) => c.as_ref().rpc_client.is_native(),
MmCoinEnum::SlpToken(ref c) => c.as_ref().rpc_client.is_native(),
#[cfg(all(not(target_arch = "wasm32"), feature = "zhtlc"))]
MmCoinEnum::ZCoin(ref c) => c.as_ref().rpc_client.is_native(),
_ => false,
}
}
}

#[async_trait]
pub trait BalanceTradeFeeUpdatedHandler {
async fn balance_updated(&self, coin: &MmCoinEnum, new_balance: &BigDecimal);
Expand Down
67 changes: 39 additions & 28 deletions mm2src/coins/qrc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use crate::utxo::{qtum, ActualTxFee, AdditionalTxData, BroadcastTxErr, FeePolicy
use crate::{BalanceError, BalanceFut, CoinBalance, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MarketCoinOps,
MmCoin, NegotiateSwapContractAddrErr, PrivKeyNotAllowed, RawTransactionFut, RawTransactionRequest,
SwapOps, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue,
TransactionDetails, TransactionEnum, TransactionFut, TransactionType, UnexpectedDerivationMethod,
ValidateAddressResult, ValidatePaymentInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest,
WithdrawResult};
TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType,
UnexpectedDerivationMethod, ValidateAddressResult, ValidatePaymentInput, WithdrawError, WithdrawFee,
WithdrawFut, WithdrawRequest, WithdrawResult};
use async_trait::async_trait;
use bigdecimal::BigDecimal;
use bitcrypto::{dhash160, sha256};
Expand Down Expand Up @@ -444,19 +444,19 @@ impl Qrc20Coin {

/// Generate and send a transaction with the specified UTXO outputs.
/// Note this function locks the `UTXO_LOCK`.
pub async fn send_contract_calls(&self, outputs: Vec<ContractCallOutput>) -> Result<TransactionEnum, String> {
pub async fn send_contract_calls(
&self,
outputs: Vec<ContractCallOutput>,
) -> Result<TransactionEnum, TransactionErr> {
// TODO: we need to somehow refactor it using RecentlySpentOutpoints cache
// Move over all QRC20 tokens should share the same cache with each other and base QTUM coin
let _utxo_lock = UTXO_LOCK.lock().await;

let platform = self.platform.clone();
let decimals = self.utxo.decimals;
let GenerateQrc20TxResult { signed, .. } = self
.generate_qrc20_transaction(outputs)
.await
.mm_err(|e| e.into_withdraw_error(platform, decimals))
.map_err(|e| ERRL!("{}", e))?;
let _tx = try_s!(self.utxo.rpc_client.send_transaction(&signed).compat().await);
.map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?;
try_tx_s!(self.utxo.rpc_client.send_transaction(&signed).compat().await, signed);
Ok(signed.into())
}

Expand Down Expand Up @@ -710,10 +710,10 @@ impl UtxoCommonOps for Qrc20Coin {
#[async_trait]
impl SwapOps for Qrc20Coin {
fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut {
let to_address = try_fus!(self.contract_address_from_raw_pubkey(fee_addr));
let amount = try_fus!(wei_from_big_decimal(&amount, self.utxo.decimals));
let to_address = try_tx_fus!(self.contract_address_from_raw_pubkey(fee_addr));
let amount = try_tx_fus!(wei_from_big_decimal(&amount, self.utxo.decimals));
let transfer_output =
try_fus!(self.transfer_output(to_address, amount, QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT));
try_tx_fus!(self.transfer_output(to_address, amount, QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT));
let outputs = vec![transfer_output];

let selfi = self.clone();
Expand All @@ -731,11 +731,11 @@ impl SwapOps for Qrc20Coin {
amount: BigDecimal,
swap_contract_address: &Option<BytesJson>,
) -> TransactionFut {
let taker_addr = try_fus!(self.contract_address_from_raw_pubkey(taker_pub));
let taker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(taker_pub));
let id = qrc20_swap_id(time_lock, secret_hash);
let value = try_fus!(wei_from_big_decimal(&amount, self.utxo.decimals));
let value = try_tx_fus!(wei_from_big_decimal(&amount, self.utxo.decimals));
let secret_hash = Vec::from(secret_hash);
let swap_contract_address = try_fus!(swap_contract_address.try_to_address());
let swap_contract_address = try_tx_fus!(swap_contract_address.try_to_address());

let selfi = self.clone();
let fut = async move {
Expand All @@ -755,11 +755,11 @@ impl SwapOps for Qrc20Coin {
amount: BigDecimal,
swap_contract_address: &Option<BytesJson>,
) -> TransactionFut {
let maker_addr = try_fus!(self.contract_address_from_raw_pubkey(maker_pub));
let maker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(maker_pub));
let id = qrc20_swap_id(time_lock, secret_hash);
let value = try_fus!(wei_from_big_decimal(&amount, self.utxo.decimals));
let value = try_tx_fus!(wei_from_big_decimal(&amount, self.utxo.decimals));
let secret_hash = Vec::from(secret_hash);
let swap_contract_address = try_fus!(swap_contract_address.try_to_address());
let swap_contract_address = try_tx_fus!(swap_contract_address.try_to_address());

let selfi = self.clone();
let fut = async move {
Expand All @@ -779,8 +779,8 @@ impl SwapOps for Qrc20Coin {
_htlc_privkey: &[u8],
swap_contract_address: &Option<BytesJson>,
) -> TransactionFut {
let payment_tx: UtxoTx = try_fus!(deserialize(taker_payment_tx).map_err(|e| ERRL!("{:?}", e)));
let swap_contract_address = try_fus!(swap_contract_address.try_to_address());
let payment_tx: UtxoTx = try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| ERRL!("{:?}", e)));
let swap_contract_address = try_tx_fus!(swap_contract_address.try_to_address());
let secret = secret.to_vec();

let selfi = self.clone();
Expand All @@ -801,9 +801,9 @@ impl SwapOps for Qrc20Coin {
_htlc_privkey: &[u8],
swap_contract_address: &Option<BytesJson>,
) -> TransactionFut {
let payment_tx: UtxoTx = try_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e)));
let payment_tx: UtxoTx = try_tx_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e)));
let secret = secret.to_vec();
let swap_contract_address = try_fus!(swap_contract_address.try_to_address());
let swap_contract_address = try_tx_fus!(swap_contract_address.try_to_address());

let selfi = self.clone();
let fut = async move {
Expand All @@ -823,8 +823,8 @@ impl SwapOps for Qrc20Coin {
_htlc_privkey: &[u8],
swap_contract_address: &Option<BytesJson>,
) -> TransactionFut {
let payment_tx: UtxoTx = try_fus!(deserialize(taker_payment_tx).map_err(|e| ERRL!("{:?}", e)));
let swap_contract_address = try_fus!(swap_contract_address.try_to_address());
let payment_tx: UtxoTx = try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| ERRL!("{:?}", e)));
let swap_contract_address = try_tx_fus!(swap_contract_address.try_to_address());

let selfi = self.clone();
let fut = async move {
Expand All @@ -844,8 +844,8 @@ impl SwapOps for Qrc20Coin {
_htlc_privkey: &[u8],
swap_contract_address: &Option<BytesJson>,
) -> TransactionFut {
let payment_tx: UtxoTx = try_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e)));
let swap_contract_address = try_fus!(swap_contract_address.try_to_address());
let payment_tx: UtxoTx = try_tx_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e)));
let swap_contract_address = try_tx_fus!(swap_contract_address.try_to_address());

let selfi = self.clone();
let fut = async move {
Expand Down Expand Up @@ -1054,10 +1054,16 @@ impl MarketCoinOps for Qrc20Coin {

fn platform_ticker(&self) -> &str { &self.0.platform }

#[inline(always)]
fn send_raw_tx(&self, tx: &str) -> Box<dyn Future<Item = String, Error = String> + Send> {
utxo_common::send_raw_tx(&self.utxo, tx)
}

#[inline(always)]
fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box<dyn Future<Item = String, Error = String> + Send> {
utxo_common::send_raw_tx_bytes(&self.utxo, tx)
}

fn wait_for_confirmations(
&self,
tx: &[u8],
Expand All @@ -1083,10 +1089,15 @@ impl MarketCoinOps for Qrc20Coin {
from_block: u64,
_swap_contract_address: &Option<BytesJson>,
) -> TransactionFut {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the MarketCoinOps::wait_for_tx_spend method shouldn't return TransactionErr because it actually doesn't broadcast any transaction to the blockchain network.
I'd add another Transaction future that returns String error instead of TransactionErr.
It's only my opinion, so it's up to you to change or not

Copy link
Member Author

@onur-ozkan onur-ozkan Apr 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can rename TransactionErr into TransactionFutErr to make it more general. In this case, you don't need to create another error type since we can use TransactionFutErr::Plain. Is this okay for you?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think TransactionErr fits better, because we have functions that don't return futures.

https://github.com/KomodoPlatform/atomicDEX-API/blob/9444edabe014b1d5519e1784159f9234e87f16ce/mm2src/coins/utxo/slp.rs#L412

I'd rather have TransactionFut = Box<dyn Future<Item = TransactionEnum, Error = String>> and TxRecoverableFut = Box<dyn Future<Item = TransactionEnum, Error = TransactionFutErr>>.
What do you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But on the other hand, it may lead to confusion in macros, because try_tx_fus will actually return TxRecoverableFut. So, as I said, it's up to you :)

let tx: UtxoTx = try_fus!(deserialize(transaction).map_err(|e| ERRL!("{:?}", e)));
let tx: UtxoTx = try_tx_fus!(deserialize(transaction).map_err(|e| ERRL!("{:?}", e)));

let selfi = self.clone();
let fut = async move { selfi.wait_for_tx_spend_impl(tx, wait_until, from_block).await };
let fut = async move {
selfi
.wait_for_tx_spend_impl(tx, wait_until, from_block)
.map_err(TransactionErr::Plain)
.await
};
Box::new(fut.boxed().compat())
}

Expand Down
33 changes: 33 additions & 0 deletions mm2src/coins/qrc20/qrc20_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use common::{block_on, DEX_FEE_ADDR_RAW_PUBKEY};
use itertools::Itertools;
use mocktopus::mocking::{MockResult, Mockable};
use rpc::v1::types::ToTxHash;
use std::mem::discriminant;

const EXPECTED_TX_FEE: i64 = 1000;
const CONTRACT_CALL_GAS_FEE: i64 = (QRC20_GAS_LIMIT_DEFAULT * QRC20_GAS_PRICE_DEFAULT) as i64;
Expand Down Expand Up @@ -925,3 +926,35 @@ fn test_negotiate_swap_contract_addr_has_fallback() {
let result = coin.negotiate_swap_contract_addr(Some(slice)).unwrap();
assert_eq!(Some(fallback_addr.to_vec().into()), result);
}

#[test]
fn test_send_contract_calls_recoverable_tx() {
let priv_key = [
3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72,
172, 110, 180, 13, 123, 179, 10, 49,
];
let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None);

let tx = TransactionEnum::UtxoTx("010000000160fd74b5714172f285db2b36f0b391cd6883e7291441631c8b18f165b0a4635d020000006a47304402205d409e141111adbc4f185ae856997730de935ac30a0d2b1ccb5a6c4903db8171022024fc59bbcfdbba283556d7eeee4832167301dc8e8ad9739b7865f67b9676b226012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff020000000000000000625403a08601012844a9059cbb000000000000000000000000ca1e04745e8ca0c60d8c5881531d51bec470743f00000000000000000000000000000000000000000000000000000000000f424014d362e096e873eb7907e205fadc6175c6fec7bc44c200ada205000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acfe967d5f".into());

let fee_addr = hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc05").unwrap();
let to_address = coin.contract_address_from_raw_pubkey(&fee_addr).unwrap();
let amount = BigDecimal::from(0.2);
let amount = wei_from_big_decimal(&amount, coin.utxo.decimals).unwrap();
let mut transfer_output = coin
.transfer_output(to_address, amount, QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT)
.unwrap();

// break the transfer output
transfer_output.value = 777;
transfer_output.gas_limit = 777;
transfer_output.gas_price = 777;

let tx_err = block_on(coin.send_contract_calls(vec![transfer_output])).unwrap_err();

// The error variant should equal to `TxRecoverable`
assert_eq!(
discriminant(&tx_err),
sergeyboyko0791 marked this conversation as resolved.
Show resolved Hide resolved
discriminant(&TransactionErr::TxRecoverable(TransactionEnum::from(tx), String::new()))
);
}
Loading