mod broadcast;
mod handshake;
mod net;
mod p2p;
mod seeds;
use std::{
collections::{HashMap, HashSet},
net::SocketAddr,
str::FromStr,
};
use bitcoin::consensus::Decodable;
#[derive(Debug, Clone)]
pub struct Transaction(bitcoin::Transaction);
impl Transaction {
pub fn from_hex(tx: impl AsRef<str>) -> Result<Self, ParseTxError> {
tx.as_ref().parse()
}
pub fn from_bytes(tx: impl AsRef<[u8]>) -> Result<Self, ParseTxError> {
tx.as_ref().try_into()
}
pub fn txid(&self) -> Txid {
Txid(self.0.txid())
}
}
impl FromStr for Transaction {
type Err = ParseTxError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(s).map_err(|_| ParseTxError::NotHex)?;
bytes.as_slice().try_into()
}
}
impl TryFrom<&[u8]> for Transaction {
type Error = ParseTxError;
fn try_from(mut value: &[u8]) -> Result<Self, Self::Error> {
let tx = bitcoin::Transaction::consensus_decode(&mut value)
.map_err(|_| ParseTxError::InvalidTxBytes)?;
Ok(Self(tx))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Txid(bitcoin::Txid);
impl std::fmt::Display for Txid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug)]
pub enum ParseTxError {
NotHex,
InvalidTxBytes,
}
impl std::error::Error for ParseTxError {}
impl std::fmt::Display for ParseTxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseTxError::NotHex => write!(f, "Transaction is not valid hex"),
ParseTxError::InvalidTxBytes => write!(f, "Transaction bytes are invalid"),
}
}
}
#[derive(Debug, Default, Clone)]
pub enum TorMode {
#[default]
BestEffort,
No,
Must,
}
#[derive(Debug, Default, Clone)]
pub enum FindPeerStrategy {
#[default]
DnsSeedWithFixedFallback,
DnsSeedOnly,
Custom(Vec<SocketAddr>),
}
#[derive(Debug, Default, Clone, Copy)]
pub enum Network {
#[default]
Mainnet,
Testnet,
Signet,
Regtest,
}
impl From<Network> for bitcoin::Network {
fn from(value: Network) -> Self {
match value {
Network::Mainnet => bitcoin::Network::Bitcoin,
Network::Testnet => bitcoin::Network::Testnet,
Network::Regtest => bitcoin::Network::Regtest,
Network::Signet => bitcoin::Network::Signet,
}
}
}
#[derive(Debug, Clone)]
pub struct Opts {
pub network: Network,
pub use_tor: TorMode,
pub find_peer_strategy: FindPeerStrategy,
pub max_time: std::time::Duration,
pub dry_run: bool,
pub target_peers: u8,
pub ua: Option<(String, u64, u64)>,
}
impl Default for Opts {
fn default() -> Self {
Self {
network: Network::default(),
use_tor: Default::default(),
find_peer_strategy: Default::default(),
max_time: std::time::Duration::from_secs(40),
dry_run: false,
target_peers: 10,
ua: None,
}
}
}
#[derive(Debug, Clone)]
pub enum Info {
ResolvingPeers,
ResolvedPeers(usize),
ConnectingToNetwork { tor_status: Option<SocketAddr> },
Broadcast { peer: String },
Done(Result<Report, Error>),
}
#[derive(Debug, Clone)]
pub struct Report {
pub success: HashSet<Txid>,
pub rejects: HashMap<Txid, String>,
}
#[derive(Debug, Clone)]
pub enum Error {
TorNotFound,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::TorNotFound => write!(f, "Tor was required but a Tor proxy was not found"),
}
}
}
pub fn broadcast(tx: Vec<Transaction>, opts: Opts) -> crossbeam_channel::Receiver<Info> {
let (broadcaster, event_rx) = broadcast::Runner::new(tx, opts);
broadcaster.run();
event_rx
}