Skip to content

Commit

Permalink
Use MTP as tx lock time for KMD interest claim #593 (#658)
Browse files Browse the repository at this point in the history
* WIP.

* Use MTP as tx lock time for KMD interest claim #593

* Remove useless test.

* Remove useless test.

* Fix build after merge.
  • Loading branch information
artemii235 authored Jun 9, 2020
1 parent 9006a9f commit f2d1603
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 22 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

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

24 changes: 21 additions & 3 deletions mm2src/coins/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ use serialization::{serialize, deserialize};
use std::collections::hash_map::{HashMap, Entry};
use std::convert::TryInto;
use std::cmp::Ordering;
use std::num::NonZeroU64;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::str::FromStr;
Expand All @@ -78,6 +79,11 @@ const KILO_BYTE: u64 = 1000;
const MAX_DER_SIGNATURE_LEN: usize = 72;
const COMPRESSED_PUBKEY_LEN: usize = 33;
const P2PKH_OUTPUT_LEN: u64 = 34;
/// Block count for KMD median time past calculation
///
/// # Safety
/// 11 > 0
const KMD_MTP_BLOCK_COUNT: NonZeroU64 = unsafe { NonZeroU64::new_unchecked(11u64) };

#[cfg(windows)]
#[cfg(feature = "native")]
Expand Down Expand Up @@ -247,6 +253,8 @@ pub struct UtxoCoinImpl { // pImpl idiom.
/// relay fee amount instead of calculated
/// https://github.com/KomodoPlatform/atomicDEX-API/issues/617
force_min_relay_fee: bool,
/// Block count for median time past calculation
mtp_block_count: NonZeroU64,
estimate_fee_mode: Option<EstimateFeeMode>,
}

Expand Down Expand Up @@ -361,6 +369,11 @@ impl UtxoCoinImpl {
.and_then(|cashaddress| cashaddress.encode()),
}
}

async fn get_current_mtp(&self) -> Result<u32, String> {
let current_block = try_s!(self.rpc_client.get_block_count().compat().await);
self.rpc_client.get_median_time_past(current_block, self.mtp_block_count).compat().await
}
}

fn payment_script(
Expand Down Expand Up @@ -784,12 +797,12 @@ impl UtxoCoin {
if self.ticker != "KMD" {
return Ok((unsigned, data));
}
unsigned.lock_time = (now_ms() / 1000) as u32 - 777;
unsigned.lock_time = try_s!(self.get_current_mtp().await);
let mut interest = 0;
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.unwrap_or(0), input.amount, tx.locktime as u64, unsigned.lock_time as u64);
interest += kmd_interest(tx.height, input.amount, tx.locktime as u64, unsigned.lock_time as u64);
}
if interest > 0 {
data.received_by_me += interest;
Expand Down Expand Up @@ -2179,6 +2192,7 @@ pub async fn utxo_coin_from_conf_and_request(
history_sync_state: Mutex::new(initial_history_state),
required_confirmations: required_confirmations.into(),
force_min_relay_fee: conf["force_min_relay_fee"].as_bool().unwrap_or (false),
mtp_block_count: json::from_value(conf["mtp_block_count"].clone()).unwrap_or (KMD_MTP_BLOCK_COUNT),
estimate_fee_mode: json::from_value(conf["estimate_fee_mode"].clone()).unwrap_or(None),
};
Ok(UtxoCoin(Arc::new(coin)))
Expand All @@ -2187,7 +2201,11 @@ pub async fn utxo_coin_from_conf_and_request(
/// Function calculating KMD interest
/// https://komodoplatform.atlassian.net/wiki/spaces/KPSD/pages/71729215/What+is+the+5+Komodo+Stake+Reward
/// https://github.com/KomodoPlatform/komodo/blob/master/src/komodo_interest.h
fn kmd_interest(height: u64, value: u64, lock_time: u64, current_time: u64) -> u64 {
fn kmd_interest(height: Option<u64>, value: u64, lock_time: u64, current_time: u64) -> u64 {
let height = match height {
Some(h) => h,
None => return 0, // return 0 if height is unknown
};
const KOMODO_ENDOFERA: u64 = 7777777;
const LOCKTIME_THRESHOLD: u64 = 500000000;
// value must be at least 10 KMD
Expand Down
79 changes: 75 additions & 4 deletions mm2src/coins/utxo/rpc_clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use bigdecimal::BigDecimal;
use bytes::{BytesMut};
use chain::{OutPoint, Transaction as UtxoTx};
use common::{StringError};
use chain::{BlockHeader, OutPoint, Transaction as UtxoTx};
use common::{median, StringError};
use common::custom_futures::{join_all_sequential, select_ok_sequential};
use common::executor::{spawn, Timer};
use common::jsonrpc_client::{JsonRpcClient, JsonRpcRemoteAddr, JsonRpcResponseFut, JsonRpcRequest, JsonRpcResponse, RpcRes};
Expand Down Expand Up @@ -34,13 +34,14 @@ use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json, Transaction as RpcTra
use rustls::{self, ClientConfig, Session};
use script::{Builder};
use serde_json::{self as json, Value as Json};
use serialization::{serialize, deserialize};
use serialization::{serialize, deserialize, CompactInteger, Reader};
use sha2::{Sha256, Digest};
use std::collections::hash_map::{HashMap, Entry};
use std::io;
use std::fmt;
use std::cmp::Ordering;
use std::net::{ToSocketAddrs, SocketAddr};
use std::num::NonZeroU64;
use std::ops::Deref;
#[cfg(not(feature = "native"))]
use std::os::raw::c_char;
Expand Down Expand Up @@ -80,6 +81,12 @@ pub enum UtxoRpcClientEnum {
Electrum(ElectrumClient),
}

impl From<ElectrumClient> for UtxoRpcClientEnum {
fn from(client: ElectrumClient) -> UtxoRpcClientEnum {
UtxoRpcClientEnum::Electrum(client)
}
}

impl Deref for UtxoRpcClientEnum {
type Target = dyn UtxoRpcClientOps;
fn deref(&self) -> &dyn UtxoRpcClientOps {
Expand Down Expand Up @@ -164,6 +171,9 @@ pub trait UtxoRpcClientOps: fmt::Debug + Send + Sync + 'static {
fn get_relay_fee(&self) -> RpcRes<BigDecimal>;

fn find_output_spend(&self, tx: &UtxoTx, vout: usize, from_block: u64) -> Box<dyn Future<Item=Option<UtxoTx>, Error=String> + Send>;

/// Get median time past for `count` blocks in the past including `starting_block`
fn get_median_time_past(&self, starting_block: u64, count: NonZeroU64) -> Box<dyn Future<Item=u32, Error=String> + Send>;
}

#[derive(Clone, Deserialize, Debug, PartialEq)]
Expand Down Expand Up @@ -476,6 +486,30 @@ impl UtxoRpcClientOps for NativeClient {
};
Box::new(fut.boxed().compat())
}

fn get_median_time_past(&self, starting_block: u64, count: NonZeroU64) -> Box<dyn Future<Item=u32, Error=String> + Send> {
let selfi = self.clone();
let fut = async move {
let starting_block_data = try_s!(selfi.get_block(starting_block.to_string()).compat().await);
if let Some(median) = starting_block_data.mediantime {
return Ok(median);
}

let mut block_timestamps = vec![starting_block_data.time];
let from = if starting_block <= count.get() {
0
} else {
starting_block - count.get() + 1
};
for block_n in from..starting_block {
let block_data = try_s!(selfi.get_block(block_n.to_string()).compat().await);
block_timestamps.push(block_data.time);
}
// can unwrap because count is non zero
Ok(median(block_timestamps.as_mut_slice()).unwrap())
};
Box::new(fut.boxed().compat())
}
}

#[cfg_attr(test, mockable)]
Expand Down Expand Up @@ -503,7 +537,7 @@ impl NativeClientImpl {
}))
}

