Skip to content

Commit d448adf

Browse files
committed
feat(client): add get_block_txs
1 parent 0297e02 commit d448adf

3 files changed

Lines changed: 114 additions & 0 deletions

File tree

src/async.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,24 @@ impl<S: Sleeper> AsyncClient<S> {
473473
self.get_response_json(&path).await
474474
}
475475

476+
/// Get up to 25 [`Transaction`]s from a [`Block`], given it's [`BlockHash`],
477+
/// beginning at `start_index` (starts from 0 if `start_index` is `None`).
478+
///
479+
/// The `start_index` value MUST be a multiple of 25,
480+
/// even though this is not documented on the Esplora specification.
481+
pub async fn get_block_txs(
482+
&self,
483+
blockhash: &BlockHash,
484+
start_index: Option<u32>,
485+
) -> Result<Vec<Tx>, Error> {
486+
let path = match start_index {
487+
None => format!("/block/{blockhash}/txs"),
488+
Some(start_index) => format!("/block/{blockhash}/txs/{start_index}"),
489+
};
490+
491+
self.get_response_json(&path).await
492+
}
493+
476494
/// Gets some recent block summaries starting at the tip or at `height` if
477495
/// provided.
478496
///

src/blocking.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,24 @@ impl BlockingClient {
401401
self.get_response_json(&path)
402402
}
403403

404+
/// Get up to 25 [`Transaction`]s from a [`Block`], given it's [`BlockHash`],
405+
/// beginning at `start_index` (starts from 0 if `start_index` is `None`).
406+
///
407+
/// The `start_index` value MUST be a multiple of 25,
408+
/// even though this is not documented on the Esplora specification.
409+
pub fn get_block_txs(
410+
&self,
411+
blockhash: &BlockHash,
412+
start_index: Option<u32>,
413+
) -> Result<Vec<Tx>, Error> {
414+
let path = match start_index {
415+
None => format!("/block/{blockhash}/txs"),
416+
Some(start_index) => format!("/block/{blockhash}/txs/{start_index}"),
417+
};
418+
419+
self.get_response_json(&path)
420+
}
421+
404422
/// Gets some recent block summaries starting at the tip or at `height` if
405423
/// provided.
406424
///

src/lib.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ mod test {
266266
use super::*;
267267
use electrsd::{corepc_node, ElectrsD};
268268
use lazy_static::lazy_static;
269+
use std::collections::HashSet;
269270
use std::env;
270271
use std::str::FromStr;
271272
use tokio::sync::Mutex;
@@ -1028,6 +1029,83 @@ mod test {
10281029
});
10291030
}
10301031

1032+
#[cfg(all(feature = "blocking", feature = "async"))]
1033+
#[tokio::test]
1034+
async fn test_get_block_txs() {
1035+
let (blocking_client, async_client) = setup_clients().await;
1036+
let address = BITCOIND
1037+
.client
1038+
.new_address_with_type(AddressType::Legacy)
1039+
.unwrap();
1040+
// Create 30 transactions and mine a block.
1041+
let mut mined_txids = Vec::new();
1042+
for _ in 0..30 {
1043+
let txid = BITCOIND
1044+
.client
1045+
.send_to_address(&address, Amount::from_sat(1000))
1046+
.unwrap()
1047+
.txid()
1048+
.unwrap();
1049+
mined_txids.push(txid);
1050+
}
1051+
let _miner = MINER.lock().await;
1052+
generate_blocks_and_wait(1);
1053+
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
1054+
// Get the chain tip's blockhash.
1055+
let blockhash = blocking_client.get_tip_hash().unwrap();
1056+
let txs_blocking = blocking_client.get_block_txs(&blockhash, None).unwrap();
1057+
let txs_async = async_client.get_block_txs(&blockhash, None).await.unwrap();
1058+
1059+
let txs_blocking: Vec<Transaction> = txs_blocking
1060+
.into_iter()
1061+
.map(|esplora_tx| esplora_tx.to_tx())
1062+
.collect();
1063+
let txs_async: Vec<Transaction> = txs_async
1064+
.into_iter()
1065+
.map(|esplora_tx| esplora_tx.to_tx())
1066+
.collect();
1067+
1068+
// Assert that we only get 25 transactions, as per the Esplora specification.
1069+
assert_eq!(txs_blocking.len(), 25);
1070+
assert_eq!(txs_blocking, txs_async);
1071+
1072+
// Compare the received transactions (skipping the coinbase transaction).
1073+
// All 24 non-coinbase transactions should be from our expected set.
1074+
let expected_txids: HashSet<Txid> = mined_txids.iter().copied().collect();
1075+
let received_txids: HashSet<Txid> = txs_blocking
1076+
.iter()
1077+
.skip(1)
1078+
.map(|tx| tx.compute_txid())
1079+
.collect();
1080+
assert_eq!(received_txids.len(), 24);
1081+
assert!(received_txids.is_subset(&expected_txids));
1082+
1083+
let txs_blocking_offset: Vec<Transaction> = blocking_client
1084+
.get_block_txs(&blockhash, Some(25))
1085+
.unwrap()
1086+
.into_iter()
1087+
.map(|esplora_tx| esplora_tx.to_tx())
1088+
.collect();
1089+
let txs_async_offset: Vec<Transaction> = async_client
1090+
.get_block_txs(&blockhash, Some(25))
1091+
.await
1092+
.unwrap()
1093+
.into_iter()
1094+
.map(|esplora_tx| esplora_tx.to_tx())
1095+
.collect();
1096+
1097+
// 31 transactions on the block minus `start_index` of 25 yields 6 transactions.
1098+
assert_eq!(txs_blocking_offset.len(), 6);
1099+
assert_eq!(txs_blocking_offset, txs_async_offset);
1100+
1101+
// Compare the expected and received transactions from index 25 through 30.
1102+
let received_offset_txids: HashSet<Txid> = txs_blocking_offset
1103+
.iter()
1104+
.map(|tx| tx.compute_txid())
1105+
.collect();
1106+
assert!(received_offset_txids.is_subset(&expected_txids));
1107+
}
1108+
10311109
#[cfg(all(feature = "blocking", feature = "async"))]
10321110
#[tokio::test]
10331111
async fn test_get_blocks() {

0 commit comments

Comments
 (0)