1
0
Fork 0
mirror of https://github.com/romanz/electrs.git synced 2025-02-24 15:02:21 +01:00

Return fee for unconfirmed transactions history

Following https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#blockchain.scripthash.get_mempool

Otherwise, the client will disconnect:
https://github.com/spesmilo/electrum/issues/6289
This commit is contained in:
Roman Zeyde 2020-06-28 09:11:56 +03:00
parent 536e920c1f
commit e55381fa66
No known key found for this signature in database
GPG key ID: 87CAE5FA46917CBB
3 changed files with 58 additions and 14 deletions

View file

@ -175,8 +175,12 @@ impl Tracker {
} }
} }
pub fn get_txn(&self, txid: &Txid) -> Option<Transaction> { pub fn has_txn(&self, txid: &Txid) -> bool {
self.items.get(txid).map(|stats| stats.tx.clone()) self.items.contains_key(txid)
}
pub fn get_fee(&self, txid: &Txid) -> Option<u64> {
self.items.get(txid).map(|stats| stats.entry.fee())
} }
/// Returns vector of (fee_rate, vsize) pairs, where fee_{n-1} > fee_n and vsize_n is the /// Returns vector of (fee_rate, vsize) pairs, where fee_{n-1} > fee_n and vsize_n is the

View file

@ -38,6 +38,7 @@ struct SpendingInput {
pub struct Status { pub struct Status {
confirmed: (Vec<FundingOutput>, Vec<SpendingInput>), confirmed: (Vec<FundingOutput>, Vec<SpendingInput>),
mempool: (Vec<FundingOutput>, Vec<SpendingInput>), mempool: (Vec<FundingOutput>, Vec<SpendingInput>),
txn_fees: HashMap<Txid, u64>,
} }
fn calc_balance((funding, spending): &(Vec<FundingOutput>, Vec<SpendingInput>)) -> i64 { fn calc_balance((funding, spending): &(Vec<FundingOutput>, Vec<SpendingInput>)) -> i64 {
@ -46,6 +47,25 @@ fn calc_balance((funding, spending): &(Vec<FundingOutput>, Vec<SpendingInput>))
funded as i64 - spent as i64 funded as i64 - spent as i64
} }
pub struct HistoryItem {
height: i32,
tx_hash: Txid,
fee: Option<u64>, // need to be set only for unconfirmed transactions (i.e. height <= 0)
}
impl HistoryItem {
pub fn to_json(&self) -> Value {
let mut result = json!({ "height": self.height, "tx_hash": self.tx_hash.to_hex()});
self.fee.map(|f| {
result
.as_object_mut()
.unwrap()
.insert("fee".to_string(), json!(f))
});
result
}
}
impl Status { impl Status {
fn funding(&self) -> impl Iterator<Item = &FundingOutput> { fn funding(&self) -> impl Iterator<Item = &FundingOutput> {
self.confirmed.0.iter().chain(self.mempool.0.iter()) self.confirmed.0.iter().chain(self.mempool.0.iter())
@ -63,7 +83,7 @@ impl Status {
calc_balance(&self.mempool) calc_balance(&self.mempool)
} }
pub fn history(&self) -> Vec<(i32, Txid)> { pub fn history(&self) -> Vec<HistoryItem> {
let mut txns_map = HashMap::<Txid, i32>::new(); let mut txns_map = HashMap::<Txid, i32>::new();
for f in self.funding() { for f in self.funding() {
txns_map.insert(f.txn_id, f.height as i32); txns_map.insert(f.txn_id, f.height as i32);
@ -71,10 +91,16 @@ impl Status {
for s in self.spending() { for s in self.spending() {
txns_map.insert(s.txn_id, s.height as i32); txns_map.insert(s.txn_id, s.height as i32);
} }
let mut txns: Vec<(i32, Txid)> = let mut items: Vec<HistoryItem> = txns_map
txns_map.into_iter().map(|item| (item.1, item.0)).collect(); .into_iter()
txns.sort_unstable(); .map(|item| HistoryItem {
txns height: item.1,
tx_hash: item.0,
fee: self.txn_fees.get(&item.0).cloned(),
})
.collect();
items.sort_unstable_by_key(|item| item.height);
items
} }
pub fn unspent(&self) -> Vec<&FundingOutput> { pub fn unspent(&self) -> Vec<&FundingOutput> {
@ -102,8 +128,8 @@ impl Status {
} else { } else {
let mut hash = FullHash::default(); let mut hash = FullHash::default();
let mut sha2 = Sha256::new(); let mut sha2 = Sha256::new();
for (height, txn_id) in txns { for item in txns {
let part = format!("{}:{}:", txn_id.to_hex(), height); let part = format!("{}:{}:", item.tx_hash.to_hex(), item.height);
sha2.input(part.as_bytes()); sha2.input(part.as_bytes());
} }
sha2.result(&mut hash); sha2.result(&mut hash);
@ -302,10 +328,10 @@ impl Query {
&self, &self,
script_hash: &[u8], script_hash: &[u8],
confirmed_funding: &[FundingOutput], confirmed_funding: &[FundingOutput],
tracker: &Tracker,
) -> Result<(Vec<FundingOutput>, Vec<SpendingInput>)> { ) -> Result<(Vec<FundingOutput>, Vec<SpendingInput>)> {
let mut funding = vec![]; let mut funding = vec![];
let mut spending = vec![]; let mut spending = vec![];
let tracker = self.tracker.read().unwrap();
let txid_prefixes = txids_by_script_hash(tracker.index(), script_hash); let txid_prefixes = txids_by_script_hash(tracker.index(), script_hash);
for t in self.load_txns_by_prefix(tracker.index(), txid_prefixes)? { for t in self.load_txns_by_prefix(tracker.index(), txid_prefixes)? {
funding.extend(self.find_funding_outputs(&t, script_hash)); funding.extend(self.find_funding_outputs(&t, script_hash));
@ -329,16 +355,30 @@ impl Query {
.chain_err(|| "failed to get confirmed status")?; .chain_err(|| "failed to get confirmed status")?;
timer.observe_duration(); timer.observe_duration();
let tracker = self.tracker.read().unwrap();
let timer = self let timer = self
.duration .duration
.with_label_values(&["mempool_status"]) .with_label_values(&["mempool_status"])
.start_timer(); .start_timer();
let mempool = self let mempool = self
.mempool_status(script_hash, &confirmed.0) .mempool_status(script_hash, &confirmed.0, &tracker)
.chain_err(|| "failed to get mempool status")?; .chain_err(|| "failed to get mempool status")?;
timer.observe_duration(); timer.observe_duration();
Ok(Status { confirmed, mempool }) let mut txn_fees = HashMap::new();
let funding_txn_ids = mempool.0.iter().map(|funding| funding.txn_id);
let spending_txn_ids = mempool.1.iter().map(|spending| spending.txn_id);
for mempool_txid in funding_txn_ids.chain(spending_txn_ids) {
tracker
.get_fee(&mempool_txid)
.map(|fee| txn_fees.insert(mempool_txid, fee));
}
Ok(Status {
confirmed,
mempool,
txn_fees,
})
} }
fn lookup_confirmed_blockhash( fn lookup_confirmed_blockhash(
@ -346,7 +386,7 @@ impl Query {
tx_hash: &Txid, tx_hash: &Txid,
block_height: Option<u32>, block_height: Option<u32>,
) -> Result<Option<BlockHash>> { ) -> Result<Option<BlockHash>> {
let blockhash = if self.tracker.read().unwrap().get_txn(&tx_hash).is_some() { let blockhash = if self.tracker.read().unwrap().has_txn(&tx_hash) {
None // found in mempool (as unconfirmed transaction) None // found in mempool (as unconfirmed transaction)
} else { } else {
// Lookup in confirmed transactions' index // Lookup in confirmed transactions' index

View file

@ -227,7 +227,7 @@ impl Connection {
status status
.history() .history()
.into_iter() .into_iter()
.map(|item| json!({"height": item.0, "tx_hash": item.1.to_hex()})) .map(|item| item.to_json())
.collect() .collect()
))) )))
} }