/// https://bitcoin.org/en/developer-reference#getblock
/// https://developer.bitcoin.org/reference/rpc/getblock.html
/// Always returns verbose block
pub fn get_block(&self, height: String) -> RpcRes<VerboseBlockClient> {
let verbose = true;
Expand Down Expand Up @@ -623,6 +657,13 @@ pub enum ElectrumNonce {
Hash(H256Json),
}

#[derive(Debug, Deserialize)]
pub struct ElectrumBlockHeadersRes {
count: u64,
pub hex: BytesJson,
max: u64,
}

/// The block header compatible with Electrum 1.2
#[derive(Debug, Deserialize)]
pub struct ElectrumBlockHeaderV12 {
Expand Down Expand Up @@ -1043,6 +1084,12 @@ impl ElectrumClient {
None => rpc_func!(self, "blockchain.estimatefee", n_blocks),
}
}

/// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers
pub fn blockchain_block_headers(&self, start_height: u64, count: NonZeroU64, cp_height: u64)
-> RpcRes<ElectrumBlockHeadersRes> {
rpc_func!(self, "blockchain.block.headers", start_height, count, cp_height)
}
}

#[cfg_attr(test, mockable)]
Expand Down Expand Up @@ -1178,6 +1225,30 @@ impl UtxoRpcClientOps for ElectrumClient {
};
Box::new(fut.boxed().compat())
}

fn get_median_time_past(&self, starting_block: u64, count: NonZeroU64) -> Box<dyn Future<Item=u32, Error=String> + Send> {
let from = if starting_block <= count.get() {
0
} else {
starting_block - count.get() + 1
};
Box::new(self.blockchain_block_headers(from, count, 0)
.map_err(|e| ERRL!("{}", e))
.and_then(|res| {
if res.count == 0 {
return ERR!("Server returned zero count");
}
let len = CompactInteger::from(res.count);
let mut serialized = serialize(&len).take();
serialized.extend(res.hex.0.into_iter());
let mut reader = Reader::new(serialized.as_slice());
let headers = try_s!(reader.read_list::<BlockHeader>().map_err(|e| ERRL!("{:?}", e)));
let mut timestamps: Vec<_> = headers.into_iter().map(|block| block.time).collect();
// can unwrap because count is non zero
Ok(median(timestamps.as_mut_slice()).unwrap())
})
)
}
}

#[cfg_attr(test, mockable)]
Expand Down
Loading

0 comments on commit f2d1603

Please sign in to comment.