Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ rand = "^0.7"

# Optional dependencies
sled = { version = "0.34", optional = true }
electrum-client = { version = "0.10", optional = true }
electrum-client = { git = "https://github.com/tnull/rust-electrum-client", branch = "TheCharlatan-verboseTransactionGet-rebased", optional = true}
rusqlite = { version = "0.27.0", optional = true }
ahash = { version = "0.7.6", optional = true }
reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] }
Expand Down
7 changes: 7 additions & 0 deletions src/blockchain/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ impl GetTx for AnyBlockchain {
}
}

#[maybe_async]
impl GetTxStatus for AnyBlockchain {
fn get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, Error> {
maybe_await!(impl_inner_method!(self, get_tx_status, txid))
}
}

#[maybe_async]
impl GetBlockHash for AnyBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
Expand Down
36 changes: 36 additions & 0 deletions src/blockchain/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,42 @@ impl GetTx for ElectrumBlockchain {
}
}

impl GetTxStatus for ElectrumBlockchain {
fn get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, Error> {
let tx_verbose_res = self.client.transaction_get_verbose(txid)?;
if let Some(confirmations) = tx_verbose_res.confirmations {
if confirmations > 0 {
// TODO: this is unfortunately race-y since a new block might have come in between the
// first and second request.
let cur_height = self
.client
.block_headers_subscribe()
.map(|data| data.height as u32)?;

// Calculate the confirmation block height. If a transaction has one confirmation, it was confirmed in
// the current tip.
let block_height = cur_height + 1 - confirmations;

return Ok(Some(TxStatus {
confirmed: true,
block_height: Some(block_height),
block_hash: tx_verbose_res.blockhash,
block_time: tx_verbose_res.blocktime.map(|t| t as u64),
}));
} else {
return Ok(Some(TxStatus {
confirmed: false,
block_height: None,
block_hash: None,
block_time: None,
}));
}
}

Ok(None)
}
}

impl GetBlockHash for ElectrumBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
let block_header = self.client.block_header(height as usize)?;
Expand Down
12 changes: 4 additions & 8 deletions src/blockchain/esplora/api.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! structs from the esplora API
//!
//! see: <https://github.com/Blockstream/esplora/blob/master/API.md>
use crate::blockchain::TxStatus;
use crate::BlockTime;
use bitcoin::{OutPoint, Script, Transaction, TxIn, TxOut, Txid, Witness};
use bitcoin::{BlockHash, OutPoint, Script, Transaction, TxIn, TxOut, Txid, Witness};
use std::convert::From;

#[derive(serde::Deserialize, Clone, Debug)]
pub struct PrevOut {
Expand All @@ -29,13 +31,6 @@ pub struct Vout {
pub scriptpubkey: Script,
}

#[derive(serde::Deserialize, Clone, Debug)]
pub struct TxStatus {
pub confirmed: bool,
pub block_height: Option<u32>,
pub block_time: Option<u64>,
}

#[derive(serde::Deserialize, Clone, Debug)]
pub struct Tx {
pub txid: Txid,
Expand Down Expand Up @@ -84,6 +79,7 @@ impl Tx {
confirmed: true,
block_height: Some(height),
block_time: Some(timestamp),
..
} => Some(BlockTime { timestamp, height }),
_ => None,
}
Expand Down
21 changes: 21 additions & 0 deletions src/blockchain/esplora/reqwest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ impl GetTx for EsploraBlockchain {
}
}

#[maybe_async]
impl GetTxStatus for EsploraBlockchain {
fn get_tx_status(&self, txid: &Txid) -> Result<TxStatus, Error> {
Ok(await_or_block!(self.url_client._get_tx_status(txid))?)
}
}

#[maybe_async]
impl GetBlockHash for EsploraBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
Expand Down Expand Up @@ -232,6 +239,20 @@ impl UrlClient {
Ok(Some(deserialize(&resp.error_for_status()?.bytes().await?)?))
}

async fn _get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, EsploraError> {
let resp = self
.client
.get(&format!("{}/tx/{}/status", self.url, txid))
.send()
.await?;

if let StatusCode::NOT_FOUND = resp.status() {
return Ok(None);
}

Ok(Some(resp.error_for_status()?.json().await?))
}

