Refactor REST Transaction to TransactionValue conversion
This commit is contained in:
parent
3133bcee05
commit
a25a24a6e5
251
src/rest.rs
251
src/rest.rs
@ -5,8 +5,8 @@ use crate::errors;
|
||||
use crate::new_index::{compute_script_hash, Query, SpendingInput, Utxo};
|
||||
use crate::util::Address;
|
||||
use crate::util::{
|
||||
full_hash, get_script_asm, is_coinbase, script_to_address, BlockHeaderMeta, BlockId, FullHash,
|
||||
TransactionStatus,
|
||||
full_hash, get_script_asm, has_prevout, is_coinbase, script_to_address, BlockHeaderMeta,
|
||||
BlockId, FullHash, TransactionStatus,
|
||||
};
|
||||
|
||||
#[cfg(feature = "liquid")]
|
||||
@ -28,7 +28,7 @@ use elements::confidential::{Asset, Value};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::HashMap;
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
@ -104,24 +104,51 @@ struct TransactionValue {
|
||||
status: Option<TransactionStatus>,
|
||||
}
|
||||
|
||||
impl From<Transaction> for TransactionValue {
|
||||
fn from(tx: Transaction) -> Self {
|
||||
let vin = tx
|
||||
impl
|
||||
From<(
|
||||
Transaction,
|
||||
Option<BlockId>,
|
||||
&HashMap<OutPoint, TxOut>,
|
||||
&Config,
|
||||
)> for TransactionValue
|
||||
{
|
||||
fn from(
|
||||
(tx, blockid, prevouts, config): (
|
||||
Transaction,
|
||||
Option<BlockId>,
|
||||
&HashMap<OutPoint, TxOut>,
|
||||
&Config,
|
||||
),
|
||||
) -> Self {
|
||||
let vins: Vec<TxInValue> = tx
|
||||
.input
|
||||
.iter()
|
||||
.map(|el| TxInValue::from(el.clone())) // TODO avoid clone
|
||||
.map(|txin| {
|
||||
let prevout = prevouts.get(&txin.previous_output);
|
||||
TxInValue::from((txin.clone(), prevout, config)) // TODO avoid clone
|
||||
})
|
||||
.collect();
|
||||
let vout: Vec<TxOutValue> = tx
|
||||
let vouts: Vec<TxOutValue> = tx
|
||||
.output
|
||||
.iter()
|
||||
.map(|el| TxOutValue::from(el.clone())) // TODO avoid clone
|
||||
.map(|txout| TxOutValue::from((txout.clone(), config))) // TODO avoid clone
|
||||
.collect();
|
||||
let bytes = serialize(&tx);
|
||||
|
||||
#[cfg(not(feature = "liquid"))]
|
||||
let fee = None; // added later
|
||||
let fee = if config.prevout_enabled && !vins.iter().any(|vin| vin.prevout.is_none()) {
|
||||
let total_in: u64 = vins
|
||||
.iter()
|
||||
.map(|vin| vin.prevout.as_ref().unwrap().value)
|
||||
.sum();
|
||||
let total_out: u64 = vouts.iter().map(|vout| vout.value).sum();
|
||||
Some(total_in - total_out)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(feature = "liquid")]
|
||||
let fee = vout
|
||||
let fee = vouts
|
||||
.iter()
|
||||
.find(|vout| vout.scriptpubkey_type == "fee")
|
||||
.map(|vout| vout.value.unwrap())
|
||||
@ -131,24 +158,16 @@ impl From<Transaction> for TransactionValue {
|
||||
txid: tx.txid(),
|
||||
version: tx.version,
|
||||
locktime: tx.lock_time,
|
||||
vin,
|
||||
vout,
|
||||
vin: vins,
|
||||
vout: vouts,
|
||||
size: bytes.len() as u32,
|
||||
weight: tx.get_weight() as u32,
|
||||
fee,
|
||||
status: None,
|
||||
status: Some(TransactionStatus::from(blockid)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Transaction, Option<BlockId>)> for TransactionValue {
|
||||
fn from((tx, blockid): (Transaction, Option<BlockId>)) -> Self {
|
||||
let mut value = TransactionValue::from(tx);
|
||||
value.status = Some(TransactionStatus::from(blockid));
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
struct TxInValue {
|
||||
txid: Sha256dHash,
|
||||
@ -166,8 +185,8 @@ struct TxInValue {
|
||||
issuance: Option<IssuanceValue>,
|
||||
}
|
||||
|
||||
impl From<TxIn> for TxInValue {
|
||||
fn from(txin: TxIn) -> Self {
|
||||
impl From<(TxIn, Option<&TxOut>, &Config)> for TxInValue {
|
||||
fn from((txin, prevout, config): (TxIn, Option<&TxOut>, &Config)) -> Self {
|
||||
#[cfg(not(feature = "liquid"))]
|
||||
let witness = if txin.witness.len() > 0 {
|
||||
Some(txin.witness.iter().map(|w| hex::encode(w)).collect())
|
||||
@ -182,7 +201,7 @@ impl From<TxIn> for TxInValue {
|
||||
TxInValue {
|
||||
txid: txin.previous_output.txid,
|
||||
vout: txin.previous_output.vout,
|
||||
prevout: None, // added later
|
||||
prevout: prevout.map(|prevout| TxOutValue::from((prevout.clone(), config))), // TODO avoid clone()
|
||||
scriptsig_asm: get_script_asm(&txin.script_sig),
|
||||
witness,
|
||||
is_coinbase,
|
||||
@ -223,8 +242,8 @@ struct TxOutValue {
|
||||
pegout: Option<PegOutRequest>,
|
||||
}
|
||||
|
||||
impl From<TxOut> for TxOutValue {
|
||||
fn from(txout: TxOut) -> Self {
|
||||
impl From<(TxOut, &Config)> for TxOutValue {
|
||||
fn from((txout, config): (TxOut, &Config)) -> Self {
|
||||
#[cfg(not(feature = "liquid"))]
|
||||
let value = txout.value;
|
||||
|
||||
@ -256,6 +275,7 @@ impl From<TxOut> for TxOutValue {
|
||||
|
||||
let script = txout.script_pubkey;
|
||||
let script_asm = get_script_asm(&script);
|
||||
let script_addr = script_to_address(&script, &config.network_type);
|
||||
|
||||
// TODO should the following something to put inside rust-elements lib?
|
||||
let script_type = if is_fee {
|
||||
@ -283,7 +303,7 @@ impl From<TxOut> for TxOutValue {
|
||||
TxOutValue {
|
||||
scriptpubkey: script,
|
||||
scriptpubkey_asm: script_asm,
|
||||
scriptpubkey_address: None, // added later
|
||||
scriptpubkey_address: script_addr,
|
||||
scriptpubkey_type: script_type.to_string(),
|
||||
value,
|
||||
#[cfg(feature = "liquid")]
|
||||
@ -293,7 +313,11 @@ impl From<TxOut> for TxOutValue {
|
||||
#[cfg(feature = "liquid")]
|
||||
assetcommitment,
|
||||
#[cfg(feature = "liquid")]
|
||||
pegout: None, // added later
|
||||
pegout: PegOutRequest::parse(
|
||||
&vout.scriptpubkey,
|
||||
&config.parent_network,
|
||||
&config.parent_genesis_hash,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -375,92 +399,30 @@ fn ttl_by_depth(height: Option<usize>, query: &Query) -> u32 {
|
||||
})
|
||||
}
|
||||
|
||||
fn attach_tx_data(tx: TransactionValue, config: &Config, query: &Query) -> TransactionValue {
|
||||
let mut txs = vec![tx];
|
||||
attach_txs_data(&mut txs, config, query);
|
||||
txs.remove(0)
|
||||
}
|
||||
|
||||
fn attach_txs_data(txs: &mut Vec<TransactionValue>, config: &Config, query: &Query) {
|
||||
{
|
||||
// a map of prev txids/vouts to lookup, with a reference to the "next in" that spends them
|
||||
let mut lookups: BTreeMap<OutPoint, &mut TxInValue> = BTreeMap::new();
|
||||
// using BTreeMap ensures the txid keys are in order. querying the db with keys in order leverage memory
|
||||
// locality from empirical test up to 2 or 3 times faster
|
||||
|
||||
for tx in txs.iter_mut() {
|
||||
// collect lookups
|
||||
if config.prevout_enabled {
|
||||
for vin in tx.vin.iter_mut() {
|
||||
#[cfg(not(feature = "liquid"))]
|
||||
let has_prevout = !vin.is_coinbase;
|
||||
|
||||
#[cfg(feature = "liquid")]
|
||||
let has_prevout = !vin.is_coinbase
|
||||
&& !vin.is_pegin
|
||||
&& vin.txid.be_hex_string() != REGTEST_INITIAL_ISSUANCE_PREVOUT;
|
||||
|
||||
if has_prevout {
|
||||
let outpoint = OutPoint {
|
||||
txid: vin.txid,
|
||||
vout: vin.vout,
|
||||
};
|
||||
lookups.insert(outpoint, vin);
|
||||
}
|
||||
}
|
||||
}
|
||||
// attach encoded address (should ideally happen in TxOutValue::from(), but it cannot
|
||||
// easily access the network)
|
||||
for mut vout in tx.vout.iter_mut() {
|
||||
vout.scriptpubkey_address =
|
||||
script_to_address(&vout.scriptpubkey, &config.network_type);
|
||||
|
||||
#[cfg(feature = "liquid")]
|
||||
{
|
||||
vout.pegout = PegOutRequest::parse(
|
||||
&vout.scriptpubkey,
|
||||
&config.parent_network,
|
||||
&config.parent_genesis_hash,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fetch prevtxs and attach prevouts to nextins
|
||||
if config.prevout_enabled {
|
||||
let outpoints = lookups.keys().cloned().collect();
|
||||
let txos = query.lookup_txos(&outpoints);
|
||||
|
||||
for (outpoint, txo) in txos {
|
||||
let mut prevout = TxOutValue::from(txo);
|
||||
prevout.scriptpubkey_address =
|
||||
script_to_address(&prevout.scriptpubkey, &config.network_type);
|
||||
|
||||
let nextin = lookups.remove(&outpoint).unwrap();
|
||||
nextin.prevout = Some(prevout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// attach tx fee
|
||||
#[cfg(not(feature = "liquid"))]
|
||||
{
|
||||
if config.prevout_enabled {
|
||||
for mut tx in txs.iter_mut() {
|
||||
if tx.vin.iter().any(|vin| vin.prevout.is_none()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let total_in: u64 = tx
|
||||
.vin
|
||||
fn prepare_txs(
|
||||
txs: Vec<(Transaction, Option<BlockId>)>,
|
||||
query: &Query,
|
||||
config: &Config,
|
||||
) -> Vec<TransactionValue> {
|
||||
let prevouts = if config.prevout_enabled {
|
||||
let outpoints = txs
|
||||
.iter()
|
||||
.flat_map(|(tx, _)| {
|
||||
tx.input
|
||||
.iter()
|
||||
.map(|vin| vin.clone().prevout.unwrap().value) // @TODO avoid clone
|
||||
.sum();
|
||||
let total_out: u64 = tx.vout.iter().map(|vout| vout.value).sum();
|
||||
tx.fee = Some(total_in - total_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
.filter(|txin| has_prevout(txin))
|
||||
.map(|txin| txin.previous_output)
|
||||
})
|
||||
.collect();
|
||||
|
||||
query.lookup_txos(&outpoints)
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
|
||||
txs.into_iter()
|
||||
.map(|(tx, blockid)| TransactionValue::from((tx, blockid, &prevouts, config)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn run_server(config: Arc<Config>, query: Arc<Query>, daemon: Arc<Daemon>) -> Handle {
|
||||
@ -609,19 +571,21 @@ fn handle_request(
|
||||
)));
|
||||
}
|
||||
|
||||
let mut txs = txids
|
||||
let txs = txids
|
||||
.iter()
|
||||
.skip(start_index)
|
||||
.take(CHAIN_TXS_PER_PAGE)
|
||||
.map(|txid| {
|
||||
query
|
||||
.lookup_txn(&txid)
|
||||
.map(TransactionValue::from)
|
||||
// FIXME: set blockid correctly, only when the block is non-orphaned
|
||||
// alternatively, don't set "status" at all?
|
||||
.map(|tx| (tx, None))
|
||||
.ok_or_else(|| "missing tx".to_string())
|
||||
})
|
||||
.collect::<Result<Vec<TransactionValue>, _>>()?;
|
||||
attach_txs_data(&mut txs, config, query);
|
||||
json_response(txs, TTL_LONG)
|
||||
.collect::<Result<Vec<(Transaction, Option<BlockId>)>, _>>()?;
|
||||
|
||||
json_response(prepare_txs(txs, query, config), TTL_LONG)
|
||||
}
|
||||
(&Method::GET, Some(script_type @ &"address"), Some(script_str), None, None, None)
|
||||
| (&Method::GET, Some(script_type @ &"scripthash"), Some(script_str), None, None, None) => {
|
||||
@ -661,20 +625,17 @@ fn handle_request(
|
||||
.mempool()
|
||||
.history(&script_hash[..], MAX_MEMPOOL_TXS)
|
||||
.into_iter()
|
||||
.map(|tx| TransactionValue::from((tx, None))),
|
||||
.map(|tx| (tx, None)),
|
||||
);
|
||||
|
||||
txs.extend(
|
||||
query
|
||||
.chain()
|
||||
.history(&script_hash[..], None, CHAIN_TXS_PER_PAGE)
|
||||
.into_iter()
|
||||
.map(TransactionValue::from),
|
||||
.into_iter(),
|
||||
);
|
||||
|
||||
attach_txs_data(&mut txs, config, query);
|
||||
|
||||
json_response(txs, TTL_SHORT)
|
||||
json_response(prepare_txs(txs, query, config), TTL_SHORT)
|
||||
}
|
||||
|
||||
(
|
||||
@ -696,20 +657,13 @@ fn handle_request(
|
||||
let script_hash = to_scripthash(script_type, script_str, &config.network_type)?;
|
||||
let last_seen_txid = last_seen_txid.and_then(|txid| Sha256dHash::from_hex(txid).ok());
|
||||
|
||||
let mut txs = query
|
||||
.chain()
|
||||
.history(
|
||||
&script_hash[..],
|
||||
last_seen_txid.as_ref(),
|
||||
CHAIN_TXS_PER_PAGE,
|
||||
)
|
||||
.into_iter()
|
||||
.map(TransactionValue::from)
|
||||
.collect();
|
||||
let txs = query.chain().history(
|
||||
&script_hash[..],
|
||||
last_seen_txid.as_ref(),
|
||||
CHAIN_TXS_PER_PAGE,
|
||||
);
|
||||
|
||||
attach_txs_data(&mut txs, config, query);
|
||||
|
||||
json_response(txs, TTL_SHORT)
|
||||
json_response(prepare_txs(txs, query, config), TTL_SHORT)
|
||||
}
|
||||
(
|
||||
&Method::GET,
|
||||
@ -729,16 +683,14 @@ fn handle_request(
|
||||
) => {
|
||||
let script_hash = to_scripthash(script_type, script_str, &config.network_type)?;
|
||||
|
||||
let mut txs = query
|
||||
let txs = query
|
||||
.mempool()
|
||||
.history(&script_hash[..], MAX_MEMPOOL_TXS)
|
||||
.into_iter()
|
||||
.map(|tx| TransactionValue::from((tx, None)))
|
||||
.map(|tx| (tx, None))
|
||||
.collect();
|
||||
|
||||
attach_txs_data(&mut txs, config, query);
|
||||
|
||||
json_response(txs, TTL_SHORT)
|
||||
json_response(prepare_txs(txs, query, config), TTL_SHORT)
|
||||
}
|
||||
|
||||
(
|
||||
@ -768,16 +720,15 @@ fn handle_request(
|
||||
}
|
||||
(&Method::GET, Some(&"tx"), Some(hash), None, None, None) => {
|
||||
let hash = Sha256dHash::from_hex(hash)?;
|
||||
let transaction = query
|
||||
let tx = query
|
||||
.lookup_txn(&hash)
|
||||
.ok_or_else(|| HttpError::not_found("Transaction not found".to_string()))?;
|
||||
let status = query.get_tx_status(&hash);
|
||||
let ttl = ttl_by_depth(status.block_height, query);
|
||||
let confirmation = query.chain().tx_confirming_block(&hash);
|
||||
let ttl = ttl_by_depth(confirmation.as_ref().map(|c| c.height), query);
|
||||
|
||||
let mut value = TransactionValue::from(transaction);
|
||||
value.status = Some(status);
|
||||
let value = attach_tx_data(value, config, query);
|
||||
json_response(value, ttl)
|
||||
let tx = prepare_txs(vec![(tx, confirmation)], query, config).remove(0);
|
||||
|
||||
json_response(tx, ttl)
|
||||
}
|
||||
(&Method::GET, Some(&"tx"), Some(hash), Some(&"hex"), None, None) => {
|
||||
let hash = Sha256dHash::from_hex(hash)?;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user