Skip to content

Commit dd13e4e

Browse files
committed
Merge #170: feat: add batch_transaction_get_merkle
74ac97c feat: add `batch_transaction_get_merkle` (Leonardo Lima) Pull request description: partially addresses bitcoindevkit/bdk#1987 - 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. ACKs for top commit: ValuedMammal: ACK 74ac97c Tree-SHA512: 430e07d4cb3f0dc812a389b9b093b55f934ac72d32de5eebe5e072780dc4bd82c15796ba70ff3ae9354e822fac45e479618ff76e0360bd8d887edd7b62dd1327
2 parents 1372898 + 74ac97c commit dd13e4e

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<I>(
149+
&self,
150+
txids_and_heights: I,
151+
) -> Result<Vec<GetMerkleRes>, Error>
152+
where
153+
I: IntoIterator + Clone,
154+
I::Item: Borrow<(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<I>(
380+
&self,
381+
txids_and_heights: I,
382+
) -> Result<Vec<GetMerkleRes>, Error>
383+
where
384+
I: IntoIterator + Clone,
385+
I::Item: Borrow<(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<I>(
584+
&self,
585+
_: I,
586+
) -> Result<Vec<crate::GetMerkleRes>, crate::Error>
587+
where
588+
I: IntoIterator + Clone,
589+
I::Item: std::borrow::Borrow<(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<I>(
332+
&self,
333+
txids_and_heights: I,
334+
) -> Result<Vec<GetMerkleRes>, Error>
335+
where
336+
I: IntoIterator + Clone,
337+
I::Item: Borrow<(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
@@ -1125,6 +1125,17 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
11251125
Ok(serde_json::from_value(result)?)
11261126
}
11271127

1128+
fn batch_transaction_get_merkle<I>(
1129+
&self,
1130+
txids_and_heights: I,
1131+
) -> Result<Vec<GetMerkleRes>, Error>
1132+
where
1133+
I: IntoIterator + Clone,
1134+
I::Item: Borrow<(Txid, usize)>,
1135+
{
1136+
impl_batch_call!(self, txids_and_heights, transaction_get_merkle)
1137+
}
1138+
11281139
fn txid_from_pos(&self, height: usize, tx_pos: usize) -> Result<Txid, Error> {
11291140
let params = vec![Param::Usize(height), Param::Usize(tx_pos)];
11301141
let req = Request::new_id(
@@ -1471,6 +1482,98 @@ mod test {
14711482
));
14721483
}
14731484

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

0 commit comments

Comments
 (0)