upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/selftest.cpp
AgeCommit message (Collapse)Author
2026-09-05Merge upstream/main into the forkbitflash-maintainer
Brings the nine commits that are actually the latest on our lineage: isolated testnet parameters and genesis, optional 2 MB large pages for RandomX, and five README changes. This is what "up to date" means here -- not the 1.2.2x tags, which carry higher version numbers but were cut 2026-07-30, three weeks before the line we ship, and which upstream/main does not contain. Three conflicts, all resolved toward upstream's structure while keeping what this fork knows: main.cpp -- upstream moved genesis construction into BuildGenesisBlock() so mainnet and the new testnet can share it. Verified byte-for-byte before taking it: MAINNET_GENESIS_TIMESTAMP, _TIME (1753315200), _NONCE (3141), _HASH, _MERKLE, the scriptPubKey, nBits and nVersion all equal what this tree built inline. The mainnet genesis hash is unchanged and the asserts still guard it. Kept our warning about never editing those inputs, and kept our note that 1753315200 is 2025-07-24, not 2026 -- upstream's comment on that constant states the wrong year. net.h -- DEFAULT_PORT = htons(8433) becomes MAINNET_PORT/TESTNET_PORT in host order. Nothing is lost: net.cpp's GetDefaultPort() applies htons() itself and is what every caller uses, so the value reaching a sockaddr is unchanged and mainnet stays on 8433. selftest.cpp -- the known-tests string had diverged in both directions. Took the union: upstream's network-params alongside our payout-ledger, rather than dropping either. Gates: 15/15 selftests (our 14 plus upstream's network-params), consensus constants unchanged against e4f898c except the pre-existing MAX_BLOCK_SIZE_GEN, mainnet genesis intact, compactblock's 17 checks still pass, money-log check ok, GNU_STACK RW.
2026-09-03merge: upstream 1.2.20 into deploy/all-2026-08-24bitflash-maintainer
# Conflicts: # Makefile
2026-09-02selftest: cover the assembly half of the block limitsbitflash-maintainer
consensus-limits only ever asked what CheckBlock() refuses. Nothing asked what the assemblers produce, which is why a miner able to overshoot the cap by an entire mempool sat here unnoticed through both the deployed and candidate builds. Adds the other half: a template built from a 3 MB mempool stays inside the budget and passes CheckSizeLimits(); a transaction too big for the budget is skipped while the ones that fit are still taken; sigop-heavy transactions stop selection at MAX_BLOCK_SIGOPS while the block is still small. Plus the rule's edges, including that sigops exactly on the limit fit -- CheckBlock() fails on `>`, so assembly must accept what validation accepts. The test drives BlockTemplateFits the way the assemblers drive it rather than calling them: BitcoinMiner needs a CTxDB, a wallet key and cs_main, and RebuildJob is static in rpc.cpp. What that proves is the shared rule and the property it exists to give, which is where the defect was -- the budget was right and the enforcement was not. Checked against the bug: with the fit test neutered, eight of these fail, including both templates that CheckBlock() would have rejected. With the sigop comparison stricter than CheckBlock's by one, the boundary case fails.
2026-09-01wallet: refuse to mint a wallet a headless node never asked forbitflash-maintainer
v1.2.20 makes SQLite the default backend for a datadir that holds no wallet. On a desktop that is a first run. On a headless node it is usually a fault -- the datadir is not mounted, or wallet.dat was caught mid-rename by a backup -- and the new default turns that fault into a silent loss of custody rather than a loud failure. The damage is not the empty wallet, it is the marker. Creating the wallet also writes <datadir>/wallet-backend = sqlite, and every later start reads that marker first: putting the real wallet.dat back no longer does anything, because the node stays on the empty SQLite wallet it made. 1.2.19 had the same fail-open on a missing wallet, but it created a wallet.dat, and restoring the backup over it recovered the funds. That runbook stops working here. So refuse when the startup is headless and no wallet of either kind is present, and say which flag makes it deliberate: /createwallet to start a new one, or /walletbackend=bdb for the Berkeley DB path. A node that meant to create a wallet says so; a node that lost its datadir gets an error instead of a fresh identity and a re-paid ledger. Upstream keeps the old behaviour; this is a fork divergence, and the three findings that would gate enabling the SQLite backend at all -- the non-atomic backend marker, WriteHDMaster's connection-wide transaction, and the encrypt-swap crash window -- remain open against upstream.
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-23selftest: cover the payout ledger properties this series fixesbitflash-maintainer
A new `payout-ledger` self-test, 40 assertions, wired into RunSelfTest and into `make -C src tests`. It runs against a scratch data directory and an in-memory wallet map: it never opens wallet.dat, never touches a real datadir, and never sends anything. What it asserts, defect by defect: - a restart does not duplicate an in-flight record. A matured round is exploded, the process state is thrown away and reloaded from the files twice, and the in-flight list must still hold exactly one record per recipient. Both guards are checked separately -- once with the round-level set restored, and once with it deliberately emptied so only the per-recipient check against gPaying can save it. A paying file already containing the duplicate the old build wrote must collapse on load, with the attempt counts merged upwards rather than reset. - state updates address a slot, not a value. Two records equal in every field -- exactly what the old code produced -- and rescheduling or forgetting one must land on the one it was given. - the ledger file cannot be published from a stale snapshot. An older snapshot published after a newer one must not appear on disk, and four threads publishing 64 large images of the same file in different orders must leave it parseable and equal to the highest version, whatever order they finish in. - a payment with only a legacy comment is recognised as already paid, and the fallback's limits hold: it requires the right recipient, the exact amount and the right height; one legacy transaction cannot settle a second debt; a transaction carrying another round's structured metadata is never accepted as legacy evidence; and the structured match still wins where it applies. - grandfathered zero-hash rounds do not answer for each other, both as a direct test of the round comparison and end to end -- recording a txid against one zero-hash round must leave the other still owed. - the retry clock that is actually used survives a restart. - a forfeit is published rather than silently dropped: the ledger loses that recipient and only that recipient, and the round proof records it with a reason instead of going on advertising the address as owed. - on-disk compatibility, checked against the real shapes: the live production pending_payouts.json (amounts / blockHash / blockHeight / matureAtHeight and nothing else), the merged build's shape including the retryAfter this build no longer writes, and a round trip through this build. Each assertion was verified to fail when its defect is put back: seven mutations were applied one at a time to a scratch copy and every one was caught. Notably, re-introducing either half of the duplicate-record fix alone is caught by its own assertion while the other half still prevents the duplicate -- which is the defence in depth this series was asked for, checked rather than asserted.
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-23net: start every peer's stall clock stopped, not at whatever was in memorybitflash-maintainer
CNode's constructor never wrote nMsgWaitSince or nMsgWaitSize, so every peer began life with indeterminate values in the pair ProcessMessages uses to decide whether that peer has stopped sending, and the first partial message it read was measured against them. The guard restarts the clock only when nMsgWaitSince is 0 or the buffer has grown since the last look. Garbage in nMsgWaitSince is neither, so the very first look fell through to the timeout branch. A peer that merely split a message across two TCP segments -- header in one, body in the next, which is ordinary on any real link -- could be dropped as unreadable on first contact, before it had sent anything wrong and often before its version message. It was never reproducible either: what happened depended on what had last used that heap. The log reported the stall as roughly the age of the epoch: peer 127.0.0.1:8433 announced a 100 byte 'block' and has sent nothing more for 1786593923 s (0 of 100 payload bytes) -- dropping, this peer is unreadable Every node is running this. Introduced in 7f6e5ad, which added the pair to the class and to ProcessMessages but not to the constructor; the constructor went on initialising nIncompleteMessageStart, nIncompleteMessageSize and strIncompleteMessageCommand, the upstream members our implementation supersedes. 5490d66 removed their last reader from ProcessMessages, and nothing has read or written them since, so they go, with BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS -- decoy state in a networking struct is how this happened, and the merge ahead of us would carry it further. The initialiser list now follows declaration order, so a member missing from it is visible. The net-message selftest was asserting on those dead members too, which is why it could not have caught this: it read state no code path writes, and its stale-message case passed only when the garbage in nMsgWaitSince happened to look old. Rewired onto nMsgWaitSince / nMsgWaitSize / BTF_MSG_WAIT_MAX_SECS, and given the two cases it never covered -- a new peer's clock must start stopped, and a peer that is still delivering payload must not be dropped however old its clock is. Measured on the build host, 20 runs each: before, net-message failed 20/20 ("a fresh incomplete message does not disconnect immediately" failed 17/20 by itself). After, 20/20 pass. Rewired selftest against a reverted constructor fix: fails 20/20, deterministically, where the old suite caught it by luck or not at all.
2026-08-20chain: add isolated testnet genesisNsable-sh
2026-08-20net: add isolated testnet parametersNsable-sh
2026-08-19selftest: unwrap system() exit status on Linux (RunBitflashChild)bitflash
The Linux branch of the child-process helper returned system()s raw wait status instead of the exit code, so an exit code of 2 read back as 512. Every assertion that checks a specific code (== 2), like the locked recovery-audit test, failed only on Linux while the node itself was correct. Windows was unaffected (_spawnv returns the code directly). Unwrap with WIFEXITED/WEXITSTATUS so both platforms return the same value. Test harness only; no change to node behavior.
2026-08-18gui: offer to convert a Berkeley DB wallet to SQLite on first runbitflash
A first-run dialog for an existing wallet.dat with no backend choice yet: "Convert to SQLite" exports the wallet and records the sqlite marker so the next start opens it; "Keep Berkeley DB" records that choice so the offer does not return; "Decide later" leaves no marker and asks again. wallet.dat is never touched, and the change takes effect on restart -- matching the restart-to-apply model. The conversion itself is DoConvertWalletToSQLite(), a headless-testable function the button calls: it reuses the export path (extracted as ExportActiveWalletToSQLite, shared with -walletsqliteexport, which keeps its own specific messages), refuses if already on SQLite or if a wallet.sqlite exists, and writes the marker only after a verified export. New wallet-convert self-test proves the action end to end: build a Berkeley DB wallet, convert, and confirm wallet.sqlite + the marker are written, wallet.dat is left untouched, a second convert refuses, and a flagless restart opens the converted SQLite wallet with its phrase intact. All wallet suites pass. The modal itself is thin UI over this tested action.
2026-08-18wallet: make SQLite the default backend for new walletsbitflash
A fresh install with no wallet.dat now starts on the SQLite backend and creates wallet.sqlite, instead of a Berkeley DB wallet.dat. The choice is recorded in a plain-text <datadir>/wallet-backend marker so it survives a restart without a command-line flag. Backend resolution when no -walletbackend is given: an existing wallet.sqlite (or a "sqlite" marker) stays on SQLite; an existing wallet.dat stays on Berkeley DB -- nothing is converted behind the user; a fresh datadir starts on SQLite. An explicit -walletbackend=bdb or a "bdb" marker forces Berkeley DB and overrides the default. Adds CreateNewSQLiteWallet() (WalletSQLiteRuntimeOpen gains fMustExist so the create path can make a new file) which mints the first default key straight into the SQLite store via the same FinishLoadedWallet() call the Berkeley DB create path uses, then cleans up on failure. Marker read/write helpers are plain text, hand-editable. New wallet-backend-default self-test proves the resolution in child processes (fresh->SQLite+marker, existing wallet.dat->BDB, "bdb" marker->BDB, explicit flag->wallet.dat). The three wallet.dat-specific suites now ask for -walletbackend=bdb explicitly, since a flagless fresh datadir is SQLite now. All wallet suites pass.
2026-08-18wallet: one-shot storage commands honour -walletbackend=sqlitebitflash
-walletstorageaudit, -walletstoragecheck and -backupwallet all read wallet.dat regardless of the selected backend. Under -walletbackend=sqlite that is wrong, and for backup it is dangerous: it silently backs up the stale plaintext wallet.dat instead of the live wallet.sqlite. - ScanWalletRecords() routes to the active SQLite store instead of a Berkeley DB cursor (it returned an error under the runtime before). This is the single choke point audit and check scan through. - WalletSQLiteRuntimeOpenReadOnly() opens wallet.sqlite for scanning without loading or validating records, so the pre-load diagnostics can inspect a wallet the loader would refuse. main_gui opens it for audit/check when the SQLite backend is selected (and rejects an unknown backend value there too). - BackupWallet() copies wallet.sqlite (checkpoint, then the temp/old/rename dance; a checkpointed SQLite file is self-contained, no lsn_reset). wallet-sqlite-encrypt self-test proves routing on a datadir where the SQLite wallet is encrypted and wallet.dat beside it is still plaintext: audit with the flag reports the encrypted seed, without it reports the plaintext one; the SQLite backup carries no plaintext key and loadchecks as a standalone wallet.
2026-08-18wallet: bracket WriteHDMaster in a SQLite runtime transactionbitflash
The SQLite runtime wrote each record as its own autocommit, so a group that must land together -- hdmaster and hdchaincode, the two halves of an HD seed -- could be left half-written by a failure or crash between them, the incomplete-seed state -walletstoragecheck exists to catch. Berkeley DB was no worse (it is not transactional here either) but no better. Add WalletSQLiteRuntimeBeginTxn/CommitTxn/RollbackTxn -- no-ops that succeed when the SQLite backend is not active, so Berkeley DB keeps its existing per-write behaviour -- and wrap WriteHDMaster in them so the seed lands all-or-nothing under SQLite. The one BDB-atomic wallet rewrite, the encrypt path, is already matched by its own transaction in the SQLite encrypt branch; every other wallet multi-write is ordering-based on both backends by design. wallet-sqlite self-test now proves the bracket: a rolled-back runtime write leaves no record, a committed one persists, and WriteHDMaster lands both halves of the seed.
2026-08-18wallet: encrypt atomically under the SQLite backendbitflash
EncryptWallet() had only a Berkeley DB path: it rebuilt a wallet.dat temp file and swapped it in. Under -walletbackend=sqlite that was wrong -- it would leave the live wallet.sqlite unencrypted and write a divergent wallet.dat instead. Add a SQLite persistence branch that rebuilds a fresh wallet.sqlite in one transaction (public records copied from the live store, encrypted key material serialized byte-for-byte the way the BDB path writes it), checkpoints it, and swaps it in with a .before-encrypt backup, mirroring the BDB rename dance. A fresh file rather than an in-place rewrite keeps the change atomic and leaves no plaintext key in a freed page. CmdEncryptWallet skips PurgeDbEnvironmentLogs under SQLite (no Berkeley DB environment behind the wallet) and reports the backend it used. New wallet-sqlite-encrypt self-test drives the real command path in child processes: export a wallet to SQLite, encrypt it through -walletbackend=sqlite, and prove it comes out encrypted, unlockable with the right passphrase, refusing the wrong one, with its recovery phrase intact and the original wallet.dat left untouched.
2026-08-17wallet: run the node on a SQLite backend (opt-in)bitflash
Adds -walletbackend=sqlite: an opt-in flag that runs the node on <datadir>/wallet.sqlite instead of wallet.dat. It requires an export to exist first (no silent migration) and prints a step-by-step guide when one does not; wallet.dat is never opened or touched, so a plain restart goes back to Berkeley DB. - LoadWalletFromSQLiteRuntime opens the export read-write, keeps the handle open as the active backend, loads records into memory through the same visitor/reconcile/finish path as the read-only staging load, and never fabricates a default key. - Shutdown checkpoints and closes the SQLite backend; a kill before that loses nothing because WAL + synchronous=FULL already made every committed write durable. - Self-test proves runtime writes persist across a close and reopen. BDB stays the default: nothing activates the SQLite runtime unless the flag is given. One-shot audit/recovery commands still use Berkeley DB.
2026-08-17wallet: route CWalletDB through SQLite runtimeNsable-sh
2026-08-17tests: prove typed SQLite wallets loadNsable-sh
2026-08-17wallet: add typed SQLite wallet recordsNsable-sh
2026-08-14wallet: stage SQLite runtime loadingNsable-sh
2026-08-14wallet: check SQLite exports against loader typesNsable-sh
2026-08-14wallet: clean up failed SQLite migrationsNsable-sh
2026-08-14wallet: restore BDB wallets from SQLite exportsNsable-sh
2026-08-14wallet: verify SQLite wallet exportsNsable-sh
2026-08-14wallet: scan SQLite wallet recordsNsable-sh
2026-08-14wallet: export BDB records to SQLiteNsable-sh
2026-08-14wallet: add experimental SQLite record storeNsable-sh
2026-08-14db: recreate Berkeley DB environment after shutdownNsable-sh
2026-08-14tests: guard destructive wallet mutation helperNsable-sh
2026-08-14tests: prove wallet storage sanity failuresNsable-sh
2026-08-14wallet: install backups via temporary fileNsable-sh
2026-08-14tests: prove encrypted rewrite keeps public wallet recordsNsable-sh
2026-08-13wallet: add storage sanity checkNsable-sh
2026-08-13wallet: export storage audit jsonNsable-sh
2026-08-13wallet: add storage audit commandNsable-sh
2026-08-13tests: assert encryptwallet purges BDB logsNsable-sh
2026-08-13tests: cover portable wallet copiesNsable-sh
2026-08-13wallet: report locked encrypted recovery seedNsable-sh
2026-08-13net: stop managed Tor through control portNsable-sh
2026-08-13net: add managed Tor hidden service bootstrapNsable-sh
2026-08-13net: add signed direct onion peer endpointsNsable-sh
2026-08-12wallet: collapse restored legacy hdnextNsable-sh
2026-08-11net: add Tor mode for outbound discoveryNsable-sh
2026-08-11net: harden SOCKS5 proxy parsing and testsNsable-sh
2026-08-11net: add optional SOCKS5 outbound proxyNsable-sh
2026-08-11money: ParseMoney accepts full 8 decimals (0.5, 0.00000001)Bitflash
The legacy parser only accepted two decimal places (cents) and rejected 0.5, leaving six of the eight coin decimals unreachable by -sendto and the send window. Parse up to 8 fractional digits Bitcoin-style, reject >8 to avoid silent truncation. Adds the parse-money selftest (12 cases).
2026-08-11wallet: SLIP-0044 4346950 is official, drop 'provisional' namingBitflash
The coin type 4346950 (0x425446 = BTF) was merged into the SLIP-0044 registry on 2026-08-07. Rename HD_BIP44_COIN_TYPE_BITFLASH_PROVISIONAL to HD_BIP44_COIN_TYPE_BITFLASH and update comments, logs and UI strings. Value is unchanged, so derivation paths and addresses are identical.
2026-08-07test: cover pool Stratum share accountingNsable-sh
2026-08-04wallet encryption: the stack from #130–#133, with the two blockers fixed ↵Bitflash
(#134) * wallet: refuse future encrypted wallet formats * wallet: add encrypted key unlock core * wallet: add encryptwallet rewrite command * wallet: read passphrases from stdin or files * wallet: keep the derivation across encryption, and take the logs with it Two things stopped the encryption stack from being safe to merge. Both were found against a wallet holding real coin, not in the self-tests. 1. Encrypting turned a BIP44 wallet into a legacy one. LoadWallet() ends by resetting the schema and the derivation counters when no seed is in memory. That was written when "no seed after loading" could only mean the wallet had none. An encrypted wallet has a seed -- it just has not been decrypted yet -- so the reset threw away metadata that had been read correctly a moment earlier. Unlocking later restores the seed and nothing restores the rest. Measured on the wallet from the #124 proof: after encrypting, index 1 moved off BAtgSgra2X8Dqo5PJzPCLJhPwrrC2thYVJ, the address actually holding 1.00 BTF, and the audit went from "1.00 covered by the phrase" to "0.00 covered, 1.00 wallet.dat-only". Encrypt your wallet and the program told you your recovery phrase no longer covered your money. Now the condition also accepts a wallet whose seed is present but encrypted. Same wallet, before and after: schema bip44, same four derived addresses, 1.00 BTF still reported as covered. 2. The plaintext keys stayed behind in the Berkeley DB logs. wallet.dat comes out clean -- verified by scanning the raw file for the 118 bytes of a known private key. database/log.0000000002 did not: it held the whole key, and still did after a full node restart, because every record this wallet ever wrote passed through the log. That defeats the feature for the case it exists for, since our own backup advice is to copy the whole directory rather than wallet.dat alone. log_archive() cannot fix it: it only drops logs Berkeley DB considers obsolete and never the one it is writing to, and with one 10 MB log per directory that is precisely where the keys are. They also did not get there at encryption time -- they accumulated over the wallet's whole life -- so nothing done during the rewrite can archive them out. So the encrypt command now closes the environment and deletes the logs on its way out, after the flush that makes every .dat self-contained. This is not secure erasure; the blocks are unlinked, not overwritten. It closes the case of someone reading a copied data directory. Getting there uncovered a third bug, older than both. DBFlush(true) is not safe to call twice. The first call closes the environment, and txn_checkpoint() on a closed environment segfaults instead of returning an error, so the try/catch around it never sees anything. EncryptWallet() already flushes before renaming the wallet, and the command flushes again on the way out -- that pair crashed every single time. Guarded now. The same function has been calling log_archive() with the address of an uninitialised pointer since 0.1.0, on every clean shutdown, in a path whose catch(...) cannot catch a structured fault on Windows. DB_ARCH_REMOVE requires that argument to be NULL. Fixed while here, because the encryption cleanup depends on that call not being a coin flip. --------- Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
2026-08-03Activate BIP44 wallet derivation (#124)mtg0001
* Add BIP32 path derivation foundation * Add HD wallet schema metadata * Add provisional BIP44 coin type metadata * Activate BIP44 wallet derivation * wallet: scan BIP44 restore depth per branch * wallet: widen default BIP44 restore lookahead --------- Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
2026-08-03Add wallet recovery balance audit (#117)mtg0001
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
2026-08-02Add consensus block size limit (#111, from #110)Bitflash
CheckBlock() validated block size against MAX_SIZE, the serializer's generic 32 MB guard rather than a block rule; MAX_BLOCK_SIZE did not exist in this tree at all, because Bitcoin's 1 MB cap arrived in 0.3.x, after the 0.1.0 this fork came from. A miner could produce a 32 MB block and every node would download, verify and store it. Original work by mtg0001 in #110, rebased over #109 with the self-test conflict resolved, plus a comment explaining why the inherited count-against-bytes test stays. Measured before choosing the number: 5709 blocks read from blk0001.dat give a largest block of 34,290 bytes and a median of 216, so 1 MB leaves 29x of headroom and rejects nothing that exists. A node built from this branch then synced genesis to 5706 on the live network with zero size-limit rejections and zero CheckBlock errors -- the same test that caught BIP30 rejecting a real block at height 859.
2026-08-02Keep HD change recoverable and bound message stalls (#109)mtg0001
Change from an HD wallet now goes to a newly derived phrase-backed key instead of reusing a spent input's key. Reusing an input key silently moved value back to a key the phrase cannot reproduce; if derivation fails the transaction is refused rather than creating unrecoverable change. Legacy wallets with no seed keep the 0.1.0 behaviour. Restore also scans past the pre-generated key-pool window before treating an empty batch as the end. The message parser no longer consumes a header it cannot complete: it reads from a copy, so the O(n) reinsert and the 100 ms sleep inside the shared message thread are gone, and a peer that sits on an incomplete payload is disconnected after two minutes. The wire cap drops from 256 MB to MAX_SIZE. New net-message self-test covers complete, incomplete, stale and oversized headers. Verified here: clean rebuild, all self-tests pass, lock order checked (no path takes cs_keyPool before cs_mapWallet), and a node built from this branch synced from genesis to the chain tip on the live network with zero rejections.
2026-08-01wallet: reserve index 0 before deriving it, and name the address the phrase ↵Bitflash
does not cover (#104) Follow-up to #102. Reserves derivation index 0 before deriving it, so a failure between installing the seed and writing the default key cannot leave the pool free to hand that index out as an ordinary key. Renames the address that stops being the default to 'Your Address (created before the recovery phrase)', which was otherwise left under the same name as the new one. Also documents why the key-pool clear precedes the seed write, correcting a review comment of mine that asked for the reverse.
2026-08-01Keep new phrase addresses recoverable (#102)mtg0001
-newphrase installed a seed but left the wallet's visible address as the pre-seed random key and the old random pool intact, so the next address handed out -- a mining payout included -- could be one the twelve words cannot reproduce, while the window said a recovery phrase existed. Installing a phrase now clears the stale pool records (private keys stay in wallet.dat), reserves a derived default receiving key, tolerates already-stored derived keys so repeated installs do not stall the pool, and -restorephrase reports what it recovered rather than the wallet's total transaction count. Self-test covers installing a phrase over a pre-existing random pool.
2026-08-01wallet: create and restore a recovery phrase (#96)Bitflash
Adds -newphrase, -restorephrase, -restoredepth, -newaddress and -showderived, plus the window buttons that reach the same code. Restore derives forward in batches of 100 and rescans after each, stopping when a whole batch turns up nothing; the phrase is shown on the terminal only, never in debug.log. Proved end to end on a live wallet: paid a derived address, deleted wallet.dat, restored from the twelve words and recovered the coin. Coins on the pre-seed default address do not come back, and the command says so.
2026-08-01wallet: derive the key pool from a seed, so a phrase could bring it back (#93)Bitflash
Adds BIP32 derivation behind the wallet key pool, with no user-facing way to create a seed yet. wallet.dat gains hdmaster/hdchaincode/hdnext; the mnemonic is never stored. TopUpKeyPool derives m/n' when a seed exists and falls back to a random key, with a log line, if derivation fails. New -selftest=wallet-hd, wired into make tests on both platforms.
2026-08-01diag: make the self-test report where the person running it can see (#92)Bitflash
On Windows the self-test from #91 printed nothing. printf in this tree is OutputDebugStringF, which writes to the data directory debug.log, and the Windows binary is linked -mwindows with no console. A failure was still caught, since a non-zero return stops make, but it arrived with no way to tell which check failed. And because the first line printed before strSetDataDir pointed at the temporary directory, every line was appended to the real debug.log -- the file users are asked to paste into issues. The file now gets the real printf, the data directory is set before anything prints, and on Windows a console is borrowed only when stdout has nowhere to go. That last condition matters: the first version reopened stdout unconditionally and took the output away from `make tests > log`, which is the case that counts.
2026-08-01Add wallet keypool self-test (#91)mtg0001
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>