Skip to content

Commit d01b5c9

Browse files
committed
Add first-class Package type
1 parent fdd1a2a commit d01b5c9

2 files changed

Lines changed: 213 additions & 0 deletions

File tree

src/error.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,30 @@ impl core::fmt::Display for FetchBlockError {
7575
}
7676

7777
impl_sourceless_error!(FetchBlockError);
78+
79+
/// Errors when constructing transaction packages.
80+
#[derive(Debug)]
81+
pub enum InvalidPackageError {
82+
/// Packages may not include more than two transactions and must include at least one
83+
/// transaction.
84+
InvalidPackageLength(usize),
85+
/// Child transactions must spend an output from the parent.
86+
UnrelatedTransactions,
87+
}
88+
89+
impl core::fmt::Display for InvalidPackageError {
90+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91+
match self {
92+
InvalidPackageError::InvalidPackageLength(s) => {
93+
write!(
94+
f,
95+
"package must include at most two transactions, got {}",
96+
s
97+
)
98+
}
99+
InvalidPackageError::UnrelatedTransactions => {
100+
write!(f, "packages must have dependent inputs and outputs.")
101+
}
102+
}
103+
}
104+
}

src/lib.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ pub mod messages;
6060
/// The structure that communicates with the Bitcoin P2P network and collects data.
6161
pub mod node;
6262

63+
use bitcoin::OutPoint;
6364
use chain::Filter;
65+
use package_type::{OneParentOneChild, Single};
6466

6567
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
6668
use std::path::PathBuf;
@@ -431,6 +433,190 @@ impl Dialog {
431433
}
432434
}
433435

436+
/// Types of packages valid for package relay.
437+
pub mod package_type {
438+
/// Single transaction.
439+
pub struct Single;
440+
/// Exactly two transactions, one spending from the other.
441+
pub struct OneParentOneChild;
442+
}
443+
444+
mod sealed {
445+
pub trait Sealed {}
446+
}
447+
448+
impl sealed::Sealed for package_type::Single {}
449+
impl sealed::Sealed for package_type::OneParentOneChild {}
450+
451+
/// Types of packages valid for package relay.
452+
pub trait PackageType: sealed::Sealed {}
453+
454+
impl PackageType for package_type::Single {}
455+
impl PackageType for package_type::OneParentOneChild {}
456+
457+
/// Build transaction packages to submit to the network.
458+
#[derive(Debug)]
459+
pub struct PackageBuilder<P: PackageType> {
460+
package: Vec<Transaction>,
461+
_marker: core::marker::PhantomData<P>,
462+
}
463+
464+
impl<P: PackageType> PackageBuilder<P> {
465+
/// Create a package from a single transaction.
466+
pub fn new_single(parent: Transaction) -> PackageBuilder<Single> {
467+
PackageBuilder {
468+
package: vec![parent],
469+
_marker: core::marker::PhantomData,
470+
}
471+
}
472+
473+
/// Create a package from a single parent and child relationship.
474+
///
475+
/// # Errors
476+
///
477+
/// If the package is invalid for mempool submission.
478+
pub fn new_one_parent_one_child(
479+
parent: Transaction,
480+
child: Transaction,
481+
) -> Result<PackageBuilder<OneParentOneChild>, error::InvalidPackageError> {
482+
let outpoints = {
483+
let txid = parent.compute_txid();
484+
let mut outpoints = Vec::with_capacity(parent.output.len());
485+
for vout in 0..parent.output.len() {
486+
outpoints.push(OutPoint {
487+
txid,
488+
vout: vout as u32,
489+
});
490+
}
491+
outpoints
492+
};
493+
if !child
494+
.input
495+
.iter()
496+
.any(|input| outpoints.contains(&input.previous_output))
497+
{
498+
return Err(error::InvalidPackageError::UnrelatedTransactions);
499+
}
500+
Ok(PackageBuilder {
501+
package: vec![parent, child],
502+
_marker: core::marker::PhantomData,
503+
})
504+
}
505+
506+
/// Create a package from an unchecked list of transactions.
507+
///
508+
/// # Errors
509+
///
510+
/// If the package is invalid for mempool submission.
511+
pub fn new_unchecked(package: Vec<Transaction>) -> Result<Package, error::InvalidPackageError> {
512+
if package.len() > 2 {
513+
return Err(error::InvalidPackageError::InvalidPackageLength(
514+
package.len(),
515+
));
516+
}
517+
if package.is_empty() {
518+
return Err(error::InvalidPackageError::InvalidPackageLength(0));
519+
}
520+
let mut tx_iter = package.into_iter();
521+
let parent = tx_iter.next().expect("at least one transaction exists.");
522+
let outpoints = {
523+
let txid = parent.compute_txid();
524+
let mut outpoints = Vec::with_capacity(parent.output.len());
525+
for vout in 0..parent.output.len() {
526+
outpoints.push(OutPoint {
527+
txid,
528+
vout: vout as u32,
529+
});
530+
}
531+
outpoints
532+
};
533+
let child = tx_iter.next();
534+
// Replace with `let` chain in Rust version 2024
535+
if let Some(child) = child {
536+
if !child
537+
.input
538+
.iter()
539+
.any(|input| outpoints.contains(&input.previous_output))
540+
{
541+
return Err(error::InvalidPackageError::UnrelatedTransactions);
542+
}
543+
}
544+
Ok(Package {
545+
parent,
546+
child: tx_iter.next(),
547+
})
548+
}
549+
}
550+
551+
impl PackageBuilder<package_type::Single> {
552+
/// Build a package from a single transaction.
553+
pub fn build(self) -> Package {
554+
Package {
555+
parent: self
556+
.package
557+
.into_iter()
558+
.next()
559+
.expect("at least one transaction exists."),
560+
child: None,
561+
}
562+
}
563+
564+
/// Add a dependent transaction to the package.
565+
pub fn add_child(mut self, child: Transaction) -> PackageBuilder<OneParentOneChild> {
566+
self.package.push(child);
567+
PackageBuilder {
568+
package: self.package,
569+
_marker: core::marker::PhantomData,
570+
}
571+
}
572+
}
573+
574+
impl PackageBuilder<package_type::OneParentOneChild> {
575+
/// Build a package from a one-parent-one-child topology.
576+
pub fn build(self) -> Package {
577+
let mut tx_iter = self.package.into_iter();
578+
let parent = tx_iter.next().expect("at least two transactions exist");
579+
let child = tx_iter.next().expect("at least two transactions exit");
580+
Package {
581+
parent,
582+
child: Some(child),
583+
}
584+
}
585+
}
586+
587+
/// A package is a set of dependent transactions to submit to the mempool.
588+
#[derive(Debug, Clone)]
589+
pub struct Package {
590+
parent: Transaction,
591+
child: Option<Transaction>,
592+
}
593+
594+
impl Package {
595+
fn advertise_package(&self) -> Wtxid {
596+
match &self.child {
597+
Some(child) => child.compute_wtxid(),
598+
None => self.parent.compute_wtxid(),
599+
}
600+
}
601+
602+
fn parent(&self) -> Transaction {
603+
self.parent.clone()
604+
}
605+
606+
fn child(&self) -> Option<Transaction> {
607+
self.child.clone()
608+
}
609+
}
610+
611+
impl From<Transaction> for Package {
612+
fn from(value: Transaction) -> Self {
613+
Package {
614+
parent: value,
615+
child: None,
616+
}
617+
}
618+
}
619+
434620
macro_rules! impl_sourceless_error {
435621
($e:ident) => {
436622
impl std::error::Error for $e {

0 commit comments

Comments
 (0)