async fn _get_tx_no_opt(&self, txid: &Txid) -> Result<Transaction, EsploraError> {
match self._get_tx(txid).await {
Ok(Some(tx)) => Ok(tx),
Expand Down
26 changes: 25 additions & 1 deletion src/blockchain/esplora/ureq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::hashes::{sha256, Hash};
use bitcoin::{BlockHeader, Script, Transaction, Txid};

use super::api::Tx;
use super::api::{Tx, TxStatus};
use crate::blockchain::esplora::EsploraError;
use crate::blockchain::*;
use crate::database::BatchDatabase;
Expand Down Expand Up @@ -112,6 +112,12 @@ impl GetTx for EsploraBlockchain {
}
}

impl GetTxStatus for EsploraBlockchain {
fn get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, Error> {
Ok(self.url_client._get_tx_status(txid)?)
}
}

impl GetBlockHash for EsploraBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
let block_header = self.url_client._get_header(height as u32)?;
Expand Down Expand Up @@ -235,6 +241,24 @@ impl UrlClient {
}
}

fn _get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, EsploraError> {
let resp = self
.agent
.get(&format!("{}/tx/{}/status", self.url, txid))
.call();

match resp {
Ok(resp) => Ok(Some(resp.into_json()?)),
Err(ureq::Error::Status(code, _)) => {
if is_status_not_found(code) {
return Ok(None);
}
Err(EsploraError::HttpResponse(code))
}
Err(e) => Err(EsploraError::Ureq(e)),
}
}

fn _get_tx_no_opt(&self, txid: &Txid) -> Result<Transaction, EsploraError> {
match self._get_tx(txid) {
Ok(Some(tx)) => Ok(tx),
Expand Down
24 changes: 24 additions & 0 deletions src/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ pub trait GetTx {
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
}

#[maybe_async]
/// Trait for getting the status of a transaction by txid
pub trait GetTxStatus {
/// Fetch the status of a transaction given its txid
fn get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, Error>;
}

/// The confirmation status of a transaction
#[derive(serde::Deserialize, Clone, Debug, PartialEq)]
pub struct TxStatus {
confirmed: bool,
block_height: Option<u32>,
block_hash: Option<BlockHash>,
pub block_time: Option<u64>,
}
}

#[maybe_async]
/// Trait for getting block hash by block height
pub trait GetBlockHash {
Expand Down Expand Up @@ -359,6 +376,13 @@ impl<T: GetTx> GetTx for Arc<T> {
}
}

#[maybe_async]
impl<T: GetTxStatus> GetTxStatus for Arc<T> {
fn get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, Error> {
maybe_await!(self.deref().get_tx_status(txid))
}
}

#[maybe_async]
impl<T: GetHeight> GetHeight for Arc<T> {
fn get_height(&self) -> Result<u32, Error> {
Expand Down
30 changes: 30 additions & 0 deletions src/blockchain/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,36 @@ impl GetTx for RpcBlockchain {
}
}

impl GetTxStatus for RpcBlockchain {
fn get_tx_status(&self, txid: &Txid) -> Result<Option<TxStatus>, Error> {
let tx_info = self.client.get_raw_transaction_info(txid, None)?;
if let Some(confirmations) = tx_info.confirmations {
if confirmations > 0 {
// TODO: this is unfortunately race-y since a new block might have come in between the
// first and second request.
let cur_height = self.client.get_blockchain_info().map(|i| i.blocks as u32)?;

// Calculate the confirmation block height. If a transaction has one confirmation, it was confirmed in
// the current tip.
let block_height = cur_height + 1 - confirmations;

return Ok(Some(TxStatus {
confirmed: true,
block_hash: tx_info.blockhash,
block_height: Some(block_height),
block_time: tx_info.blocktime.map(|t| t as u64),
}));
}
}
return Ok(Some(TxStatus {
confirmed: false,
block_height: None,
block_hash: None,
block_time: None,
}));
}
}

impl GetHeight for RpcBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
Expand Down