Skip to content

Commit 46a8817

Browse files
committed
feat: add batch_transaction_get_merkle
- adds the new batch method for `blockchain.transaction.get_merkle`. - adds a new test for `batch_transaction_get_merkle` with 3 different txids and block_heights.
1 parent 7de4cb7 commit 46a8817

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed

src/api.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,17 @@ where
145145
(**self).transaction_get_merkle(txid, height)
146146
}
147147

148+
fn batch_transaction_get_merkle<'s, I>(
149+
&self,
150+
txids_and_heights: I,
151+
) -> Result<Vec<GetMerkleRes>, Error>
152+
where
153+
I: IntoIterator + Clone,
154+
I::Item: Borrow<&'s (Txid, usize)>,
155+
{
156+
(**self).batch_transaction_get_merkle(txids_and_heights)
157+
}
158+
148159
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error> {
149160
(**self).txid_from_pos(height, tx_pos)
150161
}
@@ -362,6 +373,17 @@ pub trait ElectrumApi {
362373
/// Returns the merkle path for the transaction `txid` confirmed in the block at `height`.
363374
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error>;
364375

376+
/// Batch version of [`transaction_get_merkle`](#method.transaction_get_merkle).
377+
///
378+
/// Take a list of `(txid, height)`, for transactions with `txid` confirmed in the block at `height`.
379+
fn batch_transaction_get_merkle<'s, I>(
380+
&self,
381+
txids_and_heights: I,
382+
) -> Result<Vec<GetMerkleRes>, Error>
383+
where
384+
I: IntoIterator + Clone,
385+
I::Item: Borrow<&'s (Txid, usize)>;
386+
365387
/// Returns a transaction hash, given a block `height` and a `tx_pos` in the block.
366388
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error>;
367389

@@ -558,6 +580,17 @@ mod test {
558580
unreachable!()
559581
}
560582

583+
fn batch_transaction_get_merkle<'s, I>(
584+
&self,
585+
_: I,
586+
) -> Result<Vec<crate::GetMerkleRes>, crate::Error>
587+
where
588+
I: IntoIterator + Clone,
589+
I::Item: std::borrow::Borrow<&'s (bitcoin::Txid, usize)>,
590+
{
591+
unreachable!()
592+
}
593+
561594
fn txid_from_pos(&self, _: usize, _: usize) -> Result<bitcoin::Txid, super::Error> {
562595
unreachable!()
563596
}

src/batch.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,17 @@ impl Batch {
6262
.push((String::from("blockchain.transaction.get"), params));
6363
}
6464

65+
/// Add one `blockchain.transaction.get_merkle` request to the batch queue
66+
pub fn transaction_get_merkle(&mut self, tx_hash_and_height: &(Txid, usize)) {
67+
let (tx_hash, height) = tx_hash_and_height;
68+
let params = vec![
69+
Param::String(format!("{:x}", tx_hash)),
70+
Param::Usize(*height),
71+
];
72+
self.calls
73+
.push((String::from("blockchain.transaction.get_merkle"), params));
74+
}
75+
6576
/// Add one `blockchain.estimatefee` request to the batch queue
6677
pub fn estimate_fee(&mut self, number: usize) {
6778
let params = vec![Param::Usize(number)];

src/client.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,22 @@ impl ElectrumApi for Client {
327327
impl_inner_call!(self, transaction_get_merkle, txid, height)
328328
}
329329

330+
#[inline]
331+
fn batch_transaction_get_merkle<'s, I>(
332+
&self,
333+
txids_and_heights: I,
334+
) -> Result<Vec<GetMerkleRes>, Error>
335+
where
336+
I: IntoIterator + Clone,
337+
I::Item: Borrow<&'s (Txid, usize)>,
338+
{
339+
impl_inner_call!(
340+
self,
341+
batch_transaction_get_merkle,
342+
txids_and_heights.clone()
343+
)
344+
}
345+
330346
#[inline]
331347
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error> {
332348
impl_inner_call!(self, txid_from_pos, height, tx_pos)

src/raw_client.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,17 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
11021102
Ok(serde_json::from_value(result)?)
11031103
}
11041104

1105+
fn batch_transaction_get_merkle<'s, I>(
1106+
&self,
1107+
txids_and_hashes: I,
1108+
) -> Result<Vec<GetMerkleRes>, Error>
1109+
where
1110+
I: IntoIterator + Clone,
1111+
I::Item: Borrow<&'s (Txid, usize)>,
1112+
{
1113+
impl_batch_call!(self, txids_and_hashes, transaction_get_merkle)
1114+
}
1115+
11051116
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error> {
11061117
let params = vec![Param::Usize(height), Param::Usize(tx_pos)];
11071118
let req = Request::new_id(
@@ -1448,6 +1459,95 @@ mod test {
14481459
));
14491460
}
14501461

