upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/Makefile
blob: 342ec07cf4d466fdbc2aa6b429a4129b770f793e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# Bitflash — top-level build
#
#   make linux    build Bitflash-*-x86_64.AppImage
#   make windows  build Bitflash-*-windows.zip (from MSYS2 UCRT64)
#   make windows-tor build Bitflash-*-windows-with-tor.zip
#   make tests    build and run standalone unit tests
#   make checksums create SHA256SUMS for release assets
#   make verify-release TAG=v1.2.13
#   make clean    remove build artifacts

ROOT    := $(shell pwd)
NPROC   := $(shell nproc 2>/dev/null || echo 2)
SUDO    := $(shell [ "$$(id -u)" = "0" ] && echo "" || echo "sudo")
# Not "1.2.20". This tree is upstream v1.2.20 plus our own hardening commits and
# a hand-merged payout ledger, so a binary built here must not be mistakable for
# the upstream release of that number -- it names every release artifact.
VERSION := 1.2.20+sec

# ---- Pinned third-party sources -------------------------------------------
#
# These were `git clone --depth 1 <url>` with no revision, which resolves to
# whatever the default branch happens to point at the moment the build runs.
# For a dependency that only had to link, that is untidy. For these two it is
# not:
#
#   RandomX is the proof of work. Its master is not frozen -- v2.0 (Mar 2026)
#   changed the algorithm -- so two builds of the same Bitflash commit, made a
#   few weeks apart, can compute different hashes for the same header. That is
#   not a supply-chain worry, it is a chain split arriving as a build artifact,
#   with no version recorded anywhere to explain it afterwards.
#
#   libsecp256k1 is signature verification, i.e. the rule deciding who may
#   spend. Whatever is on master on the day of the build is what enforces it.
#
# Pinned to full commit ids, not tags: a tag is a mutable pointer, and `git
# checkout <sha>` is the only form here that verifies what it fetched (git
# resolves the object by its own hash). Bump deliberately, and rebuild and
# resync before shipping the result.
#
# The check has to decide the exit status, so each recipe below joins its
# commands with && the whole way to the closing echo. Ending that chain with a
# `;` meant a failed `test` only skipped the build steps: the echo still ran,
# the target still exited 0, and make announced a pinned dependency that
# nothing had verified.
#
# RandomX pin is the commit this tree has been building and syncing against
# (post-v2.0.1 master, 2026-05-24) -- identical to upstream's pin.
#
# libsecp256k1 is pinned to upstream v1.2.18's commit rather than the one this
# fork carried, so signature validation matches the binaries the rest of the
# network runs. Consensus code must not differ from the network by accident.
# There must be exactly ONE assignment of each of these variables in this file:
# upstream defines them near the top too, and GNU Make takes the last `:=`, so
# a second block silently decides the build.
SECP256K1_REPO   := https://github.com/bitcoin-core/secp256k1
SECP256K1_COMMIT := 7fecac74aed8e1fd9078380d67dd04663705c989
RANDOMX_REPO     := https://github.com/tevador/RandomX
RANDOMX_COMMIT   := 1e9d4b2df63fa6edf46b06789486e23c0fcaa55a

# appimagetool came from the "continuous" release, a tag that is moved in place
# on every upstream push, fetched over the network and executed. Pinned to a
# numbered release and checked by digest before it is allowed to run.
APPIMAGETOOL_URL    := https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage
APPIMAGETOOL_SHA256 := ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0

# ---- Linux ----------------------------------------------------------------

