Skip to content

Commit 2dfc299

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 2dfc299

File tree

4 files changed

+163
-0
lines changed

4 files changed

+163
-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: 103 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,98 @@ mod test {
14481459
));
14491460
}
14501461

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

0 commit comments

Comments
 (0)