1462+
#[test]
1463+
fn test_batch_transaction_get_merkle() {
1464+
use bitcoin::Txid;
1465+
1466+
let client = RawClient::new(get_test_server(), None).unwrap();
1467+
1468+
let txid_1 =
1469+
Txid::from_str("1f7ff3c407f33eabc8bec7d2cc230948f2249ec8e591bcf6f971ca9366c8788d")
1470+
.unwrap();
1471+
let block_height_1 = 630000;
1472+
1473+
let merkle_1_pos = 68;
1474+
let merkle_1_bytes = [
1475+
34, 65, 51, 64, 49, 139, 115, 189, 185, 246, 70, 225, 168, 193, 217, 195, 47, 66, 179,
1476+
240, 153, 24, 114, 215, 144, 196, 212, 41, 39, 155, 246, 25,
1477+
];
1478+
1479+
let txid_2 =
1480+
Txid::from_str("70a8639bc9b743c0610d1231103a2f8e99f4a25670946b91f16c55a5373b37d1")
1481+
.unwrap();
1482+
let block_height_2 = 630001;
1483+
1484+
let merkle_2_pos = 25;
1485+
let merkle_2_bytes = [
1486+
169, 100, 34, 99, 168, 101, 25, 168, 184, 90, 77, 50, 151, 245, 130, 101, 193, 229,
1487+
136, 128, 63, 110, 241, 19, 242, 59, 184, 137, 245, 249, 188, 110,
1488+
];
1489+
1490+
let txid_3 =
1491+
Txid::from_str("a0db149ace545beabbd87a8d6b20ffd6aa3b5a50e58add49a3d435f898c272cf")
1492+
.unwrap();
1493+
let block_height_3 = 840000;
1494+
1495+
let merkle_3_pos = 0;
1496+
let merkle_3_bytes = [
1497+
43, 184, 95, 75, 0, 75, 230, 218, 84, 247, 102, 193, 124, 30, 133, 81, 135, 50, 113,
1498+
18, 194, 49, 239, 47, 243, 94, 186, 208, 234, 103, 198, 158,
1499+
];
1500+
1501+
let txids_and_heights = vec![
1502+
(txid_1, block_height_1),
1503+
(txid_2, block_height_2),
1504+
(txid_3, block_height_3),
1505+
];
1506+
let resp = client
1507+
.batch_transaction_get_merkle(&txids_and_heights)
1508+
.unwrap();
1509+
1510+
assert_eq!(resp[0].block_height, block_height_1);
1511+
assert_eq!(resp[0].pos, merkle_1_pos);
1512+
assert_eq!(resp[0].merkle.len(), 12);
1513+
assert_eq!(resp[0].merkle[0], merkle_1_bytes);
1514+
1515+
assert_eq!(resp[1].block_height, block_height_2);
1516+
assert_eq!(resp[1].pos, merkle_2_pos);
1517+
assert_eq!(resp[1].merkle.len(), 12);
1518+
assert_eq!(resp[1].merkle[0], merkle_2_bytes);
1519+
1520+
assert_eq!(resp[2].block_height, block_height_3);
1521+
assert_eq!(resp[2].pos, merkle_3_pos);
1522+
assert_eq!(resp[2].merkle.len(), 12);
1523+
assert_eq!(resp[2].merkle[0], merkle_3_bytes);
1524+
1525+
for (i, res) in resp.iter().enumerate() {
1526+
// Check we can verify the merkle proof validity, but fail if we supply wrong data.
1527+
let block_header = client.block_header(res.block_height).unwrap();
1528+
assert!(utils::validate_merkle_proof(
1529+
&txids_and_heights[i].0,
1530+
&block_header.merkle_root,
1531+
res
1532+
));
1533+
1534+
let mut fail_res = res.clone();
1535+
fail_res.pos = 13;
1536+
assert!(!utils::validate_merkle_proof(
1537+
&txids_and_heights[i].0,
1538+
&block_header.merkle_root,
1539+
&fail_res
1540+
));
1541+
1542+
let fail_block_header = client.block_header(res.block_height + 1).unwrap();
1543+
assert!(!utils::validate_merkle_proof(
1544+
&txids_and_heights[i].0,
1545+
&fail_block_header.merkle_root,
1546+
res
1547+
));
1548+
}
1549+
}
1550+
14511551
#[test]
14521552
fn test_txid_from_pos() {
14531553
use bitcoin::Txid;

0 commit comments

Comments
 (0)