From bc3d431730052ace9766b328df2faae830c67062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Sat, 15 Jun 2024 11:18:02 +0800 Subject: [PATCH 1/2] feat: add way to make raw req and get url for `BlockingClient` --- src/blocking.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/blocking.rs b/src/blocking.rs index 53e2a7b2..61c1de78 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -51,7 +51,13 @@ impl BlockingClient { } } - fn get_request(&self, path: &str) -> Result { + /// Get the underlying base URL. + pub fn url(&self) -> &str { + &self.url + } + + /// Perform a raw HTTP GET request with the given URI `path`. + pub fn get_request(&self, path: &str) -> Result { let mut request = minreq::get(format!("{}{}", self.url, path)); if let Some(proxy) = &self.proxy { From 3b778c57625ab8f4fb9b72b4e2013af74045a846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Sat, 15 Jun 2024 21:57:22 +0800 Subject: [PATCH 2/2] feat: add method for getting tx info via `GET /tx/:txid` Also add `size` and `weight` fields to `Tx`. --- src/api.rs | 15 +++++++++++++- src/async.rs | 20 ++++++++++++++++++ src/blocking.rs | 5 +++++ src/lib.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/api.rs b/src/api.rs index c700a38c..d4dfa1e4 100644 --- a/src/api.rs +++ b/src/api.rs @@ -4,6 +4,7 @@ pub use bitcoin::consensus::{deserialize, serialize}; pub use bitcoin::hex::FromHex; +use bitcoin::Weight; pub use bitcoin::{ transaction, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness, }; @@ -65,13 +66,17 @@ pub struct BlockStatus { pub next_best: Option, } -#[derive(Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub struct Tx { pub txid: Txid, pub version: i32, pub locktime: u32, pub vin: Vec, pub vout: Vec, + /// Transaction size in raw bytes (NOT virtual bytes). + pub size: usize, + /// Transaction weight units. + pub weight: u64, pub status: TxStatus, pub fee: u64, } @@ -147,6 +152,14 @@ impl Tx { }) .collect() } + + pub fn weight(&self) -> Weight { + Weight::from_wu(self.weight) + } + + pub fn fee(&self) -> Amount { + Amount::from_sat(self.fee) + } } fn deserialize_witness<'de, D>(d: D) -> Result>, D::Error> diff --git a/src/async.rs b/src/async.rs index a6a70c07..bf72a486 100644 --- a/src/async.rs +++ b/src/async.rs @@ -143,6 +143,26 @@ impl AsyncClient { } } + /// Get transaction info given it's [`Txid`]. + pub async fn get_tx_info(&self, txid: &Txid) -> Result, Error> { + let resp = self + .client + .get(&format!("{}/tx/{}", self.url, txid)) + .send() + .await?; + if resp.status() == StatusCode::NOT_FOUND { + return Ok(None); + } + if resp.status().is_server_error() || resp.status().is_client_error() { + Err(Error::HttpResponse { + status: resp.status().as_u16(), + message: resp.text().await?, + }) + } else { + Ok(Some(resp.json().await?)) + } + } + /// Get a [`BlockHeader`] given a particular block hash. pub async fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result { let resp = self diff --git a/src/blocking.rs b/src/blocking.rs index 61c1de78..04485720 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -213,6 +213,11 @@ impl BlockingClient { self.get_response_json(&format!("/tx/{}/status", txid)) } + /// Get transaction info given it's [`Txid`]. + pub fn get_tx_info(&self, txid: &Txid) -> Result, Error> { + self.get_opt_response_json(&format!("/tx/{}", txid)) + } + /// Get a [`BlockHeader`] given a particular block hash. pub fn get_header_by_hash(&self, block_hash: &BlockHash) -> Result { self.get_response_hex(&format!("/block/{}/header", block_hash)) diff --git a/src/lib.rs b/src/lib.rs index 9e33fce5..1523dff9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -503,6 +503,60 @@ mod test { assert!(tx_status.block_time.is_none()); } + #[cfg(all(feature = "blocking", feature = "async"))] + #[tokio::test] + async fn test_get_tx_info() { + let (blocking_client, async_client) = setup_clients().await; + + let address = BITCOIND + .client + .get_new_address(Some("test"), Some(AddressType::Legacy)) + .unwrap() + .assume_checked(); + let txid = BITCOIND + .client + .send_to_address( + &address, + Amount::from_sat(1000), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + let _miner = MINER.lock().await; + generate_blocks_and_wait(1); + + let tx_res = BITCOIND.client.get_transaction(&txid, None).unwrap(); + let tx_exp = tx_res.transaction().expect("must decode"); + + let tx_info = blocking_client + .get_tx_info(&txid) + .unwrap() + .expect("must get tx"); + let tx_info_async = async_client + .get_tx_info(&txid) + .await + .unwrap() + .expect("must get tx"); + assert_eq!(tx_info, tx_info_async); + 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!(tx_info.status.confirmed); + assert_eq!(tx_info.status.block_height, tx_res.info.blockheight); + assert_eq!(tx_info.status.block_hash, tx_res.info.blockhash); + assert_eq!(tx_info.status.block_time, tx_res.info.blocktime); + + let txid = Txid::hash(b"not exist"); + assert_eq!(blocking_client.get_tx_info(&txid).unwrap(), None); + assert_eq!(async_client.get_tx_info(&txid).await.unwrap(), None); + } + #[cfg(all(feature = "blocking", feature = "async"))] #[tokio::test] async fn test_get_header_by_hash() {