Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions Dockerfile.libbitcoin
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
FROM debian:bookworm

# ============================================================================
# libbitcoin-server with AFL++ instrumentation
# ============================================================================
#
# This Dockerfile builds libbitcoin-server (version3 branch) with AFL++
# coverage instrumentation.
#
# Build: docker build -f Dockerfile.libbitcoin -t fuzzamoto-libbitcoin .
# Run: docker run -it -v $(pwd):/fuzzamoto fuzzamoto-libbitcoin
# Binary: /opt/libbitcoin/bin/bs
# ============================================================================

ARG LLVM_V=19

# ------ Install LLVM toolchain ------

RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common \
gnupg \
ca-certificates \
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 15CF4D18AF4F7421 \
&& apt-add-repository "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-${LLVM_V} main" \
&& rm -rf /var/lib/apt/lists/*

# ------ Install build dependencies ------

RUN apt-get update && apt-get install -y --no-install-recommends \
# AFL++ build dependencies
build-essential \
ninja-build \
lld-${LLVM_V} \
llvm-${LLVM_V} \
llvm-${LLVM_V}-dev \
clang-${LLVM_V} \
# Download tools
curl \
wget \
git \
# libbitcoin build dependencies
libtool \
autotools-dev \
automake \
autoconf \
cmake \
pkg-config \
libssl-dev \
libzmq3-dev \
libicu-dev \
&& rm -rf /var/lib/apt/lists/*

# ------ Build AFL++ ------

ENV LLVM_CONFIG=llvm-config-${LLVM_V}
WORKDIR /
RUN git clone --depth 1 https://github.com/AFLplusplus/AFLplusplus \
&& cd AFLplusplus \
&& make PERFORMANCE=1 -j$(nproc) \
&& rm -rf .git

# ------ Build Boost 1.76.0 with AFL++ instrumentation ------

ARG LIBBITCOIN_PREFIX=/opt/libbitcoin
ARG BOOST_VERSION=1_76_0

WORKDIR /tmp

# Download and extract Boost
RUN wget -q https://archives.boost.io/release/1.76.0/source/boost_${BOOST_VERSION}.tar.bz2 \
&& tar -xjf boost_${BOOST_VERSION}.tar.bz2 \
&& rm boost_${BOOST_VERSION}.tar.bz2

WORKDIR /tmp/boost_${BOOST_VERSION}

# Create clang symlinks (Boost bootstrap needs unversioned names)
RUN ln -s /usr/bin/clang-${LLVM_V} /usr/local/bin/clang \
&& ln -s /usr/bin/clang++-${LLVM_V} /usr/local/bin/clang++

# Bootstrap b2 and configure AFL++ as a clang toolset variant
RUN ./bootstrap.sh --with-toolset=clang --prefix=${LIBBITCOIN_PREFIX} \
&& echo 'using clang : afl : /AFLplusplus/afl-clang-fast++ ;' > ~/user-config.jam

# Build only the Boost libraries required by libbitcoin version3
# -Wno-enum-constexpr-conversion: Required for Clang 16+ with Boost.MPL
RUN ./b2 \
toolset=clang-afl \
variant=release \
threading=multi \
link=static \
cxxflags="-std=c++17 -Wno-enum-constexpr-conversion" \
--with-chrono \
--with-date_time \
--with-filesystem \
--with-iostreams \
--with-locale \
--with-log \
--with-program_options \
--with-regex \
--with-system \
--with-thread \
--with-test \
-sICU_PATH=/usr \
-sNO_BZIP2=1 \
-sNO_ZSTD=1 \
-j$(nproc) \
-d0 \
install \
&& rm -rf /tmp/boost_${BOOST_VERSION}

# ------ Build libbitcoin-server with AFL++ instrumentation ------

ENV CC=/AFLplusplus/afl-clang-fast
ENV CXX=/AFLplusplus/afl-clang-fast++
ENV CXXFLAGS="-Wno-enum-constexpr-conversion"
ENV BOOST_ROOT=${LIBBITCOIN_PREFIX}

# Clone version3 branch (stable, compatible with Boost 1.76)
RUN git clone --depth 1 --branch version3 \
https://github.com/libbitcoin/libbitcoin-server.git /libbitcoin-server

WORKDIR /libbitcoin-server

# Build libbitcoin and all dependencies with AFL++ instrumentation
# --build-zmq: Debian Bookworm has 4.3.4, libbitcoin requires >= 4.3.5
ARG LIBBITCOIN_BUILD_DIR=/tmp/libbitcoin-build
RUN ./install.sh \
--prefix=${LIBBITCOIN_PREFIX} \
--with-boost=${LIBBITCOIN_PREFIX} \
--build-secp256k1 \
--build-zmq \
--disable-shared \
--without-tests \
--with-icu \
--build-dir=${LIBBITCOIN_BUILD_DIR} \
&& rm -rf ${LIBBITCOIN_BUILD_DIR}

# Verify AFL++ instrumentation and binary functionality
RUN nm ${LIBBITCOIN_PREFIX}/bin/bs | grep -q "__afl" \
&& ${LIBBITCOIN_PREFIX}/bin/bs --help > /dev/null

# ------ Runtime configuration ------

ENV LIBBITCOIN_PATH=${LIBBITCOIN_PREFIX}/bin/bs
ENV CC=clang-${LLVM_V}
ENV CXX=clang++-${LLVM_V}

# Install Rust toolchain for building fuzzamoto
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

RUN git config --global --add safe.directory /fuzzamoto

WORKDIR /fuzzamoto

# ============================================================================
# Usage:
# # Test libbitcoin-server starts correctly:
# /fuzzamoto/scripts/test-libbitcoin.sh
#
# # Build and run fuzzamoto integration tests:
# cargo test --release --package fuzzamoto -- libbitcoin --ignored
#
# # Build the libbitcoin scenario:
# cargo build --release --package fuzzamoto-scenarios --bin scenario-libbitcoin-generic
# ============================================================================
4 changes: 4 additions & 0 deletions fuzzamoto-scenarios/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ path = "bin/rpc_generic.rs"
[[bin]]
name = "scenario-ir"
path = "bin/ir.rs"

[[bin]]
name = "scenario-libbitcoin-generic"
path = "bin/libbitcoin_generic.rs"
13 changes: 13 additions & 0 deletions fuzzamoto-scenarios/bin/libbitcoin_generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use fuzzamoto::{
fuzzamoto_main,
scenarios::{
Scenario, ScenarioInput, ScenarioResult, generic::TestCase,
libbitcoin_generic::LibbitcoinGenericScenario,
},
targets::LibbitcoinTarget,
};

fuzzamoto_main!(
LibbitcoinGenericScenario::<fuzzamoto::connections::V1Transport, LibbitcoinTarget>,
TestCase
);
123 changes: 123 additions & 0 deletions fuzzamoto/src/scenarios/libbitcoin_generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use crate::{
connections::{Connection, ConnectionType, HandshakeOpts, Transport},
scenarios::generic::{Action, TestCase},
scenarios::{Scenario, ScenarioResult},
targets::Target,
test_utils,
};

use bitcoin::{consensus::encode, p2p::message::NetworkMessage, p2p::message_blockdata::Inventory};

/// `LibbitcoinGenericScenario` tests the P2P interface of libbitcoin-server.
///
/// Unlike `GenericScenario`, this only uses inbound connections since libbitcoin
/// does not support dynamic peer management for outbound connections.
pub struct LibbitcoinGenericScenario<TX: Transport, T: Target<TX>> {
pub target: T,
pub connections: Vec<Connection<TX>>,
pub time: u64,
_phantom: std::marker::PhantomData<(TX, T)>,
}

impl<TX: Transport, T: Target<TX>> LibbitcoinGenericScenario<TX, T> {
fn from_target(mut target: T) -> Result<Self, String> {
let genesis_block = bitcoin::blockdata::constants::genesis_block(bitcoin::Network::Regtest);
let time = genesis_block.header.time as u64;

// libbitcoin has no mocktime support, this is a no-op
let _ = target.set_mocktime(time);

// Create inbound connections
// libbitcoin does not support wtxidrelay (BIP339), addrv2 (BIP155), or erlay (BIP330)
let mut connections = Vec::new();
for _ in 0..4 {
let mut conn = target.connect(ConnectionType::Inbound)?;
conn.version_handshake(HandshakeOpts {
time: time as i64,
relay: true,
starting_height: 0,
wtxidrelay: false,
addrv2: false,
erlay: false,
})?;
connections.push(conn);
}

// Mine initial chain of 200 blocks
let mut prev_hash = genesis_block.block_hash();
let mut current_time = time;

for height in 1..=200 {
current_time += 1;
let block = test_utils::mining::mine_block(prev_hash, height, current_time as u32)?;
connections[0].send(&("block".to_string(), encode::serialize(&block)))?;
prev_hash = block.block_hash();
}

// Sync all connections
for conn in connections.iter_mut() {
conn.ping()?;
}

// Announce tip on all connections
for conn in connections.iter_mut() {
let inv = NetworkMessage::Inv(vec![Inventory::Block(prev_hash)]);
conn.send_and_recv(&("inv".to_string(), encode::serialize(&inv)), false)?;
}

Ok(Self {
target,
time: current_time,
connections,
_phantom: std::marker::PhantomData,
})
}
}

impl<'a, TX: Transport, T: Target<TX>> Scenario<'a, TestCase> for LibbitcoinGenericScenario<TX, T> {
fn new(args: &[String]) -> Result<Self, String> {
let target = T::from_path(&args[1])?;
Self::from_target(target)
}

fn run(&mut self, testcase: TestCase) -> ScenarioResult {
for action in testcase.actions {
match action {
Action::Connect { .. } => {
// Dynamic connections not supported for libbitcoin
}
Action::Message {
from,
command,
data,
} => {
if self.connections.is_empty() {
continue;
}
let idx = from as usize % self.connections.len();
let _ = self.connections[idx].send(&(command.to_string(), data));
}
Action::SetMocktime { time } => {
// No-op for libbitcoin (no mocktime support)
let _ = self.target.set_mocktime(time);
}
Action::AdvanceTime { seconds } => {
self.time += seconds as u64;
let _ = self.target.set_mocktime(self.time);
}
}
}

// Sync all connections
for conn in self.connections.iter_mut() {
let _ = conn.ping();
}

// Check target is still alive
if let Err(e) = self.target.is_alive() {
return ScenarioResult::Fail(format!("Target is not alive: {}", e));
}

ScenarioResult::Ok
}
}
1 change: 1 addition & 0 deletions fuzzamoto/src/scenarios/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod generic;
pub mod libbitcoin_generic;

/// `ScenarioInput` is a trait for scenario input types
pub trait ScenarioInput<'a>: Sized {
Expand Down
Loading