Skip to content

Commit

Permalink
Add cashaddress support #466 (#666)
Browse files Browse the repository at this point in the history
* Add cashaddr supporting for BCH
* Add green withdraw and common (my_balance, get_enabled_coins) tests

* Add red test_cashaddresses_in_tx_details_by_hash test

* Make green the test_cashaddresses_in_tx_details_by_hash test

* Fill comments and do refactoring
  • Loading branch information
sergeyboyko0791 authored Jun 9, 2020
1 parent 3a9680f commit 9006a9f
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 42 deletions.
16 changes: 8 additions & 8 deletions Cargo.lock

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

7 changes: 3 additions & 4 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ use rand::seq::SliceRandom;
use rpc::v1::types::{Bytes as BytesJson};
use serde_json::{self as json, Value as Json};
use sha3::{Keccak256, Digest};
use std::borrow::Cow;
use std::collections::HashMap;
use std::cmp::Ordering;
use std::ops::Deref;
Expand Down Expand Up @@ -421,7 +420,7 @@ async fn withdraw_impl(ctx: MmArc, coin: EthCoin, req: WithdrawRequest) -> Resul
}
Ok(TransactionDetails {
to: vec![checksum_address(&format!("{:#02x}", to_addr))],
from: vec![coin.my_address().into()],
from: vec![try_s!(coin.my_address())],
total_amount: amount_decimal,
my_balance_change: &received_by_me - &spent_by_me,
spent_by_me,
Expand Down Expand Up @@ -689,8 +688,8 @@ impl SwapOps for EthCoin {
impl MarketCoinOps for EthCoin {
fn ticker (&self) -> &str {&self.ticker[..]}

fn my_address(&self) -> Cow<str> {
checksum_address(&format!("{:#02x}", self.my_address)).into()
fn my_address(&self) -> Result<String, String> {
Ok(checksum_address(&format!("{:#02x}", self.my_address)))
}

fn my_balance(&self) -> Box<dyn Future<Item=BigDecimal, Error=String> + Send> {
Expand Down
18 changes: 11 additions & 7 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ use gstuff::{slurp};
use http::Response;
use rpc::v1::types::{Bytes as BytesJson};
use serde_json::{self as json, Value as Json};
use std::borrow::Cow;
use std::collections::hash_map::{HashMap, RawEntryMut};
use std::fmt;
use std::ops::Deref;
Expand Down Expand Up @@ -211,7 +210,7 @@ pub trait SwapOps {
pub trait MarketCoinOps {
fn ticker (&self) -> &str;

fn my_address(&self) -> Cow<str>;
fn my_address(&self) -> Result<String, String>;

fn my_balance(&self) -> Box<dyn Future<Item=BigDecimal, Error=String> + Send>;

Expand Down Expand Up @@ -367,7 +366,8 @@ pub trait MmCoin: SwapOps + MarketCoinOps + fmt::Debug + Send + Sync + 'static {

/// Path to tx history file
fn tx_history_path(&self, ctx: &MmArc) -> PathBuf {
ctx.dbdir().join("TRANSACTIONS").join(format!("{}_{}.json", self.ticker(), self.my_address()))
let my_address = self.my_address().unwrap_or(Default::default());
ctx.dbdir().join("TRANSACTIONS").join(format!("{}_{}.json", self.ticker(), my_address))
}

/// Loads existing tx history from file, returns empty vector if file is not found
Expand Down Expand Up @@ -767,10 +767,14 @@ struct EnabledCoin {
pub async fn get_enabled_coins(ctx: MmArc) -> Result<Response<Vec<u8>>, String> {
let coins_ctx: Arc<CoinsContext> = try_s!(CoinsContext::from_ctx(&ctx));
let coins = try_s!(coins_ctx.coins.sleeplock(77).await);
let enabled_coins: Vec<_> = coins.iter().map(|(ticker, coin)| EnabledCoin {
ticker: ticker.clone(),
address: coin.my_address().to_string(),
}).collect();
let enabled_coins: Vec<_> = try_s!(coins.iter().map(|(ticker, coin)| {
let address = try_s!(coin.my_address());
Ok(EnabledCoin {
ticker: ticker.clone(),
address,
})
}).collect());

let res = try_s!(json::to_vec(&json!({
"result": enabled_coins
})));
Expand Down
3 changes: 1 addition & 2 deletions mm2src/coins/test_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use common::mm_number::MmNumber;
use crate::{TradeInfo, FoundSwapTxSpend, WithdrawRequest};
use futures01::Future;
use mocktopus::macros::*;
use std::borrow::Cow;
use super::{HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionDetails, TransactionEnum, TransactionFut};

/// Dummy coin struct used in tests which functions are unimplemented but then mocked
Expand All @@ -18,7 +17,7 @@ impl MarketCoinOps for TestCoin {
unimplemented!()
}

fn my_address(&self) -> Cow<str> {
fn my_address(&self) -> Result<String, String> {
unimplemented!()
}

Expand Down
82 changes: 67 additions & 15 deletions mm2src/coins/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json};
use script::{Opcode, Builder, Script, ScriptAddress, TransactionInputSigner, UnsignedTransactionInput, SignatureVersion};
use serde_json::{self as json, Value as Json};
use serialization::{serialize, deserialize};
use std::borrow::Cow;
use std::collections::hash_map::{HashMap, Entry};
use std::convert::TryInto;
use std::cmp::Ordering;
Expand Down Expand Up @@ -159,6 +158,25 @@ enum FeePolicy {
DeductFromOutput(usize),
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "format")]
enum UtxoAddressFormat {
/// Standard UTXO address format.
/// In Bitcoin Cash context the standard format also known as 'legacy'.
#[serde(rename = "standard")]
Standard,
/// Bitcoin Cash specific address format.
/// https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md
#[serde(rename = "cashaddress")]
CashAddress { network: String },
}

impl Default for UtxoAddressFormat {
fn default() -> Self {
UtxoAddressFormat::Standard
}
}

#[derive(Debug)]
pub struct UtxoCoinImpl { // pImpl idiom.
ticker: String,
Expand Down Expand Up @@ -204,6 +222,8 @@ pub struct UtxoCoinImpl { // pImpl idiom.
key_pair: KeyPair,
/// Lock the mutex when we deal with address utxos
my_address: Address,
/// The address format indicates how to parse and display UTXO addresses over RPC calls
address_format: UtxoAddressFormat,
/// Is current coin KMD asset chain?
/// https://komodoplatform.atlassian.net/wiki/spaces/KPSD/pages/71729160/What+is+a+Parallel+Chain+Asset+Chain
asset_chain: bool,
Expand Down Expand Up @@ -332,6 +352,15 @@ impl UtxoCoinImpl {
pub fn rpc_client(&self) -> &UtxoRpcClientEnum {
&self.rpc_client
}

pub fn display_address(&self, address: &Address) -> Result<String, String> {
match &self.address_format {
UtxoAddressFormat::Standard => Ok(address.to_string()),
UtxoAddressFormat::CashAddress { network } =>
address.to_cashaddress(&network, self.pub_addr_prefix, self.p2sh_addr_prefix)
.and_then(|cashaddress| cashaddress.encode()),
}
}
}

fn payment_script(
Expand Down Expand Up @@ -760,7 +789,7 @@ impl UtxoCoin {
for input in unsigned.inputs.iter() {
let prev_hash = input.previous_output.hash.reversed().into();
let tx = try_s!(self.rpc_client.get_verbose_transaction(prev_hash).compat().await);
interest += kmd_interest(tx.height, input.amount, tx.locktime as u64, unsigned.lock_time as u64);
interest += kmd_interest(tx.height.unwrap_or(0), input.amount, tx.locktime as u64, unsigned.lock_time as u64);
}
if interest > 0 {
data.received_by_me += interest;
Expand Down Expand Up @@ -924,7 +953,7 @@ impl SwapOps for UtxoCoin {
t_addr_prefix: self.p2sh_t_addr_prefix,
};
let arc = self.clone();
let addr_string = payment_addr.to_string();
let addr_string = try_fus!(self.display_address(&payment_addr));
Either::B(client.import_address(&addr_string, &addr_string, false).map_err(|e| ERRL!("{}", e)).and_then(move |_|
arc.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out])
))
Expand Down Expand Up @@ -975,7 +1004,7 @@ impl SwapOps for UtxoCoin {
t_addr_prefix: self.p2sh_t_addr_prefix,
};
let arc = self.clone();
let addr_string = payment_addr.to_string();
let addr_string = try_fus!(self.display_address(&payment_addr));
Either::B(client.import_address(&addr_string, &addr_string, false).map_err(|e| ERRL!("{}", e)).and_then(move |_|
arc.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out])
))
Expand Down Expand Up @@ -1236,7 +1265,8 @@ impl SwapOps for UtxoCoin {
prefix: selfi.p2sh_addr_prefix,
hash,
checksum_type: selfi.checksum_type,
}.to_string();
};
let target_addr = try_s!(selfi.display_address(&target_addr));
let received_by_addr = try_s!(client.list_received_by_address(0, true, true).compat().await);
for item in received_by_addr {
if item.address == target_addr && !item.txids.is_empty() {
Expand Down Expand Up @@ -1292,8 +1322,8 @@ impl SwapOps for UtxoCoin {
impl MarketCoinOps for UtxoCoin {
fn ticker (&self) -> &str {&self.ticker[..]}

fn my_address(&self) -> Cow<str> {
self.0.my_address.to_string().into()
fn my_address(&self) -> Result<String, String> {
self.display_address(&self.my_address)
}

fn my_balance(&self) -> Box<dyn Future<Item=BigDecimal, Error=String> + Send> {
Expand Down Expand Up @@ -1359,7 +1389,7 @@ impl MarketCoinOps for UtxoCoin {
fn address_from_pubkey_str(&self, pubkey: &str) -> Result<String, String> {
let pubkey_bytes = try_s!(hex::decode(pubkey));
let addr = try_s!(address_from_raw_pubkey(&pubkey_bytes, self.pub_addr_prefix, self.pub_t_addr_prefix, self.checksum_type));
Ok(addr.to_string())
self.display_address(&addr)
}

fn display_priv_key(&self) -> String {
Expand All @@ -1368,7 +1398,11 @@ impl MarketCoinOps for UtxoCoin {
}

async fn withdraw_impl(coin: UtxoCoin, req: WithdrawRequest) -> Result<TransactionDetails, String> {
let to = try_s!(Address::from_str(&req.to));
let to = match &coin.address_format {
UtxoAddressFormat::Standard => try_s!(Address::from_str(&req.to)),
UtxoAddressFormat::CashAddress {..} => try_s!(Address::from_cashaddress(
&req.to, coin.checksum_type.clone(),coin.pub_addr_prefix, coin.p2sh_addr_prefix))
};
if to.checksum_type != coin.checksum_type {
return ERR!("Address {} has invalid checksum type, it must be {:?}", to, coin.checksum_type);
}
Expand Down Expand Up @@ -1402,9 +1436,11 @@ async fn withdraw_impl(coin: UtxoCoin, req: WithdrawRequest) -> Result<Transacti
let fee_details = UtxoFeeDetails {
amount: big_decimal_from_sat(data.fee_amount as i64, coin.decimals),
};
let my_address = try_s!(coin.my_address());
let to_address = try_s!(coin.display_address(&to));
Ok(TransactionDetails {
from: vec![coin.my_address().into()],
to: vec![format!("{}", to)],
from: vec![my_address],
to: vec![to_address],
total_amount: big_decimal_from_sat(data.spent_by_me as i64, coin.decimals),
spent_by_me: big_decimal_from_sat(data.spent_by_me as i64, coin.decimals),
received_by_me: big_decimal_from_sat(data.received_by_me as i64, coin.decimals),
Expand Down Expand Up @@ -1476,6 +1512,13 @@ impl MmCoin for UtxoCoin {
let mut my_balance: Option<BigDecimal> = None;
let history = self.load_history_from_file(&ctx);
let mut history_map: HashMap<H256Json, TransactionDetails> = history.into_iter().map(|tx| (H256Json::from(tx.tx_hash.as_slice()), tx)).collect();
let my_address = match self.my_address() {
Ok(addr) => addr,
Err(e) => {
log!("Error on getting self address: " [e] ". Stop tx history");
return;
}
};

let mut success_iteration = 0i32;
loop {
Expand Down Expand Up @@ -1545,7 +1588,7 @@ impl MmCoin for UtxoCoin {
"coin" => self.ticker.clone(), "client" => "native", "method" => "listtransactions");

all_transactions.into_iter().filter_map(|item| {
if item.address == self.my_address() {
if item.address == my_address {
Some((item.txid, item.blockindex))
} else {
None
Expand Down Expand Up @@ -1730,10 +1773,12 @@ impl MmCoin for UtxoCoin {
}
// remove address duplicates in case several inputs were spent from same address
// or several outputs are sent to same address
let mut from_addresses: Vec<String> = from_addresses.into_iter().flatten().map(|addr| addr.to_string()).collect();
let mut from_addresses: Vec<String> =
try_s!(from_addresses.into_iter().flatten().map(|addr| selfi.display_address(&addr)).collect());
from_addresses.sort();
from_addresses.dedup();
let mut to_addresses: Vec<String> = to_addresses.into_iter().flatten().map(|addr| addr.to_string()).collect();
let mut to_addresses: Vec<String> =
try_s!(to_addresses.into_iter().flatten().map(|addr| selfi.display_address(&addr)).collect());
to_addresses.sort();
to_addresses.dedup();

Expand All @@ -1750,7 +1795,7 @@ impl MmCoin for UtxoCoin {
fee_details: Some(UtxoFeeDetails {
amount: fee,
}.into()),
block_height: verbose_tx.height,
block_height: verbose_tx.height.unwrap_or(0),
coin: selfi.ticker.clone(),
internal_id: tx.hash().reversed().to_vec().into(),
timestamp: verbose_tx.time.into(),
Expand Down Expand Up @@ -1958,6 +2003,12 @@ pub async fn utxo_coin_from_conf_and_request(
checksum_type,
};

let address_format = if conf["address_format"].is_null() {
UtxoAddressFormat::Standard
} else {
try_s!(json::from_value(conf["address_format"].clone()))
};

let rpc_client = match req["method"].as_str() {
Some("enable") => {
if cfg!(feature = "native") {
Expand Down Expand Up @@ -2116,6 +2167,7 @@ pub async fn utxo_coin_from_conf_and_request(
wif_prefix,
tx_version,
my_address: my_address.clone(),
address_format,
asset_chain,
tx_fee,
version_group_id,
Expand Down
Loading

0 comments on commit 9006a9f

Please sign in to comment.