Skip to content

Commit

Permalink
feat(tx): add new sign_raw_transaction rpc for UTXO and EVM coins (#1930
Browse files Browse the repository at this point in the history
)
  • Loading branch information
dimxy authored Dec 19, 2023
1 parent 466d4f2 commit 7382588
Show file tree
Hide file tree
Showing 31 changed files with 945 additions and 165 deletions.
104 changes: 86 additions & 18 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/******************************************************************************
* Copyright © 2023 Pampex LTD and TillyHK LTD *
* Copyright © 2023 Pampex LTD and TillyHK LTD *
* *
* See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE *
* and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Komodo DeFi Framework software, including this file may be copied, modified, propagated*
* Komodo DeFi Framework software, including this file may be copied, modified, propagated *
* or distributed except according to the terms contained in the *
* LICENSE-COPYRIGHT-NOTICE file. *
* *
Expand Down Expand Up @@ -89,17 +89,17 @@ use super::{coin_conf, lp_coinfind_or_err, AsyncMutex, BalanceError, BalanceFut,
PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut,
RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundError, RefundPaymentArgs,
RefundResult, RewardTarget, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared,
SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError,
SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError,
TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails,
TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxMarshalingErr,
UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr,
ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError,
VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError,
WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput,
WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, EARLY_CONFIRMATION_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};
SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignEthTransactionParams,
SignRawTransactionEnum, SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs,
SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult,
TradePreimageValue, Transaction, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut,
TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs,
ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut,
ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps,
WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput,
WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult,
EARLY_CONFIRMATION_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 Down Expand Up @@ -2058,6 +2058,7 @@ impl WatcherOps for EthCoin {
}
}

#[async_trait]
#[cfg_attr(test, mockable)]
impl MarketCoinOps for EthCoin {
fn ticker(&self) -> &str { &self.ticker[..] }
Expand Down Expand Up @@ -2169,6 +2170,14 @@ impl MarketCoinOps for EthCoin {
)
}

async fn sign_raw_tx(&self, args: &SignRawTransactionRequest) -> RawTransactionResult {
if let SignRawTransactionEnum::ETH(eth_args) = &args.tx {
sign_raw_eth_tx(self, eth_args).await
} else {
MmError::err(RawTransactionError::InvalidParam("eth type expected".to_string()))
}
}

fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box<dyn Future<Item = (), Error = String> + Send> {
macro_rules! update_status_with_error {
($status: ident, $error: ident) => {
Expand Down Expand Up @@ -2406,19 +2415,19 @@ lazy_static! {

type EthTxFut = Box<dyn Future<Item = SignedEthTx, Error = TransactionErr> + Send + 'static>;

async fn sign_and_send_transaction_with_keypair(
async fn sign_transaction_with_keypair(
ctx: MmArc,
coin: &EthCoin,
key_pair: &KeyPair,
value: U256,
action: Action,
data: Vec<u8>,
gas: U256,
) -> Result<SignedEthTx, TransactionErr> {
) -> Result<(SignedEthTx, Vec<Web3Instance>), TransactionErr> {
let mut status = ctx.log.status_handle();
macro_rules! tags {
() => {
&[&"sign-and-send"]
&[&"sign"]
};
}
let _nonce_lock = coin.nonce_lock.lock().await;
Expand All @@ -2440,7 +2449,29 @@ async fn sign_and_send_transaction_with_keypair(
data,
};

let signed = tx.sign(key_pair.secret(), coin.chain_id);
Ok((
tx.sign(key_pair.secret(), coin.chain_id),
web3_instances_with_latest_nonce,
))
}

async fn sign_and_send_transaction_with_keypair(
ctx: MmArc,
coin: &EthCoin,
key_pair: &KeyPair,
value: U256,
action: Action,
data: Vec<u8>,
gas: U256,
) -> Result<SignedEthTx, TransactionErr> {
let mut status = ctx.log.status_handle();
macro_rules! tags {
() => {
&[&"sign-and-send"]
};
}
let (signed, web3_instances_with_latest_nonce) =
sign_transaction_with_keypair(ctx, coin, key_pair, value, action, data, gas).await?;
let bytes = Bytes(rlp::encode(&signed).to_vec());
status.status(tags!(), "send_raw_transaction…");

Expand All @@ -2450,7 +2481,8 @@ async fn sign_and_send_transaction_with_keypair(
try_tx_s!(select_ok(futures).await.map_err(|e| ERRL!("{}", e)), signed);

status.status(tags!(), "get_addr_nonce…");
coin.wait_for_addr_nonce_increase(coin.my_address, nonce).await;
coin.wait_for_addr_nonce_increase(coin.my_address, signed.transaction.unsigned.nonce)
.await;
Ok(signed)
}

Expand Down Expand Up @@ -2502,6 +2534,42 @@ async fn sign_and_send_transaction_with_metamask(
}
}

/// Sign eth transaction
async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> RawTransactionResult {
let ctx = MmArc::from_weak(&coin.ctx)
.ok_or("!ctx")
.map_to_mm(|err| RawTransactionError::TransactionError(err.to_string()))?;
let value = wei_from_big_decimal(args.value.as_ref().unwrap_or(&BigDecimal::from(0)), coin.decimals)?;
let action = if let Some(to) = &args.to {
Call(Address::from_str(to).map_to_mm(|err| RawTransactionError::InvalidParam(err.to_string()))?)
} else {
Create
};
let data = hex::decode(args.data.as_ref().unwrap_or(&String::from("")))?;
match coin.priv_key_policy {
// TODO: use zeroise for privkey
EthPrivKeyPolicy::Iguana(ref key_pair)
| EthPrivKeyPolicy::HDWallet {
activated_key: ref key_pair,
..
} => {
return sign_transaction_with_keypair(ctx, coin, key_pair, value, action, data, args.gas_limit)
.await
.map(|(signed_tx, _)| RawTransactionRes {
tx_hex: signed_tx.tx_hex().into(),
})
.map_to_mm(|err| RawTransactionError::TransactionError(err.get_plain_text_format()));
},
#[cfg(target_arch = "wasm32")]
EthPrivKeyPolicy::Metamask(_) => MmError::err(RawTransactionError::InvalidParam(
"sign raw eth tx not implemented for Metamask".into(),
)),
EthPrivKeyPolicy::Trezor => MmError::err(RawTransactionError::InvalidParam(
"sign raw eth tx not implemented for Trezor".into(),
)),
}
}

impl EthCoin {
/// Downloads and saves ETH transaction history of my_address, relies on Parity trace_filter API
/// https://wiki.parity.io/JSONRPC-trace-module#trace_filter, this requires tracing to be enabled
Expand Down
27 changes: 24 additions & 3 deletions mm2src/coins/eth/eth_wasm_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ async fn test_send() {
console::log_1(&format!("{:?}", block).into());
}

#[wasm_bindgen_test]
async fn test_init_eth_coin() {
async fn init_eth_coin_helper() -> Result<(MmArc, MmCoinEnum), String> {
let conf = json!({
"coins": [{
"coin": "ETH",
Expand All @@ -96,5 +95,27 @@ async fn test_init_eth_coin() {
"urls":[ETH_DEV_NODE],
"swap_contract_address":ETH_DEV_SWAP_CONTRACT
});
let _coin = lp_coininit(&ctx, "ETH", &req).await.unwrap();
Ok((ctx.clone(), lp_coininit(&ctx, "ETH", &req).await?))
}

#[wasm_bindgen_test]
async fn test_init_eth_coin() { let (_ctx, _coin) = init_eth_coin_helper().await.unwrap(); }

#[wasm_bindgen_test]
async fn wasm_test_sign_eth_tx() {
// we need to hold ref to _ctx until the end of the test (because of the weak ref to MmCtx in EthCoinImpl)
let (_ctx, coin) = init_eth_coin_helper().await.unwrap();
let sign_req = json::from_value(json!({
"coin": "ETH",
"type": "ETH",
"tx": {
"to": "0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94".to_string(),
"value": "1.234",
"gas_limit": "21000"
}
}))
.unwrap();
let res = coin.sign_raw_tx(&sign_req).await;
console::log_1(&format!("res={:?}", res).into());
assert!(res.is_ok());
}
28 changes: 18 additions & 10 deletions mm2src/coins/lightning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ use crate::utxo::{sat_from_big_decimal, utxo_common, BlockchainNetwork};
use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee,
FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum,
NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr,
RawTransactionError, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs,
RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs,
SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee,
TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionEnum, TransactionErr,
TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin,
ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr,
ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput,
VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward,
WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput,
WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest};
RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError,
RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput,
SendPaymentArgs, SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps,
TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction,
TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxMarshalingErr,
UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs,
ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut,
ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult,
WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput,
WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest};
use async_trait::async_trait;
use bitcoin::bech32::ToBase32;
use bitcoin::hashes::Hash;
Expand Down Expand Up @@ -1028,6 +1028,7 @@ impl WatcherOps for LightningCoin {
}
}

#[async_trait]
impl MarketCoinOps for LightningCoin {
fn ticker(&self) -> &str { &self.conf.ticker }

Expand Down Expand Up @@ -1106,6 +1107,13 @@ impl MarketCoinOps for LightningCoin {
))
}

#[inline(always)]
async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult {
MmError::err(RawTransactionError::NotImplemented {
coin: self.ticker().to_string(),
})
}

// 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, input: ConfirmPaymentInput) -> Box<dyn Future<Item = (), Error = String> + Send> {
Expand Down
7 changes: 5 additions & 2 deletions mm2src/coins/lightning/ln_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::*;
use crate::lightning::ln_db::{DBChannelDetails, HTLCStatus, LightningDB, PaymentType};
use crate::lightning::ln_errors::{SaveChannelClosingError, SaveChannelClosingResult};
use crate::lightning::ln_sql::SqliteLightningDB;
use crate::utxo::UtxoCommonOps;
use bitcoin::blockdata::script::Script;
use bitcoin::blockdata::transaction::Transaction;
use bitcoin::consensus::encode::serialize_hex;
Expand Down Expand Up @@ -219,7 +220,9 @@ fn sign_funding_transaction(
.activated_key_or_err()
.map_err(|e| SignFundingTransactionError::Internal(e.to_string()))?;

let prev_script = Builder::build_p2pkh(&my_address.hash);
let prev_script = coin
.script_for_address(my_address)
.map_err(|e| SignFundingTransactionError::Internal(e.to_string()))?;
let signed = sign_tx(
unsigned,
key_pair,
Expand Down Expand Up @@ -529,7 +532,7 @@ impl LightningEventHandler {
let keys_manager = self.keys_manager.clone();

let fut = async move {
let change_destination_script = Builder::build_witness_script(&my_address.hash).to_bytes().take().into();
let change_destination_script = Builder::build_p2witness(&my_address.hash).to_bytes().take().into();
let feerate_sat_per_1000_weight = platform.get_est_sat_per_1000_weight(ConfirmationTarget::Normal);
let output_descriptors = outputs.iter().collect::<Vec<_>>();
let claiming_tx = match keys_manager.spend_spendable_outputs(
Expand Down
Loading

0 comments on commit 7382588

Please sign in to comment.