upleb.uk

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

summaryrefslogtreecommitdiff
path: root/Makefile
AgeCommit message (Collapse)Author
2026-09-03merge: upstream 1.2.20 into deploy/all-2026-08-24bitflash-maintainer
# Conflicts: # Makefile
2026-09-02build: say so when apt-get update fails instead of passing over itbitflash-maintainer
34cb6e6 added `apt-get update;` before the install. The semicolon means a failed update does not stop the install, which is the behaviour we want -- one unreachable repository should not block an install the lists already on disk can satisfy -- but it was punctuation rather than a decision, and it was silent. The install then failed several seconds later with "unable to locate package" and nothing tied the two together. Kept it non-fatal, made it explicit with `|| echo`, and wrote the reasoning down next to it. The install itself is still the last command in the branch, so a failed install is still the target's status. Measured all four combinations with apt-get shimmed: a failed update now warns and carries on, a failed install still aborts.
2026-09-02build: let a failed pin check decide the exit status everywherebitflash-maintainer
fd627b1's message said the rewrite hardened a pin check that used `;` where it needed `&&`. It hardened one of four. Three recipes still ended their chain with `echo "==> ... done"; ` after a semicolon, so a mismatched pin failed the `test`, skipped every build step, and then ran the echo anyway -- exit 0, and make announced a pinned dependency that nothing had verified. deps-windows also had the same break after mkdir and after `make randomx`. The one the earlier message did not mention is deps-secp256k1, on the Linux path: libsecp256k1 is signature verification, so that was the worst of the three to leave reporting success on an unverified checkout. Joined each chain with && the whole way to the closing echo, in deps-secp256k1, and in both halves of deps-windows. Verified by extracting each recipe as make expands it, splitting it into logical recipe lines and running each in its own shell the way make does, with git reporting a sha that does not match the pin: all three previously exited 0 and now exit 1, and each deps-windows half fails independently rather than relying on the other. With a matching sha the recipes still exit 0, still print "done", and still write the marker. The Windows path was not executed -- there is no MSYS2 here -- so pacman was shimmed and only the shell logic below it was measured.
2026-09-02build: quote the RandomX path so the guard cannot be stepped aroundbitflash-maintainer
The marker guard added in fd627b1 used $(RANDOMX_DIR) unquoted, so an operator-supplied value that expands to anything other than one word walked straight past it. With a glob matching two directories, `[ -d $(RANDOMX_DIR) ]` became a two-argument test, died with "unexpected operator" rather than answering, and short-circuited the && before the marker was ever looked for -- leaving `rm -rf` to run against every match. A value holding a space or a `&&` broke the outer test the same way and reported "already built", and an apostrophe made the recipe unparseable. So the commit's claim to refuse any directory lacking the marker was falsifiable. Quote every use, and reject up front the values that cannot name one directory: empty and / now refuse deliberately rather than by way of the one-argument `[ -d ]` quirk that happened to catch them, and whitespace and glob characters are refused outright because src/Makefile interpolates the same value into -I and -L unquoted, where neither survives. A relative value is refused too: it named a different directory here than in src/, and cd'ing into it made the later mkdir -p resolve against the new cwd, so the build landed in randomx-build/randomx-build/build and cmake found nothing. Verified against the recipe text as make expands it, with rm replaced by an echo, over empty, /, globs matching one and two directories, spaces, tabs, relative paths, symlinks, apostrophes, semicolons, backticks and newlines, under both dash and bash: every run either refuses with a message or passes rm exactly one argument, identical to the path the guard tested.
2026-09-02build: let appimagetool's exit status decide, and check the artefactbitflash-maintainer
The appimage recipe piped the tool through grep ... || true, so a failed run still ended with an unconditional "Built:" echo and make reported success with no AppImage on disk. Run appimagetool bare so its status propagates, then require the artefact to exist, be non-empty, and be newer than src/bitflash before announcing it. Closes #29
2026-09-02build: ask apt for libboost-dev, and update the package lists firstbitflash-maintainer
libboost-system-dev is gone from Ubuntu 26.04 -- Boost.System has been header-only since 1.69, and this tree only includes two boost headers and links no boost library. The recipe also installed without an apt-get update, so a fresh image failed on stale or empty lists. Closes #28
2026-09-02build: stop rm -rf'ing a RandomX tree the build did not createbitflash-maintainer
deps-randomx built into $(HOME)/RandomX and deleted that path whenever the library was missing, taking out hand-managed checkouts the build never owned. Default the build to the in-tree $(ROOT)/randomx-build, like secp256k1-build, overridable via RANDOMX_DIR and exported so src/Makefile's own fallback resolves to the same tree. As a backstop, the recipe writes a .bitflash-built marker after a successful build and refuses to delete any directory that lacks it, so a user-supplied RANDOMX_DIR pointing at a pre-existing tree is never removed. Closes #27
2026-09-01Merge upstream v1.2.20 into the forkbitflash-maintainer
Merges upstream tag v1.2.20 (6aa3dade) into our 1.2.19+sec tree. The merge base is bc3de30's descendant 9e39a50 -- upstream's own v1.2.19 -- so this is an ordinary three-way merge, not a replay. Five conflict blocks in four files, all resolved below. No consensus change. CheckBlock, ConnectBlock, DisconnectBlock, ConnectInputs, AcceptBlock, AcceptTransaction, GetNextWorkRequired and GetBlockValue hash identical to v1.2.19; script.cpp and script.h are byte-identical on both sides. main.cpp is +3 lines and main.h +2, all wallet-backend plumbing. rpc.cpp is untouched, so the payout ledger arrives unchanged. Makefile, VERSION. OURS, renumbered. 1.2.20+sec, for the same reason 1.2.19 carried +sec: this tree is not the upstream release of that number and the string names every release artifact. Makefile, AppImage recipe. OURS for the AppDir path, THEIRS for the bundled library. Upstream's hunk reverts the icon paths to a hardcoded /tmp/bitflash.AppDir, which is the local-user pre-placement hazard the fork moved out of /tmp in the first place; taking their side wholesale would have reintroduced it silently, inside a conflict about something else. The AppDir stays $(APPDIR), and libsqlite3.so.0 joins the bundled list, which the new backend genuinely needs. src/Makefile, src/makefile.mingw, HEADERS. BOTH. Ours drops market.h, theirs adds wallet_sqlite.h; the resolution does both. NODE_OBJS auto-merged correctly in both files -- obj/wallet_sqlite.o present, obj/market.o still gone -- and was verified rather than assumed, because that line has trapped this fork before. src/selftest.cpp, known-tests list. BOTH, union. Upstream adds eight wallet-storage tests, we keep payout-ledger. The dispatch chain itself auto-merged and still routes payout-ledger to RunPayoutLedgerSelfTest. Hardening verified intact after merge: the TLS 1.2 floor in nostr.cpp, the /rvrelay pin, opt-in managed Tor, strict sig3-on-onion descriptor verification, and the marketplace removal. Upstream shipped no build logs or __pycache__ this time; the three added files are docs/storage.md and src/wallet_sqlite.{cpp,h}. The SQLite backend this release introduces is NOT enabled here. An existing wallet.dat resolves to Berkeley DB on every branch of the new selection logic, and the conversion wizard is GUI-only and not compiled into the headless node. The review findings that gate ever turning it on -- the non-atomic backend marker, the connection-wide WriteHDMaster transaction, and the encrypt-swap crash window -- are recorded on the tracker, not fixed here.
2026-08-26Merge upstream v1.2.19 into the forkbitflash-maintainer
Merges upstream tag v1.2.19 (9e39a50) into merge/upstream-1.2.18 (991853f). Unlike the 1.2.15->1.2.18 replay, bc3de30 is a true ancestor of our branch via a9b4f89, so this is an ordinary three-way merge with merge-base bc3de30. Delta: 36 files, +3265/-163, the Tor onion transport (managed Tor, signed direct onion endpoints, onion-only dial mode) and the nHDNext restore fix. src/main.cpp, main.h, script.cpp/h and db.cpp/h are byte-identical between the two tags, and the poll() socket loop carries no diff hunks, so the merge is consensus-neutral and the soak-tested socket paths are unchanged. relay/ is untouched; the payout ledger (rpc.cpp) is untouched. 7 conflict blocks in 6 files, resolved as follows. Makefile -- VERSION. Ours: stays 1.2.19+sec with the naming comment updated, so artifacts from this tree cannot be mistaken for the upstream release. src/Makefile, src/makefile.mingw -- HEADERS list. Ours (no market.h) plus their tor.h. NODE_OBJS auto-merged correctly: no market.o, tor.o present. src/selftest.cpp -- known-tests list. Union: payout-ledger (ours) and managed-tor (theirs). src/main_gui.cpp -- one block. Our /dumpaddresses kept; their BtfStopManagedTor() on the StartNode failure path taken. The /rvrelay pin, /noannounce and the marketplace removal auto-merged and were verified intact. src/nostr.cpp -- two blocks in ResolveDescriptor. Signature takes both out-params (our fExchangeOk, their rawOut). The body keeps our drain-to-EOSE read -- returning early poisons the pooled socket for the next caller -- and adds their rawOut capture. Our TLS 1.2 floor (SSL_CTX_set_min_proto_version) is untouched by this merge; upstream 1.2.19 still has no floor of its own. Upstream's release commit also added four build logs and a compiled Python cache (build-1219.log, build-17.log, build-appimage.log, build-node.log, scripts/__pycache__/bitflash_chain.cpython-313.pyc). Removed here; they are build output, not source.
2026-08-23build: version this tree 1.2.18+sec, not 1.2.18bitflash-maintainer
VERSION names every release artifact -- the AppImage, the windows zip and the node binary. This tree is upstream v1.2.18 plus 24 commits of our own and a payout ledger that is neither side's, so a binary built here must not be mistakable for the upstream release of that number.
2026-08-23Merge upstream v1.2.18 into the forkbitflash-maintainer
Merges upstream tag v1.2.18 (bc3de30) into our 24 commits. The merge base is a259215, upstream's own commit, whose src/ tree is byte-identical to 68de575 -- our commits were replayed onto a259215 first so that git had a real base to work from. That produced 28 conflict hunks in 9 files instead of the 21 conflict events / 51 hunks a direct cherry-pick onto the tag would have forced, most of which were artifacts of lifting our commits away from their own predecessors. No consensus change. main.cpp's GetBlockValue, GetNextWorkRequired, ConnectInputs, ConnectBlock, CheckBlock, AcceptBlock and AcceptTransaction are untouched by this merge; script.cpp, serialize.h and randomx_pow.h are identical on both sides. The frozen-chain oracle reproduces the pinned UTXO root 116a2b6b... byte for byte, and the merged binary loads the same 12,586-block chain to the same tip aac622f0a3b2f8... with all blocks re-verified and none rejected. Which side won, per file and per hunk. src/rpc.cpp -- 15 hunks. 1. RebuildJob transaction selection. THEIRS for the multi-pass loop and the nFees fix, OURS for the cap. Their vfAlreadyAdded/fFoundSomething loop lands dependent transactions that our single pass dropped deterministically, and their nFees is accumulated across the scan instead of being redeclared inside it -- ours passed a hardcoded 0 to GetBlockValue, so every pool block built here discarded its fees. But their bound is MAX_SIZE/2 = 16 MB, and MAX_BLOCK_SIZE is 1 MB, so their template can exceed what their own CheckBlock accepts. Cap kept at MAX_BLOCK_SIZE/2, with our inner break so a single pass cannot overshoot by more than one transaction. Our RebuildJob sync gating and deaf-node detection are kept; upstream has none. 2. struct PendingPayout. OURS for the key, THEIRS for paidTxids. Their id is the string "block-<height>", which is height keying -- the exact bug 44849e6 fixed after a 280-block reorg left ~14.4k BTF owed against blocks that no longer existed. Height keying also lets the crash-recovery probe match the previous round's transaction after a reorg and silently skip a miner, so their id is dropped entirely and blockHash stays the key. paidTxids is taken because it is what makes an entry survivable. Their nextAttempt and our retryAfter are the same concept under two names; ours is kept and theirs dropped. matureAtHeight gained an explicit = 0, which neither side had in our style of initialiser. 3./4. Ledger writer and loader. Mechanical follow-ons of 2. The loader keeps ours and takes their tolerance for an entry with no usable height, guarded so it cannot fire on a well-formed entry. One redundancy was removed rather than merged: their unguarded `if (obj.contains("blockHeight")) get<int>()` sat two lines above our is_number-guarded version, and an unguarded get<int>() on a malformed value throws out of the whole loop, losing every entry in the file. Verified against the live production pending_payouts.json (2 entries, keys amounts/blockHash/blockHeight/matureAtHeight): loads both. A file in the new shape, with retryAfter and paidTxids, also loads. 5. Insertion point after the loader. BOTH. Our PayingRecord/gPaying in-flight ledger and their PoolRoundProof/PoolRoundToJson are disjoint features that landed at the same offset. Both kept, in full. 6. Writer tail. THEIRS, copied into our writer. Checking fclose() matters here because fclose is where the buffered write actually reaches the OS: renaming a short .tmp over a good file is precisely the truncation the atomic write exists to prevent. The Windows remove()-before-rename() is required because rename() there will not replace an existing file. 7. Loader bodies. BOTH -- LoadPaying and LoadPoolRounds are different functions that happened to share a common tail. 8. Function block, 111 vs 36 lines. BOTH, with one deliberate deletion. PayoutBlockStillOnChain, PayoutComment, RecordPoolRound, MarkPoolRoundPaid all kept. WalletAlreadyPaid is DELETED and ReconcilePayouts rewritten, because our reconciliation and their FindRecordedPoolPayout are two answers to the same crash-recovery question and running both would give the same debt two authorities. Their wallet probe wins: it matches structured metadata rather than a comment string, so it can tell two rounds paying the same address the same amount apart. ReconcilePayouts keeps only the job that is still real -- dropping in-flight records the ledger already shows paid, and arming each restored record for its one wallet probe. It no longer touches gPendingPayouts at all; under the new model dropping ledger entries there would reintroduce the loss this merge adopts their model to prevent. Their gPayoutExecMutex/gPayoutThreadRunning re-declarations were dropped as duplicates of ours, and gPayoutExecQueue was not taken: gPaying is the exec queue in this tree, at finer granularity. 9. QueuePayouts signature. THEIRS for the round-proof arguments, OURS for the type of blockHash. It stays a uint256 and is converted to hex only where the round proof needs text -- a ledger key that has been through ToString() is a key that can be compared wrong. 10. Maturity policy. OURS. matureAtHeight = blockHeight + COINBASE_SPENDABLE_DEPTH (120), not + COINBASE_MATURITY (100). At depth 100 the block's own coinbase is still 20 blocks short of spendable, so the payment must come from unrelated mature coin and the pool runs a permanent float. Our own history contradicts itself here -- d6972a0 argued for COINBASE_MATURITY citing "96% of 934 payments", 44849e6 reversed it on the grounds that the measurement was taken while the node was alone on a private fork paying itself. HEAD's position is kept. This is a considered disagreement with upstream, not an oversight. The live production ledger confirms it: both entries have matureAtHeight - blockHeight = 120. 11. Comment above FlushMaturePayouts. Rewritten to describe the merged design. 12. FlushMaturePayouts, the central difference. THEIRS for the model. The ledger entry now survives on disk until every recipient has a txid, instead of being erased as soon as its payments were handed off -- the old ordering left a window where the debt was described by nothing at all, and a container restart on 2026-07-30 silently dropped ~1,156 BTF through it. Our gPaying is kept on top as the in-flight working set, because it carries the per-recipient retry and deferral state a per-round nextAttempt cannot express. It is explicitly a cache derived from the ledger, not a second record of debt: if it were lost the next flush would rebuild it. 13. PayoutThreadFn, 79 vs 70 lines. OURS for the loop and the reorg re-check, THEIRS for the per-recipient dedupe. The re-check on every attempt is load-bearing and upstream has no equivalent -- it decides once, at maturity, and a payment can sit through hours of retries during which the reorg that kills its funding block can land. Their paidTxids check is taken, against the ledger rather than against the batch. Our deferral policy is kept: waiting for coin is not a failure and must not consume the retry budget. Two fixes upstream does not have are folded in here rather than deferred to a follow-up commit: - FindRecordedPoolPayout runs once per record, on its first attempt, not on every attempt. It is a linear scan of mapWallet under cs_mapWallet, per recipient; upstream repeats it for every retry, which with 2000 rotating payout addresses and a budget of 1440 retries is a full wallet scan per address per minute for a day. The question it answers can only change across a restart, and every restart re-arms the flag. - The ledger file write no longer happens under gPayoutLedgerMutex in the per-recipient loop. SavePendingPayoutsLocked was split into a serialise step that needs the lock and a write step that does not, so the mutex is held only long enough to read the structure. The orphan scan in FlushMaturePayouts was likewise restructured so the ledger mutex is not held across PayoutBlockStillOnChain's cs_main acquisition. One case the merged model created and neither side handled: under survives-until-fully-paid, our GIVING UP path dropped the in-flight record but left the recipient owed in the ledger forever, so every later flush would explode it again -- a forfeit that silently becomes an infinite retry. ForfeitPayoutRecipient writes the amount off so the entry can retire. 14. QueuePayouts call site. Follow-on of 9. 15. ThreadRPCServer startup. BOTH. LoadPoolRounds added; LoadPaying and ReconcilePayouts stay, and stay before the payout thread starts. Crash safety, stated as the property that must hold: after kill -9 at any point, restart pays each recipient exactly once. Before SendMoney, the ledger has no txid and the payment is retried. Between SendMoney and the ledger write, the wallet holds the transaction and the first-attempt probe finds it. After the ledger write, paidTxids suppresses it. If the in-flight file is lost entirely, the ledger rebuilds it and the probe still covers the middle window. src/nostr.cpp -- 3 hunks. THEIRS as the base, with two things restored from ours. Theirs loads the Windows ROOT certificate store, which ours lacks and genuinely needs, since OpenSSL's default verify paths are usually empty there and our version would fail closed. But the plan's claim that theirs is a superset of our 06bceb4 is FALSE: upstream's nostr.cpp contains no min_proto_version, no TLS1_2, no SSL_OP_NO_*, and no set_options anywhere, so taking it wholesale would have removed the TLS-version floor outright. Restored. Second, their SSL_get_verify_result check sits after a successful handshake, where with SSL_VERIFY_PEER set it is nearly dead code -- a bad certificate aborts inside SSL_connect. The reporting belongs in the failure branch, which is where the merge put it; their post-handshake duplicate was dropped. Our relay-list and socket-pooling hunks auto-merged and were verified intact. src/net.cpp -- 1 hunk. THEIRS. Their guard, hSocket == INVALID_SOCKET || pnode->hSocket != hSocket, subsumes ours and additionally catches descriptor recycling, which ours did not; our version also referenced a pnode that only existed in the select() loop. Our other db34011 hunk, nulling pnode->hSocket in the poll-failure branch, auto-merged and was NOT re-applied -- plan section 7 Batch D says to re-apply it, and that instruction is stale. Two pieces of residue the merge left behind were cleaned up: a comment still describing select() semantics, and a pnode->hSocket != INVALID_SOCKET guard made redundant by a continue two lines above it. src/main.cpp -- 2 hunks. h1 BOTH, pure add/add adjacency between our ParticipantHashThreadCount and their SecureClear forward declarations. h2 OURS, and their handler discarded: 44004e3 no-ops checkorder/submitorder but keeps market.cpp and market.h, whereas 11766be deletes the marketplace outright including CRequestTracker, subscribe/publish and mapProducts. Ours is strictly more. src/main_gui.cpp -- 2 hunks. BOTH, additive on both sides. Our /payoutaddr, /payoutfile and /hashthreads alongside their /stratumbridge and /stratumbridgeport. src/btfrv.cpp -- 1 hunk, not predicted by the plan. False adjacency: our INVALID_SOCKET check and their #endif landed at the same offset. Ordering is load-bearing -- our check goes inside the #else arm, before the #endif, or the non-proxy build silently loses it. The BITFLASH_NODE_BUILD arm already returns early, so both paths now have the check. Makefile -- 2 hunks plus a hazard with no conflict marker. THEIRS for both dependency-fetch recipes: checkout --detach FETCH_HEAD followed by test "$(git rev-parse HEAD)" = "$(..._COMMIT)" asserts the pin actually landed, where ours only checked it out. Everything of ours that they lack is kept: the appimagetool 1.9.1 release pin with a sha256 check, where upstream fetches the mutable "continuous" tag and executes it undigested, and the move of both the tool and the AppDir out of /tmp, where any local user could pre-place an executable and have it run as whoever runs make. The hazard: both sides added a pin block in a different place, so git merged both, outside any conflict region. SECP256K1_COMMIT and RANDOMX_COMMIT ended up assigned twice, near L16 and near L44, and GNU Make takes the last := -- a naive resolution would have silently built against our old secp256k1 pin while appearing to adopt upstream's. The duplicate block was deleted by hand. Each of the four variables is now assigned exactly once, and make -n resolves SECP256K1_COMMIT to 7fecac74 with 6e2c8bc4 appearing nowhere. Also applied here, from plan phase 4: SECP256K1_COMMIT moved to upstream's 7fecac74aed8e1fd9078380d67dd04663705c989 so signature validation matches the binaries the rest of the network runs. secp256k1 was rebuilt from scratch against the new pin on the build host and the checked-out dependency commit verified. RANDOMX_COMMIT already matched at 1e9d4b2d and is unchanged. src/Makefile, src/makefile.mingw -- 1 hunk each, both the HEADERS list. Ours drops market.h, theirs adds proxy.h; the resolution does both. Taking theirs wholesale would have left the Makefiles referencing a header git had already deleted in our favour, silently, without a conflict. Files the plan predicted would conflict and did not: net.h, main.h and db.cpp auto-merged. Verified rather than assumed. net.h carries both our BTF_MSG_WAIT_MAX_SECS work, including the constructor fix from 2d84982 that starts nMsgWaitSince and nMsgWaitSize at 0, and their comment rewrite with extern int nPeersWatched. The dead members that fix removed are gone from every file. Deviations from MERGE-1.2.18-PLAN.md. - Section 6's mechanism was not used. Replayed onto the real merge base and merged, rather than cherry-picking onto the tag. Same result, 28 hunks instead of 51. - Section 2 calls 09ff575 a superset of our 06bceb4. It is not; upstream has no TLS-version floor at all. Ours restored on top of theirs. - Section 7 Batch D says to re-apply our db34011 line into their poll-failure branch. It auto-merged; re-applying would have duplicated it. - Section 2 says do not take the stratum bridge. It is taken, and left off. fStratumBridge defaults false in main.cpp, the thread is only spawned when /stratumbridge is passed, and the listener binds 127.0.0.1 only. Excising it would cost real divergence and future conflict for no gain while it is disabled. Its defects -- select()/FD_SET on a node that can hold more than 1024 descriptors, and an uncapped thread per connection -- are real only when it is enabled and are being reported upstream separately. - Section 7's staged batches were not run as separate gated commits. The conflict surface is interleaved within single functions, chiefly the payout ledger, and splitting it would have meant committing states that do not build. The merge is one commit, as briefed. - Batch F's two fixes are folded into hunk 13 rather than deferred, because they change the same lines the resolution rewrites. - The plan did not anticipate the btfrv.cpp conflict, the duplicate pin block, the market.h trap in the two Makefiles, or the give-up/infinite-retry interaction created by adopting upstream's ledger model.
2026-08-23build: pin the dependencies that decide consensus, and check what we runbitflash-maintainer
The two dependency clones were `git clone --depth 1 <url>` with no revision, which resolves to whatever the default branch points at the moment the build happens to run. For RandomX that is the proof of work. Its master is not frozen -- v2.0 changed the algorithm in March -- so two builds of the same Bitflash commit, made weeks apart, can hash the same header differently. That is not a supply-chain worry, it is a chain split delivered as a build artifact, with no version recorded anywhere to explain it afterwards. For libsecp256k1 it is signature verification, the rule deciding who may spend. Both now fetch a full commit id, which is the only form here that verifies what it got, since git resolves the object by its own hash. The RandomX pin is the commit this tree has been building and syncing against, so today's behaviour is unchanged; secp256k1 is pinned to the v0.8.0 release commit. appimagetool came from the "continuous" tag, which upstream moves in place on every push, fetched over the network and executed unverified. Pinned to a numbered release and checked by digest before it is allowed to run. It also ran from /tmp on a fixed name, guarded by `[ ! -x /tmp/appimagetool ]` -- an executable already sitting there is taken as the tool, so any local account could leave one and have it run as whoever built. The AppDir had the same shape one step later, through symlinks on the rm -rf. Both moved under the build tree, which the build already owns, and cleaned by `make clean`. (cherry picked from commit 664aebca08d17a9dbae12c25f21cd8ef2abe7aa6)
2026-08-19build: VERSION 1.2.20bitflash
Name the release artifacts for v1.2.20. Matches BITFLASH_VERSION_STRING.
2026-08-14wallet: add experimental SQLite record storeNsable-sh
2026-08-13release: v1.2.19 (nHDNext restore fix, Tor onion transport: managed Tor, ↵bitflash
direct onion peers, onion-only mode)
2026-08-13release: package Windows builds with managed TorNsable-sh
2026-08-12release: v1.2.18 (Tor/SOCKS5 outbound privacy, poll() socket handler, new ↵bitflash
app icon)
2026-08-12release: bump version to 1.2.17 (hardened audit tooling)bitflash
2026-08-10release: bump version to 1.2.16Bitflash
2026-08-07fuzz: add script parser harness (#5)Nsable-sh
2026-08-04fuzz: add P2P message parser harness (#142)mtg0001
* fuzz: add P2P message parser harness * fuzz: avoid duplicate legacy header includes * fuzz: seed the corpus with our network magic, not Bitcoin's The two seed inputs began f5 be b6 d9, which is Bitcoin's message start. Ours is bf 20 5c fd. CMessageHeader::IsValid rejects on that field first, so both seeds died at the first check and the smoke run exercised the reject path and nothing else. libFuzzer would have to mutate its way onto four exact bytes before reaching any parser, which does not happen in 128 runs. The job would have gone green while covering none of what it was added to cover. --------- Co-authored-by: Gilcimar Duarte <ti@gpm.infra> Co-authored-by: Bitflash-sh <btf01@proton.me>
2026-08-04build: pin external source dependencies (#139)mtg0001
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
2026-08-04Add release verification tooling (#108)mtg0001
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
2026-08-03Bump to 1.2.15 (#120)Bitflash
2026-08-02Bump to 1.2.14 (#116)Bitflash
A block had no size limit, spending could move coins onto a key your recovery phrase cannot reproduce, and one peer could hold up the whole node with a message it never finished sending. All three are fixed here, and all three were found by auditing the tree rather than by anyone hitting them. ## Blocks now have a size limit — and this one is consensus `CheckBlock()` measured a block against `MAX_SIZE`, the serializer's generic 32 MB guard. There was no `MAX_BLOCK_SIZE` anywhere in the tree: Bitcoin's 1 MB cap arrived in 0.3.x, after the 0.1.0 this project came from, so the inheritance was simply missing. A miner could produce a 32 MB block and every node would download, verify and store it. The cap is now **1 MB**, chosen against the chain rather than tradition. Reading all 5709 blocks out of `blk0001.dat`: | | | |---|---| | largest block ever mined | 34,290 bytes | | median | 216 bytes | | p99 | 14,451 bytes | 1 MB leaves **29× of headroom** over the largest block this chain has ever carried, so nothing existing is rejected. A node built with the rule then synced genesis → 5706 on the live network with zero rejections, which is the same test that once caught BIP30 rejecting a real block at height 859. **Because it is consensus, it matters that you upgrade.** Until most nodes have it, a block above 1 MB would be accepted by old nodes and rejected by new ones — which is how a chain splits. Nobody is mining such blocks today; the gap is worth closing before somebody does. ## Spending could put your change on a key the phrase cannot recover When a wallet spends, the leftover goes back to you as *change*. This tree sent change to **the same key as one of the coins being spent** — 0.1.0 behaviour, and a privacy problem long tracked as #23. With recovery phrases it became a way to lose money. If the coin being spent sat on a key created *before* the seed — the wallet's original default address, an imported key, anything older than `-newphrase` — then the change went back to that same pre-seed key. So spending quietly moved your balance onto a key the twelve words do not reproduce, in a wallet you believed the words covered. Change now comes from a newly derived, phrase-backed key. **If that derivation fails, the transaction is refused rather than created**, because a failed send is recoverable and unrecoverable change is not. Wallets with no seed keep the old behaviour exactly. Restore also now scans past the pre-generated key-pool window before accepting an empty batch as the end, so a restore cannot stop short while used addresses sit just beyond it. ## One peer could hold up the whole node `ProcessMessages` runs in a single thread that serves every peer. When a message header announced more bytes than had arrived, the old code consumed the header, inserted it back at the *front* of the receive buffer — an O(n) memmove on a buffer that could hold 256 MB — and then slept 100 ms **inside that shared thread**. A peer that announced a large message and dripped bytes could make every pass pay both costs, forever, while looking perfectly active to the inactivity reaper. The parser now reads the header from a copy without consuming it, never sleeps in the shared thread, and disconnects a peer that sits on an incomplete message for two minutes. The wire cap drops from 256 MB to `MAX_SIZE`. ## The rendezvous holds more than one registration A node's registration at a rendezvous relay was consumed by the first caller, and registering again *replaced* the previous one — so a node was reachable to exactly one peer at a time and unreachable for the whole gap between being paired and registering again. Measured on a live node: descriptors resolved 53 times out of 54, its own registrations paired 11 out of 12, and **half of every outbound dial failed**. All three relays now keep a queue per node and pair with the oldest registration still alive. **This is already deployed** — you get it without upgrading. The matching node-side change, which parks spare registrations, is deliberately **not** in this release: it measured worse in the field than the code it replaced and will not ship until that is explained. ## Restoring from a phrase now looks far enough Found by proving the change fix above with real coin, and caused by it. Restoring derived forward in batches of a hundred and stopped at the first batch that turned up nothing. The wallet digs a wider gap than that by itself: a restore leaves the derivation counter at the depth it scanned, the key pool derives a hundred more, and change takes the index after those. So the address used *after* a restore can sit two hundred indices past the last one used before it, with nothing in between. On a real wallet: receive, restore, spend once — the outputs land at indices 201 and 302. Restoring again reported ``` Restored: 1 transaction(s) across 201 derived addresses ``` and the spend was invisible, both the payment and the change. The money was derived and covered by the phrase; the search stopped short of it. Three quiet batches instead of one, and the same wallet reports `2 transaction(s) across 601 derived addresses`. Change returning to an input's key used to hide this, because that key sat at a low index the scan had already passed. Fixing recoverability moved change out to where the search stopped looking, so the two had to be fixed together. The gap is self-inflicted and grows with each restore, so a long enough history can still outrun a fixed number of quiet batches. `-restoredepth=N` looks as far as you ask. The structural fix is tracked separately. ## A headless node can finally spend `SendMoney()` has been in this tree since 0.1.0 and only the window ever called it, so a node without a screen could be paid and could never pay — it could hold a balance with no way to move it. Found while trying to prove the change fix, which needs a spend. ``` bitflash -sendto=ADDRESS,AMOUNT ``` Refuses an address that does not decode, an amount that is zero, negative or not a number, and a wallet whose chain is not loaded — all before touching the wallet. A transaction created while the node is stopped is not broadcast until the node starts. ## Also - `-help` printed nothing at all on Windows. This tree remaps `printf` to the debug-log writer, and a `-mwindows` binary has no console, so the one command whose job is to tell you something returned 0 in silence. - The README described a program from several releases ago — deterministic restore listed as an open issue, mining mode described as forgotten between restarts, and every flag written in the slash form that MSYS2 rewrites into a path. - Diagnostics now report why a socket close failed, per error code and per site, and flag a handle handed back while some part of the program still claims it. ## Downloads | File | Platform | Notes | |------|----------|-------| | `Bitflash-1.2.14-windows.zip` | Windows x86_64 | Extract and run | | `Bitflash-1.2.14-x86_64.AppImage` | Linux x86_64 | Desktop/GUI — `chmod +x` and run | | `bitflash-node-1.2.14-x86_64` | Linux x86_64 | Headless server node, no libGL or FUSE | Verify with `sha256sum -c SHA256SUMS`. Everything in [v1.2.13](https://github.com/Bitflash-sh/bitflash/releases/tag/v1.2.13) is included.
2026-08-01Bump to 1.2.13 (#105)Bitflash
Two things a node could do wrong without ever saying so: close a peer's socket twice, and hand out addresses a recovery phrase cannot reproduce. ## If you created a recovery phrase on 1.2.12, do this first Run, with the same twelve words: ``` Bitflash.exe -restorephrase="your twelve words here" ``` 1.2.12 installed the seed but left the wallet's visible address as the old random key, and left the old random key pool in place. The window said a recovery phrase existed while the next address the wallet handed out — a mining payout included — could be one the words cannot reproduce. Restoring with the same phrase repairs it: the pool is cleared, and the default address becomes a derived one. Coins already sitting on pre-phrase addresses stay where they are. They are still yours, still spendable, and still **not** covered by the words — which is why `wallet.dat` backups keep mattering. The wallet now names that address `Your Address (created before the recovery phrase)` instead of leaving two entries both called `Your Address` with only one of them recoverable. Thanks to **@mtg0001**, who found this within hours of 1.2.12 shipping (#102). ## A peer's socket was closed twice, and the second close hit whoever had the number `CNode::Disconnect()` closed the socket and left the handle number in the object; the destructor then closed that same number again. There is no assignment of `INVALID_SOCKET` to that field anywhere in the tree, so the guard meant to prevent this never fired. A double close is not a harmless no-op. This node opens hundreds of sockets a minute, and Windows reissues a handle number as soon as its last close lands — so the second close takes down a connection that now belongs to something else entirely. The same omission left a closed handle in the `select()` set, where it makes `select()` fail on every iteration for as long as that node is in the list. The node then spends its socket loop in an error path instead of reading its peers. **That is the deaf-node behaviour this project has been chasing since 1.2.7**, and the `dropping node: its socket is no longer valid` line in the log is the recovery path, not the disease. Measured, two nodes on the same machine and the same network, stopped at matched socket volume: | | sockets opened | failed closes | `no longer valid` | peers held | |---|---|---|---|---| | without the fix | 552 | 4 | **741** | 11 | | **with the fix** | 547 | **0** | **0** | **21** | Half the peers, on the same machine, at the same minute. All 741 drops were the same peer, over and over — one dead handle in the set is enough. ## The socket leak was already fixed, and the way it was being measured was wrong Earlier notes, including 1.2.12's, said sockets were still leaking. They were not. Windows lists a wildcard-bound socket **twice** in `Get-NetTCPConnection` — once for the binding (`0.0.0.0:port`, state `Bound`) and once for the connection — and 35 of one node's 38 `Bound` rows shared a local port with one of its own established connections. Counting those rows makes the number climb whenever the node talks to more peers, which is why the fix that worked looked like it had done nothing. Counting **orphans** instead — a `Bound` row whose port appears nowhere else in that process — measured on three machines the same evening: | node | version | uptime | orphans | |---|---|---|---| | 32-core miner | 1.2.10 | 26 h | **1262**, still taking on 30–90 an hour | | second miner | 1.2.11 | 4h40m | 3 | | test bench | 1.2.12 | 2 min | 3 | The leak was real and **1.2.11 ended it**. If you are still on 1.2.10 or earlier, that alone is worth the upgrade. ## Also in this release - The diagnostics report now says why a close failed, per error code and per site, totals what the node believes it is holding, and counts a handle handed out while some part of the program still claims it (#98, #100, #101). - Every header is listed in both makefiles (#99). `sockcount.h` was missing, so editing it rebuilt some objects and not others and left two layouts of the same shared struct in one binary — a warning in the middle of a build log and nothing else. ## Downloads | File | Platform | Notes | |------|----------|-------| | `Bitflash-1.2.13-windows.zip` | Windows x86_64 | Extract and run | | `Bitflash-1.2.13-x86_64.AppImage` | Linux x86_64 | Desktop/GUI — `chmod +x` and run | | `bitflash-node-1.2.13-x86_64` | Linux x86_64 | Headless server node, no libGL or FUSE | Verify with `sha256sum -c SHA256SUMS`. Everything in [v1.2.12](https://github.com/Bitflash-sh/bitflash/releases/tag/v1.2.12) is included.
2026-08-01Bump to 1.2.12 (#97)Bitflash
Version bump for the 1.2.12 release. One line of Makefile; the release notes live here. ## The reason this release is not optional **#87 — a node that was mining before 1.2.11 can come back as a relay and never say so.** The 1.2.11 mining rework left `fGenerateBitcoins` readable on its own: a wallet carrying that flag from an older build, with no mining mode stored beside it, loads as "not mining" and the window shows nothing wrong. It happened here, on a real node, and it was found only because somebody looked at the hashrate. Anyone running 1.2.11 who mined on an earlier build should assume they are affected. ## Recovery phrase (#93, #96) Twelve words that bring a wallet back. Proved end to end before merging: a live wallet was paid on a derived address, `wallet.dat` was deleted, and the coin came back from the phrase alone. - `-newphrase` creates a phrase, installs the seed, fills the key pool from it and shows the words once. It refuses when a seed already exists. - `-restorephrase="WORDS"` installs the seed and walks forward in batches of 100, rescanning after each and stopping when a batch turns up nothing. `-restoredepth=N` to look further. - `-newaddress` prints the next receiving address; `-showderived=N` lists what a phrase produces. - The same two operations are reachable from the window. **What the phrase does not cover, stated plainly:** keys that existed before the seed was installed — including the wallet's original default address — are not derived from it and do not come back from the words. The commands say this at the point of use. Keep the `wallet.dat` backup as well; the phrase is a second line, not a replacement. The words are printed to the terminal only, never to `debug.log`. ## Wallet safety - **#88** — `ScanForWalletTransactions` plus `-rescan`: the node can now walk the chain for coins a key owns but the wallet never saw. `-importwallet` scans too. Without this, restoring from a phrase would have derived the right keys and found nothing. - **#89, #90** — a backup panel in the window, with the default backup written outside the directory it protects. - **#95** — a scan with no chain data loaded is refused instead of reporting "no transactions found", which read as a verdict to somebody who had just lost a wallet. ## Tests and diagnostics **#91** wallet key pool self-test, **#92** self-test output where the person running it can see it, **#94** MSYS2 argument conversion (use `-selftest=...`, not `/selftest=...`, under MSYS2). `make tests` runs the BIP32/BIP39 vectors and both wallet self-tests on Linux and Windows. ## Still not fixed The socket accounting shipped in 1.2.11 is instrumentation, not a fix — sockets still leak. The three obvious causes have been measured and eliminated; the next step is reading a real node's diagnostic report, not another guess.
2026-08-01diag: let the node say what is wrong with it (#81)Bitflash
* Bump to 1.2.11 One change, and it is the reason a node on Windows kept collecting peers it could not hear. FD_SETSIZE was never defined in this tree, so it defaulted to 64 and select() could watch the listen socket plus 63 peers -- everything past that stayed connected and was never read again. Windows FD_SET stops adding once its array is full and says nothing, so there was no error to notice. Because new connections go on the end of vNodes, it was always the newest connection that starved, including the ones dialed to fix connectivity. Measured on two nodes: 277 and 90 CNode objects against 93 and 90 live sockets, 316 and 355 sockets in a bound-but-never-connected state, and a status bar reporting hundreds of peers for a node really talking to thirty. 1.2.11 raises FD_SETSIZE to 1024 on Windows, caps connections at 125, drops a node stuck undeliverable after 60 seconds instead of holding it forever, stops answering a failed select() with POSIX code that has no meaning on Windows, and warns when there are more peers than select can watch. * diag: let the node say what is wrong with it Every hard problem in this project so far was found from outside the node -- sockets read over WMI, committed memory compared against a README, a grep over somebody debug.log. In each case the node knew and had no way to say so. One report, rendered identically for the GUI panel and the log: peers held vs peers actually watched by select(), blocks received and how many arrived without a parent, proof-of-work mode with the live miner thread count, and per peer the age of the last message in each direction plus anything stuck unsent.
2026-08-01Add BIP39 and BIP32 foundation tests (#83)mtg0001
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
2026-07-31Bump to 1.2.10 (#77)Bitflash
1.2.9 shipped before the last seven merges landed, and one of them is holding two live nodes hostage. Measured on a 32-core node running 1.2.9 today: 65,134 MB committed, which is 30 RandomX datasets at 2,142 MB each -- one per miner thread, for a node that advertises 2 GB. A 4-core node on the same build sits at 6,568 MB, three datasets. #68 allocates the dataset once and closes the window where a thread could mine against one that was not finished being filled. The rest of the release: - #67 orphans no longer stack above earned blocks in the GUI list - #69 no submitting a solution for a parent that is no longer the tip - #70 a ceiling on orphan blocks, and getblocks stops repeating itself - #71 Berkeley DB cursors get closed instead of left to the handle - #73 bounds on what a peer can make the node allocate - #74 the recipient no longer collects the fee when a transaction retries
2026-07-31Bump to 1.2.9 (#66)Bitflash
relay.nostr.band went down and stayed down. Every node running 1.2.8 or earlier spends ten seconds of every discovery pass waiting for it and writes an error line each time -- the seed logged 2029 of them. #64 replaces it with a relay that answers and backs off from any relay that stops answering, so the next one to go down costs nothing.
2026-07-31Bump to 1.2.8 (#63)Bitflash
Six changes since 1.2.7, and the reason not to sit on this is #58: coinbase transactions were not unique, two of them overwrote older ones on the main chain, and 100 BTF stopped being reachable. Nothing in how they were built had changed since, so it could happen again at any time. The height now goes into the coinbase on both paths that build one (#59, #60), which makes a repeat impossible without any coordination. Also in: a node now notices a peer it can no longer hear and stays audible to peers that check the same way (#55); it announces its height in the handshake and says out loud when it falls behind the network (#56); script evaluation has ceilings on operation count and stack depth (#57); the tail of the chain is verified at startup and a bad tip is backed out of instead of being mined on (#61); and the wallet now believes the chain about what it has already spent, which is what a restored backup gets wrong (#62).
2026-07-30Bump to 1.2.7 (#53)Bitflash
Rendezvous no longer waits forever for a dial (#51), keys can be exported and imported as text (#49), and mining uses every core instead of one (#52). #51 is the reason not to sit on this: without it a node goes silently deaf after a while and only a restart brings it back.
2026-07-30Bump to 1.2.6 (#48)Bitflash
Wallet backup and clean shutdown (#46). Two reasons not to sit on this: every Linux node aborts on the way out instead of releasing wallet.dat, and until a node has shut down cleanly once, the wallet it wrote cannot be moved anywhere else.
2026-07-30Bump to 1.2.5 (#45)Bitflash
Headless mining fix (#42), ARM64 build (#41), format-string checking (#44), gitignore for key material (#43). #42 is the reason this release exists: -gen has mined nothing on headless nodes since v1.2.0.
2026-07-29Bump to 1.2.4 (#39)Bitflash
Seeds (#36), headless node (#37), README (#35), wallet crash fix (#38).
2026-07-29build: headless Linux node that does not need libGL (#37)Bitflash
The GUI build requires libGL at load time even when started with -nogui. The dynamic loader resolves every dependency before main() runs, so the flag arrives far too late -- on a clean server the binary dies with error while loading shared libraries: libGL.so.1: cannot open shared object file and nothing suggests it would otherwise have worked. A server is where a node most naturally lives, so this is the deployment that mattered most and the one that failed. The AppImage additionally needs FUSE, which minimal servers lack. Bundling libGL into the AppImage would be the wrong fix: GL has to come from the host's driver stack, and shipping our own breaks machines that have a real GPU. So: a second binary, built from the same node objects without ImGui, GLFW or OpenGL. `make linux-node` produces bitflash-node-<version>-x86_64, 2.5 MB stripped, with no graphics or FUSE dependency at all. `make linux` now builds it alongside the AppImage. Anyone wanting a window keeps using the AppImage. Two small moves were needed. MainFrameRepaint is called from main.cpp and net.cpp but defined in gui.cpp, so headless.cpp provides an empty one rather than putting ifdefs at six call sites. DateTimeStr also lived in gui.cpp while main.cpp logs block times with it -- nothing about formatting a date is GUI work, so it moved to util.cpp and both builds share one definition. Verified on Debian 13: ldd bitflash-node | grep -iE 'libGL|glfw|X11' -> nothing ldd bitflash | grep -iE 'libGL|glfw' -> libglfw.so.3, libGL.so.1, libGLdispatch.so.0 and at runtime, from an empty datadir: btfseed: reached ygffz37...btf via 90.156.222.107:8434 rendezvous: registered at 90.156.222.107:8434, waiting for a dial self-resolve OK (meeting_node=90.156.222.107:8434) btfpeers: kept 15 of 15 descriptor(s) first block at ~30s, 3182 blocks accepted, synced to the tip The GUI builds on Linux and Windows were rebuilt and are unaffected.
2026-07-29Bump to 1.2.3 for the connectivity release (#33)Bitflash
Everything since 1.2.2 is about nodes being able to find and reach each other, which is the problem this project actually had: #30 rendezvous registration and waiting split apart. Registering used to block until somebody dialled, so a node could not advertise a meeting node until it had already been reached at one. This is the root cause of the flaky connectivity, not dead relays or stale descriptors. #26 a node no longer advertises a rendezvous it is not registered at #25 .btf peers that answered are remembered and dialled first (36.3s -> 2.1s) #32 peers exchange signed descriptors, so discovery survives a relay outage #29 debug.log stopped dropping lines under concurrency #28 /port, so two nodes can share a machine
2026-07-28Bump to 1.2.2 for the hardening release (#24)Bitflash
2026-07-28Bump to 1.2.1 for the consensus security release (#14)Bitflash
2026-07-28Release plumbing: bump to 1.2.0, add python3-pil to Linux deps (#10)Bitflash
The AppImage step converts src/rc/bitcoin.ico with PIL, but python3-pil was never in deps-apt, so 'make linux' failed at packaging on a clean Debian install -- the AppImage the README points Linux users at could not actually be produced by following the documented steps. Version bump only renames the produced artefacts; no rebuild needed.
2026-07-27Add Linux GUI installer via Wine (#3)Victorio Nascimento
* Add Linux GUI installer via Wine * Fix launcher creation and add BTF_RELAY env var support * Pool server, Linux GUI, unified binary, README update * Root Makefile: make linux / make windows, cleaner README * Remove headless.cpp * Fix XPM color count mismatch causing toolbar image errors on Linux * XPM icons: same code on Windows and Linux, no ifdefs around image loading * Fix GTK icon crash and Linux printf format * btfrv: 10s socket timeout on relay connects * Faster peer discovery: more dials per pass, shorter backoff and sleep * Parallel btf peer connections * Fix GetMyExternalIP - was disabled, now uses modern IP echo services * Fix tunnel disconnect on idle and restore external IP detection * Filter stale Nostr descriptors * Sort peers by freshness, fresh first * Pool: add .btf Stratum listener — miners connect via rendezvous, no port forwarding needed * btf pool: self-replacing listeners, one per relay, no queuing between miners * Mining modes: solo/operator/participant — pool server on-demand, participant Stratum client * Status bar: show external IP and .btf address * README: clean rewrite, concept-first, minimal * make linux produces AppImage * make linux produces AppImage * Pool dashboard: connect instructions, tabs, share bars * Perf fixes: GetMyExternalIP threaded, btf listener counted * Perf fixes: GetMyExternalIP threaded, btf listener counted * Fix pool payouts: defer until coinbase maturity * Pool: clean mode switching, IP warning * Pool: clean mode switching * Fix gPoolRunning: remove static * AppImage: use real icon from bitcoin.ico * Fix exit crash; Mining menu * Fix exit crash; Mining menu * Fix exit crash; Mining menu * Fix exit crash; Mining menu * Fix exit crash; Mining menu * Windows build: use mingw32-make * Windows build: simpler make invocation * Add deps-windows target * Windows build fixes * Windows build fixes * Windows makefile: fix obj dir * Windows: output to repo root * Windows: bundle DLLs into zip * Replace wxWidgets with ImGui+GLFW: same build on Linux and Windows * Fix %I64d that slipped back through rebase (main.h, net.h) * MinGW: add _mkdir shim for non-MSVC Windows; fix %I64d * Fix gPoolRunning extern outside ifndef _WIN32 * Remove -lboost_system: boost is header-only in this codebase * Status bar: copy buttons for IP and .btf address * Windows: add app icon via windres, strip wx stuff from rc file * Clean GUI: single Options menu item, bigger font, better spacing, cleaner options dialog; update README * GUI: embed Roboto font at 16px for clear readability on all platforms * Windows: windres depends on obj dir target * Fix balance hidden off-screen: put on own line below address bar * Windows: inline mkdir in every rule, remove dir target that confused make * Windows: move all target to top so it's the default rule * Fix pool server not starting: always start when operator selected, re-sync dialog state on open * Write debug.log to data directory (AppData/Roaming/Bitflash on Windows) * Participant miner: use wallet address as username so payouts go to wallet * Add .gitattributes to lock LF line endings, prevent CRLF drift * Refactor pool mining to btf-only transport * Persist pool settings and startup wiring * Prune stale peers and expose pool queries * Fix build regressions and add CLI mode/arg parity * Fix rpc miner stats declaration order * Centralize pool runtime extern declarations * Separate operator/participant dashboards and restore peer stats * Add live telemetry strip and clearer per-mode runtime metrics * Reduce mining dashboard clutter and remove manual pool entry * Count only authorized pool miners * Seed initial pool job on startup * Use a real share target for pool work * Switch pool accounting to round-based shares * Fix nostr pool announcer stats call sites * Fix pool stats definition order * Improve pool worker status display * Add mutex include for participant status * Replace ImClamp in dashboard layout * Clarify participant work status * Log participant status transitions * Reduce peer discovery log noise * Add participant status badge * Track participant auth acknowledgements * Log and validate pool job delivery --------- Co-authored-by: Bitflash Pool Dev <pool@bitflash>