# RandomX used to build in $(HOME)/RandomX, and deps-randomx would rm -rf that
# path whenever the library was missing -- deleting a tree the build did not
# create and does not own (issue #27; a hand-managed pinned checkout there is
# what this machine's miners build against). The default is now in-tree, like
# secp256k1-build. A host that deliberately keeps RandomX elsewhere can set
# RANDOMX_DIR=... explicitly; the recipe below will still never delete a
# directory that lacks the marker file this build writes after a successful
# build. Exported so src/Makefile's own RANDOMX_DIR ?= picks the same tree.
#
# Every use of it below is quoted, and the recipe rejects a value that cannot
# name exactly one directory before it reaches anything destructive. Unquoted,
# the marker guard could be stepped around: a value holding a glob that matches
# two directories made `[ -d $(RANDOMX_DIR) ]` a two-argument test, which dies
# with "unexpected operator" instead of answering, so the && short-circuited,
# the guard was skipped, and the rm -rf below ran against every match. A value
# holding a space or a `&&` broke the outer test the same way and reported
# "already built". Whitespace and glob characters are refused outright rather
# than merely quoted, because src/Makefile interpolates this same value into
# -I$(RANDOMX_DIR)/src and -L$(RANDOMX_DIR)/build unquoted, where neither can
# survive. A relative value is refused rather than silently made absolute: it
# would mean two different directories to this Makefile and to src/Makefile,
# whose cwd is src/, and a command-line assignment reaches the sub-make through
# MAKEFLAGS, where it outranks anything exported from here.
RANDOMX_DIR ?= $(ROOT)/randomx-build
export RANDOMX_DIR

linux: deps-linux
	$(MAKE) -C src -f Makefile -j$(NPROC)
	strip src/bitflash
	$(MAKE) -f Makefile appimage
	$(MAKE) -f Makefile linux-node

# Headless node, no ImGui/GLFW/OpenGL. The GUI build needs libGL present at load
# time even when started with -nogui -- the dynamic loader resolves everything
# before main() runs, so the flag comes far too late. On a clean server, which is
# where a node most naturally lives, that is a hard failure. This binary has no
# such dependency and needs no FUSE either, so it drops onto a VPS and runs.
linux-node: deps-linux
	$(MAKE) -C src -f Makefile bitflash-node -j$(NPROC)
	strip src/bitflash-node
	cp src/bitflash-node bitflash-node-$(VERSION)-x86_64
	@echo "Built: bitflash-node-$(VERSION)-x86_64"

deps-linux: deps-apt deps-secp256k1 deps-randomx

# libboost-dev, not libboost-system-dev: Boost.System has been header-only
# since 1.69 and Ubuntu 26.04 (resolute) no longer ships the -system package.
# This tree only includes two boost headers and links no boost library. The
# apt-get update comes first: a fresh image's package lists are empty, and
# install then fails on the first name it cannot find.
#
# A failed update is deliberately not fatal: one unreachable repository should
# not block an install the lists already on disk can satisfy. It does say so,
# though -- "unable to locate package" several seconds later is otherwise hard
# to connect back to it. The install is the last command in the branch, so its
# status is the target's, and that one does abort.
deps-apt:
	@if command -v apt-get >/dev/null 2>&1; then \
	  $(SUDO) apt-get update || \
	    echo "  warning: apt-get update failed, using the package lists on disk"; \
	  $(SUDO) apt-get install -y build-essential cmake git pkg-config autoconf \
	    libtool libssl-dev libdb5.3++-dev libsodium-dev nlohmann-json3-dev \
	    libboost-dev libglfw3-dev libgl-dev libsqlite3-dev \
	    python3-pil clang; \
	else \
	  echo "Not an apt system — install: g++ cmake git autoconf libtool"; \
	  echo "  libssl libdb++ libsodium nlohmann-json boost glfw3 opengl sqlite3 python3-pil"; \
	fi

deps-secp256k1:
	@if [ ! -f /usr/local/lib/libsecp256k1.a ]; then \
	  echo "==> building libsecp256k1"; \
	  rm -rf $(ROOT)/secp256k1-build && \
	  git init -q $(ROOT)/secp256k1-build && \
	  cd $(ROOT)/secp256k1-build && \
	  git remote add origin $(SECP256K1_REPO) && \
	  git fetch -q --depth 1 origin $(SECP256K1_COMMIT) && \
	  git checkout -q --detach FETCH_HEAD && \
	  test "$$(git rev-parse HEAD)" = "$(SECP256K1_COMMIT)" && \
	  echo "==> libsecp256k1 pinned at $(SECP256K1_COMMIT)" && \
	  cd $(ROOT)/secp256k1-build && ./autogen.sh && \
	  ./configure --enable-module-schnorrsig --enable-module-extrakeys \
	    --disable-shared --with-pic --disable-benchmark --disable-tests && \
	  make -j$(NPROC) && $(SUDO) make install && $(SUDO) ldconfig && \
	  echo "==> libsecp256k1 done"; \
	else \
	  echo "==> libsecp256k1 already installed"; \
	fi

