Skip to content

Commit

Permalink
Fixes and improvements (v0.3.0)
Browse files Browse the repository at this point in the history
- lib: rename `UseTor` to `TorMode`
- lib: allow custom user agent, time and blockheight during handshakes
- lib: change the `Info::Done` variant to enable error outcomes
- lib: bugfix, send the `Info::Done` event if Tor required but not found
- bin: replace the `no_tor` switch with the `tor_mode` enum
- bin: introduce the `-v`/`--verbose` switch to enable debug output
  • Loading branch information
alfred-hodler committed May 27, 2024
1 parent 6a31444 commit e77fe4d
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 87 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,26 @@ Install with Cargo: `cargo install pushtx-cli`
### Library

```rust
// our hex-encoded transaction that we want to parse and broadcast
// this is our hex-encoded transaction that we want to parse and broadcast
let tx = "6afcc7949dd500000....".parse().unwrap();

// we start the broadcast process and acquire a receiver to the info events
let receiver = pushtx::broadcast(vec![tx], pushtx::Opts::default());

// start reading info events until `Done` is received
let how_many = loop {
match receiver.recv().unwrap() {
pushtx::Info::Done { broadcasts, .. } => break broadcasts,
loop {
match receiver.recv().unwrap() {
pushtx::Info::Done(Ok(report)) => {
println!("we successfully broadcast to {} peers", report.broadcasts);
break;
}
pushtx::Info::Done(Err(err)) => {
println!("we failed to broadcast to any peers, reason = {err}");
break;
}
_ => {}
}
};

println!("we successfully broadcast to {how_many} peers");
}
```

