Skip to content

Commit 58bb324

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 Signed-off-by: Elias Rohrer <dev@tnull.de>
1 parent 4212a0b commit 58bb324

File tree

4 files changed

+101
-0
lines changed

4 files changed

+101
-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
}
@@ -245,6 +252,21 @@ pub trait ElectrumApi {
245252
self.transaction_broadcast_raw(&buffer)
246253
}
247254

255+
/// Broadcasts a package of transactions to the network.
256+
///
257+
/// The package must consist of a child with its parents, where none of the parents
258+
/// depend on one another. The package must be topologically sorted, with the child
259+
/// being the last element in the array.
260+
///
261+
/// This method was added in protocol v1.6 for package relay support.
262+
fn transaction_broadcast_package(
263+
&self,
264+
txs: &[Transaction],
265+
) -> Result<BroadcastPackageRes, Error> {
266+
let raw_txs: Vec<Vec<u8>> = txs.iter().map(serialize).collect();
267+
self.transaction_broadcast_package_raw(&raw_txs)
268+
}
269+
248270
/// Executes the requested API call returning the raw answer.
249271
fn raw_call(
250272
&self,
@@ -377,6 +399,18 @@ pub trait ElectrumApi {
377399
/// Broadcasts the raw bytes of a transaction to the network.
378400
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error>;
379401

402+
/// Broadcasts a package of raw transactions to the network.
403+
///
404+
/// The package must consist of a child with its parents, where none of the parents
405+
/// depend on one another. The package must be topologically sorted, with the child
406+
/// being the last element in the array.
407+
///
408+
/// This method was added in protocol v1.6 for package relay support.
409+
fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
410+
&self,
411+
raw_txs: &[T],
412+
) -> Result<BroadcastPackageRes, Error>;
413+
380414
/// Returns the merkle path for the transaction `txid` confirmed in the block at `height`.
381415
fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result<GetMerkleRes, Error>;
382416

@@ -589,6 +623,13 @@ mod test {
589623
unreachable!()
590624
}
591625

626+
fn transaction_broadcast_package_raw<T: AsRef<[u8]>>(
627+
&self,
628+
_: &[T],
629+
) -> Result<super::BroadcastPackageRes, super::Error> {
630+
unreachable!()
631+
}
632+
592633
fn transaction_get_merkle(
593634
&self,
594635
_: &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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,6 +1211,25 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
12111211
Ok(serde_json::from_value(result)?)
12121212
}
12131213

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

1368+
#[test]
1369+
fn test_transaction_broadcast_package() {
1370+
let client = get_test_client();
1371+
1372+
// Empty package should return an error or unsuccessful response
1373+
let resp = client.transaction_broadcast_package_raw::<Vec<u8>>(&[]);
1374+
// The server may reject an empty package with a protocol error
1375+
assert!(resp.is_err() || !resp.unwrap().success);
1376+
}
1377+
13491378
#[test]
13501379
#[ignore = "depends on a live server"]
13511380
fn test_batch_response_ordering() {

src/types.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,29 @@ pub struct TxidFromPosRes {
336336
pub merkle: Vec<[u8; 32]>,
337337
}
338338

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+
339362
/// Notification of a new block header
340363
#[derive(Clone, Debug, Deserialize)]
341364
pub struct HeaderNotification {

0 commit comments

Comments
 (0)