Skip to content
Draft
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
64 changes: 19 additions & 45 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,13 @@ use serde::Deserialize;
use std::collections::HashMap;

pub use bitcoin::consensus::{deserialize, serialize};
use bitcoin::hash_types::TxMerkleNode;
pub use bitcoin::hex::FromHex;
pub use bitcoin::{
absolute, block, transaction, Address, Amount, Block, BlockHash, CompactTarget, FeeRate,
OutPoint, Script, ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid, Weight, Witness,
Wtxid,
};

/// Information about a previous output.
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct PrevOut {
/// The value of the previous output, in satoshis.
pub value: u64,
/// The ScriptPubKey that the previous output is locked to, as a [`ScriptBuf`].
pub scriptpubkey: ScriptBuf,
}

/// Information about an input from a [`Transaction`].
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Vin {
Expand All @@ -44,7 +34,7 @@ pub struct Vin {
pub vout: u32,
/// The previous output amount and ScriptPubKey.
/// `None` if this is a coinbase input.
pub prevout: Option<PrevOut>,
pub prevout: Option<Vout>,
/// The ScriptSig authorizes spending this input.
pub scriptsig: ScriptBuf,
/// The Witness that authorizes spending this input, if this is a SegWit spend.
Expand All @@ -60,7 +50,8 @@ pub struct Vin {
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Vout {
/// The value of the output, in satoshis.
pub value: u64,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub value: Amount,
/// The ScriptPubKey that the output is locked to, as a [`ScriptBuf`].
pub scriptpubkey: ScriptBuf,
}
Expand Down Expand Up @@ -135,11 +126,12 @@ pub struct Tx {
/// The [`Transaction`] size in raw bytes (NOT virtual bytes).
pub size: usize,
/// The [`Transaction`]'s weight units.
pub weight: u64,
pub weight: Weight,
/// The confirmation status of the [`Transaction`].
pub status: TxStatus,
/// The fee amount paid by the [`Transaction`], in satoshis.
pub fee: u64,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub fee: Amount,
}

/// Information about a bitcoin [`Block`].
Expand Down Expand Up @@ -204,20 +196,6 @@ pub struct BlockTime {
pub height: u32,
}

/// Summary about a [`Block`].
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct BlockSummary {
/// The [`Block`]'s hash.
pub id: BlockHash,
/// The [`Block`]'s timestamp and height.
#[serde(flatten)]
pub time: BlockTime,
/// The [`BlockHash`] of the previous [`Block`] (`None` for the genesis [`Block`]).
pub previousblockhash: Option<BlockHash>,
/// The Merkle root of the [`Block`]'s [`Transaction`]s.
pub merkle_root: TxMerkleNode,
}

/// Statistics about an [`Address`].
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct AddressStats {
Expand All @@ -235,11 +213,13 @@ pub struct AddressTxsSummary {
/// The number of funded [`TxOut`]s.
pub funded_txo_count: u32,
/// The sum of the funded [`TxOut`]s, in satoshis.
pub funded_txo_sum: u64,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub funded_txo_sum: Amount,
/// The number of spent [`TxOut`]s.
pub spent_txo_count: u32,
/// The sum of the spent [`TxOut`]s, in satoshis.
pub spent_txo_sum: u64,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub spent_txo_sum: Amount,
/// The total number of [`Transaction`]s.
pub tx_count: u32,
}
Expand Down Expand Up @@ -280,6 +260,7 @@ pub struct Utxo {
/// The confirmation status of the [`TxOut`].
pub status: UtxoStatus,
/// The value of the [`TxOut`] as an [`Amount`].
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub value: Amount,
}

Expand All @@ -291,7 +272,8 @@ pub struct MempoolStats {
/// The total size of mempool [`Transaction`]s, in virtual bytes.
pub vsize: usize,
/// The total fee paid by mempool [`Transaction`]s, in satoshis.
pub total_fee: u64,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub total_fee: Amount,
/// The mempool's fee rate distribution histogram.
///
/// An array of `(feerate, vsize)` tuples, where each entry's `vsize` is the total vsize
Expand All @@ -306,11 +288,13 @@ pub struct MempoolRecentTx {
/// The [`Transaction`]'s ID, as a [`Txid`].
pub txid: Txid,
/// The [`Amount`] of fees paid by the transaction, in satoshis.
pub fee: u64,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub fee: Amount,
/// The [`Transaction`]'s size, in virtual bytes.
pub vsize: usize,
/// Combined [`Amount`] of the [`Transaction`], in satoshis.
pub value: u64,
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub value: Amount,
}

/// The result for a broadcasted package of [`Transaction`]s.
Expand Down Expand Up @@ -394,7 +378,7 @@ impl Tx {
.iter()
.cloned()
.map(|vout| TxOut {
value: Amount::from_sat(vout.value),
value: vout.value,
script_pubkey: vout.scriptpubkey,
})
.collect(),
Expand Down Expand Up @@ -422,21 +406,11 @@ impl Tx {
.map(|vin| {
vin.prevout.map(|po| TxOut {
script_pubkey: po.scriptpubkey,
value: Amount::from_sat(po.value),
value: po.value,
})
})
.collect()
}

/// Get the weight of a [`Tx`].
pub fn weight(&self) -> Weight {
Weight::from_wu(self.weight)
}

/// Get the fee paid by a [`Tx`].
pub fn fee(&self) -> Amount {
Amount::from_sat(self.fee)
}
}

fn deserialize_witness<'de, D>(d: D) -> Result<Vec<Vec<u8>>, D::Error>
Expand Down
20 changes: 10 additions & 10 deletions src/async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ use log::{debug, error, info, trace};
use reqwest::{header, Body, Client, Response};

use crate::{
AddressStats, BlockInfo, BlockStatus, BlockSummary, Builder, Error, MempoolRecentTx,
MempoolStats, MerkleProof, OutputStatus, ScriptHashStats, SubmitPackageResult, Tx, TxStatus,
Utxo, BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
AddressStats, BlockInfo, BlockStatus, Builder, Error, MempoolRecentTx, MempoolStats,
MerkleProof, OutputStatus, ScriptHashStats, SubmitPackageResult, Tx, TxStatus, Utxo,
BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
};

/// An async client for interacting with an Esplora API server.
Expand Down Expand Up @@ -477,11 +477,11 @@ impl<S: Sleeper> AsyncClient<S> {
self.get_response_json(&path).await
}

/// Get transaction history for the specified address/scripthash,
/// sorted with newest first. Returns 25 transactions per page.
/// More can be requested by specifying the last txid seen by the previous
/// query.
pub async fn scripthash_txs(
/// Get transaction history for the specified [`Script`] hash,
/// sorted by newest first. Returns 25 transactions per page.
/// More can be requested by specifying
/// the last [`Txid`] seen by the previous query.
pub async fn get_scripthash_txs(
&self,
script: &Script,
last_seen: Option<Txid>,
Expand Down Expand Up @@ -564,12 +564,12 @@ impl<S: Sleeper> AsyncClient<S> {
///
/// The maximum number of summaries returned depends on the backend itself:
/// esplora returns `10` while [mempool.space](https://mempool.space/docs/api) returns `15`.
pub async fn get_blocks(&self, height: Option<u32>) -> Result<Vec<BlockSummary>, Error> {
pub async fn get_blocks(&self, height: Option<u32>) -> Result<Vec<BlockInfo>, Error> {
let path = match height {
Some(height) => format!("/blocks/{height}"),
None => "/blocks".to_string(),
};
let blocks: Vec<BlockSummary> = self.get_response_json(&path).await?;
let blocks: Vec<BlockInfo> = self.get_response_json(&path).await?;
if blocks.is_empty() {
return Err(Error::InvalidResponse);
}
Expand Down
24 changes: 12 additions & 12 deletions src/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::{Address, Block, BlockHash, MerkleBlock, Script, Transaction, Txid};

use crate::{
AddressStats, BlockInfo, BlockStatus, BlockSummary, Builder, Error, MempoolRecentTx,
MempoolStats, MerkleProof, OutputStatus, ScriptHashStats, SubmitPackageResult, Tx, TxStatus,
Utxo, BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
AddressStats, BlockInfo, BlockStatus, Builder, Error, MempoolRecentTx, MempoolStats,
MerkleProof, OutputStatus, ScriptHashStats, SubmitPackageResult, Tx, TxStatus, Utxo,
BASE_BACKOFF_MILLIS, RETRYABLE_ERROR_CODES,
};

/// A blocking client for interacting with an Esplora API server.
Expand Down Expand Up @@ -436,11 +436,11 @@ impl BlockingClient {
self.get_response_json(&path)
}

/// Get transaction history for the specified scripthash,
/// sorted with newest first. Returns 25 transactions per page.
/// More can be requested by specifying the last txid seen by the previous
/// query.
pub fn scripthash_txs(
/// Get transaction history for the specified [`Script`] hash,
/// sorted by newest first. Returns 25 transactions per page.
/// More can be requested by specifying
/// the last [`Txid`] seen by the previous query.
pub fn get_scripthash_txs(
&self,
script: &Script,
last_seen: Option<Txid>,
Expand Down Expand Up @@ -494,17 +494,17 @@ impl BlockingClient {
self.get_response_json(&path)
}

/// Gets some recent block summaries starting at the tip or at `height` if
/// provided.
/// Get recent block summaries starting at the tip,
/// or at `height` if provided.
///
/// The maximum number of summaries returned depends on the backend itself:
/// esplora returns `10` while [mempool.space](https://mempool.space/docs/api) returns `15`.
pub fn get_blocks(&self, height: Option<u32>) -> Result<Vec<BlockSummary>, Error> {
pub fn get_blocks(&self, height: Option<u32>) -> Result<Vec<BlockInfo>, Error> {
let path = match height {
Some(height) => format!("/blocks/{height}"),
None => "/blocks".to_string(),
};
let blocks: Vec<BlockSummary> = self.get_response_json(&path)?;
let blocks: Vec<BlockInfo> = self.get_response_json(&path)?;
if blocks.is_empty() {
return Err(Error::InvalidResponse);
}
Expand Down
25 changes: 14 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,8 +574,8 @@ mod test {
assert_eq!(tx_info.txid, txid);
assert_eq!(tx_info.to_tx(), tx_exp);
assert_eq!(tx_info.size, tx_exp.total_size());
assert_eq!(tx_info.weight(), tx_exp.weight());
assert_eq!(tx_info.fee(), tx_res.fee.unwrap().unsigned_abs());
assert_eq!(tx_info.weight, tx_exp.weight());
assert_eq!(tx_info.fee, tx_res.fee.unwrap().unsigned_abs());
assert!(tx_info.status.confirmed);
assert_eq!(tx_info.status.block_height, Some(tx_block_height));
assert_eq!(tx_info.status.block_hash, tx_res.block_hash);
Expand Down Expand Up @@ -915,13 +915,13 @@ mod test {
.tx;
let script = &expected_tx.output[0].script_pubkey;
let scripthash_txs_txids: Vec<Txid> = blocking_client
.scripthash_txs(script, None)
.get_scripthash_txs(script, None)
.unwrap()
.iter()
.map(|tx| tx.txid)
.collect();
let scripthash_txs_txids_async: Vec<Txid> = async_client
.scripthash_txs(script, None)
.get_scripthash_txs(script, None)
.await
.unwrap()
.iter()
Expand Down Expand Up @@ -1013,7 +1013,7 @@ mod test {
let start_height = BITCOIND.client.get_block_count().unwrap().0;
let blocks1 = blocking_client.get_blocks(None).unwrap();
let blocks_async1 = async_client.get_blocks(None).await.unwrap();
assert_eq!(blocks1[0].time.height, start_height as u32);
assert_eq!(blocks1[0].height, start_height as u32);
assert_eq!(blocks1, blocks_async1);
generate_blocks_and_wait(10);
let blocks2 = blocking_client.get_blocks(None).unwrap();
Expand All @@ -1028,7 +1028,7 @@ mod test {
.await
.unwrap();
assert_eq!(blocks3, blocks_async3);
assert_eq!(blocks3[0].time.height, start_height as u32);
assert_eq!(blocks3[0].height, start_height as u32);
assert_eq!(blocks3, blocks1);
let blocks_genesis = blocking_client.get_blocks(Some(0)).unwrap();
let blocks_genesis_async = async_client.get_blocks(Some(0)).await.unwrap();
Expand Down Expand Up @@ -1092,7 +1092,10 @@ mod test {
let address_stats_async = async_client.get_address_stats(&address).await.unwrap();
assert_eq!(address_stats_blocking, address_stats_async);
assert_eq!(address_stats_async.chain_stats.funded_txo_count, 1);
assert_eq!(address_stats_async.chain_stats.funded_txo_sum, 1000);
assert_eq!(
address_stats_async.chain_stats.funded_txo_sum,
Amount::from_sat(1000)
);
}

#[cfg(all(feature = "blocking", feature = "async"))]
Expand Down Expand Up @@ -1167,7 +1170,7 @@ mod test {
);
assert_eq!(
scripthash_stats_blocking_legacy.chain_stats.funded_txo_sum,
1000
Amount::from_sat(1000)
);
assert_eq!(scripthash_stats_blocking_legacy.chain_stats.tx_count, 1);

Expand All @@ -1187,7 +1190,7 @@ mod test {
scripthash_stats_blocking_p2sh_segwit
.chain_stats
.funded_txo_sum,
1000
Amount::from_sat(1000)
);
assert_eq!(
scripthash_stats_blocking_p2sh_segwit.chain_stats.tx_count,
Expand All @@ -1208,7 +1211,7 @@ mod test {
);
assert_eq!(
scripthash_stats_blocking_bech32.chain_stats.funded_txo_sum,
1000
Amount::from_sat(1000)
);
assert_eq!(scripthash_stats_blocking_bech32.chain_stats.tx_count, 1);

Expand All @@ -1226,7 +1229,7 @@ mod test {
);
assert_eq!(
scripthash_stats_blocking_bech32m.chain_stats.funded_txo_sum,
1000
Amount::from_sat(1000)
);
assert_eq!(scripthash_stats_blocking_bech32m.chain_stats.tx_count, 1);
}
Expand Down
Loading