deps-randomx:
	@ws="$$(printf ' \t')"; \
	case "$(RANDOMX_DIR)" in \
	  "")        echo "ERROR: RANDOMX_DIR is empty"; exit 1 ;; \
	  /)         echo "ERROR: RANDOMX_DIR is /, refusing"; exit 1 ;; \
	  *[$$ws]*)  echo "ERROR: RANDOMX_DIR contains whitespace: $(RANDOMX_DIR)"; exit 1 ;; \
	  *[*?[]*)   echo "ERROR: RANDOMX_DIR contains a glob character: $(RANDOMX_DIR)"; exit 1 ;; \
	  /*)        ;; \
	  *)         echo "ERROR: RANDOMX_DIR must be an absolute path: $(RANDOMX_DIR)"; exit 1 ;; \
	esac
	@if [ ! -f "$(RANDOMX_DIR)/build/librandomx.a" ]; then \
	  if [ -d "$(RANDOMX_DIR)" ] && [ ! -f "$(RANDOMX_DIR)/.bitflash-built" ]; then \
	    echo "ERROR: $(RANDOMX_DIR) exists but was not created by this build"; \
	    echo "       refusing to rm -rf it -- remove it yourself or set RANDOMX_DIR"; \
	    exit 1; \
	  fi; \
	  echo "==> building RandomX"; \
	  rm -rf "$(RANDOMX_DIR)" && \
	  git init -q "$(RANDOMX_DIR)" && \
	  cd "$(RANDOMX_DIR)" && \
	  git remote add origin $(RANDOMX_REPO) && \
	  git fetch -q --depth 1 origin $(RANDOMX_COMMIT) && \
	  git checkout -q --detach FETCH_HEAD && \
	  test "$$(git rev-parse HEAD)" = "$(RANDOMX_COMMIT)" && \
	  echo "==> RandomX pinned at $(RANDOMX_COMMIT)" && \
	  mkdir -p "$(RANDOMX_DIR)/build" && \
	  cd "$(RANDOMX_DIR)/build" && cmake .. -DCMAKE_BUILD_TYPE=Release && \
	  make -j$(NPROC) randomx && \
	  touch "$(RANDOMX_DIR)/.bitflash-built" && \
	  echo "==> RandomX done"; \
	else \
	  echo "==> RandomX already built"; \
	fi

# APPDIR and the tool both used fixed paths under /tmp, on a directory every
# local account can write. `[ ! -x /tmp/appimagetool ]` means an executable
# already sitting at that name is taken as the tool and run, so anyone with a
# shell on the build machine could have put one there first and had it execute
# as whoever ran make; `rm -rf /tmp/bitflash.AppDir` then mkdir is the same
# problem one step later, through symlinks. Both now live under the build tree,
# which the build already owns.
APPDIR       := $(ROOT)/build/bitflash.AppDir
APPIMAGETOOL := $(ROOT)/build/appimagetool

appimage: src/bitflash
	@echo "==> building AppImage"
	@mkdir -p $(ROOT)/build
	@if [ ! -x "$(APPIMAGETOOL)" ]; then \
	  echo "  fetching appimagetool"; \
	  wget -q "$(APPIMAGETOOL_URL)" -O "$(APPIMAGETOOL).tmp" || \
	    { echo "  ERROR: appimagetool download failed"; rm -f "$(APPIMAGETOOL).tmp"; exit 1; }; \
	  echo "$(APPIMAGETOOL_SHA256)  $(APPIMAGETOOL).tmp" | sha256sum -c - >/dev/null 2>&1 || \
	    { echo "  ERROR: appimagetool digest mismatch, refusing to run it"; \
	      echo "         expected $(APPIMAGETOOL_SHA256)"; \
	      echo "         got      $$(sha256sum "$(APPIMAGETOOL).tmp" | cut -d" " -f1)"; \
	      rm -f "$(APPIMAGETOOL).tmp"; exit 1; }; \
	  chmod +x "$(APPIMAGETOOL).tmp" && mv "$(APPIMAGETOOL).tmp" "$(APPIMAGETOOL)"; \
	fi
	@rm -rf $(APPDIR)
	@mkdir -p $(APPDIR)/usr/bin \
	          $(APPDIR)/usr/lib \
	          $(APPDIR)/usr/share/applications \
	          $(APPDIR)/usr/share/icons/hicolor/256x256/apps
	@cp src/bitflash $(APPDIR)/usr/bin/bitflash
	@printf '#!/bin/sh\nHERE="$$(dirname "$$(readlink -f "$$0")")"\nexport LD_LIBRARY_PATH="$$HERE/usr/lib:$$LD_LIBRARY_PATH"\nexec "$$HERE/usr/bin/bitflash" "$$@"\n' \
	  > $(APPDIR)/AppRun && chmod +x $(APPDIR)/AppRun
	@printf '[Desktop Entry]\nName=Bitflash\nExec=bitflash\nIcon=bitflash\nType=Application\nCategories=Finance;\n' \
	  > $(APPDIR)/bitflash.desktop
	@cp $(APPDIR)/bitflash.desktop \
	    $(APPDIR)/usr/share/applications/
	@APPDIR=$(APPDIR) python3 -c "\
import os; \
from PIL import Image; \
d = os.environ['APPDIR']; \
ico = Image.open('src/rc/bitcoin.ico'); \
img = ico.convert('RGBA'); \
img.save(d + '/bitflash.png'); \
import shutil; shutil.copy(d + '/bitflash.png', \
d + '/usr/share/icons/hicolor/256x256/apps/bitflash.png')"
	@for lib in libsodium.so.23 libdb_cxx-5.3.so libsqlite3.so.0 libglfw.so.3; do \
	  path=$$(ldconfig -p | grep " $$lib " | awk '{print $$NF}' | head -1); \
	  [ -z "$$path" ] && path=$$(find /usr/lib /lib -name "$$lib" 2>/dev/null | head -1); \
	  [ -n "$$path" ] && cp -L "$$path" $(APPDIR)/usr/lib/ && echo "  bundled $$lib" || echo "  missing $$lib"; \
	done
# appimagetool's output used to be piped through grep ... || true, which
# discarded its exit status: the target succeeded and announced "Built:"
# even when no AppImage existed. Run the tool bare so its status decides,
# then require the artefact to exist, be non-empty, and be newer than the
# binary it was packaged from before saying so.
	@ARCH=x86_64 $(APPIMAGETOOL) --no-appstream $(APPDIR) \
	  $(ROOT)/Bitflash-$(VERSION)-x86_64.AppImage
	@test -s $(ROOT)/Bitflash-$(VERSION)-x86_64.AppImage || \
	  { echo "ERROR: Bitflash-$(VERSION)-x86_64.AppImage missing or empty"; exit 1; }
	@test $(ROOT)/Bitflash-$(VERSION)-x86_64.AppImage -nt src/bitflash || \
	  { echo "ERROR: Bitflash-$(VERSION)-x86_64.AppImage is not newer than src/bitflash"; exit 1; }
	@echo "Built: Bitflash-$(VERSION)-x86_64.AppImage"

# ---- Windows (MSYS2 UCRT64) -----------------------------------------------

deps-windows:
	pacman -S --noconfirm --needed \
	  mingw-w64-ucrt-x86_64-gcc \
	  mingw-w64-ucrt-x86_64-make \
	  mingw-w64-ucrt-x86_64-glfw \
	  mingw-w64-ucrt-x86_64-openssl \
	  mingw-w64-ucrt-x86_64-db \
	  mingw-w64-ucrt-x86_64-sqlite3 \
	  mingw-w64-ucrt-x86_64-libsodium \
	  mingw-w64-ucrt-x86_64-nlohmann-json \
	  mingw-w64-ucrt-x86_64-boost \
	  mingw-w64-ucrt-x86_64-cmake \
	  autoconf automake libtool
	@if [ ! -f deps/lib/libsecp256k1.a ]; then \
	  rm -rf secp256k1-build && \
	  git init -q secp256k1-build && \
	  cd secp256k1-build && \
	  git remote add origin $(SECP256K1_REPO) && \
	  git fetch -q --depth 1 origin $(SECP256K1_COMMIT) && \
	  git checkout -q --detach FETCH_HEAD && \
	  test "$$(git rev-parse HEAD)" = "$(SECP256K1_COMMIT)" && \
	  echo "==> secp256k1 pinned at $(SECP256K1_COMMIT)" && \
	  cd $(ROOT)/secp256k1-build && ./autogen.sh && \
	  ./configure --prefix=$(ROOT)/deps \
	    --enable-module-schnorrsig --enable-module-extrakeys \
	    --disable-shared --with-pic --disable-benchmark --disable-tests && \
	  make install && \
	  echo "==> secp256k1 done"; \
	else \
	  echo "==> secp256k1 already built"; \
	fi
	@if [ ! -f RandomX/build/librandomx.a ]; then \
	  rm -rf RandomX && \
	  git init -q RandomX && \
	  cd RandomX && \
	  git remote add origin $(RANDOMX_REPO) && \
	  git fetch -q --depth 1 origin $(RANDOMX_COMMIT) && \
	  git checkout -q --detach FETCH_HEAD && \
	  test "$$(git rev-parse HEAD)" = "$(RANDOMX_COMMIT)" && \
	  echo "==> RandomX pinned at $(RANDOMX_COMMIT)" && \
	  mkdir -p $(ROOT)/RandomX/build && \
	  cd $(ROOT)/RandomX/build && cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release && \
	  make randomx && \
	  echo "==> RandomX done"; \
	else \
	  echo "==> RandomX already built"; \
	fi

windows: deps-windows
	cd src && make -f makefile.mingw
	@rm -rf Bitflash-$(VERSION)-windows
	@mkdir -p Bitflash-$(VERSION)-windows
	@cp src/bitflash.exe Bitflash-$(VERSION)-windows/Bitflash.exe
	@ldd src/bitflash.exe | grep ucrt64 | awk '{print $$3}' | \
	  xargs -I{} cp {} Bitflash-$(VERSION)-windows/ 2>/dev/null || true
	@zip -r Bitflash-$(VERSION)-windows.zip Bitflash-$(VERSION)-windows/
	@echo "Built: Bitflash-$(VERSION)-windows.zip"

windows-tor: windows
	./scripts/package-windows-tor.sh

# ---- Clean ----------------------------------------------------------------

clean:
	$(MAKE) -C src -f Makefile clean 2>/dev/null || true
	cd src && make -f makefile.mingw clean 2>/dev/null || true
	rm -f Bitflash-*.AppImage Bitflash-*.zip
	rm -rf Bitflash-*-windows build

tests:
	$(MAKE) -C src -f Makefile tests

fuzz-net-message-smoke: deps-linux
	$(MAKE) -C src -f Makefile fuzz-net-message-smoke

fuzz-script-smoke: deps-linux
	$(MAKE) -C src -f Makefile fuzz-script-smoke

checksums:
	./scripts/make-release-checksums.sh

sign-checksums:
	./scripts/make-release-checksums.sh --sign $(if $(KEY),--local-user $(KEY),)

verify-release:
	./scripts/verify-release.sh $(if $(TAG),$(TAG),latest)

.PHONY: linux windows windows-tor clean appimage \
        tests fuzz-net-message-smoke fuzz-script-smoke checksums sign-checksums verify-release \
        deps-linux deps-windows deps-apt deps-secp256k1 deps-randomx