Skip to content

Commit

Permalink
Broadcast to a single peer
Browse files Browse the repository at this point in the history
The problem with blasting transactions to multiple peers is that it
increases the chance of landing on a surveillance node. This isn't a
problem when using Tor; it is mainly a problem for clearnet connections.

This change ensures that we broadcast to just one peer and then wait for
a roundtrip confirmation in the form of an `inventory` message from
other connected peers.

This is marginally slower than the blasting strategy but it increases
both privacy for clearnet connections and gives us certainty in the form
of verifying that our transaction has propagated through the network.

The selected broadcast peer is given 10 seconds for our transaction(s)
to appear on the network, after which we rotate the peer.

Other changes:

- bump version to 0.4.0 for both lib and bin
- redo the `Report` struct to better reflect completion outcomes
- deprecate the `--tesnet` switch in bin in favor of `--network`
- remove the `send_unsolicited` option (unnecessary)
  • Loading branch information
alfred-hodler committed May 31, 2024
1 parent 1368a3d commit 880ccb8
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 150 deletions.
11 changes: 2 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ also works.
1. Resolve peers through DNS seeds.
2. Detect if Tor is present.
3. Connect to 10 random peers, through Tor if possible.
4. Broadcast the transaction.
5. Disconnect.
4. Broadcast the transaction to a single peer.
5. Wait until the transaction is seen on the network.
6. Disconnect.

### Executable

