From 66fd923186e3b54fcc4dcbdb693c81e08c4d14d9 Mon Sep 17 00:00:00 2001 From: Pantamis Date: Sat, 24 Jul 2021 13:10:07 +0200 Subject: [PATCH 1/9] Add outpoint subscribe method --- src/electrum.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++- src/tracker.rs | 4 +++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/electrum.rs b/src/electrum.rs index ba182bb07..5ddc419ff 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Context, Result}; use bitcoin::{ consensus::{deserialize, serialize}, hashes::hex::{FromHex, ToHex}, - BlockHash, Txid, + BlockHash, OutPoint, Transaction, Txid, }; use rayon::prelude::*; use serde_derive::Deserialize; @@ -275,6 +275,78 @@ impl Rpc { Ok(status) } + fn outpoint_subscribe(&self, (txid, vout): (Txid, u32)) -> Result { + let funding = OutPoint { txid, vout }; + + let funding_blockhash = self.tracker.get_blockhash_by_txid(funding.txid); + let spending_blockhash = self.tracker.get_blockhash_spending_by_outpoint(funding); + + let mut inputs_funding_confirmed = true; + + let funding_tx = self.daemon.get_transaction(&funding.txid, funding_blockhash); + if funding_tx.is_err() { + return Ok(json!({})); + } + + if funding_blockhash.is_none() && funding_tx.unwrap().input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { + inputs_funding_confirmed = false; + } + + let funding_height = self.tracker.chain().get_block_height(&funding_blockhash.unwrap()).unwrap(); + + if spending_blockhash.is_none() { + let txids: Vec = self + .daemon + .get_mempool_txids() + .unwrap() + .iter() + .filter_map(|mempool_txid| { + let mempool_tx = self.daemon.get_transaction(mempool_txid, None); + if is_spending(&mempool_tx.unwrap(), funding) { + Some(mempool_txid); + } + None + }) + .collect(); + if txids.len() > 1 { + return Ok(panic!("double spend of {}: {:?}", txid, txids)); + } + if txids.is_empty() { + let spending_tx = self.daemon.get_transaction(&txids[0], spending_blockhash); + if spending_tx.unwrap().input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { + if inputs_funding_confirmed { + return Ok(json!({"height": 0, "spender_txhash": txids[0], "spender_height": -1})); + } + return Ok(json!({"height": -1, "spender_txhash": txids[0], "spender_height": -1})); + } + return Ok(json!({"height": funding_height, "spender_txhash": txids[0], "spender_height": 0})); + } + return Ok(json!({"height": funding_height})); + } + + let spending_height = self.tracker.chain().get_block_height(&spending_blockhash.unwrap()).unwrap(); + + let txids: Vec = self + .daemon + .get_block_txids(spending_blockhash.unwrap()) + .unwrap() + .iter() + .filter_map(|block_txid| { + let block_tx = self.daemon.get_transaction(&block_txid, spending_blockhash); + if is_spending(&block_tx.unwrap(), funding) { + Some(block_txid); + } + None + }) + .collect(); + + if txids.len() > 1 { + return Ok(panic!("double spend of {}: {:?}", txid, txids)); + } + return Ok(json!({"height": funding_height, "spender_txhash": txids[0], "spender_height": spending_height})); + + } + fn transaction_broadcast(&self, (tx_hex,): (String,)) -> Result { let tx_bytes = Vec::from_hex(&tx_hex).context("non-hex transaction")?; let tx = deserialize(&tx_bytes).context("invalid transaction")?; @@ -384,6 +456,7 @@ impl Rpc { Call::Donation => Ok(Value::Null), Call::EstimateFee(args) => self.estimate_fee(args), Call::HeadersSubscribe => self.headers_subscribe(client), + Call::OutpointSubscribe(args) => self.outpoint_subscribe(args), Call::MempoolFeeHistogram => self.get_fee_histogram(), Call::PeersSubscribe => Ok(json!([])), Call::Ping => Ok(Value::Null), @@ -423,6 +496,7 @@ enum Call { EstimateFee((u16,)), HeadersSubscribe, MempoolFeeHistogram, + OutpointSubscribe((Txid, u32)), PeersSubscribe, Ping, RelayFee, @@ -441,6 +515,7 @@ impl Call { "blockchain.block.headers" => Call::BlockHeaders(convert(params)?), "blockchain.estimatefee" => Call::EstimateFee(convert(params)?), "blockchain.headers.subscribe" => Call::HeadersSubscribe, + "blockchain.outpoint.subscribe" => Call::OutpointSubscribe(convert(params)?), "blockchain.relayfee" => Call::RelayFee, "blockchain.scripthash.get_balance" => Call::ScriptHashGetBalance(convert(params)?), "blockchain.scripthash.get_history" => Call::ScriptHashGetHistory(convert(params)?), @@ -484,3 +559,8 @@ fn result_msg(id: Value, result: Value) -> Value { fn error_msg(id: Value, error: RpcError) -> Value { json!({"jsonrpc": "2.0", "id": id, "error": error.to_value()}) } + +fn is_spending(tx: &Transaction, funding: OutPoint) -> bool { + tx.input.iter().any(|txi| txi.previous_output == funding) +} + diff --git a/src/tracker.rs b/src/tracker.rs index 6574b4b11..788118298 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -101,4 +101,8 @@ impl Tracker { // Note: there are two blocks with coinbase transactions having same txid (see BIP-30) self.index.filter_by_txid(txid).next() } + + pub fn get_blockhash_spending_by_outpoint(&self, funding: OutPoint) -> Option { + self.index.filter_by_spending(funding).next() + } } From 6f7a679e05db5b239ea3d83ca796650c488b07cc Mon Sep 17 00:00:00 2001 From: Pantamis Date: Mon, 26 Jul 2021 20:06:20 +0200 Subject: [PATCH 2/9] Change search heuristic for scripthash then block --- src/electrum.rs | 48 +++++++++++++++++++++++++++++------------------- src/status.rs | 8 ++++---- src/tracker.rs | 4 ++++ 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index 5ddc419ff..919fdb2b6 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -284,34 +284,41 @@ impl Rpc { let mut inputs_funding_confirmed = true; let funding_tx = self.daemon.get_transaction(&funding.txid, funding_blockhash); + if funding_tx.is_err() { return Ok(json!({})); } - if funding_blockhash.is_none() && funding_tx.unwrap().input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { + let funding_inputs = &funding_tx.as_ref().unwrap().input; + let outpoint_script = &funding_tx.as_ref().unwrap().output.get(vout as usize).unwrap().script_pubkey; + + if funding_blockhash.is_none() && funding_inputs.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { inputs_funding_confirmed = false; } + + let scripthash = ScriptHash::new(&outpoint_script); + let outpoint_scripthash_status = self.new_status(scripthash); + let funding_height = self.tracker.chain().get_block_height(&funding_blockhash.unwrap()).unwrap(); if spending_blockhash.is_none() { - let txids: Vec = self - .daemon - .get_mempool_txids() - .unwrap() + let txids: Vec = outpoint_scripthash_status.unwrap() + .get_mempool(&(self.tracker).mempool()) .iter() - .filter_map(|mempool_txid| { - let mempool_tx = self.daemon.get_transaction(mempool_txid, None); + .filter_map(|tx_entry| { + let mempool_tx = self.daemon.get_transaction(&tx_entry.txid, None); if is_spending(&mempool_tx.unwrap(), funding) { - Some(mempool_txid); + Some(tx_entry.txid) + } else { + None } - None }) .collect(); if txids.len() > 1 { return Ok(panic!("double spend of {}: {:?}", txid, txids)); } - if txids.is_empty() { + if !txids.is_empty() { let spending_tx = self.daemon.get_transaction(&txids[0], spending_blockhash); if spending_tx.unwrap().input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { if inputs_funding_confirmed { @@ -326,23 +333,26 @@ impl Rpc { let spending_height = self.tracker.chain().get_block_height(&spending_blockhash.unwrap()).unwrap(); - let txids: Vec = self - .daemon - .get_block_txids(spending_blockhash.unwrap()) - .unwrap() + let txids: Vec = outpoint_scripthash_status.unwrap() + .get_confirmed(&self.tracker.chain()) .iter() - .filter_map(|block_txid| { - let block_tx = self.daemon.get_transaction(&block_txid, spending_blockhash); - if is_spending(&block_tx.unwrap(), funding) { - Some(block_txid); + .filter(|confirmed_entry| { + confirmed_entry.height == spending_height + }) + .filter_map(|script_block_entry| { + let script_block_tx = self.daemon.get_transaction(&script_block_entry.txid, spending_blockhash); + if is_spending(&script_block_tx.unwrap(), funding) { + Some(script_block_entry.txid) + } else { + None } - None }) .collect(); if txids.len() > 1 { return Ok(panic!("double spend of {}: {:?}", txid, txids)); } + if txids.is_empty() { return Ok(json!({"WTF": "spending tx not found but exist ?"})); } return Ok(json!({"height": funding_height, "spender_txhash": txids[0], "spender_height": spending_height})); } diff --git a/src/status.rs b/src/status.rs index d2bd588c5..5eb0e5586 100644 --- a/src/status.rs +++ b/src/status.rs @@ -46,8 +46,8 @@ impl TxEntry { } pub(crate) struct ConfirmedEntry { - txid: Txid, - height: usize, + pub txid: Txid, + pub height: usize, } impl ConfirmedEntry { @@ -62,8 +62,8 @@ impl ConfirmedEntry { } pub(crate) struct MempoolEntry { - txid: Txid, - has_unconfirmed_inputs: bool, + pub txid: Txid, + pub has_unconfirmed_inputs: bool, fee: Amount, } diff --git a/src/tracker.rs b/src/tracker.rs index 788118298..2dff02c5a 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -44,6 +44,10 @@ impl Tracker { self.index.chain() } + pub(crate) fn mempool(&self) -> &Mempool { + &self.mempool + } + pub(crate) fn fees_histogram(&self) -> &Histogram { self.mempool.fees_histogram() } From e882b368868010c201ac9bb5990f09ee9208c70a Mon Sep 17 00:00:00 2001 From: Pantamis Date: Sat, 24 Jul 2021 13:10:07 +0200 Subject: [PATCH 3/9] Add outpoint subscribe method --- src/electrum.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++- src/status.rs | 8 ++--- src/tracker.rs | 8 +++++ 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index ba182bb07..919fdb2b6 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Context, Result}; use bitcoin::{ consensus::{deserialize, serialize}, hashes::hex::{FromHex, ToHex}, - BlockHash, Txid, + BlockHash, OutPoint, Transaction, Txid, }; use rayon::prelude::*; use serde_derive::Deserialize; @@ -275,6 +275,88 @@ impl Rpc { Ok(status) } + fn outpoint_subscribe(&self, (txid, vout): (Txid, u32)) -> Result { + let funding = OutPoint { txid, vout }; + + let funding_blockhash = self.tracker.get_blockhash_by_txid(funding.txid); + let spending_blockhash = self.tracker.get_blockhash_spending_by_outpoint(funding); + + let mut inputs_funding_confirmed = true; + + let funding_tx = self.daemon.get_transaction(&funding.txid, funding_blockhash); + + if funding_tx.is_err() { + return Ok(json!({})); + } + + let funding_inputs = &funding_tx.as_ref().unwrap().input; + let outpoint_script = &funding_tx.as_ref().unwrap().output.get(vout as usize).unwrap().script_pubkey; + + if funding_blockhash.is_none() && funding_inputs.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { + inputs_funding_confirmed = false; + } + + + let scripthash = ScriptHash::new(&outpoint_script); + let outpoint_scripthash_status = self.new_status(scripthash); + + let funding_height = self.tracker.chain().get_block_height(&funding_blockhash.unwrap()).unwrap(); + + if spending_blockhash.is_none() { + let txids: Vec = outpoint_scripthash_status.unwrap() + .get_mempool(&(self.tracker).mempool()) + .iter() + .filter_map(|tx_entry| { + let mempool_tx = self.daemon.get_transaction(&tx_entry.txid, None); + if is_spending(&mempool_tx.unwrap(), funding) { + Some(tx_entry.txid) + } else { + None + } + }) + .collect(); + if txids.len() > 1 { + return Ok(panic!("double spend of {}: {:?}", txid, txids)); + } + if !txids.is_empty() { + let spending_tx = self.daemon.get_transaction(&txids[0], spending_blockhash); + if spending_tx.unwrap().input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { + if inputs_funding_confirmed { + return Ok(json!({"height": 0, "spender_txhash": txids[0], "spender_height": -1})); + } + return Ok(json!({"height": -1, "spender_txhash": txids[0], "spender_height": -1})); + } + return Ok(json!({"height": funding_height, "spender_txhash": txids[0], "spender_height": 0})); + } + return Ok(json!({"height": funding_height})); + } + + let spending_height = self.tracker.chain().get_block_height(&spending_blockhash.unwrap()).unwrap(); + + let txids: Vec = outpoint_scripthash_status.unwrap() + .get_confirmed(&self.tracker.chain()) + .iter() + .filter(|confirmed_entry| { + confirmed_entry.height == spending_height + }) + .filter_map(|script_block_entry| { + let script_block_tx = self.daemon.get_transaction(&script_block_entry.txid, spending_blockhash); + if is_spending(&script_block_tx.unwrap(), funding) { + Some(script_block_entry.txid) + } else { + None + } + }) + .collect(); + + if txids.len() > 1 { + return Ok(panic!("double spend of {}: {:?}", txid, txids)); + } + if txids.is_empty() { return Ok(json!({"WTF": "spending tx not found but exist ?"})); } + return Ok(json!({"height": funding_height, "spender_txhash": txids[0], "spender_height": spending_height})); + + } + fn transaction_broadcast(&self, (tx_hex,): (String,)) -> Result { let tx_bytes = Vec::from_hex(&tx_hex).context("non-hex transaction")?; let tx = deserialize(&tx_bytes).context("invalid transaction")?; @@ -384,6 +466,7 @@ impl Rpc { Call::Donation => Ok(Value::Null), Call::EstimateFee(args) => self.estimate_fee(args), Call::HeadersSubscribe => self.headers_subscribe(client), + Call::OutpointSubscribe(args) => self.outpoint_subscribe(args), Call::MempoolFeeHistogram => self.get_fee_histogram(), Call::PeersSubscribe => Ok(json!([])), Call::Ping => Ok(Value::Null), @@ -423,6 +506,7 @@ enum Call { EstimateFee((u16,)), HeadersSubscribe, MempoolFeeHistogram, + OutpointSubscribe((Txid, u32)), PeersSubscribe, Ping, RelayFee, @@ -441,6 +525,7 @@ impl Call { "blockchain.block.headers" => Call::BlockHeaders(convert(params)?), "blockchain.estimatefee" => Call::EstimateFee(convert(params)?), "blockchain.headers.subscribe" => Call::HeadersSubscribe, + "blockchain.outpoint.subscribe" => Call::OutpointSubscribe(convert(params)?), "blockchain.relayfee" => Call::RelayFee, "blockchain.scripthash.get_balance" => Call::ScriptHashGetBalance(convert(params)?), "blockchain.scripthash.get_history" => Call::ScriptHashGetHistory(convert(params)?), @@ -484,3 +569,8 @@ fn result_msg(id: Value, result: Value) -> Value { fn error_msg(id: Value, error: RpcError) -> Value { json!({"jsonrpc": "2.0", "id": id, "error": error.to_value()}) } + +fn is_spending(tx: &Transaction, funding: OutPoint) -> bool { + tx.input.iter().any(|txi| txi.previous_output == funding) +} + diff --git a/src/status.rs b/src/status.rs index d2bd588c5..5eb0e5586 100644 --- a/src/status.rs +++ b/src/status.rs @@ -46,8 +46,8 @@ impl TxEntry { } pub(crate) struct ConfirmedEntry { - txid: Txid, - height: usize, + pub txid: Txid, + pub height: usize, } impl ConfirmedEntry { @@ -62,8 +62,8 @@ impl ConfirmedEntry { } pub(crate) struct MempoolEntry { - txid: Txid, - has_unconfirmed_inputs: bool, + pub txid: Txid, + pub has_unconfirmed_inputs: bool, fee: Amount, } diff --git a/src/tracker.rs b/src/tracker.rs index 6574b4b11..2dff02c5a 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -44,6 +44,10 @@ impl Tracker { self.index.chain() } + pub(crate) fn mempool(&self) -> &Mempool { + &self.mempool + } + pub(crate) fn fees_histogram(&self) -> &Histogram { self.mempool.fees_histogram() } @@ -101,4 +105,8 @@ impl Tracker { // Note: there are two blocks with coinbase transactions having same txid (see BIP-30) self.index.filter_by_txid(txid).next() } + + pub fn get_blockhash_spending_by_outpoint(&self, funding: OutPoint) -> Option { + self.index.filter_by_spending(funding).next() + } } From 0751b34a54a44512e9144b8fab36926f5ab9e69e Mon Sep 17 00:00:00 2001 From: Pantamis Date: Mon, 26 Jul 2021 22:42:09 +0200 Subject: [PATCH 4/9] remove parenthesis --- src/electrum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electrum.rs b/src/electrum.rs index 919fdb2b6..442a75606 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -304,7 +304,7 @@ impl Rpc { if spending_blockhash.is_none() { let txids: Vec = outpoint_scripthash_status.unwrap() - .get_mempool(&(self.tracker).mempool()) + .get_mempool(&self.tracker.mempool()) .iter() .filter_map(|tx_entry| { let mempool_tx = self.daemon.get_transaction(&tx_entry.txid, None); From 245390d233819d4663329c4772847ed0644b487a Mon Sep 17 00:00:00 2001 From: Pantamis Date: Mon, 26 Jul 2021 22:42:09 +0200 Subject: [PATCH 5/9] limiting search to spending block --- src/electrum.rs | 69 +++++++++++++++++-------------------------------- src/status.rs | 8 +++--- 2 files changed, 27 insertions(+), 50 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index 919fdb2b6..74e671aae 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -16,6 +16,7 @@ use crate::{ config::Config, daemon::{self, extract_bitcoind_error, Daemon}, merkle::Proof, + mempool::Entry, metrics::Histogram, status::Status, tracker::Tracker, @@ -283,71 +284,47 @@ impl Rpc { let mut inputs_funding_confirmed = true; - let funding_tx = self.daemon.get_transaction(&funding.txid, funding_blockhash); + let funding_tx = self.daemon.get_transaction(&funding.txid, funding_blockhash)?; - if funding_tx.is_err() { - return Ok(json!({})); - } - - let funding_inputs = &funding_tx.as_ref().unwrap().input; - let outpoint_script = &funding_tx.as_ref().unwrap().output.get(vout as usize).unwrap().script_pubkey; + let funding_inputs = &funding_tx.input; if funding_blockhash.is_none() && funding_inputs.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { inputs_funding_confirmed = false; } - - let scripthash = ScriptHash::new(&outpoint_script); - let outpoint_scripthash_status = self.new_status(scripthash); - - let funding_height = self.tracker.chain().get_block_height(&funding_blockhash.unwrap()).unwrap(); + let funding_height = match &funding_blockhash { + Some(funding_blockhash) => self.tracker.chain().get_block_height(funding_blockhash).ok_or_else(|| anyhow::anyhow!("Blockhash not found"))?, + None => 0, + }; if spending_blockhash.is_none() { - let txids: Vec = outpoint_scripthash_status.unwrap() - .get_mempool(&(self.tracker).mempool()) - .iter() - .filter_map(|tx_entry| { - let mempool_tx = self.daemon.get_transaction(&tx_entry.txid, None); - if is_spending(&mempool_tx.unwrap(), funding) { - Some(tx_entry.txid) - } else { - None - } - }) - .collect(); - if txids.len() > 1 { - return Ok(panic!("double spend of {}: {:?}", txid, txids)); + let spending_entries: Vec<&Entry> = self.tracker.mempool().filter_by_spending(&funding); + if spending_entries.len() > 1 { + return Ok(panic!("double spend of {}", txid)); } - if !txids.is_empty() { - let spending_tx = self.daemon.get_transaction(&txids[0], spending_blockhash); - if spending_tx.unwrap().input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { + if !spending_entries.is_empty() { + let spending_tx = self.daemon.get_transaction(&spending_entries[0].txid, None)?; + if spending_tx.input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { if inputs_funding_confirmed { - return Ok(json!({"height": 0, "spender_txhash": txids[0], "spender_height": -1})); + return Ok(json!({"height": 0, "spender_txhash": spending_entries[0].txid, "spender_height": -1})); } - return Ok(json!({"height": -1, "spender_txhash": txids[0], "spender_height": -1})); + return Ok(json!({"height": -1, "spender_txhash": spending_entries[0].txid, "spender_height": -1})); } - return Ok(json!({"height": funding_height, "spender_txhash": txids[0], "spender_height": 0})); + return Ok(json!({"height": funding_height, "spender_txhash": spending_entries[0].txid, "spender_height": 0})); } return Ok(json!({"height": funding_height})); } let spending_height = self.tracker.chain().get_block_height(&spending_blockhash.unwrap()).unwrap(); - let txids: Vec = outpoint_scripthash_status.unwrap() - .get_confirmed(&self.tracker.chain()) - .iter() - .filter(|confirmed_entry| { - confirmed_entry.height == spending_height - }) - .filter_map(|script_block_entry| { - let script_block_tx = self.daemon.get_transaction(&script_block_entry.txid, spending_blockhash); - if is_spending(&script_block_tx.unwrap(), funding) { - Some(script_block_entry.txid) - } else { - None + let mut txids: Vec = Vec::new(); + self.daemon.for_blocks(spending_blockhash.into_iter(), |_, block| { + for tx in block.txdata.into_iter() { + if is_spending(&tx, funding) { + txids.push(tx.txid()) } - }) - .collect(); + } + })?; if txids.len() > 1 { return Ok(panic!("double spend of {}: {:?}", txid, txids)); diff --git a/src/status.rs b/src/status.rs index 5eb0e5586..d2bd588c5 100644 --- a/src/status.rs +++ b/src/status.rs @@ -46,8 +46,8 @@ impl TxEntry { } pub(crate) struct ConfirmedEntry { - pub txid: Txid, - pub height: usize, + txid: Txid, + height: usize, } impl ConfirmedEntry { @@ -62,8 +62,8 @@ impl ConfirmedEntry { } pub(crate) struct MempoolEntry { - pub txid: Txid, - pub has_unconfirmed_inputs: bool, + txid: Txid, + has_unconfirmed_inputs: bool, fee: Amount, } From 857c451db8f4039c6a5ff2ffa7802c771a5dd49c Mon Sep 17 00:00:00 2001 From: Pantamis Date: Wed, 28 Jul 2021 20:34:05 +0200 Subject: [PATCH 6/9] More idiomatic code --- src/electrum.rs | 82 ++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index 74e671aae..4b0d8929f 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -10,13 +10,12 @@ use serde_json::{self, json, Value}; use std::collections::HashMap; use std::iter::FromIterator; - + use crate::{ cache::Cache, config::Config, daemon::{self, extract_bitcoind_error, Daemon}, merkle::Proof, - mempool::Entry, metrics::Histogram, status::Status, tracker::Tracker, @@ -282,14 +281,17 @@ impl Rpc { let funding_blockhash = self.tracker.get_blockhash_by_txid(funding.txid); let spending_blockhash = self.tracker.get_blockhash_spending_by_outpoint(funding); - let mut inputs_funding_confirmed = true; + let mut funding_inputs_confirmed = true; - let funding_tx = self.daemon.get_transaction(&funding.txid, funding_blockhash)?; + let funding_tx = match self.daemon.get_transaction(&funding.txid, funding_blockhash) { + Ok(tx) => tx, + Err(_) => return Ok(json!({})), + }; let funding_inputs = &funding_tx.input; if funding_blockhash.is_none() && funding_inputs.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { - inputs_funding_confirmed = false; + funding_inputs_confirmed = false; } let funding_height = match &funding_blockhash { @@ -297,41 +299,45 @@ impl Rpc { None => 0, }; - if spending_blockhash.is_none() { - let spending_entries: Vec<&Entry> = self.tracker.mempool().filter_by_spending(&funding); - if spending_entries.len() > 1 { - return Ok(panic!("double spend of {}", txid)); - } - if !spending_entries.is_empty() { - let spending_tx = self.daemon.get_transaction(&spending_entries[0].txid, None)?; - if spending_tx.input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { - if inputs_funding_confirmed { - return Ok(json!({"height": 0, "spender_txhash": spending_entries[0].txid, "spender_height": -1})); + let tx_candidates: Vec = match spending_blockhash { + None => self.tracker.mempool().filter_by_spending(&funding).iter().map(|e| e.txid).collect(), + Some(spending_blockhash) => { + let mut txids: Vec = Vec::new(); + self.daemon.for_blocks(Some(spending_blockhash).into_iter(), |_, block| { + for tx in block.txdata.into_iter() { + if is_spending(&tx, funding) { + txids.push(tx.txid()) + } } - return Ok(json!({"height": -1, "spender_txhash": spending_entries[0].txid, "spender_height": -1})); - } - return Ok(json!({"height": funding_height, "spender_txhash": spending_entries[0].txid, "spender_height": 0})); - } - return Ok(json!({"height": funding_height})); - } - - let spending_height = self.tracker.chain().get_block_height(&spending_blockhash.unwrap()).unwrap(); - - let mut txids: Vec = Vec::new(); - self.daemon.for_blocks(spending_blockhash.into_iter(), |_, block| { - for tx in block.txdata.into_iter() { - if is_spending(&tx, funding) { - txids.push(tx.txid()) + })?; + txids + }, + }; + + let mut spender_txids = tx_candidates.iter(); + + match spender_txids.next() { + Some(spender_txid) => if let Some(double_spending_txid) = spender_txids.next() { + return bail!("double spend of {}: {}", spender_txid, double_spending_txid) + } else { + match spending_blockhash { + Some(spending_blockhash) => { + let spending_height = self.tracker.chain().get_block_height(&spending_blockhash).ok_or_else(|| anyhow::anyhow!("Blockhash not found"))?; + return Ok(json!({"height": funding_height, "spender_txhash": spender_txid, "spender_height": spending_height})) + } + None => { + let spending_tx = self.daemon.get_transaction(&spender_txid, None)?; + if spending_tx.input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { + if funding_inputs_confirmed { + return Ok(json!({"height": funding_height, "spender_txhash": spender_txid, "spender_height": -1})); + } + return Ok(json!({"height": -1, "spender_txhash": spender_txid, "spender_height": -1})); + } else {return Ok(json!({"height": funding_height, "spender_txhash": spender_txid, "spender_height": 0}));} + } } - } - })?; - - if txids.len() > 1 { - return Ok(panic!("double spend of {}: {:?}", txid, txids)); - } - if txids.is_empty() { return Ok(json!({"WTF": "spending tx not found but exist ?"})); } - return Ok(json!({"height": funding_height, "spender_txhash": txids[0], "spender_height": spending_height})); - + }, + None => return Ok(json!({"height": funding_height})), + }; } fn transaction_broadcast(&self, (tx_hex,): (String,)) -> Result { From 6351ef768586688582d1a1a45ad6627c34a6ef39 Mon Sep 17 00:00:00 2001 From: Pantamis Date: Wed, 28 Jul 2021 22:15:34 +0200 Subject: [PATCH 7/9] Clean up unused stuff for lower diff --- src/electrum.rs | 2 +- src/status.rs | 8 ++++---- src/tracker.rs | 4 ---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index 4b0d8929f..7a65105ea 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -10,7 +10,7 @@ use serde_json::{self, json, Value}; use std::collections::HashMap; use std::iter::FromIterator; - + use crate::{ cache::Cache, config::Config, diff --git a/src/status.rs b/src/status.rs index 5eb0e5586..d2bd588c5 100644 --- a/src/status.rs +++ b/src/status.rs @@ -46,8 +46,8 @@ impl TxEntry { } pub(crate) struct ConfirmedEntry { - pub txid: Txid, - pub height: usize, + txid: Txid, + height: usize, } impl ConfirmedEntry { @@ -62,8 +62,8 @@ impl ConfirmedEntry { } pub(crate) struct MempoolEntry { - pub txid: Txid, - pub has_unconfirmed_inputs: bool, + txid: Txid, + has_unconfirmed_inputs: bool, fee: Amount, } diff --git a/src/tracker.rs b/src/tracker.rs index 2dff02c5a..6bbe2884d 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -105,8 +105,4 @@ impl Tracker { // Note: there are two blocks with coinbase transactions having same txid (see BIP-30) self.index.filter_by_txid(txid).next() } - - pub fn get_blockhash_spending_by_outpoint(&self, funding: OutPoint) -> Option { - self.index.filter_by_spending(funding).next() - } } From f927f676562c228d448e3e4bd8c50019ad79cd54 Mon Sep 17 00:00:00 2001 From: Pantamis Date: Thu, 29 Jul 2021 01:14:04 +0200 Subject: [PATCH 8/9] improve the great match --- src/electrum.rs | 60 ++++++++++++++++++++++++------------------------- src/tracker.rs | 4 ++++ 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/electrum.rs b/src/electrum.rs index 7a65105ea..8fb9e804f 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -281,19 +281,11 @@ impl Rpc { let funding_blockhash = self.tracker.get_blockhash_by_txid(funding.txid); let spending_blockhash = self.tracker.get_blockhash_spending_by_outpoint(funding); - let mut funding_inputs_confirmed = true; - let funding_tx = match self.daemon.get_transaction(&funding.txid, funding_blockhash) { Ok(tx) => tx, Err(_) => return Ok(json!({})), }; - let funding_inputs = &funding_tx.input; - - if funding_blockhash.is_none() && funding_inputs.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { - funding_inputs_confirmed = false; - } - let funding_height = match &funding_blockhash { Some(funding_blockhash) => self.tracker.chain().get_block_height(funding_blockhash).ok_or_else(|| anyhow::anyhow!("Blockhash not found"))?, None => 0, @@ -304,11 +296,8 @@ impl Rpc { Some(spending_blockhash) => { let mut txids: Vec = Vec::new(); self.daemon.for_blocks(Some(spending_blockhash).into_iter(), |_, block| { - for tx in block.txdata.into_iter() { - if is_spending(&tx, funding) { - txids.push(tx.txid()) - } - } + let iter = block.txdata.into_iter().filter(|tx| is_spending(&tx, funding)).map(|tx| tx.txid()); + txids.extend(iter); })?; txids }, @@ -316,27 +305,36 @@ impl Rpc { let mut spender_txids = tx_candidates.iter(); - match spender_txids.next() { - Some(spender_txid) => if let Some(double_spending_txid) = spender_txids.next() { - return bail!("double spend of {}: {}", spender_txid, double_spending_txid) + let spender_txid = spender_txids.next(); + let double_spending_txid = spender_txids.next(); // slice-based operator is fused, so this is OK + + let funding_inputs_confirmed = !(funding_blockhash.is_none() && funding_inputs.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none())); + match (spender_txid, double_spending_txid, spending_blockhash) { + (Some(spender_txid), Some(double_spending_txid), _) => bail!("double spend of {}: {}", spender_txid, double_spending_txid), + (None, _, Some(spending_blockhash)) => bail!("Spending transaction {} wrongly indexed in block {}", funding.txid, spending_blockhash), + (Some(spender_txid), None, Some(spending_blockhash)) => { + let spending_height = self.tracker.chain().get_block_height(&spending_blockhash).ok_or_else(|| anyhow::anyhow!("Blockhash not found"))?; + return Ok(json!({"height": funding_height, "spender_txhash": spender_txid, "spender_height": spending_height})); + }, + (Some(spender_txid), None, None) => { + let spending_tx = self.daemon.get_transaction(&spender_txid, None)?; + if funding_inputs_confirmed { + if spending_tx.input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { + return Ok(json!({"height": funding_height, "spender_txhash": spender_txid, "spender_height": -1})); + } else { + return Ok(json!({"height": funding_height, "spender_txhash": spender_txid, "spender_height": 0})); + } } else { - match spending_blockhash { - Some(spending_blockhash) => { - let spending_height = self.tracker.chain().get_block_height(&spending_blockhash).ok_or_else(|| anyhow::anyhow!("Blockhash not found"))?; - return Ok(json!({"height": funding_height, "spender_txhash": spender_txid, "spender_height": spending_height})) - } - None => { - let spending_tx = self.daemon.get_transaction(&spender_txid, None)?; - if spending_tx.input.iter().any(|txi| self.tracker.get_blockhash_by_txid(txi.previous_output.txid).is_none()) { - if funding_inputs_confirmed { - return Ok(json!({"height": funding_height, "spender_txhash": spender_txid, "spender_height": -1})); - } - return Ok(json!({"height": -1, "spender_txhash": spender_txid, "spender_height": -1})); - } else {return Ok(json!({"height": funding_height, "spender_txhash": spender_txid, "spender_height": 0}));} - } + return Ok(json!({"height": -1, "spender_txhash": spender_txid, "spender_height": -1})); } }, - None => return Ok(json!({"height": funding_height})), + (None, _, None) => { + if funding_inputs_confirmed { + return Ok(json!({"height": funding_height})); + } else { + return Ok(json!({"height": -1})); + } + }, }; } diff --git a/src/tracker.rs b/src/tracker.rs index 6bbe2884d..2dff02c5a 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -105,4 +105,8 @@ impl Tracker { // Note: there are two blocks with coinbase transactions having same txid (see BIP-30) self.index.filter_by_txid(txid).next() } + + pub fn get_blockhash_spending_by_outpoint(&self, funding: OutPoint) -> Option { + self.index.filter_by_spending(funding).next() + } } From 9b63419f0ccd9da65bdbc150542f23569fbb2346 Mon Sep 17 00:00:00 2001 From: Pantamis Date: Thu, 29 Jul 2021 15:11:07 +0200 Subject: [PATCH 9/9] handle not existing transaction error --- src/electrum.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/electrum.rs b/src/electrum.rs index 8fb9e804f..4b16d5cf8 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -4,6 +4,7 @@ use bitcoin::{ hashes::hex::{FromHex, ToHex}, BlockHash, OutPoint, Transaction, Txid, }; +use bitcoincore_rpc::jsonrpc; use rayon::prelude::*; use serde_derive::Deserialize; use serde_json::{self, json, Value}; @@ -283,7 +284,12 @@ impl Rpc { let funding_tx = match self.daemon.get_transaction(&funding.txid, funding_blockhash) { Ok(tx) => tx, - Err(_) => return Ok(json!({})), + Err(error) => { + match error.downcast_ref::() { + Some(bitcoincore_rpc::Error::JsonRpc(jsonrpc::Error::Rpc(jsonrpc::error::RpcError { code: -5, .. }))) => return Ok(json!({})), + _ => return Err(error), + } + }, }; let funding_inputs = &funding_tx.input; let funding_height = match &funding_blockhash {