181 lines
8.2 KiB
Bash
181 lines
8.2 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
#
|
||
|
|
# Build xmrblocks (the Onion Monero Blockchain Explorer) reproducibly.
|
||
|
|
#
|
||
|
|
# WHY THIS SCRIPT EXISTS
|
||
|
|
# ----------------------
|
||
|
|
# xmrblocks is not a client of monerod. It links 21 static libraries out of a
|
||
|
|
# compiled monero source tree and opens the LMDB directly, so building it means
|
||
|
|
# first compiling monero itself. Measured on Debian 13: a single cc1plus peaks
|
||
|
|
# at ~3.1 GB and a -j4 build peaks at ~5.5 GB. monerod-box has 2 GB, so it
|
||
|
|
# cannot build this at any level of parallelism - the binary has to be built
|
||
|
|
# elsewhere and shipped.
|
||
|
|
#
|
||
|
|
# Shipping a binary that only one machine can produce is how estates end up
|
||
|
|
# with artefacts nobody can rebuild. This script is the answer to that: it runs
|
||
|
|
# the whole build inside a debian:13 container, so any machine with Docker and
|
||
|
|
# ~6 GB of RAM produces the same binary. Nothing is installed on the host.
|
||
|
|
#
|
||
|
|
# WHAT IT GUARANTEES
|
||
|
|
# - the monero source is the GPG-signed release tarball, verified against
|
||
|
|
# binaryFate's key (the same chain roles/monero uses for the daemon)
|
||
|
|
# - the explorer is pinned to an exact commit
|
||
|
|
# - the one upstream patch is applied from an anchor, and the script FAILS
|
||
|
|
# if the anchor is missing rather than silently skipping it
|
||
|
|
#
|
||
|
|
# WHAT IT IS NOT
|
||
|
|
# Not bit-for-bit reproducible. Debian's toolchain, timestamps and build
|
||
|
|
# paths all leak in. It is repeatable, not deterministic; the MANIFEST
|
||
|
|
# records what went in so two builds can be compared.
|
||
|
|
#
|
||
|
|
# Usage: scripts/build-xmrblocks.sh [output-dir]
|
||
|
|
# Needs: docker, ~6 GB RAM available to it, ~2 GB disk
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
# --- Pins. Change these together and rebuild. --------------------------------
|
||
|
|
MONERO_VERSION="0.18.5.1"
|
||
|
|
MONERO_SRC_TARBALL="monero-source-v${MONERO_VERSION}.tar.bz2"
|
||
|
|
MONERO_SRC_URL="https://downloads.getmonero.org/cli/${MONERO_SRC_TARBALL}"
|
||
|
|
# From the GPG-signed hashes.txt. Verified by the container, not trusted from here.
|
||
|
|
MONERO_SRC_SHA256="9ec6ed0fd37db9d81cf7738a5f0536cf9aec6ed8ef8fd48649a59a6aaf20de3d"
|
||
|
|
BINARYFATE_FPR="81AC591FE9C4B65C5806AFC3F0AF4D462A0BDF92"
|
||
|
|
|
||
|
|
EXPLORER_REPO="https://github.com/moneroexamples/onion-monero-blockchain-explorer.git"
|
||
|
|
EXPLORER_COMMIT="29593b709edeb23daba035009ccd134a5862543a"
|
||
|
|
|
||
|
|
BUILD_IMAGE="debian:13" # must match the TARGET host's Debian release
|
||
|
|
JOBS="${JOBS:-4}"
|
||
|
|
|
||
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
|
|
SIGNING_KEY="${REPO_ROOT}/ansible/roles/monero/files/binaryfate.asc"
|
||
|
|
OUT_DIR="${1:-$HOME/personal_infra_artifacts/xmrblocks}"
|
||
|
|
|
||
|
|
command -v docker >/dev/null || { echo "docker is required" >&2; exit 1; }
|
||
|
|
[ -f "$SIGNING_KEY" ] || { echo "missing signing key: $SIGNING_KEY" >&2; exit 1; }
|
||
|
|
|
||
|
|
WORK="$(mktemp -d)"
|
||
|
|
trap 'rm -rf "$WORK"' EXIT
|
||
|
|
cp "$SIGNING_KEY" "$WORK/binaryfate.asc"
|
||
|
|
mkdir -p "$OUT_DIR"
|
||
|
|
|
||
|
|
echo "==> building xmrblocks"
|
||
|
|
echo " monero ${MONERO_VERSION} (signed source tarball)"
|
||
|
|
echo " explorer ${EXPLORER_COMMIT}"
|
||
|
|
echo " image ${BUILD_IMAGE}, -j${JOBS}"
|
||
|
|
echo " output ${OUT_DIR}"
|
||
|
|
|
||
|
|
cat > "$WORK/build.sh" <<INNER
|
||
|
|
#!/bin/bash
|
||
|
|
set -euo pipefail
|
||
|
|
export DEBIAN_FRONTEND=noninteractive
|
||
|
|
apt-get update -qq
|
||
|
|
# libasio-dev is NOT in upstream's dependency list. That list is Ubuntu-based
|
||
|
|
# and omits it; on Debian 13 Crow fails with "asio.hpp: No such file".
|
||
|
|
# libminiupnpc-dev is needed because monero stopped vendoring miniupnp as a
|
||
|
|
# submodule while the explorer's FindMonero.cmake still expects it in-tree.
|
||
|
|
apt-get install -y -qq --no-install-recommends \\
|
||
|
|
build-essential cmake pkg-config git ca-certificates curl bzip2 gnupg \\
|
||
|
|
libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind-dev \\
|
||
|
|
libreadline-dev libhidapi-dev libusb-1.0-0-dev libprotobuf-dev \\
|
||
|
|
protobuf-compiler libboost-all-dev libcurl4-openssl-dev \\
|
||
|
|
libasio-dev libminiupnpc-dev >/dev/null
|
||
|
|
|
||
|
|
cd /build
|
||
|
|
|
||
|
|
echo "--> verifying the monero source tarball"
|
||
|
|
export GNUPGHOME=/build/gnupg; mkdir -p \$GNUPGHOME; chmod 700 \$GNUPGHOME
|
||
|
|
gpg --batch --import /build/binaryfate.asc 2>/dev/null
|
||
|
|
gpg --batch --with-colons --fingerprint | awk -F: '/^fpr:/{print \$10}' \\
|
||
|
|
| grep -qx "${BINARYFATE_FPR}" || { echo "signing key is not binaryFate's"; exit 1; }
|
||
|
|
curl -sSL -o hashes.txt https://www.getmonero.org/downloads/hashes.txt
|
||
|
|
gpg --batch --status-fd 2 --output hashes-verified.txt --decrypt hashes.txt 2>gpg.err
|
||
|
|
grep -q "VALIDSIG.*${BINARYFATE_FPR}" gpg.err || { echo "hashes.txt did not verify"; exit 1; }
|
||
|
|
curl -sSL -o "${MONERO_SRC_TARBALL}" "${MONERO_SRC_URL}"
|
||
|
|
ACTUAL=\$(sha256sum "${MONERO_SRC_TARBALL}" | cut -d' ' -f1)
|
||
|
|
[ "\$ACTUAL" = "${MONERO_SRC_SHA256}" ] || { echo "tarball sha mismatch"; exit 1; }
|
||
|
|
grep -Fxq "\$ACTUAL ${MONERO_SRC_TARBALL}" hashes-verified.txt \\
|
||
|
|
|| { echo "tarball is not the one binaryFate signed"; exit 1; }
|
||
|
|
echo " source verified against the signed hashes"
|
||
|
|
|
||
|
|
tar xjf "${MONERO_SRC_TARBALL}"
|
||
|
|
SRC=/build/monero-source-v${MONERO_VERSION}
|
||
|
|
|
||
|
|
echo "--> building the monero libraries xmrblocks links against"
|
||
|
|
# Only the libraries. Upstream's Dockerfile runs a full \`make\`, which also
|
||
|
|
# builds monerod, both wallets and the test suite - none of which are linked
|
||
|
|
# into xmrblocks. USE_DEVICE_TREZOR=OFF drops the generated protobuf units too;
|
||
|
|
# an explorer has no use for hardware wallets.
|
||
|
|
mkdir -p \$SRC/build/release && cd \$SRC/build/release
|
||
|
|
cmake -D CMAKE_BUILD_TYPE=Release -D BUILD_TESTS=OFF -D USE_DEVICE_TREZOR=OFF ../.. >/dev/null
|
||
|
|
cmake --build . -j${JOBS} --target \\
|
||
|
|
common blocks cryptonote_basic cryptonote_core multisig cryptonote_protocol \\
|
||
|
|
daemonizer mnemonics epee lmdb device wallet-crypto blockchain_db ringct \\
|
||
|
|
wallet cncrypto easylogging version checkpoints randomx hardforks >/dev/null
|
||
|
|
|
||
|
|
echo "--> fetching the explorer at ${EXPLORER_COMMIT}"
|
||
|
|
cd /build
|
||
|
|
git clone -q "${EXPLORER_REPO}" explorer
|
||
|
|
cd explorer && git checkout -q "${EXPLORER_COMMIT}"
|
||
|
|
|
||
|
|
# monero v0.18.5.0 changed get_pruned_transaction_hash from returning the hash
|
||
|
|
# to returning bool with an out-param. The explorer still calls the old form,
|
||
|
|
# so it does not compile against the daemon we actually run. Anchored on the
|
||
|
|
# exact line: if upstream fixes this, the anchor vanishes and we stop loudly
|
||
|
|
# rather than building something unexpected.
|
||
|
|
OLD=' txd.hash = get_pruned_transaction_hash(tx, tx.prunable_hash);'
|
||
|
|
NEW=' get_pruned_transaction_hash(tx, tx.prunable_hash, txd.hash);'
|
||
|
|
if grep -qF "\$OLD" src/page.h; then
|
||
|
|
sed -i "s|\$OLD|\$NEW|" src/page.h
|
||
|
|
echo " applied get_pruned_transaction_hash patch"
|
||
|
|
elif grep -qF "\$NEW" src/page.h; then
|
||
|
|
echo " upstream already uses the new signature - patch not needed"
|
||
|
|
else
|
||
|
|
echo "PATCH ANCHOR NOT FOUND in src/page.h - upstream changed; review before building" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "--> building xmrblocks"
|
||
|
|
mkdir -p build && cd build
|
||
|
|
# Xmr_MINIUPNPC_LIBRARY: FindMonero.cmake searches for miniupnpc *inside* the
|
||
|
|
# monero build tree with NO_DEFAULT_PATH, but monero no longer vendors it.
|
||
|
|
# Point it at the system library instead of patching their cmake.
|
||
|
|
cmake -D MONERO_DIR=\$SRC \\
|
||
|
|
-D Xmr_MINIUPNPC_LIBRARY=/usr/lib/x86_64-linux-gnu/libminiupnpc.a .. >/dev/null
|
||
|
|
cmake --build . -j${JOBS} >/dev/null
|
||
|
|
[ -x xmrblocks ] || { echo "no xmrblocks produced"; exit 1; }
|
||
|
|
./xmrblocks --help >/dev/null 2>&1 || true
|
||
|
|
|
||
|
|
echo "--> collecting"
|
||
|
|
install -m 0755 xmrblocks /out/xmrblocks
|
||
|
|
rm -rf /out/templates && cp -r templates /out/templates
|
||
|
|
chmod -R a+rX /out/templates
|
||
|
|
|
||
|
|
{
|
||
|
|
echo "artifact: xmrblocks"
|
||
|
|
echo "built: \$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||
|
|
echo "build-image: ${BUILD_IMAGE}"
|
||
|
|
echo "monero-version: ${MONERO_VERSION}"
|
||
|
|
echo "monero-src-sha: ${MONERO_SRC_SHA256}"
|
||
|
|
echo "explorer-commit: ${EXPLORER_COMMIT}"
|
||
|
|
echo "cmake-flags: USE_DEVICE_TREZOR=OFF BUILD_TESTS=OFF CMAKE_BUILD_TYPE=Release"
|
||
|
|
echo "patches: get_pruned_transaction_hash -> out-param form (monero >= 0.18.5.0)"
|
||
|
|
echo "xmrblocks-sha256: \$(sha256sum /out/xmrblocks | cut -d' ' -f1)"
|
||
|
|
echo "templates-sha256: \$(find /out/templates -type f | sort | xargs sha256sum | sha256sum | cut -d' ' -f1)"
|
||
|
|
echo "needed-libs:"
|
||
|
|
objdump -p /out/xmrblocks 2>/dev/null | awk '/NEEDED/{print " " \$2}' | sort
|
||
|
|
} > /out/MANIFEST
|
||
|
|
INNER
|
||
|
|
chmod +x "$WORK/build.sh"
|
||
|
|
|
||
|
|
docker run --rm \
|
||
|
|
-v "$WORK":/build \
|
||
|
|
-v "$OUT_DIR":/out \
|
||
|
|
"$BUILD_IMAGE" bash /build/build.sh
|
||
|
|
|
||
|
|
echo
|
||
|
|
echo "==> done"
|
||
|
|
sed -n '1,12p' "$OUT_DIR/MANIFEST"
|
||
|
|
echo
|
||
|
|
echo " pin xmrblocks_sha256 in ansible/roles/xmrblocks/defaults/main.yml to:"
|
||
|
|
grep '^xmrblocks-sha256:' "$OUT_DIR/MANIFEST" | awk '{print " " $2}'
|