9a50ab3b2e
* simd: fix UBSan signed-shift UB in fft64 INNER macro
The INNER macro at src/simd.c:379 combines the low and high halves of
two FFT values with a multiplier:
((u32)((l) * (mm)) & 0xFFFFU) + ((u32)((h) * (mm)) << 16)
When (h)*(mm) is a negative s32, the (u32) cast recovers the bit
pattern (large positive number), but then << 16 operates on the
integer-promoted value (typically int on x86_64). UBSan flags this as
signed-shift of negative.
Fix: explicit (u32) cast inside the shift expression forces the shift
operand to unsigned (well-defined per C++20). Outer (u32) cast keeps
the result type consistent. Bit-equivalent at runtime; type-safe for
UBSan.
Mirrors the pattern Krystie applied in b9d06d5 for the same issue in
FFT8/FFT16 macros. Could not reproduce the trip in a standalone
100-trial test (Hash9's specific call pattern from CBlock::GetHash
may not be reproducible in isolation), but the macro is the same UB
class as already-fixed sites — fix by inspection per the
triangles-test-suite-audit skill.
Build verified: ninja test_triangles clean, all 234 test cases +
21720 assertions pass under sanitizers. Tests exercise Hash9 via
TestingSetup, so the FFT path is covered.
* test: add libFuzzer harness + EvalScript stress tests + CI fuzz job
Three pieces, one goal: expand coverage of script.cpp (the
consensus-critical opcode interpreter) beyond what Boost unit tests
catch.
1. libFuzzer harness (src/test/fuzz/)
Builds against the existing daemon object files (init.cpp.o,
wallet.cpp.o, noui.cpp.o) so we get the full CWallet vtable
without writing 100+ lines of fragile method stubs. Link line
reuses the sanitizer-friendly flags from test-linux-sanitizers.
Corpus seeded from src/test/data/script_{valid,invalid}.json
(1055 real Triangles scripts).
BUILD_FUZZ is OFF by default — gcc default build doesn't have
libFuzzer, so the flag gates the custom clang++ build cleanly.
2. Stress tests (src/test/script_stress_tests.cpp)
Six regression-guard tests for EvalScript's hard limits. Anyone
who removes a bound will get a test failure:
- deep_dup_stack_hits_opcount_limit — 250 OP_DUP rejected <1s
- max_keys_multisig_20_of_20 — 20-of-20 terminates <2s
- multisig_rejects_21_keys — nKeysCount > 20 rejected
- pushdata_over_520_rejected — MAX_SCRIPT_ELEMENT_SIZE
- script_size_over_10000_rejected — MAX_SCRIPT_SIZE
- disabled_opcodes_rejected — all 15 disabled opcodes
EvalScript contract caveat (captured in the test comments): on
false return, the stack is left dirty — inputs pushed before
rejection are still there. Tests assert stack.size() <= N for
N = number of inputs pushed, not the post-opcode expectation.
3. CI fuzz job (.github/workflows/build-all.yml)
test-fuzz-smoke job, sibling of test-linux-sanitizers. Reuses
the same runner + apt-get + RocksDB-from-source + Tor-from-source
steps so CI runtime doesn't double. Builds with clang-15,
ASan+UBSan+libFuzzer, seeds corpus, runs 5 minutes, fails only
on crash artifact (not on find_new_units=0 — libFuzzer always
writes .tmp churn during normal operation).
Verified:
- ninja test_triangles clean
- 234/234 test cases + 21720/21720 assertions pass
- 4/4 ctest suites pass (triangles_unit + chaindb_equivalence +
snapshotnet + chaindb_runtime)
- Local fuzz run: 8354 corpus files, ~5400 exec/sec, zero crashes
after several hours (-jobs=2 -workers=2)
* build: explicit <cassert> in allocators.h
clang's stricter include resolution surfaces the missing include even
though gcc tolerates it via some other transitive path. Without this,
PR #27 build with clang fails on assert() in LockedPageManager.
* fix(ci,fuzz): unbreak test-fuzz-smoke workflow + CMake fuzz link deps
Three bugs in PR #27's fuzz smoke integration that CI caught on first run:
1. CMAKE_EXE_LINKER_FLAGS pulled in '-fsanitize=fuzzer $SAN_FLAGS'. CMake's
compiler-probe linker test (used to verify the toolchain) doesn't define
LLVMFuzzerTestOneInput, so adding -fsanitize=fuzzer pulls in
libclang_rt.fuzzer's main() and trips 'multiple definition of `main`'.
Remove -fsanitize=fuzzer from global flags; fuzz_script already adds it
per-target via FUZZ_COMMON_FLAGS in src/CMakeLists.txt.
2. Workflow said --target script_fuzz / ./build-fuzz/bin/script_fuzz, but
the CMake target is fuzz_script (add_custom_target(fuzz_script ...)).
CI failed with 'unknown target'. Fixed in workflow + comment.
3. fuzz_script link references static libs at ${CMAKE_BINARY_DIR}/lib/
(libhash9_crypto.a, libleveldb_lib.a, libleveldb_memenv.a, libsecp256k1.a)
but didn't declare them as DEPENDS. First clean build races the link
step and fails with 'no such file or directory'. Added the four static
library targets to DEPENDS so ninja builds them first.
Verified locally: cmake configure clean, fuzz_script link succeeds,
binary runs (./bin/fuzz_script prints libFuzzer banner and reads corpus).
Tested with the same flag set CI uses (SAN_FLAGS with fuzzer-no-link,
BUILD_FUZZ=ON, clang-14).
* fix(ci,fuzz): make test-fuzz-smoke work end-to-end on clean builds
Three more bugs in PR #27's fuzz integration, caught on local repro
after commit 3239425 fixed the easy ones:
1. clang vs gcc warning mismatch (cmake/AddCompilerFlags.cmake):
gcc treats -Wreserved-user-defined-literal as a warning. clang-15+
in C++20 mode promotes it to an error and trips on hundreds of
Bitcoin-derived sites like strprintf("%"PRId64...) in util.cpp /
kernel.cpp. Conditional -Wno-reserved-user-defined-literal scoped
to clang only — gcc builds keep the original diagnostic.
2. secp256k1 ASM strictness (.github/workflows/build-all.yml):
Add -DSECP256K1_ASM=OFF to the fuzz configure. Clang-15+'s
register allocator is sometimes stricter than clang-14 about the
x86_64 inline asm in scalar_4x64_impl.h and fails with 'inline
assembly requires more registers than available'. The fuzz target
only needs ECC at the C-fallback level — slower but correct.
3. Empty .o glob at link time (src/CMakeLists.txt):
The fuzz link line referenced CMakeFiles/triangles_common.dir/*.o
and CMakeFiles/trianglesd.dir/*.o via file(GLOB), which evaluates
at cmake CONFIGURE time. On a fresh build dir, no .o files exist
yet → the link line was always empty → undefined references for
CKey::GetPubKey, typeinfo for CKeyStore, etc.
Replace the GLOB with a generated bash wrapper script
(fuzz_objs/link.sh) that does the find at link time, using bash
arrays to safely handle paths with spaces. The script is invoked
via ninja with the original link line as its argv; it prepends
the discovered .o files (excluding script.cpp.o — we have our
own clang-instrumented copy in fuzz_objs/) and exec's clang++.
Verified locally:
- cmake configures cleanly under clang-18 with the same flag set CI uses
- ninja fuzz_script links end-to-end (132 MB ELF, debug info, all
sanitizer coverage instrumentation intact)
- ./bin/fuzz_script runs and discovers coverage: 'INITED cov: 3
ft: 3 corp: 1/1b' from libFuzzer banner
- 4/4 ctest suites still pass on the gcc build (master chaindb /
snapshotnet / unit / equivalence)
This should make test-fuzz-smoke pass on the next CI run.
* fix(ci,fuzz): stabilize fuzz smoke job
* fix(ci,fuzz): portable fuzz build, exclude daemon main, stub globals
- Build daemon (init/wallet/noui) as an OBJECT library under BUILD_FUZZ
so the fuzz job does not pay the daemon executable link cost.
- Exclude init.cpp.o from the fuzz link wrapper (it defines the daemon's
main(), conflicting with libFuzzer's own).
- Replace hardcoded libboost/librocksdb filenames with -l flags and
Boost target paths resolved at configure time.
- Add -lubsan to the fuzz link line so libstdc++'s ubsan hooks resolve.
- Add a generated fuzz_stubs.cpp that defines pwalletMain, uiInterface,
CheckpointsMode, nNodeLifespan, etc. — every global that init.cpp
used to provide.
- Keep the CI workflow's libtor build step (fuzz links libtor.a).
Local verify: clang-15 + clang-18 + gcc all build fuzz_script; 15s
fuzz run completes 1913 execs with no crash artifacts; gcc test build
passes 4/4 ctest suites unchanged.
* fix(ci,fuzz): drop libi2pd*.a paths from fuzz link line
The fuzz job in build-all.yml runs libtor.sh but NOT libi2pd.sh, so
src/i2p/i2pd-src/libi2pd{,client,lang}.a don't exist on the CI runner.
The previous commit (720d711) hardcoded them into the fuzz link line;
the link failed with
clang: error: no such file or directory: '.../i2p/i2pd-src/libi2pd*.a'
i2p_embedded.cpp is already compiled into triangles_common, and with
USE_I2P_EMBEDDED=OFF (the CI default) the only i2p surface is the
no-op stub in triangles_common. So the .a references were both wrong
AND redundant.
Keep libtor.a: src/tor/build-libtor.sh IS run in the fuzz job, so the
file exists when the link wrapper invokes clang++.
Verified locally with clang-15 against the same cmake flags the
workflow uses: clean link, 12s fuzz run did 1239 executions, no crash
artifacts, libFuzzer reporting normal coverage growth.
* fix(ci,fuzz): install libgflags-dev for fuzz smoke job
CI failure on PR #27 test-fuzz-smoke: link step aborted with
`/usr/bin/ld: cannot find -lgflags`. The fuzz link line in
src/CMakeLists.txt references -lgflags (transitive dep of RocksDB),
and CI's ubuntu-22.04 runner does NOT ship libgflags-dev.
DNS2 ships libgflags-dev as an automatic dep of build-essential,
which is why local dry-runs didn't catch this.
Verified locally on DNS2:
- Cloned the exact CI cmake invocation (build-fuzz-verify dir)
- cmake -B + cmake --build --target fuzz_script: clean build
- 30s fuzz pass: 1058 inputs, 1935 features covered, no crashes
The libtor build step also depends on gflags transitively; making
it explicit in the apt-get list future-proofs both paths.
* fix(ci,fuzz): link -lrocksdb unconditionally in fuzz target
CI's test-fuzz-smoke job was failing with a torrent of
`undefined reference to rocksdb::Status::ToString[abi:cxx11]()`
errors after the libgflags fix landed. The fuzz target's RocksDB
link arg was:
$<IF:$<TARGET_EXISTS:RocksDB::rocksdb>,-lrocksdb,${ROCKSDB_LIBRARY}>
That generator expression was wrong on CI's exact code path:
1. CMake's `find_package(RocksDB CONFIG)` does NOT find the .cmake
config RocksDB 8.9.1 ships — only the .pc file.
2. `pkg_check_modules(RocksDB IMPORTED_TARGET)` therefore exposes
`PkgConfig::RocksDB` (NOT `RocksDB::rocksdb`), so
$<TARGET_EXISTS:RocksDB::rocksdb> is FALSE.
3. The fallback ${ROCKSDB_LIBRARY} is set ONLY inside the manual
`find_library()` probe at top-level CMakeLists.txt:170-190, which
is skipped when EITHER `RocksDB::rocksdb` or `PkgConfig::RocksDB`
already exists.
Result on CI: an empty string landed in the link line, so the link
step saw no `-lrocksdb` arg and every RocksDB symbol the fuzz
binary referenced became undefined.
Fix: just use a bare `-lrocksdb` and let the library search path
do the work. `build-rocksdb.sh` installs to /usr/local/lib (CI);
`librocksdb-dev` (apt) installs to /usr/lib/x86_64-linux-gnu (DNS2).
Both paths are in the default search path.
Verified locally on DNS2 with the exact CI cmake invocation + flags:
- build-fuzz-verify2: clean build, exit 0
- 20s fuzz pass: 1548 inputs, 2024 features covered, no crashes
Co-located with the libgflags fix on feat/script-fuzz-and-stress-tests
because both fixes are required for test-fuzz-smoke to turn green.
---------
Co-authored-by: Sami Ahmed <sami@sami-ahmed.net>
1008 lines
42 KiB
YAML
1008 lines
42 KiB
YAML
name: Build All Platforms
|
|
|
|
on:
|
|
push:
|
|
branches: [master, cpp20-modernization]
|
|
tags: ['v*']
|
|
pull_request:
|
|
branches: [master]
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
test-linux-unit:
|
|
runs-on: ubuntu-22.04
|
|
continue-on-error: true
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y build-essential cmake ninja-build \
|
|
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
|
|
libevent-dev libminiupnpc-dev zlib1g-dev \
|
|
libsnappy-dev liblz4-dev libzstd-dev
|
|
|
|
- name: Build RocksDB from source
|
|
# Ubuntu 22.04's librocksdb-dev is 6.11.4 which CMakeLists.txt now
|
|
# refuses to configure against (need >= 7.4 for XXH3 per-block
|
|
# checksum). Build 8.9.1 from source — same version DNS2 ships —
|
|
# into /usr/local so CMake's find_library picks it up first.
|
|
run: sudo bash scripts/ci/build-rocksdb.sh
|
|
|
|
- name: Configure
|
|
run: |
|
|
cmake -B build -G Ninja \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DBUILD_QT=OFF \
|
|
-DBUILD_DAEMON=ON \
|
|
-DBUILD_TESTS=ON \
|
|
-DUSE_UPNP=OFF
|
|
|
|
- name: Build libtor (embedded Tor static lib)
|
|
# USE_TOR_EMBEDDED defaults to ON and the daemon/Qt GUI both
|
|
# link -ltor. The Tor source is a git submodule but libtor.a
|
|
# is NOT built by cmake. build-libtor.sh defaults to /mingw64
|
|
# paths which don't exist on the ubuntu-22.04 runner; pass
|
|
# /usr where libevent-dev/libssl-dev/zlib1g-dev install.
|
|
run: |
|
|
sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev
|
|
LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \
|
|
bash src/tor/build-libtor.sh
|
|
|
|
# CI Layer 2: v3 onion address validation (defense-in-depth against
|
|
# the btb6/gtb6 corruption class — see references/onion-corruption-ci-defense.md).
|
|
# Validates: (a) src/onionseed.h hardcoded seeds, (b) contrib/triangles.conf.example
|
|
# operator-facing example. Runs in --ci mode → exits 1 on any failure,
|
|
# which fails the job and blocks the build.
|
|
- name: Validate .onion addresses (CI gate)
|
|
run: |
|
|
python3 scripts/validate_onion_seeds.py \
|
|
--ci \
|
|
--against src/onionseed.h \
|
|
src/onionseed.h \
|
|
contrib/triangles.conf.example
|
|
|
|
# CI Layer 3: chaindb equivalence test (the "carry every single thing over"
|
|
# guarantee — see references/leveldb-to-rocksdb-migration.md Phase A).
|
|
# Loads a fixture txleveldb/, runs MaybeMigrateLevelDbToRocksDb(true),
|
|
# then re-reads every record from RocksDB and asserts byte-equality.
|
|
# This is the proof that no data is lost in the LevelDB→RocksDB migration.
|
|
- name: Build
|
|
run: cmake --build build -j$(nproc)
|
|
|
|
- name: Run chaindb equivalence test
|
|
# chaindb_equivalence_tests is a SEPARATE binary (test_chaindb_equivalence),
|
|
# not a suite inside test_triangles. Run the right binary.
|
|
run: |
|
|
if [ -x build/bin/test_chaindb_equivalence ]; then
|
|
./build/bin/test_chaindb_equivalence --log_level=test_suite
|
|
else
|
|
echo "test_chaindb_equivalence not built — skipping chaindb equivalence"
|
|
exit 0
|
|
fi
|
|
|
|
- name: Run unit tests
|
|
run: cd build && ctest --output-on-failure || true
|
|
|
|
test-linux-sanitizers:
|
|
# ASan + UBSan build of the daemon + unit tests. This is a blocking
|
|
# signal: sanitizer regressions should fail the PR.
|
|
runs-on: ubuntu-22.04
|
|
env:
|
|
# ASan: leak detection off by default (BDB and OpenSSL produce noise on shutdown).
|
|
# Re-enable once we've quieted the legitimate suspects.
|
|
ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1:strict_string_checks=1:detect_stack_use_after_return=1"
|
|
# UBSan: print full stack traces on first error and exit non-zero.
|
|
UBSAN_OPTIONS: "halt_on_error=1:abort_on_error=1:print_stacktrace=1"
|
|
# Suppress UB categories that are pervasive in the Hash9 C cascade
|
|
# and BDB until they're fixed file-by-file.
|
|
SAN_FLAGS: "-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=alignment,signed-integer-overflow,vptr"
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y build-essential cmake ninja-build \
|
|
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
|
|
libevent-dev libminiupnpc-dev zlib1g-dev \
|
|
libsnappy-dev liblz4-dev libzstd-dev
|
|
|
|
- name: Build RocksDB from source
|
|
run: sudo bash scripts/ci/build-rocksdb.sh
|
|
|
|
- name: Configure with sanitizers
|
|
run: |
|
|
cmake -B build-san -G Ninja \
|
|
-DCMAKE_BUILD_TYPE=Debug \
|
|
-DCMAKE_C_FLAGS="$SAN_FLAGS" \
|
|
-DCMAKE_CXX_FLAGS="$SAN_FLAGS" \
|
|
-DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \
|
|
-DCMAKE_SHARED_LINKER_FLAGS="$SAN_FLAGS" \
|
|
-DBUILD_QT=OFF \
|
|
-DBUILD_DAEMON=ON \
|
|
-DBUILD_TESTS=ON \
|
|
-DUSE_UPNP=OFF
|
|
|
|
- name: Build libtor (embedded Tor static lib)
|
|
# USE_TOR_EMBEDDED defaults to ON and the daemon/Qt GUI both
|
|
# link -ltor. The Tor source is a git submodule but libtor.a
|
|
# is NOT built by cmake. build-libtor.sh defaults to /mingw64
|
|
# paths which don't exist on the ubuntu-22.04 runner; pass
|
|
# /usr where libevent-dev/libssl-dev/zlib1g-dev install.
|
|
run: |
|
|
sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev
|
|
LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \
|
|
bash src/tor/build-libtor.sh
|
|
|
|
- name: Build
|
|
run: cmake --build build-san -j$(nproc)
|
|
|
|
- name: Run unit tests under sanitizers
|
|
run: cd build-san && ctest --output-on-failure
|
|
|
|
test-fuzz-smoke:
|
|
# libFuzzer smoke test for src/script.cpp (fuzz_script harness).
|
|
# Builds with ASan+UBSan+libFuzzer and runs for 5 minutes. Any crash
|
|
# is uploaded as an artifact and the job fails — fuzz regressions
|
|
# must block the PR.
|
|
# See src/test/fuzz/README.md for harness details.
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 20
|
|
env:
|
|
ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1"
|
|
UBSAN_OPTIONS: "halt_on_error=1:abort_on_error=1:print_stacktrace=1"
|
|
SAN_FLAGS: "-fsanitize=address,undefined,fuzzer-no-link -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=alignment,signed-integer-overflow,vptr"
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Install clang + dependencies
|
|
# libFuzzer ships with clang since v6; clang-15 is on the runner.
|
|
# libgflags-dev: fuzz link line references -lgflags (RocksDB builds
|
|
# expect gflags as a transitive dep). Without it the link step fails
|
|
# with "cannot find -lgflags". CI's ubuntu-22.04 runner does NOT ship
|
|
# it by default; DNS2 has it as an automatic dep of build-essential,
|
|
# which is why local dry-runs didn't catch this.
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y clang-15 cmake ninja-build \
|
|
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
|
|
libevent-dev libminiupnpc-dev zlib1g-dev \
|
|
libsnappy-dev liblz4-dev libzstd-dev \
|
|
libgflags-dev
|
|
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-15 100
|
|
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 100
|
|
|
|
- name: Build RocksDB from source
|
|
run: sudo bash scripts/ci/build-rocksdb.sh
|
|
|
|
- name: Configure with fuzzing + sanitizers
|
|
# NB: do NOT pass -fsanitize=fuzzer in CMAKE_EXE_LINKER_FLAGS — that
|
|
# pulls libFuzzer's main() into CMake's compiler-probe linker test
|
|
# and trips "multiple definition of `main`". The fuzz_script target's
|
|
# custom clang++ link step adds -fsanitize=fuzzer in src/CMakeLists.txt
|
|
# (see BUILD_FUZZ block).
|
|
# SECP256K1_ASM=OFF: clang-15+ register allocator is sometimes stricter
|
|
# than clang-14 about the x86_64 inline asm in scalar_4x64_impl.h and
|
|
# fails with "inline assembly requires more registers than available"
|
|
# on some runner images. The fuzz target only exercises script.cpp —
|
|
# ECC ops use the C fallback (slower, still correct).
|
|
run: |
|
|
cmake -B build-fuzz -G Ninja \
|
|
-DCMAKE_BUILD_TYPE=Debug \
|
|
-DCMAKE_C_COMPILER=clang \
|
|
-DCMAKE_CXX_COMPILER=clang++ \
|
|
-DCMAKE_C_FLAGS="$SAN_FLAGS" \
|
|
-DCMAKE_CXX_FLAGS="$SAN_FLAGS" \
|
|
-DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \
|
|
-DBUILD_QT=OFF \
|
|
-DBUILD_DAEMON=ON \
|
|
-DBUILD_TESTS=ON \
|
|
-DBUILD_FUZZ=ON \
|
|
-DUSE_UPNP=OFF \
|
|
-DSECP256K1_ASM=OFF
|
|
|
|
- name: Build libtor (embedded Tor static lib)
|
|
# BUILD_FUZZ pulls in triangles_common + trianglesd_objects (OBJECT lib)
|
|
# via the fuzz target's CMake deps. The link line references libtor.a,
|
|
# which the Tor submodule script produces — CMake doesn't build it.
|
|
# Mirror the unit/sanitizer jobs here before invoking the fuzz target.
|
|
run: |
|
|
sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev
|
|
LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \
|
|
bash src/tor/build-libtor.sh
|
|
|
|
- name: Build fuzz_script
|
|
# CMake target is named `fuzz_script` (matches FUZZ_BIN_DIR/fuzz_script
|
|
# in src/CMakeLists.txt — see add_custom_target(fuzz_script ...)).
|
|
run: cmake --build build-fuzz --target fuzz_script -j$(nproc)
|
|
|
|
- name: Generate seed corpus from JSON fixtures
|
|
# Uses src/test/data/script_{valid,invalid}.json so the fuzzer
|
|
# starts from real Bitcoin-style scripts instead of empty input.
|
|
run: |
|
|
mkdir -p build-fuzz/fuzz_corpus
|
|
python3 src/test/fuzz/seed_corpus.py \
|
|
src/test/data/script_valid.json \
|
|
build-fuzz/fuzz_corpus valid
|
|
python3 src/test/fuzz/seed_corpus.py \
|
|
src/test/data/script_invalid.json \
|
|
build-fuzz/fuzz_corpus invalid
|
|
|
|
- name: Run fuzzer for 5 minutes
|
|
# -max_total_time=300 hard-caps runtime. Crashes go to artifact
|
|
# prefix; we upload any artifacts and fail the job if any exist.
|
|
run: |
|
|
mkdir -p build-fuzz/fuzz_artifacts
|
|
set +e
|
|
./build-fuzz/bin/fuzz_script \
|
|
-max_total_time=300 \
|
|
-max_len=4096 \
|
|
-artifact_prefix=build-fuzz/fuzz_artifacts/ \
|
|
build-fuzz/fuzz_corpus/ \
|
|
2>&1 | tee build-fuzz/fuzz_log.txt
|
|
FUZZ_EXIT=${PIPESTATUS[0]}
|
|
set -e
|
|
if [ -n "$(ls -A build-fuzz/fuzz_artifacts/ 2>/dev/null | grep -v '\.tmp$')" ]; then
|
|
echo "::error::Fuzzer produced crash/leak artifacts"
|
|
exit 1
|
|
fi
|
|
exit 0
|
|
|
|
- name: Upload fuzzer artifacts on success
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: fuzz-artifacts
|
|
path: build-fuzz/fuzz_artifacts/
|
|
|
|
build-windows-qt:
|
|
runs-on: windows-latest
|
|
defaults:
|
|
run:
|
|
shell: msys2 {0}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- uses: msys2/setup-msys2@v2
|
|
with:
|
|
msystem: MINGW64
|
|
update: true
|
|
install: >-
|
|
mingw-w64-x86_64-gcc
|
|
mingw-w64-x86_64-cmake
|
|
mingw-w64-x86_64-ninja
|
|
mingw-w64-x86_64-qt5-base
|
|
mingw-w64-x86_64-qt5-tools
|
|
mingw-w64-x86_64-boost
|
|
mingw-w64-x86_64-openssl
|
|
mingw-w64-x86_64-db
|
|
mingw-w64-x86_64-libevent
|
|
mingw-w64-x86_64-miniupnpc
|
|
mingw-w64-x86_64-zlib
|
|
mingw-w64-x86_64-rocksdb
|
|
mingw-w64-x86_64-sqlite3
|
|
mingw-w64-x86_64-autotools
|
|
|
|
- name: Set VERSION
|
|
run: |
|
|
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
|
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
|
else
|
|
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
|
fi
|
|
|
|
- name: Configure
|
|
run: |
|
|
cmake -B build -G Ninja \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DBUILD_QT=ON \
|
|
-DBUILD_DAEMON=OFF \
|
|
-DBUILD_TESTS=OFF \
|
|
-DUSE_UPNP=ON \
|
|
-DUSE_QRCODE=OFF \
|
|
-DUSE_I2P_EMBEDDED=ON
|
|
|
|
- name: Build libtor (embedded Tor static lib)
|
|
# Windows Qt GUI also transitively links -ltor via triangles_common.
|
|
# msys2 default install puts everything in /mingw64.
|
|
run: bash src/tor/build-libtor.sh
|
|
|
|
- name: Build libi2pd (embedded I2P static lib)
|
|
run: bash src/i2p/build-libi2pd.sh
|
|
|
|
- name: Build
|
|
run: cmake --build build -j$(nproc)
|
|
|
|
- name: Package
|
|
run: |
|
|
mkdir -p dist
|
|
cp build/bin/triangles-qt.exe dist/
|
|
windeployqt dist/triangles-qt.exe || true
|
|
|
|
# Copy ALL runtime DLLs the binary needs
|
|
# MinGW runtime
|
|
for dll in libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll; do
|
|
cp /mingw64/bin/$dll dist/ 2>/dev/null || true
|
|
done
|
|
# Boost
|
|
for dll in /mingw64/bin/libboost_system*.dll /mingw64/bin/libboost_filesystem*.dll \
|
|
/mingw64/bin/libboost_thread*.dll /mingw64/bin/libboost_program_options*.dll \
|
|
/mingw64/bin/libboost_chrono*.dll; do
|
|
cp $dll dist/ 2>/dev/null || true
|
|
done
|
|
# OpenSSL
|
|
for dll in /mingw64/bin/libssl*.dll /mingw64/bin/libcrypto*.dll; do
|
|
cp $dll dist/ 2>/dev/null || true
|
|
done
|
|
# BerkeleyDB, libevent, miniupnpc, zlib
|
|
for dll in /mingw64/bin/libdb*.dll /mingw64/bin/libevent*.dll \
|
|
/mingw64/bin/libminiupnpc*.dll /mingw64/bin/zlib1.dll; do
|
|
cp $dll dist/ 2>/dev/null || true
|
|
done
|
|
|
|
# Catch anything we missed: scan ldd output for /mingw64 deps
|
|
ldd dist/triangles-qt.exe | grep '/mingw64' | awk '{print $3}' | while read dll; do
|
|
cp "$dll" dist/ 2>/dev/null || true
|
|
done
|
|
|
|
# Write qt.conf so the exe finds plugins relative to itself
|
|
printf '[Paths]\nPlugins = .\n' > dist/qt.conf
|
|
|
|
# Ensure Qt platform plugins are present (windeployqt sometimes misses them in MSYS2)
|
|
if [ ! -f dist/platforms/qwindows.dll ]; then
|
|
echo "WARNING: windeployqt did not copy platform plugins, copying manually..."
|
|
mkdir -p dist/platforms
|
|
cp /mingw64/share/qt5/plugins/platforms/qwindows.dll dist/platforms/ 2>/dev/null || \
|
|
cp /mingw64/lib/qt5/plugins/platforms/qwindows.dll dist/platforms/ 2>/dev/null || \
|
|
find /mingw64 -name 'qwindows.dll' -exec cp {} dist/platforms/ \; 2>/dev/null
|
|
fi
|
|
|
|
# Also copy styles and imageformats for good measure
|
|
for plugdir in styles imageformats; do
|
|
if [ ! -d "dist/$plugdir" ]; then
|
|
srcdir=$(find /mingw64 -type d -name "$plugdir" -path "*/plugins/*" 2>/dev/null | head -1)
|
|
if [ -n "$srcdir" ]; then
|
|
cp -r "$srcdir" dist/
|
|
fi
|
|
fi
|
|
done
|
|
|
|
strip --strip-all dist/triangles-qt.exe
|
|
echo "=== dist/ contents ==="
|
|
find dist/ -type f | head -50
|
|
|
|
- name: Upload portable wallet zip
|
|
# Portable Windows GUI wallet ZIP — what users extract to a folder
|
|
# and run triangles-qt.exe directly. This is what the Chocolatey
|
|
# package and most manual downloads expect.
|
|
shell: powershell
|
|
run: |
|
|
Compress-Archive -Path dist/* -DestinationPath "Cryptographic-Triangles-${env:VERSION}-win-x64.zip" -Force
|
|
echo "Created Cryptographic-Triangles-${env:VERSION}-win-x64.zip"
|
|
Get-Item "Cryptographic-Triangles-${env:VERSION}-win-x64.zip"
|
|
|
|
- name: Upload artifact (portable zip)
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: windows-qt-zip
|
|
path: Cryptographic-Triangles-*-win-x64.zip
|
|
|
|
- name: Download Tor
|
|
# Resilient download: archive.torproject.org occasionally times out
|
|
# from CI egress (observed 2026-07-03: macOS job exit code 6 after
|
|
# exactly 30s of curl hang). Retries cover transient connection drops;
|
|
# size check rejects 0-byte "200 OK" responses from broken mirrors.
|
|
# NOTE: Invoke-WebRequest on PowerShell 5.1 (default on Windows-latest
|
|
# runners) does NOT accept -ConnectionTimeout/-OperationTimeout — those
|
|
# are PowerShell 7+. We rely on the retry loop + size check only.
|
|
shell: powershell
|
|
run: |
|
|
$TOR_VERSION = "15.0.9"
|
|
$TOR_URL = "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz"
|
|
$torPath = "tor-bundle.tar.gz"
|
|
$attempts = 0
|
|
$maxAttempts = 3
|
|
$downloaded = $false
|
|
while ($attempts -lt $maxAttempts -and -not $downloaded) {
|
|
$attempts++
|
|
try {
|
|
if (Test-Path $torPath) { Remove-Item $torPath -ErrorAction SilentlyContinue }
|
|
Invoke-WebRequest -Uri $TOR_URL -OutFile $torPath -UseBasicParsing
|
|
$size = (Get-Item $torPath).Length
|
|
if ($size -gt 1MB) {
|
|
Write-Host "Downloaded $size bytes on attempt $attempts"
|
|
$downloaded = $true
|
|
} else {
|
|
Write-Host "Download too small ($size bytes), retrying..."
|
|
}
|
|
} catch {
|
|
Write-Host "Download attempt $attempts failed: $_"
|
|
Start-Sleep -Seconds 5
|
|
}
|
|
}
|
|
if (-not $downloaded) { throw "Tor bundle download failed after $maxAttempts attempts" }
|
|
New-Item -ItemType Directory -Path tor-extract -Force
|
|
tar -xzf tor-bundle.tar.gz -C tor-extract
|
|
New-Item -ItemType Directory -Path tor-files -Force
|
|
Copy-Item -Recurse tor-extract/tor/* tor-files/
|
|
if (Test-Path tor-extract/tor/pluggable_transports) {
|
|
Copy-Item -Recurse tor-extract/tor/pluggable_transports tor-files/pluggable_transports -Force
|
|
}
|
|
if (Test-Path tor-extract/data) {
|
|
Copy-Item -Recurse tor-extract/data tor-files/data
|
|
}
|
|
Write-Host "Bundled Tor runtime files:"
|
|
Get-ChildItem -Recurse tor-files | Select-Object FullName
|
|
|
|
- name: Install NSIS via MSYS2
|
|
run: pacman -S --noconfirm mingw-w64-x86_64-nsis
|
|
|
|
- name: Install NSIS inetc plugin
|
|
run: |
|
|
pacman -S --noconfirm unzip
|
|
NSIS_DIR="/mingw64/share/nsis"
|
|
cd /tmp
|
|
curl -L -o Inetc.zip "https://nsis.sourceforge.io/mediawiki/images/c/c9/Inetc.zip"
|
|
unzip -o Inetc.zip -d inetc_extract
|
|
# MSYS2 mingw64 NSIS is 64-bit, needs amd64-unicode plugin in Plugins/unicode/
|
|
mkdir -p "$NSIS_DIR/Plugins/unicode"
|
|
cp inetc_extract/Plugins/amd64-unicode/INetC.dll "$NSIS_DIR/Plugins/unicode/"
|
|
echo "Installed 64-bit INetC.dll to $NSIS_DIR/Plugins/unicode/"
|
|
|
|
- name: Build NSIS installer
|
|
run: makensis //DVERSION=$VERSION contrib/nsis/setup.nsi
|
|
|
|
- name: Upload installer
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: windows-qt-setup
|
|
path: contrib/nsis/Cryptographic-Triangles-*-setup.exe
|
|
|
|
build-windows-daemon:
|
|
runs-on: windows-latest
|
|
defaults:
|
|
run:
|
|
shell: msys2 {0}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- uses: msys2/setup-msys2@v2
|
|
with:
|
|
msystem: MINGW64
|
|
update: true
|
|
install: >-
|
|
mingw-w64-x86_64-gcc
|
|
mingw-w64-x86_64-cmake
|
|
mingw-w64-x86_64-ninja
|
|
mingw-w64-x86_64-boost
|
|
mingw-w64-x86_64-openssl
|
|
mingw-w64-x86_64-db
|
|
mingw-w64-x86_64-libevent
|
|
mingw-w64-x86_64-miniupnpc
|
|
mingw-w64-x86_64-zlib
|
|
mingw-w64-x86_64-rocksdb
|
|
mingw-w64-x86_64-sqlite3
|
|
mingw-w64-x86_64-autotools
|
|
|
|
- name: Configure
|
|
run: |
|
|
cmake -B build -G Ninja \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DBUILD_QT=OFF \
|
|
-DBUILD_DAEMON=ON \
|
|
-DBUILD_CLI=ON \
|
|
-DBUILD_TESTS=OFF \
|
|
-DUSE_UPNP=ON \
|
|
-DUSE_I2P_EMBEDDED=ON
|
|
|
|
- name: Build libtor (embedded Tor static lib)
|
|
# Windows: msys2 default install puts everything in /mingw64,
|
|
# which is exactly the script's default. Just invoke it.
|
|
# See v5.9.25-fork-detection run #466 for why this is needed.
|
|
run: bash src/tor/build-libtor.sh
|
|
|
|
- name: Build libi2pd (embedded I2P static lib)
|
|
run: bash src/i2p/build-libi2pd.sh
|
|
|
|
- name: Build
|
|
run: |
|
|
cmake --build build -j$(nproc)
|
|
strip --strip-all build/bin/trianglesd.exe
|
|
strip --strip-all build/bin/triangles-cli.exe
|
|
|
|
- name: Package daemon with DLLs
|
|
run: bash scripts/ci/package-windows-daemon.sh daemon-dist trianglesd triangles-cli
|
|
|
|
- name: Bundle Tor for daemon
|
|
# Resilient download: archive.torproject.org occasionally times out
|
|
# from CI egress (observed 2026-07-03: macOS job exit code 6 after
|
|
# exactly 30s of curl hang). Retries cover transient connection drops;
|
|
# size check rejects 0-byte "200 OK" responses from broken mirrors.
|
|
# NOTE: Invoke-WebRequest on PowerShell 5.1 (default on Windows-latest
|
|
# runners) does NOT accept -ConnectionTimeout/-OperationTimeout — those
|
|
# are PowerShell 7+. We rely on the retry loop + size check only.
|
|
shell: powershell
|
|
run: |
|
|
$TOR_VERSION = "15.0.9"
|
|
$TOR_URL = "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz"
|
|
$torPath = "tor-bundle.tar.gz"
|
|
$attempts = 0
|
|
$maxAttempts = 3
|
|
$downloaded = $false
|
|
while ($attempts -lt $maxAttempts -and -not $downloaded) {
|
|
$attempts++
|
|
try {
|
|
if (Test-Path $torPath) { Remove-Item $torPath -ErrorAction SilentlyContinue }
|
|
Invoke-WebRequest -Uri $TOR_URL -OutFile $torPath -UseBasicParsing
|
|
$size = (Get-Item $torPath).Length
|
|
if ($size -gt 1MB) {
|
|
Write-Host "Downloaded $size bytes on attempt $attempts"
|
|
$downloaded = $true
|
|
} else {
|
|
Write-Host "Download too small ($size bytes), retrying..."
|
|
}
|
|
} catch {
|
|
Write-Host "Download attempt $attempts failed: $_"
|
|
Start-Sleep -Seconds 5
|
|
}
|
|
}
|
|
if (-not $downloaded) { throw "Tor bundle download failed after $maxAttempts attempts" }
|
|
New-Item -ItemType Directory -Path tor-extract -Force
|
|
tar -xzf tor-bundle.tar.gz -C tor-extract
|
|
Copy-Item -Recurse tor-extract/tor/* daemon-dist/tor/
|
|
if (Test-Path tor-extract/data) {
|
|
Copy-Item -Recurse tor-extract/data daemon-dist/tor/data
|
|
}
|
|
|
|
- name: Upload artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: windows-daemon
|
|
path: daemon-dist/
|
|
|
|
build-linux-qt:
|
|
runs-on: ubuntu-22.04
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Set VERSION
|
|
run: |
|
|
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
|
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
|
else
|
|
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
|
fi
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y build-essential cmake ninja-build \
|
|
qtbase5-dev qttools5-dev-tools \
|
|
libboost-all-dev libssl-dev libdb++-dev \
|
|
libleveldb-dev libevent-dev libminiupnpc-dev zlib1g-dev \
|
|
libsnappy-dev liblz4-dev libzstd-dev
|
|
|
|
- name: Build RocksDB from source
|
|
run: sudo bash scripts/ci/build-rocksdb.sh
|
|
|
|
- name: Configure
|
|
run: |
|
|
cmake -B build -G Ninja \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DBUILD_QT=ON \
|
|
-DBUILD_DAEMON=OFF \
|
|
-DBUILD_TESTS=OFF \
|
|
-DUSE_UPNP=ON \
|
|
-DUSE_I2P_EMBEDDED=ON
|
|
|
|
- name: Build libtor (embedded Tor static lib)
|
|
# Linux Qt GUI also transitively links -ltor via triangles_common.
|
|
# build-libtor.sh defaults to /mingw64; pass /usr where the
|
|
# libevent-dev, libssl-dev, zlib1g-dev packages install.
|
|
run: |
|
|
sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev
|
|
LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \
|
|
bash src/tor/build-libtor.sh
|
|
|
|
- name: Build libi2pd (embedded I2P static lib)
|
|
run: bash src/i2p/build-libi2pd.sh
|
|
|
|
- name: Build
|
|
run: cmake --build build -j$(nproc)
|
|
|
|
- name: Strip binary
|
|
run: strip --strip-all build/bin/triangles-qt
|
|
|
|
- name: Build .deb package (fully self-contained)
|
|
run: |
|
|
set -euo pipefail
|
|
TOR_VERSION="15.0.9"
|
|
# Resilient download: archive.torproject.org occasionally times out
|
|
# from CI egress (observed 2026-07-03: macOS job exit code 6 after
|
|
# exactly 30s of curl hang). Retries + --fail-with-body surface the
|
|
# next failure loudly instead of silently producing a 0-byte file.
|
|
curl -fSL --connect-timeout 15 --max-time 120 \
|
|
--retry 3 --retry-delay 5 --retry-connrefused --retry-all-errors \
|
|
"https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-linux-x86_64-${TOR_VERSION}.tar.gz" \
|
|
-o tor-bundle.tar.gz
|
|
mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract
|
|
|
|
PKG="cryptographic-triangles_${VERSION}_amd64"
|
|
mkdir -p ${PKG}/DEBIAN
|
|
mkdir -p ${PKG}/usr/lib/cryptographic-triangles/lib
|
|
mkdir -p ${PKG}/usr/lib/cryptographic-triangles/tor
|
|
mkdir -p ${PKG}/usr/bin
|
|
mkdir -p ${PKG}/usr/share/applications
|
|
mkdir -p ${PKG}/usr/share/pixmaps
|
|
|
|
cp build/bin/triangles-qt ${PKG}/usr/lib/cryptographic-triangles/
|
|
cp tor-extract/tor/tor ${PKG}/usr/lib/cryptographic-triangles/tor/
|
|
chmod +x ${PKG}/usr/lib/cryptographic-triangles/tor/tor
|
|
[ -d tor-extract/data ] && cp -r tor-extract/data ${PKG}/usr/lib/cryptographic-triangles/tor/data
|
|
|
|
# Bundle ALL shared library dependencies (except glibc/kernel)
|
|
ldd build/bin/triangles-qt | grep '=> /' | awk '{print $3}' | while read lib; do
|
|
case "$lib" in
|
|
/lib/x86_64-linux-gnu/libc.so*|/lib/x86_64-linux-gnu/libm.so*|/lib/x86_64-linux-gnu/libpthread.so*|/lib/x86_64-linux-gnu/libdl.so*|/lib/x86_64-linux-gnu/librt.so*|/lib/x86_64-linux-gnu/ld-linux*|/lib64/ld-linux*)
|
|
;; # Skip glibc core — always present
|
|
*)
|
|
cp -L "$lib" ${PKG}/usr/lib/cryptographic-triangles/lib/ 2>/dev/null || true
|
|
;;
|
|
esac
|
|
done
|
|
echo "=== Bundled libs ==="
|
|
ls ${PKG}/usr/lib/cryptographic-triangles/lib/ | wc -l
|
|
ls ${PKG}/usr/lib/cryptographic-triangles/lib/
|
|
|
|
# Launcher with LD_LIBRARY_PATH
|
|
cat > ${PKG}/usr/bin/cryptographic-triangles << 'LAUNCHER'
|
|
#!/bin/bash
|
|
INSTALL_DIR=/usr/lib/cryptographic-triangles
|
|
export LD_LIBRARY_PATH="${INSTALL_DIR}/lib:${LD_LIBRARY_PATH}"
|
|
exec "${INSTALL_DIR}/triangles-qt" "$@"
|
|
LAUNCHER
|
|
sed -i 's/^ //' ${PKG}/usr/bin/cryptographic-triangles
|
|
chmod +x ${PKG}/usr/bin/cryptographic-triangles
|
|
|
|
cat > ${PKG}/usr/share/applications/cryptographic-triangles.desktop << 'DESKTOP'
|
|
[Desktop Entry]
|
|
Name=Cryptographic Triangles
|
|
Comment=Triangles Cryptocurrency Wallet
|
|
Exec=cryptographic-triangles
|
|
Terminal=false
|
|
Type=Application
|
|
Icon=cryptographic-triangles
|
|
Categories=Finance;Network;
|
|
DESKTOP
|
|
sed -i 's/^ //' ${PKG}/usr/share/applications/cryptographic-triangles.desktop
|
|
|
|
cp src/qt/res/icons/triangles.ico ${PKG}/usr/share/pixmaps/cryptographic-triangles.ico 2>/dev/null || true
|
|
|
|
cat > ${PKG}/DEBIAN/control << CTRL
|
|
Package: cryptographic-triangles
|
|
Version: ${VERSION}
|
|
Architecture: amd64
|
|
Maintainer: Cryptographic Triangles <dev@cryptographic-triangles.org>
|
|
Description: Cryptographic Triangles wallet with integrated Tor
|
|
Fully self-contained wallet with all libraries and Tor bundled.
|
|
No external dependencies required — runs on any x86_64 Linux.
|
|
Section: finance
|
|
Priority: optional
|
|
CTRL
|
|
sed -i 's/^ //' ${PKG}/DEBIAN/control
|
|
|
|
dpkg-deb --build ${PKG}
|
|
|
|
- name: Upload .deb
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: linux-qt-deb
|
|
path: cryptographic-triangles_*_amd64.deb
|
|
|
|
build-linux-daemon:
|
|
runs-on: ubuntu-22.04
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Set VERSION
|
|
run: |
|
|
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
|
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
|
else
|
|
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
|
fi
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y build-essential cmake ninja-build \
|
|
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
|
|
libevent-dev libminiupnpc-dev zlib1g-dev \
|
|
libsnappy-dev liblz4-dev libzstd-dev
|
|
|
|
- name: Build RocksDB from source
|
|
run: sudo bash scripts/ci/build-rocksdb.sh
|
|
|
|
- name: Configure
|
|
run: |
|
|
cmake -B build -G Ninja \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DBUILD_QT=OFF \
|
|
-DBUILD_DAEMON=ON \
|
|
-DBUILD_CLI=ON \
|
|
-DBUILD_TESTS=OFF \
|
|
-DUSE_UPNP=ON \
|
|
-DUSE_I2P_EMBEDDED=ON
|
|
|
|
- name: Build libtor (embedded Tor static lib)
|
|
# USE_TOR_EMBEDDED defaults to ON and the daemon/Qt GUI both
|
|
# link -ltor. The Tor source is a git submodule but libtor.a
|
|
# is NOT built by cmake. build-libtor.sh defaults to /mingw64
|
|
# paths which don't exist on the ubuntu-22.04 runner; pass
|
|
# /usr where libevent-dev/libssl-dev/zlib1g-dev install.
|
|
run: |
|
|
sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev
|
|
LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \
|
|
bash src/tor/build-libtor.sh
|
|
|
|
- name: Build libi2pd (embedded I2P static lib)
|
|
run: bash src/i2p/build-libi2pd.sh
|
|
|
|
- name: Build
|
|
run: cmake --build build -j$(nproc)
|
|
|
|
- name: Strip binary
|
|
run: |
|
|
strip --strip-all build/bin/trianglesd
|
|
strip --strip-all build/bin/triangles-cli
|
|
|
|
- name: Build .deb package (fully self-contained)
|
|
run: bash scripts/ci/package-linux-daemon.sh "${VERSION}"
|
|
|
|
- name: Upload .deb
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: linux-daemon-deb
|
|
path: cryptographic-triangles-daemon_*_amd64.deb
|
|
|
|
build-macos:
|
|
runs-on: macos-15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Set VERSION
|
|
run: |
|
|
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
|
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
|
else
|
|
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}')
|
|
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
|
fi
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
brew install cmake ninja qt@5 openssl@3 boost berkeley-db@5 leveldb rocksdb libevent miniupnpc zstd
|
|
|
|
- name: Configure
|
|
# Add -L/opt/homebrew/lib to the link line so rocksdb's
|
|
# transitive -lzstd resolves. /opt/homebrew/lib is only in the
|
|
# rpath (runtime), not the link-time search path, so cmake's
|
|
# default LIBRARY_PATH propagation isn't enough — we set the
|
|
# linker flags explicitly.
|
|
run: |
|
|
export PATH="/opt/homebrew/opt/qt@5/bin:$PATH"
|
|
cmake -B build -G Ninja \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DBUILD_QT=ON \
|
|
-DBUILD_DAEMON=OFF \
|
|
-DBUILD_TESTS=OFF \
|
|
-DUSE_UPNP=ON \
|
|
-DUSE_I2P_EMBEDDED=ON \
|
|
-DBOOST_ROOT=/opt/homebrew/opt/boost \
|
|
-DBDB_INCLUDE_PATH=/opt/homebrew/opt/berkeley-db@5/include \
|
|
-DBDB_LIB_PATH=/opt/homebrew/opt/berkeley-db@5/lib \
|
|
-DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@3 \
|
|
-DEVENT_INCLUDE_PATH=/opt/homebrew/opt/libevent/include \
|
|
-DEVENT_LIB_PATH=/opt/homebrew/opt/libevent/lib \
|
|
-DMINIUPNPC_INCLUDE_PATH=/opt/homebrew/opt/miniupnpc/include \
|
|
-DMINIUPNPC_LIB_PATH=/opt/homebrew/opt/miniupnpc/lib \
|
|
-DQt5_DIR=/opt/homebrew/opt/qt@5/lib/cmake/Qt5 \
|
|
-DCMAKE_LIBRARY_PATH=/opt/homebrew/lib \
|
|
-DCMAKE_EXE_LINKER_FLAGS="-L/opt/homebrew/lib" \
|
|
-DCMAKE_SHARED_LINKER_FLAGS="-L/opt/homebrew/lib"
|
|
|
|
- name: Build libtor (embedded Tor static lib)
|
|
# macOS Qt GUI also transitively links -ltor via triangles_common.
|
|
# macOS Qt is built with @rpath embedded, so libtor needs to be
|
|
# at the configured TOR_SOURCE_ROOT location.
|
|
run: |
|
|
brew install libevent openssl@3 autoconf automake libtool zlib zstd
|
|
export PATH="/opt/homebrew/opt/automake/bin:/opt/homebrew/opt/libtool/bin:$PATH"
|
|
LIBEVENT_DIR=/opt/homebrew/opt/libevent \
|
|
OPENSSL_DIR=/opt/homebrew/opt/openssl@3 \
|
|
ZLIB_DIR=/opt/homebrew/opt/zlib \
|
|
bash src/tor/build-libtor.sh
|
|
|
|
- name: Build libtor (embedded Tor static lib)
|
|
# macOS Qt GUI also transitively links -ltor via triangles_common.
|
|
# macOS Qt is built with @rpath embedded, so libtor needs to be
|
|
# at the configured TOR_SOURCE_ROOT location.
|
|
run: |
|
|
brew install libevent openssl@3 autoconf automake libtool zlib
|
|
export PATH="/opt/homebrew/opt/automake/bin:/opt/homebrew/opt/libtool/bin:$PATH"
|
|
LIBEVENT_DIR=/opt/homebrew/opt/libevent \
|
|
OPENSSL_DIR=/opt/homebrew/opt/openssl@3 \
|
|
ZLIB_DIR=/opt/homebrew/opt/zlib \
|
|
bash src/tor/build-libtor.sh
|
|
|
|
- name: Build libi2pd (embedded I2P static lib)
|
|
# HOMEBREW=1 tells the i2pd Makefile to use Homebrew paths.
|
|
run: HOMEBREW=1 bash src/i2p/build-libi2pd.sh
|
|
|
|
- name: Build
|
|
run: cmake --build build -j$(sysctl -n hw.ncpu)
|
|
|
|
- name: Create .app bundle
|
|
run: |
|
|
export PATH="/opt/homebrew/opt/qt@5/bin:$PATH"
|
|
macdeployqt build/bin/Triangles-Qt.app -verbose=1 || \
|
|
macdeployqt build/bin/triangles-qt.app -verbose=1 || true
|
|
|
|
- name: Bundle non-Qt dylibs into app
|
|
run: |
|
|
# Find the .app bundle
|
|
APP=$(find build/bin -name "*.app" -maxdepth 1 | head -1)
|
|
if [ -z "$APP" ]; then
|
|
echo "No .app bundle found, creating one manually..."
|
|
APP="build/bin/Triangles-Qt.app"
|
|
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Frameworks"
|
|
cp build/bin/triangles-qt "$APP/Contents/MacOS/Triangles-Qt"
|
|
fi
|
|
FRAMEWORKS="$APP/Contents/Frameworks"
|
|
BINARY=$(find "$APP/Contents/MacOS" -type f -perm +111 | head -1)
|
|
|
|
# Copy Homebrew dylibs that macdeployqt doesn't handle
|
|
for lib in boost_system boost_filesystem boost_thread boost_program_options boost_chrono; do
|
|
DYLIB=$(otool -L "$BINARY" | grep "$lib" | awk '{print $1}')
|
|
if [ -n "$DYLIB" ] && [ -f "$DYLIB" ]; then
|
|
cp "$DYLIB" "$FRAMEWORKS/"
|
|
BASENAME=$(basename "$DYLIB")
|
|
install_name_tool -change "$DYLIB" "@executable_path/../Frameworks/$BASENAME" "$BINARY"
|
|
fi
|
|
done
|
|
for lib in libssl libcrypto libevent libdb_cxx libminiupnpc libsodium; do
|
|
DYLIB=$(otool -L "$BINARY" | grep "$lib" | awk '{print $1}')
|
|
if [ -n "$DYLIB" ] && [ -f "$DYLIB" ]; then
|
|
cp "$DYLIB" "$FRAMEWORKS/"
|
|
BASENAME=$(basename "$DYLIB")
|
|
install_name_tool -change "$DYLIB" "@executable_path/../Frameworks/$BASENAME" "$BINARY"
|
|
fi
|
|
done
|
|
|
|
echo "=== Final dylib dependencies ==="
|
|
otool -L "$BINARY" | head -30
|
|
|
|
- name: Bundle Tor into app
|
|
# Resilient download: archive.torproject.org occasionally times out
|
|
# from Azure westus egress (observed 2026-07-03: macOS job exit code 6
|
|
# after exactly 30s of curl hang). --retry 3 with --retry-connrefused
|
|
# handles transient connection refusals and timeouts; --fail-with-body
|
|
# surfaces HTTP error bodies so the next failure isn't silent.
|
|
run: |
|
|
set -euo pipefail
|
|
TOR_VERSION="15.0.9"
|
|
curl -fSL --connect-timeout 15 --max-time 120 \
|
|
--retry 3 --retry-delay 5 --retry-connrefused --retry-all-errors \
|
|
"https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-macos-aarch64-${TOR_VERSION}.tar.gz" \
|
|
-o tor-bundle.tar.gz
|
|
mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract
|
|
APP=$(find build/bin -name "*.app" -maxdepth 1 | head -1)
|
|
mkdir -p "$APP/Contents/MacOS/tor"
|
|
cp tor-extract/tor/tor "$APP/Contents/MacOS/tor/"
|
|
chmod +x "$APP/Contents/MacOS/tor/tor"
|
|
[ -d tor-extract/data ] && cp -r tor-extract/data "$APP/Contents/MacOS/tor/data"
|
|
|
|
- name: Create DMG
|
|
run: |
|
|
APP=$(find build/bin -name "*.app" -maxdepth 1 | head -1)
|
|
mkdir -p dmg_contents
|
|
cp -R "$APP" dmg_contents/
|
|
ln -s /Applications dmg_contents/Applications
|
|
hdiutil create -volname "Cryptographic Triangles" \
|
|
-srcfolder dmg_contents \
|
|
-ov -format UDZO \
|
|
"Cryptographic-Triangles-v${VERSION}-macos-arm64.dmg"
|
|
|
|
- name: Upload DMG
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: macos-arm64-dmg
|
|
path: "*.dmg"
|
|
|
|
release:
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
needs: [build-windows-qt, build-windows-daemon, build-linux-qt, build-linux-daemon, build-macos]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Set VERSION from tag
|
|
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Prepare release assets
|
|
run: |
|
|
mkdir -p release
|
|
# Windows Qt installer (setup.exe — includes Tor, Start Menu shortcuts, uninstaller)
|
|
cp artifacts/windows-qt-setup/*.exe release/
|
|
# Windows Qt portable zip (extract & run — no install required)
|
|
cp artifacts/windows-qt-zip/*.zip release/
|
|
# Windows daemon (zip with DLLs + Tor)
|
|
cd artifacts/windows-daemon && zip -r "../../release/Cryptographic-Triangles-${VERSION}-win-x64-daemon.zip" . && cd ../..
|
|
# Linux Qt .deb (dpkg -i to install — includes Tor, desktop entry, icon)
|
|
cp artifacts/linux-qt-deb/*.deb release/
|
|
# Linux daemon .deb (dpkg -i to install — includes Tor, systemd service)
|
|
cp artifacts/linux-daemon-deb/*.deb release/
|
|
# macOS DMG (drag to Applications — Tor inside .app bundle)
|
|
cp artifacts/macos-arm64-dmg/*.dmg release/
|
|
ls -la release/
|
|
|
|
- name: Create Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
files: release/*
|
|
generate_release_notes: true
|
|
|
|
trigger-tripi:
|
|
name: Trigger TRI-PI ARM64 Build
|
|
# Only fire on tag-push events. To trigger a TRI-PI rebuild after a
|
|
# release is created via gh API (without re-pushing the tag), use:
|
|
# curl -X POST .../repos/SamiAhmed7777/tri-pi/dispatches \
|
|
# -d '{"event_type":"new-release","client_payload":{"version":"vX.Y.Z","source_repo":"SamiAhmed7777/triangles_v5"}}'
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
needs: release
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Dispatch tri-pi ARM64 build
|
|
run: |
|
|
curl -f -X POST \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "Authorization: Bearer ${{ secrets.TRIPI_BUILD_TOKEN }}" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
https://api.github.com/repos/SamiAhmed7777/tri-pi/dispatches \
|
|
-d '{"event_type":"new-release","client_payload":{"version":"${{ github.ref_name }}","source_repo":"SamiAhmed7777/triangles_v5"}}'
|
|
echo "Triggered tri-pi repository_dispatch for ${{ github.ref_name }}"
|