diff --git a/src/electrum.rs b/src/electrum.rs index 3277c40..f4a2f3b 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -256,7 +256,7 @@ impl Rpc { self.tracker.get_history(&self.new_status(scripthash)?) } }; - Ok(json!(history_entries.collect::>())) + Ok(json!(history_entries)) } fn scripthash_subscribe( diff --git a/src/status.rs b/src/status.rs index d2bd588..b2f2881 100644 --- a/src/status.rs +++ b/src/status.rs @@ -4,7 +4,7 @@ use bitcoin::{ Amount, Block, BlockHash, OutPoint, SignedAmount, Transaction, Txid, }; use rayon::prelude::*; -use serde_json::{json, Value}; +use serde::ser::{Serialize, Serializer}; use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; @@ -45,44 +45,74 @@ impl TxEntry { } } -pub(crate) struct ConfirmedEntry { - txid: Txid, - height: usize, +enum Height { + Confirmed { height: usize }, + Unconfirmed { has_unconfirmed_inputs: bool }, } -impl ConfirmedEntry { - pub fn hash(&self, engine: &mut sha256::HashEngine) { +impl Height { + fn as_i64(&self) -> i64 { + match self { + Self::Confirmed { height } => i64::try_from(*height).unwrap(), + Self::Unconfirmed { + has_unconfirmed_inputs: true, + } => -1, + Self::Unconfirmed { + has_unconfirmed_inputs: false, + } => 0, + } + } +} + +impl Serialize for Height { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_i64(self.as_i64()) + } +} + +impl std::fmt::Display for Height { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_i64().fmt(f) + } +} + +#[derive(Serialize)] +pub(crate) struct HistoryEntry { + #[serde(rename = "tx_hash")] + txid: Txid, + height: Height, + #[serde( + skip_serializing_if = "Option::is_none", + with = "bitcoin::util::amount::serde::as_sat::opt" + )] + fee: Option, +} + +impl HistoryEntry { + fn hash(&self, engine: &mut sha256::HashEngine) { let s = format!("{}:{}:", self.txid, self.height); engine.input(s.as_bytes()); } - pub fn value(&self) -> Value { - json!({"tx_hash": self.txid, "height": self.height}) - } -} - -pub(crate) struct MempoolEntry { - txid: Txid, - has_unconfirmed_inputs: bool, - fee: Amount, -} - -impl MempoolEntry { - fn height(&self) -> isize { - if self.has_unconfirmed_inputs { - -1 - } else { - 0 + fn confirmed(txid: Txid, height: usize) -> Self { + Self { + txid, + height: Height::Confirmed { height }, + fee: None, } } - pub fn hash(&self, engine: &mut sha256::HashEngine) { - let s = format!("{}:{}:", self.txid, self.height()); - engine.input(s.as_bytes()); - } - - pub fn value(&self) -> Value { - json!({"tx_hash": self.txid, "height": self.height(), "fee": self.fee.as_sat()}) + fn unconfirmed(txid: Txid, has_unconfirmed_inputs: bool, fee: Amount) -> Self { + Self { + txid, + height: Height::Unconfirmed { + has_unconfirmed_inputs, + }, + fee: Some(fee), + } } } @@ -204,20 +234,25 @@ impl Status { } } - pub(crate) fn get_confirmed(&self, chain: &Chain) -> Vec { + pub(crate) fn get_history(&self, chain: &Chain, mempool: &Mempool) -> Vec { + let mut result = self.get_confirmed(chain); + result.extend(self.get_mempool(mempool)); + result + } + + fn get_confirmed(&self, chain: &Chain) -> Vec { self.confirmed_entries(chain) .collect::>() .into_iter() .flat_map(|(height, entries)| { - entries.iter().map(move |e| ConfirmedEntry { - txid: e.txid, - height, - }) + entries + .iter() + .map(move |e| HistoryEntry::confirmed(e.txid, height)) }) .collect() } - pub(crate) fn get_mempool(&self, mempool: &Mempool) -> Vec { + fn get_mempool(&self, mempool: &Mempool) -> Vec { let mut entries = self .mempool .iter() @@ -226,11 +261,7 @@ impl Status { entries.sort_by_key(|e| (e.has_unconfirmed_inputs, e.txid)); entries .into_iter() - .map(|e| MempoolEntry { - txid: e.txid, - has_unconfirmed_inputs: e.has_unconfirmed_inputs, - fee: e.fee, - }) + .map(|e| HistoryEntry::unconfirmed(e.txid, e.has_unconfirmed_inputs, e.fee)) .collect() } @@ -417,3 +448,33 @@ fn filter_inputs(tx: &Transaction, outpoints: &HashSet) -> Vec impl Iterator { - let confirmed = status - .get_confirmed(self.index.chain()) - .into_iter() - .map(|entry| entry.value()); - let mempool = status - .get_mempool(&self.mempool) - .into_iter() - .map(|entry| entry.value()); - confirmed.chain(mempool) + pub(crate) fn get_history(&self, status: &Status) -> Vec { + status.get_history(self.index.chain(), &self.mempool) } pub fn sync(&mut self, daemon: &Daemon) -> Result<()> {