Skip to content

Commit

Permalink
Add outpoint subscribe method
Browse files Browse the repository at this point in the history
  • Loading branch information
Pantamis committed Jul 26, 2021
1 parent 8be997f commit e882b36
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 5 deletions.
92 changes: 91 additions & 1 deletion src/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -275,6 +275,88 @@ impl Rpc {
Ok(status)
}

fn outpoint_subscribe(&self, (txid, vout): (Txid, u32)) -> Result<Value> {
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<Txid> = 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<Txid> = 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<Value> {
let tx_bytes = Vec::from_hex(&tx_hex).context("non-hex transaction")?;
let tx = deserialize(&tx_bytes).context("invalid transaction")?;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -423,6 +506,7 @@ enum Call {
EstimateFee((u16,)),
HeadersSubscribe,
MempoolFeeHistogram,
OutpointSubscribe((Txid, u32)),
PeersSubscribe,
Ping,
RelayFee,
Expand All @@ -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)?),
Expand Down Expand Up @@ -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)
}

8 changes: 4 additions & 4 deletions src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ impl TxEntry {
}

pub(crate) struct ConfirmedEntry {
txid: Txid,
height: usize,
pub txid: Txid,
pub height: usize,
}

impl ConfirmedEntry {
Expand All @@ -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,
}

Expand Down
8 changes: 8 additions & 0 deletions src/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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<BlockHash> {
self.index.filter_by_spending(funding).next()
}
}

0 comments on commit e882b36

Please sign in to comment.