use crate::cache::TransactionCache;
use crate::chaindef::BlockHash;
use crate::chaindef::Transaction;
use crate::chaindef::TxOut;
use crate::daemon::Daemon;
use crate::def::COIN;
use crate::mempool::ConfirmationState;
use crate::mempool::Tracker;
use crate::query::header::HeaderQuery;
use crate::rpc::encoding::blockhash_to_hex;
use anyhow::{Context, Result};
use bitcoincash::blockdata::script::Script;
use bitcoincash::consensus::encode::{deserialize, serialize};
use bitcoincash::hash_types::Txid;
use bitcoincash::hashes::hex::ToHex;
use bitcoincash::network::constants::Network;
use bitcoincash::util::address::{Address, AddressType};
use rust_decimal::prelude::*;
use serde_json::Value;
use std::sync::{Arc, RwLock};
fn get_address_type(out: &TxOut, network: Network) -> Option<&str> {
#[cfg(feature = "nexa")]
{
use crate::nexa::transaction::TxOutType;
if out.txout_type == (TxOutType::TEMPLATE as u8) {
return Some("scripttemplate");
}
}
let script = &out.script_pubkey;
if script.is_op_return() {
return Some("nulldata");
}
let address = Address::from_script(script, network).ok()?;
let address_type = address.address_type();
match address_type {
Some(AddressType::P2pkh) => Some("pubkeyhash"),
Some(AddressType::P2sh) => Some("scripthash"),
_ => {
if !address.is_standard() {
Some("nonstandard")
} else {
None
}
}
}
}
#[cfg(not(feature = "nexa"))]
fn get_addresses(script: &Script, network: Network) -> Vec<String> {
use crate::bch::cashaddr::encode as bchaddr_encode;
use crate::bch::cashaddr::version_byte_flags;
use bitcoincash::util::address::Payload::{PubkeyHash, ScriptHash};
let address = match Address::from_script(script, network).ok() {
Some(a) => a,
None => return vec![],
};
match address.payload {
PubkeyHash(pubhash) => {
let hash = pubhash.as_hash().to_vec();
let encoded = bchaddr_encode(&hash, version_byte_flags::TYPE_P2PKH, network);
let encoded_token =
bchaddr_encode(&hash, version_byte_flags::TYPE_P2PKH_TOKEN, network);
match encoded {
Ok(addr) => {
let addr_token = encoded_token.unwrap();
vec![addr, addr_token]
}
_ => vec![],
}
}
ScriptHash(scripthash) => {
let hash = scripthash.as_hash().to_vec();
let encoded = bchaddr_encode(&hash, version_byte_flags::TYPE_P2SH, network);
let encoded_token = bchaddr_encode(&hash, version_byte_flags::TYPE_P2SH_TOKEN, network);
match encoded {
Ok(addr) => {
let addr_token = encoded_token.unwrap();
vec![addr, addr_token]
}
_ => vec![],
}
}
_ => vec![],
}
}
#[cfg(feature = "nexa")]
fn get_addresses(_script: &Script, _network: Network) -> Vec<String> {
vec![]
}
fn value_from_amount(amount: u64) -> Value {
if amount == 0 {
return json!(0.0);
}
let satoshis = Decimal::new(amount as i64, 0);
json!(satoshis.checked_div(Decimal::new(COIN as i64, 0)).unwrap())
}
pub struct TxQuery {
tx_cache: TransactionCache,
daemon: Daemon,
mempool: Arc<RwLock<Tracker>>,
header: Arc<HeaderQuery>,
duration: Arc<prometheus::HistogramVec>,
network: Network,
}
impl TxQuery {
pub fn new(
tx_cache: TransactionCache,
daemon: Daemon,
mempool: Arc<RwLock<Tracker>>,
header: Arc<HeaderQuery>,
duration: Arc<prometheus::HistogramVec>,
network: Network,
) -> TxQuery {
TxQuery {
tx_cache,
daemon,
mempool,
header,
duration,
network,
}
}
pub fn get(
&self,
txid: &Txid,
blockhash: Option<&BlockHash>,
blockheight: Option<u32>,
) -> Result<Transaction> {
let _timer = self.duration.with_label_values(&["load_txn"]).start_timer();
if let Some(tx) = self.tx_cache.get(txid) {
return Ok(tx);
}
let hash: Option<BlockHash> = match blockhash {
Some(hash) => Some(*hash),
None => match self.header.get_by_txid(*txid, blockheight) {
Ok(header) => header.map(|h| h.block_hash()),
Err(_) => None,
},
};
self.load_txn_from_bitcoind(txid, hash.as_ref())
}
pub fn get_unconfirmed(&self, txid: &Txid) -> Result<Transaction> {
if let Some(tx) = self.tx_cache.get(txid) {
Ok(tx)
} else {
self.load_txn_from_bitcoind(txid, None)
}
}
#[cfg(not(feature = "nexa"))]
fn inputs_to_json(&self, tx: &Transaction) -> Vec<Value> {
tx.input.iter().map(|txin| json!({
"coinbase": if tx.is_coin_base() { Some(txin.script_sig.to_hex()) } else { None },
"sequence": txin.sequence,
"txid": txin.previous_output.txid.to_hex(),
"vout": txin.previous_output.vout,
"scriptSig": {
"asm": txin.script_sig.asm(),
"hex": txin.script_sig.to_hex(),
},
})).collect::<Vec<Value>>()
}
#[cfg(feature = "nexa")]
fn inputs_to_json(&self, tx: &Transaction) -> Vec<Value> {
tx.input.iter().map(|txin| {
let mut outpoint = txin.previous_output.hash.to_vec();
outpoint.reverse();
json!({
"coinbase": if tx.is_coin_base() { Some(txin.script_sig.to_hex()) } else { None },
"sequence": txin.sequence,
"outpoint": outpoint.to_hex(),
"scriptSig": {
"asm": txin.script_sig.asm(),
"hex": txin.script_sig.to_hex(),
},
})}).collect::<Vec<Value>>()
}
#[cfg(feature = "nexa")]
fn outputs_to_json(&self, tx: &Transaction) -> Vec<Value> {
use crate::nexa::{token::parse_token_from_scriptpubkey, transaction::TxOutType};
tx.output.iter().enumerate().map(|(n, txout)| {
let group = if txout.txout_type == (TxOutType::TEMPLATE as u8) {
parse_token_from_scriptpubkey(&txout.script_pubkey)
} else {
None
};
json!({
"type": txout.txout_type,
"value": value_from_amount(txout.value),
"value_satoshi": txout.value,
"value_coin": value_from_amount(txout.value),
"n": n,
"scriptPubKey": {
"asm": txout.script_pubkey.asm(),
"hex": txout.script_pubkey.to_hex(),
"type": get_address_type(txout, self.network).unwrap_or_default(),
"addresses": get_addresses(&txout.script_pubkey, self.network),
"groupTokenID": if let Some((id, _)) = group.as_ref() { Some(id.to_hex()) } else { None },
"groupQuantity": if let Some((_, amount)) = group { Some(amount) } else { None },
}
})
}).collect::<Vec<Value>>()
}
#[cfg(not(feature = "nexa"))]
fn outputs_to_json(&self, tx: &Transaction) -> Vec<Value> {
tx.output
.iter()
.enumerate()
.map(|(n, txout)| {
json!({
"value_satoshi": txout.value,
"value_coin": value_from_amount(txout.value),
"value": value_from_amount(txout.value),
"n": n,
"scriptPubKey": {
"asm": txout.script_pubkey.asm(),
"hex": txout.script_pubkey.to_hex(),
"type": get_address_type(txout, self.network).unwrap_or_default(),
"addresses": get_addresses(&txout.script_pubkey, self.network),
},
})
})
.collect::<Vec<Value>>()
}
pub fn get_verbose(&self, txid: &Txid) -> Result<Value> {
let header = self.header.get_by_txid(*txid, None).unwrap_or_default();
let blocktime = header.as_ref().map(|header| header.time);
let height = header
.as_ref()
.map(|header| self.header.get_height(header).unwrap());
let confirmations = match height {
Some(ref height) => {
let best = self.header.best();
let best_height = self.header.get_height(&best).unwrap();
Some(1 + best_height - height)
}
None => None,
};
let (blockhash, blockhash_hex) = if let Some(h) = header {
let hash = h.block_hash();
(Some(hash), Some(blockhash_to_hex(&hash)))
} else {
(None, None)
};
let tx = self.get(txid, blockhash.as_ref(), None)?;
let tx_serialized = serialize(&tx);
#[allow(unused_mut)]
let mut tx_details = json!({
"blockhash": blockhash_hex,
"blocktime": blocktime,
"height": height,
"confirmations": confirmations,
"hash": tx.txid().to_hex(),
"txid": tx.txid().to_hex(),
"size": tx_serialized.len(),
"hex": hex::encode(tx_serialized),
"locktime": tx.lock_time,
"time": blocktime,
"version": tx.version,
"vin": self.inputs_to_json(&tx),
"vout": self.outputs_to_json(&tx),
});
#[cfg(feature = "nexa")]
{
tx_details
.as_object_mut()
.unwrap()
.insert("txidem".to_string(), json!(tx.txidem().to_hex()));
}
Ok(json!(tx_details))
}
fn load_txn_from_bitcoind(
&self,
txid: &Txid,
blockhash: Option<&BlockHash>,
) -> Result<Transaction> {
let value: Value = self
.daemon
.gettransaction_raw(txid, blockhash, false)?;
let value_hex: &str = value.as_str().context("non-string tx")?;
let serialized_tx = hex::decode(value_hex).context("non-hex tx")?;
let tx: Transaction =
deserialize(&serialized_tx).context("failed to parse serialized tx")?;
if txid != &tx.txid() {
bail!(
"Requested transaction with txid {}, but received one with txid {}",
txid,
tx.txid()
);
}
self.tx_cache.put(txid, serialized_tx);
Ok(tx)
}
pub fn get_confirmation_height(&self, txid: Txid) -> Option<i64> {
{
let mempool = self.mempool.read().unwrap();
match mempool.tx_confirmation_state(&txid, None) {
ConfirmationState::InMempool => return Some(0),
ConfirmationState::UnconfirmedParent => return Some(-1),
_ => (),
};
}
self.header
.get_confirmed_height_for_tx(txid)
.map(|height| height as i64)
}
}