Skip to content

Commit

Permalink
feat(tendermint): pubkey-only activation and unsigned tx (#2088)
Browse files Browse the repository at this point in the history
This commit implements pubkey-only mode for the Tendermint protocol, which means we can use any external wallet for wallet and swap operations on Tendermint.

Additionally, ibc_withdraw RPC is removed and withdraw is refactored for Tendermint to support IBC transfers by automatically finding IBC channels whenever possible.
  • Loading branch information
onur-ozkan authored May 14, 2024
1 parent 7f08cae commit 52326c4
Show file tree
Hide file tree
Showing 54 changed files with 1,310 additions and 805 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 9 additions & 5 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@ impl Deref for EthCoin {

#[async_trait]
impl SwapOps for EthCoin {
fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut {
fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut {
let address = try_tx_fus!(addr_from_raw_pubkey(fee_addr));

Box::new(
Expand Down Expand Up @@ -2929,8 +2929,10 @@ impl EthCoin {
coin: self.ticker.clone(),
fee_details: fee_details.map(|d| d.into()),
block_height: trace.block_number,
tx_hash: format!("{:02x}", BytesJson(raw.hash.as_bytes().to_vec())),
tx_hex: BytesJson(rlp::encode(&raw).to_vec()),
tx: TransactionData::new_signed(
BytesJson(rlp::encode(&raw).to_vec()),
format!("{:02x}", BytesJson(raw.hash.as_bytes().to_vec())),
),
internal_id,
timestamp: block.timestamp.into_or_max(),
kmd_rewards: None,
Expand Down Expand Up @@ -3300,8 +3302,10 @@ impl EthCoin {
coin: self.ticker.clone(),
fee_details: fee_details.map(|d| d.into()),
block_height: block_number.as_u64(),
tx_hash: format!("{:02x}", BytesJson(raw.hash.as_bytes().to_vec())),
tx_hex: BytesJson(rlp::encode(&raw).to_vec()),
tx: TransactionData::new_signed(
BytesJson(rlp::encode(&raw).to_vec()),
format!("{:02x}", BytesJson(raw.hash.as_bytes().to_vec())),
),
internal_id: BytesJson(internal_id.to_vec()),
timestamp: block.timestamp.into_or_max(),
kmd_rewards: None,
Expand Down
2 changes: 2 additions & 0 deletions mm2src/coins/eth/eth_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ fn test_withdraw_impl_manual_fee() {
gas_price: 1.into(),
}),
memo: None,
ibc_source_channel: None,
};
coin.get_balance().wait().unwrap();

Expand Down Expand Up @@ -297,6 +298,7 @@ fn test_withdraw_impl_fee_details() {
gas_price: 1.into(),
}),
memo: None,
ibc_source_channel: None,
};
coin.get_balance().wait().unwrap();

Expand Down
6 changes: 3 additions & 3 deletions mm2src/coins/eth/eth_withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use super::{checksum_address, get_eth_gas_details, u256_to_big_decimal, wei_from
use crate::eth::{Action, Address, EthTxFeeDetails, KeyPair, SignedEthTx, UnSignedEthTx};
use crate::hd_wallet::{HDCoinWithdrawOps, HDWalletOps, WithdrawFrom, WithdrawSenderAddress};
use crate::rpc_command::init_withdraw::{WithdrawInProgressStatus, WithdrawTaskHandleShared};
use crate::{BytesJson, CoinWithDerivationMethod, EthCoin, GetWithdrawSenderAddress, PrivKeyPolicy, TransactionDetails};
use crate::{BytesJson, CoinWithDerivationMethod, EthCoin, GetWithdrawSenderAddress, PrivKeyPolicy, TransactionData,
TransactionDetails};
use async_trait::async_trait;
use bip32::DerivationPath;
use common::custom_futures::timeout::FutureTimerExt;
Expand Down Expand Up @@ -308,8 +309,7 @@ where
my_balance_change: &received_by_me - &spent_by_me,
spent_by_me,
received_by_me,
tx_hex,
tx_hash: tx_hash_str,
tx: TransactionData::new_signed(tx_hex, tx_hash_str),
block_height: 0,
fee_details: Some(fee_details.into()),
coin: coin.ticker.clone(),
Expand Down
1 change: 1 addition & 0 deletions mm2src/coins/hd_wallet/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ impl From<HDWalletStorageError> for AccountUpdatingError {
fn from(e: HDWalletStorageError) -> Self { AccountUpdatingError::WalletStorageError(e) }
}

#[derive(Display)]
pub enum HDWithdrawError {
UnexpectedFromAddress(String),
UnknownAccount { account_id: u32 },
Expand Down
2 changes: 1 addition & 1 deletion mm2src/coins/lightning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ impl LightningCoin {
#[async_trait]
impl SwapOps for LightningCoin {
// Todo: This uses dummy data for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning
fn send_taker_fee(&self, _fee_addr: &[u8], _dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut {
fn send_taker_fee(&self, _fee_addr: &[u8], _dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut {
let fut = async move { Ok(TransactionEnum::LightningPayment(PaymentHash([1; 32]))) };
Box::new(fut.boxed().compat())
}
Expand Down
60 changes: 55 additions & 5 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,7 @@ pub enum WatcherRewardError {
/// Swap operations (mostly based on the Hash/Time locked transactions implemented by coin wallets).
#[async_trait]
pub trait SwapOps {
fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut;
fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionFut;

fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut;

Expand Down Expand Up @@ -1950,6 +1950,10 @@ pub trait GetWithdrawSenderAddress {
) -> MmResult<WithdrawSenderAddress<Self::Address, Self::Pubkey>, WithdrawError>;
}

/// TODO: Avoid using a single request structure on every platform.
/// Instead, accept a generic type from withdraw implementations.
/// This way we won't have to update the payload for every platform when
/// one of them requires specific addition.
#[derive(Clone, Deserialize)]
pub struct WithdrawRequest {
coin: String,
Expand All @@ -1961,6 +1965,8 @@ pub struct WithdrawRequest {
max: bool,
fee: Option<WithdrawFee>,
memo: Option<String>,
/// Tendermint specific field used for manually providing the IBC channel IDs.
ibc_source_channel: Option<String>,
/// Currently, this flag is used by ETH/ERC20 coins activated with MetaMask **only**.
#[cfg(target_arch = "wasm32")]
#[serde(default)]
Expand Down Expand Up @@ -2015,6 +2021,7 @@ impl WithdrawRequest {
max: true,
fee: None,
memo: None,
ibc_source_channel: None,
#[cfg(target_arch = "wasm32")]
broadcast: false,
}
Expand Down Expand Up @@ -2157,15 +2164,14 @@ pub enum TransactionType {
token_id: Option<BytesJson>,
},
NftTransfer,
TendermintIBCTransfer,
}

/// Transaction details
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct TransactionDetails {
/// 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,
#[serde(flatten)]
pub tx: TransactionData,
/// Coins are sent from these addresses
from: Vec<String>,
/// Coins are sent to these addresses
Expand Down Expand Up @@ -2199,6 +2205,40 @@ pub struct TransactionDetails {
memo: Option<String>,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum TransactionData {
Signed {
/// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction
tx_hex: BytesJson,
/// Transaction hash in hexadecimal format
tx_hash: String,
},
/// This can contain entirely different data depending on the platform.
/// TODO: Perhaps using generics would be more suitable here?
Unsigned(Json),
}

impl TransactionData {
pub fn new_signed(tx_hex: BytesJson, tx_hash: String) -> Self { Self::Signed { tx_hex, tx_hash } }

pub fn new_unsigned(unsigned_tx_data: Json) -> Self { Self::Unsigned(unsigned_tx_data) }

pub fn tx_hex(&self) -> Option<&BytesJson> {
match self {
TransactionData::Signed { tx_hex, .. } => Some(tx_hex),
TransactionData::Unsigned(_) => None,
}
}

pub fn tx_hash(&self) -> Option<&str> {
match self {
TransactionData::Signed { tx_hash, .. } => Some(tx_hash),
TransactionData::Unsigned(_) => None,
}
}
}

#[derive(Clone, Copy, Debug)]
pub struct BlockHeightAndTime {
height: u64,
Expand Down Expand Up @@ -2807,6 +2847,13 @@ pub enum WithdrawError {
},
#[display(fmt = "Nft Protocol is not supported yet!")]
NftProtocolNotSupported,
#[display(fmt = "'chain_registry_name' was not found in coins configuration for '{}'", _0)]
RegistryNameIsMissing(String),
#[display(
fmt = "IBC channel could not found for '{}' address. Consider providing it manually with 'ibc_source_channel' in the request.",
_0
)]
IBCChannelCouldNotFound(String),
}

impl HttpStatusCode for WithdrawError {
Expand All @@ -2832,6 +2879,8 @@ impl HttpStatusCode for WithdrawError {
| WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_)
| WithdrawError::CoinDoesntSupportNftWithdraw { .. }
| WithdrawError::NotEnoughNftsAmount { .. }
| WithdrawError::RegistryNameIsMissing(_)
| WithdrawError::IBCChannelCouldNotFound(_)
| WithdrawError::MyAddressNotNftOwner { .. } => StatusCode::BAD_REQUEST,
WithdrawError::HwError(_) => StatusCode::GONE,
#[cfg(target_arch = "wasm32")]
Expand Down Expand Up @@ -5381,6 +5430,7 @@ pub mod for_tests {
max: false,
fee,
memo: None,
ibc_source_channel: None,
};
let init = init_withdraw(ctx.clone(), withdraw_req).await.unwrap();
let timeout = wait_until_ms(150000);
Expand Down
10 changes: 5 additions & 5 deletions mm2src/coins/my_tx_history_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::tx_history_storage::{CreateTxHistoryStorageError, FilteringAddresses,
use crate::utxo::utxo_common::big_decimal_from_sat_unsigned;
use crate::MyAddressError;
use crate::{coin_conf, lp_coinfind_or_err, BlockHeightAndTime, CoinFindError, HDPathAccountToAddressId,
HistorySyncState, MmCoin, MmCoinEnum, Transaction, TransactionDetails, TransactionType, TxFeeDetails,
UtxoRpcError};
HistorySyncState, MmCoin, MmCoinEnum, Transaction, TransactionData, TransactionDetails, TransactionType,
TxFeeDetails, UtxoRpcError};
use async_trait::async_trait;
use bitcrypto::sha256;
use common::{calc_total_pages, ten, HttpStatusCode, PagingOptionsEnum, StatusCode};
Expand Down Expand Up @@ -237,13 +237,13 @@ impl<'a, Addr: Clone + DisplayAddress + Eq + std::hash::Hash, Tx: Transaction> T
| TransactionType::RemoveDelegation
| TransactionType::FeeForTokenTx
| TransactionType::StandardTransfer
| TransactionType::NftTransfer => tx_hash.clone(),
| TransactionType::NftTransfer
| TransactionType::TendermintIBCTransfer => tx_hash.clone(),
};

TransactionDetails {
coin: self.coin,
tx_hex: self.tx.tx_hex().into(),
tx_hash: tx_hash.to_tx_hash(),
tx: TransactionData::new_signed(self.tx.tx_hex().into(), tx_hash.to_tx_hash()),
from,
to,
total_amount: self.total_amount,
Expand Down
22 changes: 12 additions & 10 deletions mm2src/coins/qrc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi
RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs,
RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs,
SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee,
TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails,
TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr,
UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr,
ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput,
VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError,
WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput,
WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult};
TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionData,
TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType,
TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs,
ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput,
ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward,
WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput,
WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult};
use async_trait::async_trait;
use bitcrypto::{dhash160, sha256};
use chain::TransactionOutput;
Expand Down Expand Up @@ -763,7 +763,7 @@ impl UtxoCommonOps for Qrc20Coin {

#[async_trait]
impl SwapOps for Qrc20Coin {
fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut {
fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut {
let to_address = try_tx_fus!(self.contract_address_from_raw_pubkey(fee_addr));
let amount = try_tx_fus!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.utxo.decimals));
let transfer_output =
Expand Down Expand Up @@ -1609,8 +1609,10 @@ async fn qrc20_withdraw(coin: Qrc20Coin, req: WithdrawRequest) -> WithdrawResult
spent_by_me: qrc20_amount,
received_by_me,
my_balance_change,
tx_hash: signed.hash().reversed().to_vec().to_tx_hash(),
tx_hex: serialize(&signed).into(),
tx: TransactionData::new_signed(
serialize(&signed).into(),
signed.hash().reversed().to_vec().to_tx_hash(),
),
fee_details: Some(fee_details.into()),
block_height: 0,
coin: conf.ticker.clone(),
Expand Down
11 changes: 9 additions & 2 deletions mm2src/coins/qrc20/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ impl Qrc20Coin {
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)));
let qtum_tx: UtxoTx = try_s!(deserialize(
try_s!(qtum_details.tx.tx_hex().ok_or("unexpected tx type")).as_slice()
)
.map_err(|e| ERRL!("{:?}", e)));

let miner_fee = {
let total_qtum_fee = match qtum_details.fee_details {
Expand Down Expand Up @@ -227,7 +230,11 @@ impl Qrc20Coin {
miner_fee: BigDecimal,
) -> Result<TxTransferMap, String> {
let my_address = try_s!(self.utxo.derivation_method.single_addr_or_err().await);
let tx_hash: H256Json = try_s!(H256Json::from_str(&qtum_details.tx_hash));
let tx_hash: H256Json = try_s!(H256Json::from_str(try_s!(qtum_details
.tx
.tx_hash()
.ok_or("unexpected tx type"))));

if qtum_tx.outputs.len() <= (receipt.output_index as usize) {
return ERR!(
"Length of the transaction {:?} outputs less than output_index {}",
Expand Down
Loading

0 comments on commit 52326c4

Please sign in to comment.