Expand All @@ -42,7 +43,7 @@ Install with Cargo: `cargo install pushtx-cli`
loop {
match receiver.recv().unwrap() {
pushtx::Info::Done(Ok(report)) => {
println!("we successfully broadcast to {} peers", report.broadcasts);
println!("{} transactions broadcast successfully", report.success.len());
break;
}
pushtx::Info::Done(Err(err)) => {
Expand Down
4 changes: 2 additions & 2 deletions pushtx-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pushtx-cli"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
authors = ["Alfred Hodler <[email protected]>"]
license = "MIT"
Expand All @@ -18,5 +18,5 @@ anyhow = "1.0.86"
clap = { version = "4.5.4", features = ["derive"] }
env_logger = { version = "0.11.3", default-features = false }
log = "0.4.20"
pushtx = { version = "0.3.0", path = "../pushtx" }
pushtx = { version = "0.4.0", path = "../pushtx" }
thiserror = "1.0.61"
5 changes: 3 additions & 2 deletions pushtx-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ also works.
1. Resolve peers through DNS seeds.
2. Detect if Tor is present.
3. Connect to 10 random peers, through Tor if possible.
4. Broadcast the transaction.
5. Disconnect.
4. Broadcast the transaction to a single peer.
5. Wait until the transaction is seen on the network.
6. Disconnect.

### Usage

Expand Down
Binary file modified pushtx-cli/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 75 additions & 26 deletions pushtx-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use pushtx::*;

use core::panic;
use std::collections::HashSet;
use std::io::Read;
use std::path::PathBuf;

Expand All @@ -17,20 +18,22 @@ use clap::Parser;
///
/// More verbose (debug) output can be enabled by specifying the
/// -v or --verbose switch up to three times.
///
/// Copyright (c) 2024 Alfred Hodler <[email protected]>
#[derive(Parser)]
#[command(version, about, long_about, verbatim_doc_comment, name = "pushtx")]
struct Cli {
/// Tor mode. Default is `try`.
#[arg(short = 'm', long)]
tor_mode: Option<TorMode>,
/// Tor mode.
#[arg(short = 'm', long, default_value_t = TorMode::Try)]
tor_mode: TorMode,

/// Dry-run mode. Performs the whole process except the sending part.
#[arg(short, long)]
dry_run: bool,

/// Connect to testnet instead of mainnet.
#[arg(short, long)]
testnet: bool,
/// The network to use.
#[arg(short, long, default_value_t = Network::Mainnet)]
network: Network,

/// Zero or one paths to a file containing line-delimited hex encoded transactions
///
Expand Down Expand Up @@ -104,16 +107,13 @@ fn main() -> anyhow::Result<()> {
Err(err) => Err(err),
}?;

let txids: HashSet<_> = txs.iter().map(|tx| tx.txid()).collect();

let receiver = broadcast(
txs,
Opts {
use_tor: cli.tor_mode.unwrap_or_default().into(),
network: if cli.testnet {
Network::Testnet
} else {
Network::Mainnet
},
send_unsolicited: true,
use_tor: cli.tor_mode.into(),
network: cli.network.into(),
dry_run: cli.dry_run,
..Default::default()
},
Expand All @@ -124,23 +124,31 @@ fn main() -> anyhow::Result<()> {
Ok(Info::ResolvingPeers) => println!("* Resolving peers from DNS..."),
Ok(Info::ResolvedPeers(n)) => println!("* Resolved {n} peers"),
Ok(Info::ConnectingToNetwork { tor_status }) => {
let network = if cli.testnet { "testnet" } else { "mainnet" };
println!("* Connecting to the P2P network ({network})...");
println!("* Connecting to the P2P network ({})...", cli.network);
match tor_status {
Some(proxy) => println!(" - using Tor proxy found at {proxy}"),
None => println!(" - not using Tor"),
}
}
Ok(Info::Broadcast { peer }) => println!("* Successful broadcast to peer {}", peer),
Ok(Info::Done(Ok(Report {
broadcasts,
rejects,
}))) => {
println!("* Done! Broadcast to {broadcasts} peers with {rejects} rejections");
break Ok(());
Ok(Info::Broadcast { peer }) => println!("* Broadcast to peer {}", peer),
Ok(Info::Done(Ok(Report { success, rejects }))) => {
let difference: Vec<_> = txids.difference(&success).collect();
if difference.is_empty() {
println!("* Done! Broadcast successful");
break Ok(());
} else {
println!("* Failed to broadcast one or more transactions");
for missing in difference {
println!(" - failed: {missing}");
}
for (r_txid, r_reason) in rejects {
println!(" - reject: {r_txid}: {r_reason}");
}
break Err(Error::Partial.into());
}
}
Ok(Info::Done(Err(error))) => {
break Err(Error::FailedToBroadcast(error).into());
break Err(Error::Broadcast(error).into());
}
Err(_) => panic!("worker thread disconnected"),
}
Expand All @@ -156,14 +164,15 @@ enum Error {
#[error("Empty transaction set, did you pass at least one transaction?")]
EmptyTxSet,
#[error("Failed to broadcast: {0}")]
FailedToBroadcast(pushtx::Error),
Broadcast(pushtx::Error),
#[error("Failed to broadcast one or more transactions")]
Partial,
}

/// Determines how to use Tor.
#[derive(Debug, Default, Clone, clap::ValueEnum)]
#[derive(Debug, Clone, clap::ValueEnum)]
pub enum TorMode {
/// Use Tor if available. If not available, connect through clearnet.
#[default]
Try,
/// Do not use Tor even if available and running.
No,
Expand All @@ -180,3 +189,43 @@ impl From<TorMode> for pushtx::TorMode {
}
}
}

impl std::fmt::Display for TorMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
TorMode::Try => "try",
TorMode::No => "no",
TorMode::Must => "must",
};
write!(f, "{}", name)
}
}

/// The Bitcoin network to connect to.
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum Network {
Mainnet,
Testnet,
Signet,
}

impl From<Network> for pushtx::Network {
fn from(value: Network) -> Self {
match value {
Network::Mainnet => Self::Mainnet,
Network::Testnet => Self::Testnet,
Network::Signet => Self::Signet,
}
}
}

impl std::fmt::Display for Network {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
Network::Mainnet => "mainnet",
Network::Testnet => "testnet",
Network::Signet => "signet",
};
write!(f, "{}", name)
}
}
3 changes: 1 addition & 2 deletions pushtx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pushtx"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
authors = ["Alfred Hodler <[email protected]>"]
license = "MIT"
Expand All @@ -18,5 +18,4 @@ fastrand = "2.0.2"
hex = "0.4.3"
log = "0.4.20"
peerlink = { version = "0.8.0", features = ["socks"] }
port_check = "0.2.1"
sha3 = "0.10.8"
7 changes: 4 additions & 3 deletions pushtx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ also works.
1. Resolve peers through DNS seeds.
2. Detect if Tor is present.
3. Connect to 10 random peers, through Tor if possible.
4. Broadcast the transaction.
5. Disconnect.
4. Broadcast the transaction to a single peer.
5. Wait until the transaction is seen on the network.
6. Disconnect.

### Usage

Expand All @@ -31,7 +32,7 @@ also works.
loop {
match receiver.recv().unwrap() {
pushtx::Info::Done(Ok(report)) => {
println!("we successfully broadcast to {} peers", report.broadcasts);
println!("{} transactions broadcast successfully", report.success.len());
break;
}
pushtx::Info::Done(Err(err)) => {
Expand Down
Loading

0 comments on commit 880ccb8

Please sign in to comment.