diff --git a/smite-ir/src/operation.rs b/smite-ir/src/operation.rs index 22a17899..a23ca837 100644 --- a/smite-ir/src/operation.rs +++ b/smite-ir/src/operation.rs @@ -230,7 +230,7 @@ pub enum Operation { RecvChannelReady, /// Mines the given number of blocks on the Bitcoin network. MineBlocks(u8), - /// Sign wallet inputs of the transaction and broadcast it via `bitcoin-cli`. + /// Sign wallet inputs of the transaction and broadcast it via `bitcoind`. /// Input: `FundingTransaction`. BroadcastTransaction, // -- Query: read state from outside the program -- diff --git a/smite-scenarios/src/executor.rs b/smite-scenarios/src/executor.rs index bf6f6b90..99938df7 100644 --- a/smite-scenarios/src/executor.rs +++ b/smite-scenarios/src/executor.rs @@ -6,7 +6,7 @@ use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use bitcoin::{OutPoint, ScriptBuf, Txid}; -use smite::bitcoin::{BitcoinCli, TxBlockPosition, Utxo}; +use smite::bitcoin::{BitcoindClient, TxBlockPosition, Utxo}; use smite::bolt::{ AcceptChannel, AnnouncementSignatures, ChannelAnnouncement, ChannelId, ChannelReady, ChannelReadyTlvs, ChannelUpdate, Features, FromMessage, FundingCreated, FundingSigned, Message, @@ -62,7 +62,7 @@ pub const RECV_IDLE_TIMEOUT: Duration = Duration::from_secs(1); /// reconfiguring or patching CLN to poll more frequently. pub const RECV_CHANNEL_READY_TIMEOUT: Duration = Duration::from_secs(5); -/// Abstraction over bitcoin-cli operations, allowing mock implementations in tests. +/// Abstraction over bitcoind operations, allowing mock implementations in tests. pub trait BitcoinRpc { /// Mines the given number of blocks, including any transactions in the /// `private_mempool` in the first block. @@ -98,33 +98,33 @@ pub trait BitcoinRpc { fn get_transaction_block_position(&mut self, txid: Txid) -> Option; } -impl BitcoinRpc for BitcoinCli { +impl BitcoinRpc for BitcoindClient { fn mine_blocks(&mut self, num_blocks: u8, private_mempool: &[String]) { - BitcoinCli::mine_blocks(self, num_blocks, private_mempool); + BitcoindClient::mine_blocks(self, num_blocks, private_mempool); } fn get_utxos(&mut self) -> Vec { - BitcoinCli::get_utxos(self) + BitcoindClient::get_utxos(self) } fn get_new_address_script_pubkey(&mut self) -> ScriptBuf { - BitcoinCli::get_new_address_script_pubkey(self) + BitcoindClient::get_new_address_script_pubkey(self) } fn sign_and_broadcast_tx(&mut self, tx: &bitcoin::Transaction) -> Option { - BitcoinCli::sign_and_broadcast_tx(self, tx) + BitcoindClient::sign_and_broadcast_tx(self, tx) } fn lock_utxos(&mut self, outpoints: &[OutPoint]) { - BitcoinCli::lock_utxos(self, outpoints); + BitcoindClient::lock_utxos(self, outpoints); } fn get_transaction_confirmations(&mut self, txid: Txid) -> u32 { - BitcoinCli::get_transaction_confirmations(self, txid) + BitcoindClient::get_transaction_confirmations(self, txid) } fn get_transaction_block_position(&mut self, txid: Txid) -> Option { - BitcoinCli::get_transaction_block_position(self, txid) + BitcoindClient::get_transaction_block_position(self, txid) } } @@ -239,7 +239,7 @@ pub struct Executor { /// Connection used to send and receive Lightning messages. conn: C, /// Interface to bitcoind for wallet and chain operations. - bitcoin_cli: B, + bitcoind_client: B, /// Interface for interacting with the target node through RPC. rpc: R, /// Immutable state captured during snapshot setup. @@ -268,13 +268,13 @@ pub struct Executor { } impl Executor { - /// Creates an executor with the given connection, bitcoin-cli handle, + /// Creates an executor with the given connection, bitcoind client, /// program context, and target RPC handle. Channel state and negotiations /// start empty. - pub fn new(conn: C, bitcoin_cli: B, rpc: R, context: ProgramContext) -> Self { + pub fn new(conn: C, bitcoind_client: B, rpc: R, context: ProgramContext) -> Self { Self { conn, - bitcoin_cli, + bitcoind_client, rpc, context, channel_states: HashMap::new(), @@ -311,7 +311,7 @@ impl Executor { /// - input variable index out of bounds /// - input variable refers to a void instruction /// - input variable has the wrong type - /// - `MineBlocks(0)` (panics inside `BitcoinCli::mine_blocks`) + /// - `MineBlocks(0)` (panics inside `BitcoindClient::mine_blocks`) /// - `LoadShutdownScript(AnySegwit { .. })` with an out-of-range version or /// program length (panics inside the encoder) /// - `LoadBytes` / `LoadFeatures` payload exceeding `MAX_MESSAGE_SIZE` (panics @@ -381,7 +381,7 @@ impl Executor { let ft = create_funding_transaction( &variables, &instr.inputs, - &mut self.bitcoin_cli, + &mut self.bitcoind_client, )?; Some(Variable::FundingTransaction(ft)) } @@ -530,7 +530,7 @@ impl Executor { } Operation::RecvChannelReady => { - if is_channel_ready_expected(&self.channel_states, &mut self.bitcoin_cli) { + if is_channel_ready_expected(&self.channel_states, &mut self.bitcoind_client) { log::debug!("[{:?}] RecvChannelReady: waiting", start.elapsed()); recv_channel_ready(&mut self.conn, &mut self.channel_states)?; log::debug!("[{:?}] RecvChannelReady: received", start.elapsed()); @@ -545,7 +545,7 @@ impl Executor { .into_iter() .map(|(_, hex)| hex) .collect(); - self.bitcoin_cli.mine_blocks(*v, &private_mempool); + self.bitcoind_client.mine_blocks(*v, &private_mempool); self.rpc.chain_sync(); self.mined_txids.extend(self.unmined_txids.drain()); log::debug!("[{:?}] MineBlocks: mined {} block(s)", start.elapsed(), v); @@ -564,7 +564,7 @@ impl Executor { // mempool so they can be mined later. Dedup on txid so the // same transaction broadcast again before then is queued // once, regardless of any change to its signed hex. - if let Some(hex) = self.bitcoin_cli.sign_and_broadcast_tx(&ft.tx) + if let Some(hex) = self.bitcoind_client.sign_and_broadcast_tx(&ft.tx) && !self.private_mempool.iter().any(|(t, _)| *t == txid) { self.private_mempool.push((txid, hex)); @@ -582,7 +582,7 @@ impl Executor { // message will simply fail on-chain validation, which is // the intended fuzzing behaviour for a valid but // unconfirmed program. - let scid = match self.bitcoin_cli.get_transaction_block_position(txid) { + let scid = match self.bitcoind_client.get_transaction_block_position(txid) { Some(pos) => { let funding_output_index = u16::try_from(ft.vout).expect("funding output index fits in u16"); @@ -1234,7 +1234,7 @@ fn recv_channel_ready( /// `minimum_depth` confirmations (as specified in the received `accept_channel`). fn is_channel_ready_expected( channel_states: &HashMap, - bitcoin_cli: &mut impl BitcoinRpc, + bitcoind_client: &mut impl BitcoinRpc, ) -> bool { channel_states.values().any(|state| { state.commitment.commitment_number == 0 @@ -1242,7 +1242,7 @@ fn is_channel_ready_expected( && state.is_funding_outpoint_valid && !state.was_funding_mined_prematurely && !state.sent_invalid_signature - && bitcoin_cli.get_transaction_confirmations(state.config.funding_outpoint.txid) + && bitcoind_client.get_transaction_confirmations(state.config.funding_outpoint.txid) >= state.config.minimum_depth }) } diff --git a/smite-scenarios/src/executor/tests/harness.rs b/smite-scenarios/src/executor/tests/harness.rs index 64ca233a..4557b2c8 100644 --- a/smite-scenarios/src/executor/tests/harness.rs +++ b/smite-scenarios/src/executor/tests/harness.rs @@ -43,10 +43,10 @@ impl Connection for MockConnection { } } -// Mocking BitcoinCli via MockBitcoinCli +// Mocking BitcoindClient via MockBitcoindClient #[derive(Default)] -pub struct MockBitcoinCli { +pub struct MockBitcoindClient { pub mine_blocks_calls: Vec, pub mined_private_mempool: Vec, pub broadcast_calls: Vec, @@ -56,7 +56,7 @@ pub struct MockBitcoinCli { confirmations: u32, } -impl BitcoinRpc for MockBitcoinCli { +impl BitcoinRpc for MockBitcoindClient { fn mine_blocks(&mut self, num_blocks: u8, private_mempool: &[String]) { self.mine_blocks_calls.push(num_blocks); self.mined_private_mempool = private_mempool.to_vec(); @@ -124,13 +124,13 @@ impl TargetRpc for MockTargetRpc { /// An [`Executor`] wired to a mock peer and a mock bitcoind. pub struct Fixture { - executor: Executor, + executor: Executor, } impl Fixture { /// A fixture with a silent peer and a wallet holding [`sample_utxo`]. pub fn new() -> Self { - let bitcoin_cli = MockBitcoinCli { + let bitcoind_client = MockBitcoindClient { utxos: vec![sample_utxo()], change_spk: sample_change_spk(), ..Default::default() @@ -138,7 +138,7 @@ impl Fixture { Self { executor: Executor::new( MockConnection::new(), - bitcoin_cli, + bitcoind_client, MockTargetRpc::default(), sample_context(), ), @@ -147,7 +147,7 @@ impl Fixture { /// Funds the wallet with `utxos` instead of the default [`sample_utxo`]. pub fn with_utxos(mut self, utxos: Vec) -> Self { - self.executor.bitcoin_cli.utxos = utxos; + self.executor.bitcoind_client.utxos = utxos; self } @@ -206,8 +206,8 @@ impl Fixture { } /// Returns the mock bitcoind the executor drives. - pub fn bitcoin(&self) -> &MockBitcoinCli { - &self.executor.bitcoin_cli + pub fn bitcoin(&self) -> &MockBitcoindClient { + &self.executor.bitcoind_client } /// Returns the mock RPC interface to the target. diff --git a/smite-scenarios/src/scenarios/ir.rs b/smite-scenarios/src/scenarios/ir.rs index 827bfff0..12768327 100644 --- a/smite-scenarios/src/scenarios/ir.rs +++ b/smite-scenarios/src/scenarios/ir.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; -use smite::bitcoin::BitcoinCli; +use smite::bitcoin::BitcoindClient; use smite::noise::NoiseConnection; use smite::scenarios::{Scenario, ScenarioError, ScenarioResult}; use smite::violation::Violation; @@ -20,13 +20,13 @@ use crate::targets::Target; /// mutators or generators; the executor panics on invariant violations /// (out-of-bounds variable refs, type mismatches, `MineBlocks(0)`, etc.). pub struct IrScenario> { - /// Executes IR programs and owns the connection, bitcoin-cli handle, + /// Executes IR programs and owns the connection, bitcoind client, /// program context, and the target's RPC handle. Created once before the /// snapshot and reused across fuzzing runs. /// /// Declared before `target` so the peer connection closes first and doesn't /// stall the target's shutdown. - executor: Executor, + executor: Executor, target: T, // S is only used for static dispatch on S::setup(), not stored. _phantom: PhantomData, @@ -36,8 +36,8 @@ impl> Scenario for IrScenario { fn new(_args: &[String]) -> Result { let target = T::start(T::Config::default())?; let (conn, context) = S::setup(&target)?; - let bitcoin_cli = target.bitcoin_cli().clone(); - let executor = Executor::new(conn, bitcoin_cli, target.rpc(), context); + let bitcoind_client = target.bitcoind_client().clone(); + let executor = Executor::new(conn, bitcoind_client, target.rpc(), context); Ok(Self { executor, target, diff --git a/smite-scenarios/src/targets.rs b/smite-scenarios/src/targets.rs index e7e3119b..7279e891 100644 --- a/smite-scenarios/src/targets.rs +++ b/smite-scenarios/src/targets.rs @@ -11,7 +11,7 @@ pub use cln::{ClnConfig, ClnRpc, ClnTarget}; pub use eclair::{EclairConfig, EclairRpc, EclairTarget}; pub use ldk::{LdkConfig, LdkRpc, LdkTarget}; pub use lnd::{LndConfig, LndRpc, LndTarget}; -use smite::bitcoin::BitcoinCli; +use smite::bitcoin::BitcoindClient; use smite::scenarios::TargetError; use bitcoin::secp256k1; @@ -76,8 +76,8 @@ pub trait Target: Sized { /// Target's RPC handle for executing commands. fn rpc(&self) -> Self::Rpc; - /// `bitcoin-cli` wrapper for the regtest `bitcoind` instance. - fn bitcoin_cli(&self) -> &BitcoinCli; + /// JSON-RPC client for the regtest `bitcoind` instance. + fn bitcoind_client(&self) -> &BitcoindClient; /// Check if target is still alive. Returns `Err(Crashed)` if dead. /// diff --git a/smite-scenarios/src/targets/bitcoind.rs b/smite-scenarios/src/targets/bitcoind.rs index 0cff647c..fbd5d7b5 100644 --- a/smite-scenarios/src/targets/bitcoind.rs +++ b/smite-scenarios/src/targets/bitcoind.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::time::Duration; -use smite::bitcoin::BitcoinCli; +use smite::bitcoin::BitcoindClient; use smite::process::ManagedProcess; use super::TargetError; @@ -63,7 +63,7 @@ pub fn resolve_data_dir() -> Result<(PathBuf, Option), Target pub fn start( config: &BitcoindConfig, data_dir: &Path, -) -> Result<(ManagedProcess, BitcoinCli), TargetError> { +) -> Result<(ManagedProcess, BitcoindClient), TargetError> { log::info!("Starting bitcoind..."); let bitcoind_dir = data_dir.join("bitcoind"); @@ -101,28 +101,18 @@ pub fn start( } let bitcoind = ManagedProcess::spawn(&mut cmd, "bitcoind")?; - let cli = BitcoinCli { - rpc_port: config.rpc_port, - bitcoind_dir, - }; + let mut client = BitcoindClient::new(config.rpc_port); // Wait for bitcoind to be ready log::info!("Waiting for bitcoind to be ready..."); for _ in 0..30 { - let status = cli - .run() - .arg("getblockchaininfo") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - - if status.is_ok_and(|s| s.success()) { + std::thread::sleep(Duration::from_secs(1)); + + if client.is_ready() { log::info!("bitcoind is ready"); - setup_wallet(&cli)?; - return Ok((bitcoind, cli)); + setup_wallet(&mut client)?; + return Ok((bitcoind, client)); } - - std::thread::sleep(Duration::from_secs(1)); } Err(TargetError::StartFailed( @@ -131,37 +121,18 @@ pub fn start( } /// Creates wallet and generates initial blocks. -fn setup_wallet(cli: &BitcoinCli) -> Result<(), TargetError> { - // Create wallet - let status = cli - .run() - .arg("createwallet") - .arg("default") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - - // command fails if wallet already exists (i.e. SMITE_DATA_DIR was mounted) - if !status.success() { - return Err(TargetError::StartFailed( - "failed to create wallet (does it already exist?)".into(), - )); - } - - // Generate initial blocks - let status = cli - .run() - .arg("-generate") - .arg(INITIAL_BLOCKS.to_string()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - - if !status.success() { - return Err(TargetError::StartFailed( - "failed to generate initial blocks".into(), - )); - } +fn setup_wallet(client: &mut BitcoindClient) -> Result<(), TargetError> { + // Fails if the wallet already exists (i.e. SMITE_DATA_DIR was mounted). + client.create_wallet("default").map_err(|e| { + TargetError::StartFailed(format!( + "failed to create wallet (does it already exist?): {e}" + )) + })?; + + let initial_blocks = u32::try_from(INITIAL_BLOCKS).expect("fits in u32"); + client + .generate(initial_blocks) + .map_err(|e| TargetError::StartFailed(format!("failed to generate initial blocks: {e}")))?; Ok(()) } diff --git a/smite-scenarios/src/targets/cln.rs b/smite-scenarios/src/targets/cln.rs index d706b8c4..0e22128d 100644 --- a/smite-scenarios/src/targets/cln.rs +++ b/smite-scenarios/src/targets/cln.rs @@ -18,7 +18,7 @@ use std::time::Duration; use bitcoin::secp256k1; use serde::Deserialize; -use smite::bitcoin::BitcoinCli; +use smite::bitcoin::BitcoindClient; use smite::process::ManagedProcess; use super::bitcoind; @@ -174,7 +174,7 @@ pub struct ClnTarget { pubkey: secp256k1::PublicKey, addr: SocketAddr, cln_dir: PathBuf, - bitcoin_cli: BitcoinCli, + bitcoind_client: BitcoindClient, #[allow(dead_code)] // TempDir auto-cleans on drop temp_dir: Option, } @@ -196,7 +196,7 @@ impl ClnTarget { let mut cmd = Command::new("lightningd"); // LD_PRELOAD the crash handler into lightningd and its subdaemons. - // Set only on lightningd (not lightning-cli/bitcoin-cli) to avoid + // Set only on lightningd (not lightning-cli) to avoid // interfering with helper processes. if let Ok(handler) = std::env::var("SMITE_CRASH_HANDLER") { cmd.env("LD_PRELOAD", handler); @@ -319,7 +319,7 @@ impl Target for ClnTarget { fn start(config: Self::Config) -> Result { let (data_path, temp_dir) = bitcoind::resolve_data_dir()?; - let (bitcoind, bitcoin_cli) = bitcoind::start(&config.bitcoind_config(), &data_path)?; + let (bitcoind, bitcoind_client) = bitcoind::start(&config.bitcoind_config(), &data_path)?; let (cln, pubkey, cln_dir) = Self::start_cln(&config, &data_path)?; let addr = SocketAddr::from(([127, 0, 0, 1], config.cln_p2p_port)); @@ -331,7 +331,7 @@ impl Target for ClnTarget { pubkey, addr, cln_dir, - bitcoin_cli, + bitcoind_client, temp_dir, }) } @@ -350,8 +350,8 @@ impl Target for ClnTarget { } } - fn bitcoin_cli(&self) -> &BitcoinCli { - &self.bitcoin_cli + fn bitcoind_client(&self) -> &BitcoindClient { + &self.bitcoind_client } fn check_alive(&mut self) -> Result<(), TargetError> { diff --git a/smite-scenarios/src/targets/eclair.rs b/smite-scenarios/src/targets/eclair.rs index a5525606..15c1130c 100644 --- a/smite-scenarios/src/targets/eclair.rs +++ b/smite-scenarios/src/targets/eclair.rs @@ -12,7 +12,7 @@ use std::time::Duration; use bitcoin::secp256k1; use serde::Deserialize; -use smite::bitcoin::BitcoinCli; +use smite::bitcoin::BitcoindClient; use smite::process::ManagedProcess; use super::bitcoind; @@ -84,7 +84,7 @@ pub struct EclairTarget { bitcoind: ManagedProcess, pubkey: secp256k1::PublicKey, addr: SocketAddr, - bitcoin_cli: BitcoinCli, + bitcoind_client: BitcoindClient, #[allow(dead_code)] // TempDir auto-cleans on drop temp_dir: Option, } @@ -212,7 +212,7 @@ impl Target for EclairTarget { fn start(config: Self::Config) -> Result { let (data_path, temp_dir) = bitcoind::resolve_data_dir()?; - let (bitcoind, bitcoin_cli) = bitcoind::start(&config.bitcoind_config(), &data_path)?; + let (bitcoind, bitcoind_client) = bitcoind::start(&config.bitcoind_config(), &data_path)?; let (eclair, pubkey) = Self::start_eclair(&config, &data_path)?; let addr = SocketAddr::from(([127, 0, 0, 1], config.eclair_p2p_port)); @@ -223,7 +223,7 @@ impl Target for EclairTarget { bitcoind, pubkey, addr, - bitcoin_cli, + bitcoind_client, temp_dir, }) } @@ -240,8 +240,8 @@ impl Target for EclairTarget { EclairRpc } - fn bitcoin_cli(&self) -> &BitcoinCli { - &self.bitcoin_cli + fn bitcoind_client(&self) -> &BitcoindClient { + &self.bitcoind_client } fn check_alive(&mut self) -> Result<(), TargetError> { diff --git a/smite-scenarios/src/targets/ldk.rs b/smite-scenarios/src/targets/ldk.rs index 9d5a5e05..7efb85d8 100644 --- a/smite-scenarios/src/targets/ldk.rs +++ b/smite-scenarios/src/targets/ldk.rs @@ -10,7 +10,7 @@ use std::path::Path; use std::process::{Command, Stdio}; use bitcoin::secp256k1; -use smite::bitcoin::BitcoinCli; +use smite::bitcoin::BitcoindClient; use smite::process::{ManagedProcess, send_sigusr1}; use super::bitcoind; @@ -88,7 +88,7 @@ pub struct LdkTarget { bitcoind: ManagedProcess, pubkey: secp256k1::PublicKey, addr: SocketAddr, - bitcoin_cli: BitcoinCli, + bitcoind_client: BitcoindClient, #[allow(dead_code)] // TempDir auto-cleans on drop temp_dir: Option, } @@ -162,7 +162,7 @@ impl Target for LdkTarget { fn start(config: Self::Config) -> Result { let (data_path, temp_dir) = bitcoind::resolve_data_dir()?; - let (bitcoind, bitcoin_cli) = bitcoind::start(&config.bitcoind_config(), &data_path)?; + let (bitcoind, bitcoind_client) = bitcoind::start(&config.bitcoind_config(), &data_path)?; let (ldk, pubkey) = Self::start_ldk(&config, &data_path)?; let addr = SocketAddr::from(([127, 0, 0, 1], config.ldk_p2p_port)); @@ -173,7 +173,7 @@ impl Target for LdkTarget { bitcoind, pubkey, addr, - bitcoin_cli, + bitcoind_client, temp_dir, }) } @@ -192,8 +192,8 @@ impl Target for LdkTarget { } } - fn bitcoin_cli(&self) -> &BitcoinCli { - &self.bitcoin_cli + fn bitcoind_client(&self) -> &BitcoindClient { + &self.bitcoind_client } fn check_alive(&mut self) -> Result<(), TargetError> { diff --git a/smite-scenarios/src/targets/lnd.rs b/smite-scenarios/src/targets/lnd.rs index d6613562..e2b7af9a 100644 --- a/smite-scenarios/src/targets/lnd.rs +++ b/smite-scenarios/src/targets/lnd.rs @@ -10,7 +10,7 @@ use std::time::Duration; use bitcoin::secp256k1; use serde::Deserialize; -use smite::bitcoin::BitcoinCli; +use smite::bitcoin::BitcoindClient; use smite::process::ManagedProcess; use super::bitcoind; @@ -102,7 +102,7 @@ pub struct LndTarget { coverage_pipes: Option, pubkey: secp256k1::PublicKey, addr: SocketAddr, - bitcoin_cli: BitcoinCli, + bitcoind_client: BitcoindClient, #[allow(dead_code)] // TempDir auto-cleans on drop temp_dir: Option, } @@ -286,7 +286,7 @@ impl Target for LndTarget { fn start(config: Self::Config) -> Result { let (data_path, temp_dir) = bitcoind::resolve_data_dir()?; - let (bitcoind, bitcoin_cli) = bitcoind::start(&config.bitcoind_config(), &data_path)?; + let (bitcoind, bitcoind_client) = bitcoind::start(&config.bitcoind_config(), &data_path)?; let (lnd, coverage_pipes, pubkey) = Self::start_lnd(&config, &data_path)?; let addr = SocketAddr::from(([127, 0, 0, 1], config.lnd_p2p_port)); @@ -298,7 +298,7 @@ impl Target for LndTarget { coverage_pipes, pubkey, addr, - bitcoin_cli, + bitcoind_client, temp_dir, }) } @@ -315,8 +315,8 @@ impl Target for LndTarget { LndRpc } - fn bitcoin_cli(&self) -> &BitcoinCli { - &self.bitcoin_cli + fn bitcoind_client(&self) -> &BitcoindClient { + &self.bitcoind_client } fn check_alive(&mut self) -> Result<(), TargetError> { diff --git a/smite/src/bitcoin.rs b/smite/src/bitcoin.rs index 250409aa..e94fb695 100644 --- a/smite/src/bitcoin.rs +++ b/smite/src/bitcoin.rs @@ -1,9 +1,8 @@ -//! This module implements utilities for interacting with regtest -//! `bitcoind` instances via `bitcoin-cli`. +//! JSON-RPC client for the regtest `bitcoind` instances started by targets. use std::cmp::Ordering; -use std::path::PathBuf; -use std::process::Command; +use std::io::{BufRead, BufReader, Read, Write}; +use std::net::{Ipv4Addr, SocketAddr, TcpStream}; use std::str::FromStr; use bitcoin::consensus::encode::serialize_hex; @@ -60,28 +59,210 @@ struct RawTransactionInfo { blockhash: Option, } -/// Connection info for invoking `bitcoin-cli` against the regtest `bitcoind` -/// started by a target. -#[derive(Debug, Clone)] -pub struct BitcoinCli { +/// `rpcuser:rpcpassword` credentials every target starts `bitcoind` with. +const RPC_CREDENTIALS: &str = "rpcuser:rpcpass"; + +/// `getrawtransaction` error code for a transaction unknown to the node. +const RPC_INVALID_ADDRESS_OR_KEY: i64 = -5; + +/// A JSON-RPC error returned by `bitcoind`. +#[derive(Debug, Clone, Deserialize)] +pub struct RpcError { + pub code: i64, + pub message: String, +} + +impl std::fmt::Display for RpcError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "error code: {}, message: {}", self.code, self.message) + } +} + +/// Why a JSON-RPC request produced no result. +enum RequestError { + /// `bitcoind` could not be reached or did not answer with JSON-RPC. + Transport(std::io::Error), + /// `bitcoind` answered with a JSON-RPC error. + Rpc(RpcError), +} + +/// Client for the regtest `bitcoind` started by a target. +/// +/// All calls go over a single HTTP/1.1 keep-alive connection to the JSON-RPC +/// server, so no process is spawned in the fuzzing loop. +#[derive(Debug)] +pub struct BitcoindClient { /// RPC port exposed by the regtest `bitcoind` instance. pub rpc_port: u16, - /// Path passed to `bitcoin-cli -datadir`. - pub bitcoind_dir: PathBuf, + /// Keep-alive connection to the JSON-RPC server, opened on first use and + /// reopened after `bitcoind` closes it (it drops idle connections after + /// `-rpcservertimeout`). + conn: Option>, +} + +impl Clone for BitcoindClient { + /// Clones the connection info only; the clone opens its own connection. + fn clone(&self) -> Self { + Self::new(self.rpc_port) + } } -impl BitcoinCli { - /// Creates a `bitcoin-cli` command preconfigured with the connection - /// arguments for this regtest node. +impl BitcoindClient { #[must_use] - pub fn run(&self) -> Command { - let mut cmd = Command::new("bitcoin-cli"); - cmd.arg("-regtest") - .arg(format!("-datadir={}", self.bitcoind_dir.display())) - .arg(format!("-rpcport={}", self.rpc_port)) - .arg("-rpcuser=rpcuser") - .arg("-rpcpassword=rpcpass"); - cmd + pub fn new(rpc_port: u16) -> Self { + Self { + rpc_port, + conn: None, + } + } + + /// Returns `true` once `bitcoind` answers RPCs, i.e. it is up and past + /// its warmup (during which every RPC is rejected with `-28`). + pub fn is_ready(&mut self) -> bool { + self.request("getblockchaininfo", &serde_json::json!([])) + .is_ok() + } + + /// Creates a wallet named `name`, which `bitcoind` then keeps loaded. + /// + /// # Errors + /// + /// Returns the JSON-RPC error, e.g. when a wallet of that name already + /// exists in the data directory. + pub fn create_wallet(&mut self, name: &str) -> Result<(), RpcError> { + self.call("createwallet", &serde_json::json!([name])) + .map(|_| ()) + } + + /// Sends one JSON-RPC request over the keep-alive connection, treating a + /// transport failure as a programmer or environment error. + /// + /// # Panics + /// + /// If `bitcoind` cannot be reached or answers with something that is not + /// a JSON-RPC response. Startup code that expects that uses + /// [`BitcoindClient::is_ready`] instead. + fn call( + &mut self, + method: &str, + params: &serde_json::Value, + ) -> Result { + match self.request(method, params) { + Ok(result) => Ok(result), + Err(RequestError::Rpc(e)) => Err(e), + Err(RequestError::Transport(e)) => panic!("bitcoind {method} request failed: {e}"), + } + } + + /// Sends one JSON-RPC request over the keep-alive connection. + /// + /// Returns the `result` field, or the server's JSON-RPC error. A + /// connection that `bitcoind` has closed in the meantime is reopened and + /// the request retried once. + fn request( + &mut self, + method: &str, + params: &serde_json::Value, + ) -> Result { + #[derive(Deserialize)] + struct RpcResponse { + #[serde(default)] + result: serde_json::Value, + error: Option, + } + + let request = serde_json::json!({ + "jsonrpc": "1.0", + "id": "smite", + "method": method, + "params": params, + }) + .to_string(); + + let body = match self.http_post(&request) { + Ok(body) => body, + Err(e) => { + // The server closes idle keep-alive connections; reconnect once. + log::debug!("bitcoind {method} failed ({e}), retrying on a fresh connection"); + self.conn = None; + match self.http_post(&request) { + Ok(body) => body, + Err(e) => { + self.conn = None; + return Err(RequestError::Transport(e)); + } + } + } + }; + + let response: RpcResponse = serde_json::from_slice(&body).map_err(|e| { + self.conn = None; + RequestError::Transport(std::io::Error::other(format!( + "invalid JSON-RPC response: {e}" + ))) + })?; + match response.error { + Some(error) => Err(RequestError::Rpc(error)), + None => Ok(response.result), + } + } + + /// Posts `body` to the JSON-RPC endpoint and returns the response body, + /// whatever the HTTP status: `bitcoind` reports JSON-RPC errors as HTTP + /// 500 with the JSON-RPC error in the body. + fn http_post(&mut self, body: &str) -> std::io::Result> { + let conn = if let Some(conn) = self.conn.as_mut() { + conn + } else { + let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, self.rpc_port)); + let stream = TcpStream::connect(addr)?; + stream.set_nodelay(true)?; + self.conn.insert(BufReader::new(stream)) + }; + + let request = format!( + "POST / HTTP/1.1\r\n\ + Host: localhost\r\n\ + Authorization: Basic {}\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n\ + \r\n{body}", + base64(RPC_CREDENTIALS.as_bytes()), + body.len(), + ); + conn.get_mut().write_all(request.as_bytes())?; + + // Status line, then headers up to the blank line. Only + // `Content-Length` matters: bitcoind never chunks its responses. + let mut line = String::new(); + let mut content_length = None; + loop { + line.clear(); + if conn.read_line(&mut line)? == 0 { + return Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof)); + } + let trimmed = line.trim_end_matches("\r\n"); + if trimmed.is_empty() { + break; + } + if let Some(value) = trimmed + .split_once(':') + .filter(|(name, _)| name.eq_ignore_ascii_case("content-length")) + .map(|(_, value)| value.trim()) + { + content_length = Some( + value + .parse::() + .map_err(|e| std::io::Error::other(format!("bad Content-Length: {e}")))?, + ); + } + } + + let len = content_length + .ok_or_else(|| std::io::Error::other("bitcoind response without Content-Length"))?; + let mut body = vec![0u8; len]; + conn.read_exact(&mut body)?; + Ok(body) } /// Mines the given number of blocks. @@ -92,11 +273,15 @@ impl BitcoinCli { /// /// # Panics /// - /// If the `bitcoin-cli -generate` or `generateblock` command fails to - /// execute or exits non-zero. - pub fn mine_blocks(&self, num_blocks: u8, private_mempool: &[String]) { + /// If `generatetoaddress` or `generateblock` fails, e.g. `MineBlocks(0)`. + pub fn mine_blocks(&mut self, num_blocks: u8, private_mempool: &[String]) { + let mine = |this: &mut Self, n: u8| { + this.generate(u32::from(n)) + .unwrap_or_else(|e| panic!("generatetoaddress {n} failed: {e}")); + }; + if private_mempool.is_empty() { - self.generate(num_blocks); + mine(self, num_blocks); return; } @@ -104,25 +289,23 @@ impl BitcoinCli { // remaining blocks normally. self.mine_block_including(private_mempool); if num_blocks > 1 { - self.generate(num_blocks - 1); + mine(self, num_blocks - 1); } } - /// Mines `num_blocks` blocks from the node's mempool via - /// `bitcoin-cli -generate`. - fn generate(&self, num_blocks: u8) { - let mine_out = self - .run() - .arg("-generate") - .arg(num_blocks.to_string()) - .output() - .expect("bitcoin-cli -generate should not fail"); - assert!( - mine_out.status.success(), - "bitcoin-cli -generate {} failed: {}", - num_blocks, - String::from_utf8_lossy(&mine_out.stderr) - ); + /// Mines `num_blocks` blocks from the node's mempool to a fresh wallet + /// address. + /// + /// # Errors + /// + /// Returns the JSON-RPC error, e.g. for `num_blocks == 0`. + pub fn generate(&mut self, num_blocks: u32) -> Result<(), RpcError> { + let address = self.get_new_address(); + self.call( + "generatetoaddress", + &serde_json::json!([num_blocks, address.to_string()]), + ) + .map(|_| ()) } /// Mines a single block containing the current mempool together with the @@ -134,62 +317,43 @@ impl BitcoinCli { /// /// # Panics /// - /// - If `bitcoin-cli getrawmempool`, `getnewaddress`, or `generateblock` - /// fails to execute or exits non-zero. - /// - If `getrawmempool` does not return valid JSON. + /// - If `getrawmempool`, `getnewaddress`, or `generateblock` fails. /// - If `getnewaddress` does not return a valid regtest address. /// - If any transaction in `private_mempool` is consensus-invalid. /// - If the combined transaction list contains a duplicate rawtx/txid or is /// not topologically ordered. - fn mine_block_including(&self, private_mempool: &[String]) { + fn mine_block_including(&mut self, private_mempool: &[String]) { let mut txs = self.get_raw_mempool(); txs.extend_from_slice(private_mempool); - let txs_json = serde_json::to_string(&txs).expect("tx list serializes to valid JSON"); let address = self.get_new_address(); - let gen_out = self - .run() - .arg("generateblock") - .arg(address.to_string()) - .arg(&txs_json) - .output() - .expect("bitcoin-cli generateblock should not fail"); - assert!( - gen_out.status.success(), - "bitcoin-cli generateblock failed: {}", - String::from_utf8_lossy(&gen_out.stderr) - ); + self.call( + "generateblock", + &serde_json::json!([address.to_string(), txs]), + ) + .unwrap_or_else(|e| panic!("generateblock failed: {e}")); } /// Returns the txids currently in the node's mempool. /// /// # Panics /// - /// - If `bitcoin-cli getrawmempool` fails to execute or exits non-zero. - /// - If the output is not valid JSON. - fn get_raw_mempool(&self) -> Vec { - let out = self - .run() - .arg("getrawmempool") - .output() - .expect("bitcoin-cli getrawmempool should not fail"); - assert!( - out.status.success(), - "bitcoin-cli getrawmempool failed: {}", - String::from_utf8_lossy(&out.stderr) - ); - serde_json::from_slice(&out.stdout).expect("getrawmempool should return valid JSON") + /// If `getrawmempool` fails or does not return a txid list. + fn get_raw_mempool(&mut self) -> Vec { + let result = self + .call("getrawmempool", &serde_json::json!([])) + .unwrap_or_else(|e| panic!("getrawmempool failed: {e}")); + serde_json::from_value(result).expect("getrawmempool should return a txid list") } /// Returns the wallet's spendable UTXOs, sorted deterministically. /// /// # Panics /// - /// - If `bitcoin-cli listunspent` fails to execute or exits non-zero. - /// - If the output is not valid JSON, or any entry has an invalid amount, - /// txid, or hex scriptPubKey. + /// - If `listunspent` fails. + /// - If any entry has an invalid amount, txid, or hex scriptPubKey. #[must_use] - pub fn get_utxos(&self) -> Vec { + pub fn get_utxos(&mut self) -> Vec { #[derive(Deserialize)] struct UnspentOutput { txid: String, @@ -200,19 +364,11 @@ impl BitcoinCli { spendable: bool, } - let utxo_out = self - .run() - .arg("listunspent") - .output() - .expect("bitcoin-cli listunspent should not fail"); - assert!( - utxo_out.status.success(), - "bitcoin-cli listunspent failed: {}", - String::from_utf8_lossy(&utxo_out.stderr) - ); - + let result = self + .call("listunspent", &serde_json::json!([])) + .unwrap_or_else(|e| panic!("listunspent failed: {e}")); let utxos: Vec = - serde_json::from_slice(&utxo_out.stdout).expect("listunspent should return valid JSON"); + serde_json::from_value(result).expect("listunspent should return a UTXO list"); let mut spendable: Vec = utxos .into_iter() @@ -240,10 +396,10 @@ impl BitcoinCli { /// /// # Panics /// - /// - If `bitcoin-cli getnewaddress` fails to execute or exits non-zero. - /// - If the output is not valid UTF-8 or not a valid regtest address. + /// - If `getnewaddress` fails. + /// - If the result is not a valid regtest address. #[must_use] - pub fn get_new_address_script_pubkey(&self) -> ScriptBuf { + pub fn get_new_address_script_pubkey(&mut self) -> ScriptBuf { self.get_new_address().script_pubkey() } @@ -251,22 +407,16 @@ impl BitcoinCli { /// /// # Panics /// - /// - If `bitcoin-cli getnewaddress` fails to execute or exits non-zero. - /// - If the output is not valid UTF-8 or not a valid regtest address. - fn get_new_address(&self) -> Address { - let addr_out = self - .run() - .arg("getnewaddress") - .output() - .expect("bitcoin-cli getnewaddress should not fail"); - assert!( - addr_out.status.success(), - "bitcoin-cli getnewaddress failed: {}", - String::from_utf8_lossy(&addr_out.stderr) - ); - - let addr_str = String::from_utf8(addr_out.stdout).expect("bitcoin address is valid UTF-8"); - Address::from_str(addr_str.trim()) + /// - If `getnewaddress` fails. + /// - If the result is not a valid regtest address. + fn get_new_address(&mut self) -> Address { + let result = self + .call("getnewaddress", &serde_json::json!([])) + .unwrap_or_else(|e| panic!("getnewaddress failed: {e}")); + let addr_str = result + .as_str() + .expect("getnewaddress should return a string"); + Address::from_str(addr_str) .and_then(|a| a.require_network(Network::Regtest)) .expect("getnewaddress should return a valid address") } @@ -284,17 +434,12 @@ impl BitcoinCli { /// /// # Panics /// - /// - If `bitcoin-cli signrawtransactionwithwallet` fails to execute or - /// exits non-zero. - /// - If the sign output is not valid JSON. - /// - If signing returns `complete=false`. - /// - If `bitcoin-cli sendrawtransaction` fails to execute. + /// - If `signrawtransactionwithwallet` fails or returns `complete=false`. /// - If the broadcast is rejected for any reason other than a below-dust /// output or a below-minimum relay feerate. - /// - If a successful broadcast does not return a valid UTF-8 txid. /// - If the broadcasted txid does not match the given transaction's txid. #[must_use] - pub fn sign_and_broadcast_tx(&self, tx: &Transaction) -> Option { + pub fn sign_and_broadcast_tx(&mut self, tx: &Transaction) -> Option { #[derive(Deserialize)] struct SignRawTransactionResponse { hex: String, @@ -311,53 +456,35 @@ impl BitcoinCli { let tx_hex = serialize_hex(tx); - let signed_out = self - .run() - .arg("signrawtransactionwithwallet") - .arg(&tx_hex) - .output() - .expect("bitcoin-cli signrawtransactionwithwallet should not fail"); - assert!( - signed_out.status.success(), - "bitcoin-cli signrawtransactionwithwallet failed: {}", - String::from_utf8_lossy(&signed_out.stderr) - ); - - let signed_tx: SignRawTransactionResponse = serde_json::from_slice(&signed_out.stdout) - .expect("signrawtransactionwithwallet should return valid JSON"); + let result = self + .call("signrawtransactionwithwallet", &serde_json::json!([tx_hex])) + .unwrap_or_else(|e| panic!("signrawtransactionwithwallet failed: {e}")); + let signed_tx: SignRawTransactionResponse = serde_json::from_value(result) + .expect("signrawtransactionwithwallet should return hex and complete"); assert!( signed_tx.complete, "signrawtransactionwithwallet returned complete=false" ); - let broadcast_out = self - .run() - .arg("sendrawtransaction") - .arg(&signed_tx.hex) - // Disable the high-feerate cap and accept any fee rate for broadcast. - .arg("0") - .output() - .expect("bitcoin-cli sendrawtransaction should not fail"); - - if !broadcast_out.status.success() { - let stderr = String::from_utf8_lossy(&broadcast_out.stderr); + // The `0` disables the high-feerate cap so any fee rate is broadcast. + let broadcast = self.call("sendrawtransaction", &serde_json::json!([signed_tx.hex, 0])); + let broadcast_txid = match broadcast { + Ok(result) => result, // If the feerate is below the default minimum relay feerate, or any // output is below its dust threshold, return the transactions so // they can be mined directly, bypassing mempool policy. - if stderr.contains("tx with dust output") || stderr.contains("min relay fee not met") { + Err(e) if e.message.contains("dust") || e.message.contains("min relay fee not met") => { return Some(signed_tx.hex); } - panic!("bitcoin-cli sendrawtransaction failed: {stderr}"); - } + Err(e) => panic!("sendrawtransaction failed: {e}"), + }; // Safe because bitcoind descriptor wallets currently default to native // SegWit, so signing does not alter the txid computed from the unsigned // Transaction. - let broadcast_txid = String::from_utf8(broadcast_out.stdout) - .expect("sendrawtransaction should return a valid UTF-8 txid"); assert_eq!( - broadcast_txid.trim(), - txid.to_string(), + broadcast_txid.as_str(), + Some(txid.to_string().as_str()), "sendrawtransaction returned unexpected txid" ); @@ -376,9 +503,8 @@ impl BitcoinCli { /// /// # Panics /// - /// If the `bitcoin-cli lockunspent` command fails to execute or exits - /// non-zero. - pub fn lock_utxos(&self, outpoints: &[OutPoint]) { + /// If `lockunspent` fails. + pub fn lock_utxos(&mut self, outpoints: &[OutPoint]) { #[derive(Serialize)] struct LockOutpoint { txid: String, @@ -396,20 +522,8 @@ impl BitcoinCli { vout: o.vout, }) .collect(); - let locks_json = serde_json::to_string(&locks).expect("outpoints serialize to valid JSON"); - - let lock_out = self - .run() - .arg("lockunspent") - .arg("false") - .arg(&locks_json) - .output() - .expect("bitcoin-cli lockunspent should not fail"); - assert!( - lock_out.status.success(), - "bitcoin-cli lockunspent failed: {}", - String::from_utf8_lossy(&lock_out.stderr) - ); + self.call("lockunspent", &serde_json::json!([false, locks])) + .unwrap_or_else(|e| panic!("lockunspent failed: {e}")); } /// Calls `getrawtransaction 1` and returns the parsed response, or @@ -419,24 +533,16 @@ impl BitcoinCli { /// /// - If the command fails to execute. /// - If the command succeeds but its output is not valid JSON. - fn get_raw_transaction_info(&self, txid: Txid) -> Option { - let tx_out = self - .run() - .arg("getrawtransaction") - .arg(txid.to_string()) - .arg("1") - .output() - .expect("bitcoin-cli getrawtransaction should not fail"); - - // A non-zero exit means the transaction is unknown to the node. - if !tx_out.status.success() { - return None; - } - - let tx_info: RawTransactionInfo = serde_json::from_slice(&tx_out.stdout) - .expect("getrawtransaction should return valid JSON"); - - Some(tx_info) + fn get_raw_transaction_info(&mut self, txid: Txid) -> Option { + let result = match self.call( + "getrawtransaction", + &serde_json::json!([txid.to_string(), 1]), + ) { + Ok(result) => result, + Err(e) if e.code == RPC_INVALID_ADDRESS_OR_KEY => return None, + Err(e) => panic!("getrawtransaction failed: {e}"), + }; + Some(serde_json::from_value(result).expect("getrawtransaction should return tx info")) } /// Returns the number of confirmations for the transaction with the given @@ -445,10 +551,9 @@ impl BitcoinCli { /// /// # Panics /// - /// - If the `bitcoin-cli getrawtransaction` command fails to execute. - /// - If the command succeeds but its output is not valid JSON. + /// If `getrawtransaction` fails for any reason other than an unknown txid. #[must_use] - pub fn get_transaction_confirmations(&self, txid: Txid) -> u32 { + pub fn get_transaction_confirmations(&mut self, txid: Txid) -> u32 { self.get_raw_transaction_info(txid) .map_or(0, |info| info.confirmations) } @@ -462,12 +567,11 @@ impl BitcoinCli { /// /// # Panics /// - /// - If `bitcoin-cli getrawtransaction` or `getblock` fails to execute. - /// - If either command succeeds but its output is not valid JSON. + /// - If `getrawtransaction` or `getblock` fails. /// - If `getblock` returns a block whose transaction list does not contain /// the queried txid (would indicate an inconsistent bitcoind state). #[must_use] - pub fn get_transaction_block_position(&self, txid: Txid) -> Option { + pub fn get_transaction_block_position(&mut self, txid: Txid) -> Option { #[derive(Deserialize)] struct GetBlockResponse { height: u32, @@ -480,22 +584,11 @@ impl BitcoinCli { // confirmed. let blockhash = self.get_raw_transaction_info(txid)?.blockhash?; - let block_out = self - .run() - .arg("getblock") - .arg(&blockhash) - .arg("1") - .output() - .expect("bitcoin-cli getblock should not fail"); - assert!( - block_out.status.success(), - "bitcoin-cli getblock {} failed: {}", - blockhash, - String::from_utf8_lossy(&block_out.stderr) - ); - + let result = self + .call("getblock", &serde_json::json!([blockhash, 1])) + .unwrap_or_else(|e| panic!("getblock {blockhash} failed: {e}")); let block: GetBlockResponse = - serde_json::from_slice(&block_out.stdout).expect("getblock should return valid JSON"); + serde_json::from_value(result).expect("getblock should return height and tx list"); let txid_str = txid.to_string(); let tx_index = block @@ -511,3 +604,38 @@ impl BitcoinCli { }) } } + +/// Standard base64 with padding, enough for the `Authorization: Basic` header. +fn base64(input: &[u8]) -> String { + const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let mut out = String::with_capacity(input.len().div_ceil(3) * 4); + for chunk in input.chunks(3) { + let mut buf = [0u8; 3]; + buf[..chunk.len()].copy_from_slice(chunk); + let n = u32::from_be_bytes([0, buf[0], buf[1], buf[2]]); + for i in 0..4 { + if i <= chunk.len() { + let idx = usize::try_from((n >> (18 - 6 * i)) & 0x3f).expect("6-bit index"); + out.push(char::from(ALPHABET[idx])); + } else { + out.push('='); + } + } + } + out +} + +#[cfg(test)] +mod tests { + use super::base64; + + #[test] + fn base64_matches_rfc4648_vectors() { + assert_eq!(base64(b""), ""); + assert_eq!(base64(b"f"), "Zg=="); + assert_eq!(base64(b"fo"), "Zm8="); + assert_eq!(base64(b"foo"), "Zm9v"); + assert_eq!(base64(b"foob"), "Zm9vYg=="); + assert_eq!(base64(b"rpcuser:rpcpass"), "cnBjdXNlcjpycGNwYXNz"); + } +} diff --git a/smite/src/lib.rs b/smite/src/lib.rs index cee57d9a..7b1b2607 100644 --- a/smite/src/lib.rs +++ b/smite/src/lib.rs @@ -6,7 +6,7 @@ //! provides the building blocks that scenarios and targets are built on. //! //! # Modules -//! - [`bitcoin`] - Utilities for interacting with `bitcoind` instances via `bitcoin-cli`. +//! - [`bitcoin`] - JSON-RPC client for `bitcoind` instances. //! - [`bolt`] - BOLT message encoding and decoding. //! - [`channel_tx`] - BOLT 3 channel transaction construction (funding and commitment). //! - [`noise`] - BOLT 8 `Noise_XK` encrypted transport. diff --git a/workloads/cln/Dockerfile b/workloads/cln/Dockerfile index 5c202b6f..a4e85763 100644 --- a/workloads/cln/Dockerfile +++ b/workloads/cln/Dockerfile @@ -65,7 +65,6 @@ WORKDIR /tmp RUN wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ tar -xzf bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ mv bitcoin-${BITCOIN_VERSION}/bin/bitcoind /usr/local/bin/bitcoind && \ - mv bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli /usr/local/bin/bitcoin-cli && \ rm -rf bitcoin-${BITCOIN_VERSION}* # Clone CLN and build with AFL instrumentation. @@ -166,9 +165,8 @@ COPY --from=builder /usr/local/bin/lightningd /usr/local/bin/lightningd COPY --from=builder /usr/local/bin/lightning-cli /usr/local/bin/lightning-cli COPY --from=builder /usr/local/libexec/c-lightning/ /usr/local/libexec/c-lightning/ -# Copy Bitcoin Core binaries +# Copy bitcoind COPY --from=builder /usr/local/bin/bitcoind /usr/local/bin/bitcoind -COPY --from=builder /usr/local/bin/bitcoin-cli /usr/local/bin/bitcoin-cli # Copy crash handlers and cln-scenario binary COPY --from=builder /nyx-crash-handler.so /crash-handler.so / @@ -176,7 +174,7 @@ COPY --from=builder /smite/target/release/cln_${SCENARIO} /cln-scenario # Default to the local crash handler; init.sh overrides with the Nyx version. # ClnTarget forwards this as LD_PRELOAD on lightningd only, so it doesn't -# interfere with helper processes (lightning-cli, bitcoin-cli). +# interfere with helper processes (lightning-cli). ENV SMITE_CRASH_HANDLER=/crash-handler.so # ASan options for CLN binaries (instrumented with -fsanitize=address): diff --git a/workloads/cln/Dockerfile.coverage b/workloads/cln/Dockerfile.coverage index 105a39f9..1f545762 100644 --- a/workloads/cln/Dockerfile.coverage +++ b/workloads/cln/Dockerfile.coverage @@ -56,7 +56,6 @@ WORKDIR /tmp RUN wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ tar -xzf bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ mv bitcoin-${BITCOIN_VERSION}/bin/bitcoind /usr/local/bin/bitcoind && \ - mv bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli /usr/local/bin/bitcoin-cli && \ rm -rf bitcoin-${BITCOIN_VERSION}* # Clone CLN and build with LLVM source-based coverage instrumentation. @@ -131,9 +130,8 @@ COPY --from=builder /usr/local/bin/lightningd /usr/local/bin/lightningd COPY --from=builder /usr/local/bin/lightning-cli /usr/local/bin/lightning-cli COPY --from=builder /usr/local/libexec/c-lightning/ /usr/local/libexec/c-lightning/ -# Copy Bitcoin Core binaries +# Copy bitcoind COPY --from=builder /usr/local/bin/bitcoind /usr/local/bin/bitcoind -COPY --from=builder /usr/local/bin/bitcoin-cli /usr/local/bin/bitcoin-cli # Copy the cln-scenario binary COPY --from=builder /smite/target/release/cln_${SCENARIO} /cln-scenario diff --git a/workloads/eclair/Dockerfile b/workloads/eclair/Dockerfile index 3f070778..33a17704 100644 --- a/workloads/eclair/Dockerfile +++ b/workloads/eclair/Dockerfile @@ -24,7 +24,6 @@ WORKDIR /tmp RUN wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ tar -xzf bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ mv bitcoin-${BITCOIN_VERSION}/bin/bitcoind /usr/local/bin/bitcoind && \ - mv bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli /usr/local/bin/bitcoin-cli && \ rm -rf bitcoin-${BITCOIN_VERSION}* # Clone and build Eclair. @@ -102,9 +101,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Copy Eclair distribution (bin/eclair-node.sh + lib/*.jar) COPY --from=builder /opt/eclair /opt/eclair -# Copy Bitcoin Core binaries +# Copy bitcoind COPY --from=builder /usr/local/bin/bitcoind /usr/local/bin/bitcoind -COPY --from=builder /usr/local/bin/bitcoin-cli /usr/local/bin/bitcoin-cli # Copy coverage agent and JNI shared library COPY --from=builder /eclair-sancov/target/eclair-sancov-0.0.0.jar /eclair-sancov.jar diff --git a/workloads/eclair/Dockerfile.coverage b/workloads/eclair/Dockerfile.coverage index e6a41a36..4cad9256 100644 --- a/workloads/eclair/Dockerfile.coverage +++ b/workloads/eclair/Dockerfile.coverage @@ -26,7 +26,6 @@ WORKDIR /tmp RUN wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ tar -xzf bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ mv bitcoin-${BITCOIN_VERSION}/bin/bitcoind /usr/local/bin/bitcoind && \ - mv bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli /usr/local/bin/bitcoin-cli && \ rm -rf bitcoin-${BITCOIN_VERSION}* # Download JaCoCo for coverage instrumentation and report generation. @@ -77,9 +76,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Copy Eclair distribution (bin/eclair-node.sh + lib/*.jar) COPY --from=builder /opt/eclair /opt/eclair -# Copy Bitcoin Core binaries +# Copy bitcoind COPY --from=builder /usr/local/bin/bitcoind /usr/local/bin/bitcoind -COPY --from=builder /usr/local/bin/bitcoin-cli /usr/local/bin/bitcoin-cli # Copy JaCoCo agent and CLI JARs COPY --from=builder /jacoco/lib/jacocoagent.jar /jacocoagent.jar diff --git a/workloads/ldk/Dockerfile b/workloads/ldk/Dockerfile index 585c5c59..e73f97f6 100644 --- a/workloads/ldk/Dockerfile +++ b/workloads/ldk/Dockerfile @@ -39,7 +39,6 @@ WORKDIR /tmp RUN wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ tar -xzf bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ mv bitcoin-${BITCOIN_VERSION}/bin/bitcoind /usr/local/bin/bitcoind && \ - mv bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli /usr/local/bin/bitcoin-cli && \ rm -rf bitcoin-${BITCOIN_VERSION}* # Build ldk-node-wrapper with AFL instrumentation. @@ -98,9 +97,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ COPY --from=builder /ldk-wrapper/target/release/ldk-node-wrapper /usr/local/bin/ldk-node-wrapper COPY --from=builder /nyx-crash-handler.so /crash-handler.so / -# Copy Bitcoin Core binaries +# Copy bitcoind COPY --from=builder /usr/local/bin/bitcoind /usr/local/bin/bitcoind -COPY --from=builder /usr/local/bin/bitcoin-cli /usr/local/bin/bitcoin-cli # Copy the ldk-scenario binary COPY --from=builder /smite/target/release/ldk_${SCENARIO} /ldk-scenario diff --git a/workloads/ldk/Dockerfile.coverage b/workloads/ldk/Dockerfile.coverage index 54a1f9a7..48144216 100644 --- a/workloads/ldk/Dockerfile.coverage +++ b/workloads/ldk/Dockerfile.coverage @@ -35,7 +35,6 @@ WORKDIR /tmp RUN wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ tar -xzf bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ mv bitcoin-${BITCOIN_VERSION}/bin/bitcoind /usr/local/bin/bitcoind && \ - mv bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli /usr/local/bin/bitcoin-cli && \ rm -rf bitcoin-${BITCOIN_VERSION}* # Build ldk-node-wrapper with LLVM source-based coverage instrumentation. @@ -81,9 +80,8 @@ RUN ldconfig # Copy ldk-node-wrapper binary (coverage-instrumented) COPY --from=builder /ldk-wrapper/target/release/ldk-node-wrapper /usr/local/bin/ldk-node-wrapper -# Copy Bitcoin Core binaries +# Copy bitcoind COPY --from=builder /usr/local/bin/bitcoind /usr/local/bin/bitcoind -COPY --from=builder /usr/local/bin/bitcoin-cli /usr/local/bin/bitcoin-cli # Copy the ldk-scenario binary COPY --from=builder /smite/target/release/ldk_${SCENARIO} /ldk-scenario diff --git a/workloads/lnd/Dockerfile b/workloads/lnd/Dockerfile index dc2671ef..4c577eac 100644 --- a/workloads/lnd/Dockerfile +++ b/workloads/lnd/Dockerfile @@ -50,7 +50,6 @@ WORKDIR /tmp RUN wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ tar -xzf bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ mv bitcoin-${BITCOIN_VERSION}/bin/bitcoind /usr/local/bin/bitcoind && \ - mv bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli /usr/local/bin/bitcoin-cli && \ rm -rf bitcoin-${BITCOIN_VERSION}* # Build lncli without sancov.go @@ -89,9 +88,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ COPY --from=builder /lnd/cmd/lnd/lnd /usr/local/bin/lnd COPY --from=builder /lnd/cmd/lncli/lncli /usr/local/bin/lncli -# Copy Bitcoin Core binaries +# Copy bitcoind COPY --from=builder /usr/local/bin/bitcoind /usr/local/bin/bitcoind -COPY --from=builder /usr/local/bin/bitcoin-cli /usr/local/bin/bitcoin-cli # Copy the lnd-scenario binary COPY --from=builder /smite/target/release/lnd_${SCENARIO} /lnd-scenario diff --git a/workloads/lnd/Dockerfile.coverage b/workloads/lnd/Dockerfile.coverage index 146d23fe..5a27dbc9 100644 --- a/workloads/lnd/Dockerfile.coverage +++ b/workloads/lnd/Dockerfile.coverage @@ -37,7 +37,6 @@ WORKDIR /tmp RUN wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ tar -xzf bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz && \ mv bitcoin-${BITCOIN_VERSION}/bin/bitcoind /usr/local/bin/bitcoind && \ - mv bitcoin-${BITCOIN_VERSION}/bin/bitcoin-cli /usr/local/bin/bitcoin-cli && \ rm -rf bitcoin-${BITCOIN_VERSION}* # Build LND with Go's native coverage instrumentation. @@ -80,9 +79,8 @@ COPY --from=builder /lnd/cmd/lncli/lncli /usr/local/bin/lncli # Copy LND source (needed for go tool cover -html to generate annotated report) COPY --from=builder /lnd /lnd -# Copy Bitcoin Core binaries +# Copy bitcoind COPY --from=builder /usr/local/bin/bitcoind /usr/local/bin/bitcoind -COPY --from=builder /usr/local/bin/bitcoin-cli /usr/local/bin/bitcoin-cli # Copy the lnd-scenario binary COPY --from=builder /smite/target/release/lnd_${SCENARIO} /lnd-scenario