### Disclaimer
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.2.4"
version = "0.3.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.2.4", path = "../pushtx" }
pushtx = { version = "0.3.0", path = "../pushtx" }
thiserror = "1.0.61"
109 changes: 71 additions & 38 deletions pushtx-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ use clap::Parser;
/// fresh Tor circuit. Running the Tor browser in the background
/// is usually sufficient for this to work.
///
/// Logging can be enabled by running the program with the
/// following environment variable: RUST_LOG=debug.
/// Available log levels are: trace, debug, info, warn, error.
/// More verbose (debug) output can be enabled by specifying the
/// -v or --verbose switch up to three times.
#[derive(Parser)]
#[command(version, about, long_about, verbatim_doc_comment)]
#[command(version, about, long_about, verbatim_doc_comment, name = "pushtx")]
struct Cli {
/// Connect through clearnet even if Tor is available.
#[arg(short, long)]
no_tor: bool,
/// Tor mode. Default is `try`.
#[arg(short = 'm', long)]
tor_mode: Option<TorMode>,

/// Dry-run mode. Performs the whole process except the sending part.
#[arg(short, long)]
Expand All @@ -33,17 +32,33 @@ struct Cli {
#[arg(short, long)]
testnet: bool,

/// Zero or one paths to a file containing line-delimited hex encoded or binary transactions
/// Zero or one paths to a file containing line-delimited hex encoded transactions
///
/// If not present, stdin is used instead (hex only, one tx per line).
#[arg(short = 'f', long = "file", value_name = "FILE")]
txs: Option<PathBuf>,

/// Print debug info (use multiple times for more verbosity; max 3)
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
}

fn main() -> anyhow::Result<()> {
env_logger::init();
let cli = Cli::parse();

let log_level = match cli.verbose {
0 => None,
1 => Some(log::Level::Info),
2 => Some(log::Level::Debug),
3.. => Some(log::Level::Trace),
};

if let Some(level) = log_level {
env_logger::Builder::default()
.filter_level(level.to_level_filter())
.init();
}

let txs: Result<Vec<_>, Error> = match cli.txs {
Some(path) => {
let mut contents = String::new();
Expand All @@ -55,16 +70,19 @@ fn main() -> anyhow::Result<()> {
.map(|line| pushtx::Transaction::from_hex(line).map_err(Into::into))
.collect()
}
None => std::io::stdin()
.lines()
.filter_map(|line| match line {
Ok(line) if !line.trim().is_empty() => {
Some(pushtx::Transaction::from_hex(line).map_err(Into::into))
}
Ok(_) => None,
Err(err) => Some(Err(Error::Io(err))),
})
.collect(),
None => {
eprintln!("Go ahead and paste some hex-encoded transactions (one per line) ... ");
std::io::stdin()
.lines()
.filter_map(|line| match line {
Ok(line) if !line.trim().is_empty() => {
Some(pushtx::Transaction::from_hex(line).map_err(Into::into))
}
Ok(_) => None,
Err(err) => Some(Err(Error::Io(err))),
})
.collect()
}
};

if cli.dry_run {
Expand All @@ -86,16 +104,10 @@ fn main() -> anyhow::Result<()> {
Err(err) => Err(err),
}?;

let use_tor = if cli.no_tor {
pushtx::UseTor::No
} else {
pushtx::UseTor::BestEffort
};

let receiver = broadcast(
txs,
Opts {
use_tor,
use_tor: cli.tor_mode.unwrap_or_default().into(),
network: if cli.testnet {
Network::Testnet
} else {
Expand All @@ -120,16 +132,15 @@ fn main() -> anyhow::Result<()> {
}
}
Ok(Info::Broadcast { peer }) => println!("* Successful broadcast to peer {}", peer),
Ok(Info::Done {
Ok(Info::Done(Ok(Report {
broadcasts,
rejects,
}) => {
if broadcasts > 0 {
println!("* Done! Broadcast to {broadcasts} peers with {rejects} rejections");
break Ok(());
} else {
break Err(Error::FailedToBroadcast.into());
}
}))) => {
println!("* Done! Broadcast to {broadcasts} peers with {rejects} rejections");
break Ok(());
}
Ok(Info::Done(Err(error))) => {
break Err(Error::FailedToBroadcast(error).into());
}
Err(_) => panic!("worker thread disconnected"),
}
Expand All @@ -138,12 +149,34 @@ fn main() -> anyhow::Result<()> {

#[derive(Debug, thiserror::Error)]
enum Error {
#[error("IO error while parsing transaction(s): {0}")]
#[error("IO error while reading transaction(s): {0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
#[error("Error while parsing transaction(s): {0}")]
Parse(#[from] pushtx::ParseTxError),
#[error("Empty transaction set, did you pass at least one transaction?")]
EmptyTxSet,
#[error("Failed to broadcast to any peers")]
FailedToBroadcast,
#[error("Failed to broadcast: {0}")]
FailedToBroadcast(pushtx::Error),
}

/// Determines how to use Tor.
#[derive(Debug, Default, 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,
/// Exclusively use Tor. If not available, do not broadcast.
Must,
}

impl From<TorMode> for pushtx::TorMode {
fn from(value: TorMode) -> Self {
match value {
TorMode::Try => Self::BestEffort,
TorMode::No => Self::No,
TorMode::Must => Self::Must,
}
}
}
2 changes: 1 addition & 1 deletion pushtx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pushtx"
version = "0.2.4"
version = "0.3.0"
edition = "2021"
authors = ["Alfred Hodler <[email protected]>"]
license = "MIT"
Expand Down
19 changes: 12 additions & 7 deletions pushtx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@ also works.
### Usage

```rust
// our hex-encoded transaction that we want to parse and broadcast
// this is our hex-encoded transaction that we want to parse and broadcast
let tx = "6afcc7949dd500000....".parse().unwrap();

// we start the broadcast process and acquire a receiver to the info events
let receiver = pushtx::broadcast(vec![tx], pushtx::Opts::default());

// start reading info events until `Done` is received
let how_many = loop {
match receiver.recv().unwrap() {
pushtx::Info::Done { broadcasts, .. } => break broadcasts,
loop {
match receiver.recv().unwrap() {
pushtx::Info::Done(Ok(report)) => {
println!("we successfully broadcast to {} peers", report.broadcasts);
break;
}
pushtx::Info::Done(Err(err)) => {
println!("we failed to broadcast to any peers, reason = {err}");
break;
}
_ => {}
}
};

println!("we successfully broadcast to {how_many} peers");
}
```

An executable is also available (`pushtx-cli`).
Expand Down
23 changes: 14 additions & 9 deletions pushtx/src/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::time::Duration;

use crate::handshake::{self, Handshake};
use crate::p2p::{self, Outbox, Receiver, Sender};
use crate::{net, seeds, FindPeerStrategy, Info, Opts, Transaction};
use crate::{net, seeds, Error, FindPeerStrategy, Info, Opts, Report, Transaction};
use bitcoin::p2p::message::NetworkMessage;
use bitcoin::p2p::message_blockdata::Inventory;
use crossbeam_channel::RecvTimeoutError;
Expand All @@ -31,9 +31,9 @@ impl Runner {
pub fn run(self) {
std::thread::spawn(move || {
let (must_use_tor, proxy) = match self.opts.use_tor {
crate::UseTor::No => (false, None),
crate::UseTor::BestEffort => (false, detect_tor_proxy()),
crate::UseTor::Exclusively => (true, detect_tor_proxy()),
crate::TorMode::No => (false, None),
crate::TorMode::BestEffort => (false, detect_tor_proxy()),
crate::TorMode::Must => (true, detect_tor_proxy()),
};

if self.opts.dry_run {
Expand All @@ -43,10 +43,11 @@ impl Runner {
log::info!("Tor proxy status: {:?}", proxy);
if proxy.is_none() && must_use_tor {
log::error!("Tor usage required but local proxy not found");
let _ = self.info_tx.send(Info::Done(Err(Error::TorNotFound)));
return;
}

let client = p2p::client(proxy, self.opts.network);
let client = p2p::client(proxy, self.opts.network, self.opts.ua);
let mut state = HashMap::new();

let _ = self.info_tx.send(Info::ResolvingPeers);
Expand Down Expand Up @@ -202,10 +203,14 @@ impl Runner {

std::thread::sleep(std::time::Duration::from_millis(500));
client.shutdown().join().unwrap().unwrap();
let _ = self.info_tx.send(Info::Done {
broadcasts,
rejects,
});
let done = match broadcasts.try_into() {
Ok(broadcasts) => Ok(Report {
broadcasts,
rejects,
}),
Err(_) => Err(Error::Timeout),
};
let _ = self.info_tx.send(Info::Done(done));
});
}
}
Expand Down
Loading

0 comments on commit e77fe4d

Please sign in to comment.