Skip to content

Commit dfd40b3

Browse files
committed
feat: Add transaction_broadcast_package method for package relay
Add support for the `blockchain.transaction.broadcast_package` RPC method introduced in protocol v1.6 for package relay (CPFP) support. - Add `BroadcastPackageRes` and `BroadcastPackageError` types - Add `Param::StringVec` variant for array parameters - Add `transaction_broadcast_package_raw` trait method with generic `AsRef<[u8]>` - Add `transaction_broadcast_package` convenience method that serializes transactions Co-Authored-By: Claude Code AI
1 parent 8f6bda0 commit dfd40b3

File tree

4 files changed

+104
-0
lines changed

4 files changed

+104
-0
lines changed

src/api.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ where
141141
(**self).transaction_broadcast_raw(raw_tx)
142142
}
143143

144+
fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
145+
&self,
146+
raw_txs: &[T],
147+
) -> Result<BroadcastPackageRes, Error> {
148+
(**self).transaction_broadcast_package_raw(raw_txs)
149+
}
150+
144151
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error> {
145152
(**self).transaction_get_merkle(txid, height)
146153
}
@@ -253,6 +260,21 @@ pub trait ElectrumApi {
253260
self.transaction_broadcast_raw(&buffer)
254261
}
255262

263+
/// Broadcasts a package of transactions to the network.
264+
///
265+
/// The package must consist of a child with its parents, where none of the parents
266+
/// depend on one another. The package must be topologically sorted, with the child
267+
/// being the last element in the array.
268+
///
269+
/// This method was added in protocol v1.6 for package relay support.
270+
fn transaction_broadcast_package(
271+
&self,
272+
txs: &[Transaction],
273+
) -> Result<BroadcastPackageRes, Error> {
274+
let raw_txs: Vec<Vec<u8>> = txs.iter().map(serialize).collect();
275+
self.transaction_broadcast_package_raw(&raw_txs)
276+
}
277+
256278
/// Executes the requested API call returning the raw answer.
257279
fn raw_call(
258280
&self,
@@ -385,6 +407,18 @@ pub trait ElectrumApi {
385407
/// Broadcasts the raw bytes of a transaction to the network.
386408
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error>;
387409

410+
/// Broadcasts a package of raw transactions to the network.
411+
///
412+
/// The package must consist of a child with its parents, where none of the parents
413+
/// depend on one another. The package must be topologically sorted, with the child
414+
/// being the last element in the array.
415+
///
416+
/// This method was added in protocol v1.6 for package relay support.
417+
fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
418+
&self,
419+
raw_txs: &[T],
420+
) -> Result<BroadcastPackageRes, Error>;
421+
388422
/// Returns the merkle path for the transaction `txid` confirmed in the block at `height`.
389423
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error>;
390424

@@ -612,6 +646,13 @@ mod test {
612646
unreachable!()
613647
}
614648

649+
fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
650+
&self,
651+
_: &[T],
652+
) -> Result<super::BroadcastPackageRes, super::Error> {
653+
unreachable!()
654+
}
655+
615656
fn transaction_get_merkle(
616657
&self,
617658
_: &bitcoin::Txid,

src/client.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,14 @@ impl ElectrumApi for Client {
322322
impl_inner_call!(self, transaction_broadcast_raw, raw_tx)
323323
}
324324

325+
#[inline]
326+
fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
327+
&self,
328+
raw_txs: &[T],
329+
) -> Result<BroadcastPackageRes, Error> {
330+
impl_inner_call!(self, transaction_broadcast_package_raw, raw_txs)
331+
}
332+
325333
#[inline]
326334
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error> {
327335
impl_inner_call!(self, transaction_get_merkle, txid, height)

src/raw_client.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,25 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
12131213
Ok(serde_json::from_value(result)?)
12141214
}
12151215

1216+
fn transaction_broadcast_package_raw<Tx: AsRef<[u8]>>(
1217+
&self,
1218+
raw_txs: &[Tx],
1219+
) -> Result<BroadcastPackageRes, Error> {
1220+
let hex_txs: Vec<String> = raw_txs
1221+
.iter()
1222+
.map(|tx| tx.as_ref().to_lower_hex_string())
1223+
.collect();
1224+
let params = vec![Param::StringVec(hex_txs)];
1225+
let req = Request::new_id(
1226+
self.last_id.fetch_add(1, Ordering::SeqCst),
1227+
"blockchain.transaction.broadcast_package",
1228+
params,
1229+
);
1230+
let result = self.call(req)?;
1231+
1232+
Ok(serde_json::from_value(result)?)
1233+
}
1234+
12161235
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error> {
12171236
let params = vec![Param::String(format!("{:x}", txid)), Param::Usize(height)];
12181237
let req = Request::new_id(
@@ -1380,6 +1399,17 @@ mod test {
13801399
assert!(resp.incrementalrelayfee >= 0.0);
13811400
}
13821401

1402+
#[test]
1403+
#[ignore = "requires a server supporting protocol v1.6 and valid transactions"]
1404+
fn test_transaction_broadcast_package() {
1405+
let client = RawClient::new(get_test_server(), None).unwrap();
1406+
1407+
// Empty package should return an error or unsuccessful response
1408+
let resp = client.transaction_broadcast_package_raw::<Vec<u8>>(&[]);
1409+
// The server may reject an empty package with a protocol error
1410+
assert!(resp.is_err() || !resp.unwrap().success);
1411+
}
1412+
13831413
#[test]
13841414
#[ignore = "depends on a live server"]
13851415
fn test_batch_response_ordering() {

src/types.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub enum Param {
3333
Bool(bool),
3434
/// Bytes array parameter
3535
Bytes(Vec<u8>),
36+
/// String array parameter
37+
StringVec(Vec<String>),
3638
}
3739

3840
/// Fee estimation mode for [`estimate_fee`](../api/trait.ElectrumApi.html#method.estimate_fee).
@@ -334,6 +336,29 @@ pub struct TxidFromPosRes {
334336
pub merkle: Vec<[u8; 32]>,
335337
}
336338

339+
/// Error details for a transaction that failed to broadcast in a package.
340+
#[derive(Clone, Debug, Deserialize)]
341+
pub struct BroadcastPackageError {
342+
/// The txid of the transaction that failed.
343+
pub txid: Txid,
344+
/// The error message describing why the transaction was rejected.
345+
pub error: String,
346+
}
347+
348+
/// Response to a [`transaction_broadcast_package`](../client/struct.Client.html#method.transaction_broadcast_package)
349+
/// request.
350+
///
351+
/// This method was added in protocol v1.6 for package relay support.
352+
#[derive(Clone, Debug, Deserialize)]
353+
pub struct BroadcastPackageRes {
354+
/// Whether the package was successfully accepted by the mempool.
355+
pub success: bool,
356+
/// List of errors for transactions that were rejected.
357+
/// Only present if some transactions failed.
358+
#[serde(default)]
359+
pub errors: Vec<BroadcastPackageError>,
360+
}
361+
337362
/// Notification of a new block header
338363
#[derive(Clone, Debug, Deserialize)]
339364
pub struct HeaderNotification {

0 commit comments

Comments
 (0)