<feed xmlns='http://www.w3.org/2005/Atom'>
<title>npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/src/net.cpp, branch main</title>
<subtitle>[no description]</subtitle>
<id>https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/atom?h=main</id>
<link rel='self' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/'/>
<updated>2026-09-05T05:08:30+00:00</updated>
<entry>
<title>Merge upstream/main into the  fork</title>
<updated>2026-09-05T05:08:30+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-09-05T05:08:30+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=66e0b9158d050c46b1ff2509faeee3f4573cac1c'/>
<id>urn:sha1:66e0b9158d050c46b1ff2509faeee3f4573cac1c</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>merge: upstream 1.2.20 into deploy/all-2026-08-24</title>
<updated>2026-09-03T22:33:20+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-09-03T22:33:20+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=b810d86752ddb2674118f737582c924bb9eb74c1'/>
<id>urn:sha1:b810d86752ddb2674118f737582c924bb9eb74c1</id>
<content type='text'>
# Conflicts:
#	Makefile
</content>
</entry>
<entry>
<title>net: a refused thread spawn drops one peer, not the node</title>
<updated>2026-09-02T07:09:45+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-09-02T07:09:45+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=0b566b4869b9e86c90162befd87f9882b6dd143c'/>
<id>urn:sha1:0b566b4869b9e86c90162befd87f9882b6dd143c</id>
<content type='text'>
std::thread throws std::system_error when pthread_create is refused
(RLIMIT_NPROC, a container's pids cgroup, memory pressure), and nothing
caught it, so the node died by std::terminate. Threads are spawned per
peer connection, so the pressure that triggers this scales with peer
count, which is attacker-influenced. Observed killing a healthy
participant worker mid-mining.

Every spawn site in the node build, and what a failure now does:

- btftunnel.cpp: both tunnel pumps are guarded. The inbound pump starts
  first because it owns cleanup of the pump end and rendezvous socket;
  if the second spawn is refused, shutdown() wakes the first out of its
  read and it cleans up itself -- closing those descriptors from the
  failure path instead would race it onto reused descriptors. Both
  paths return INVALID_SOCKET, which every caller already treats as
  "this connection failed".
- btfrv.cpp: the relay's accept loop drops the connection it cannot
  serve and stops accepting for a moment -- longer after consecutive
  failures, capped at 5s -- instead of dying at an arbitrary one. The
  per-pair second pump now runs on the handling thread itself, leaving
  exactly one spawn to guard and nothing running yet to race.
- main.cpp: a participant that cannot spawn its hash workers stops the
  ones that did start, drops the pool connection, and returns to the
  reconnect loop, the same way an ordinary disconnect does.
- randomx_pow.cpp: dataset init joins the workers it got, releases the
  dataset, and stays in light mode -- the same trade the allocation
  failure path above it already makes.
- rpc.cpp, net.cpp: the remaining unchecked _beginthread calls (which
  report failure by value, they cannot throw) now say precisely what
  did not start. The pool's accept and payout threads failing silently
  left the pool running deaf -- nobody accepts, nobody gets paid, and
  nothing in the log says why.

Left alone deliberately: the per-relay accept spawns (rpc.cpp) already
check, log and retry on the next poll; startup-critical spawns in
net.cpp keep failing hard; the selftest-only std::thread uses may abort
a selftest run, which is acceptable where dying over one peer is not.

Verified: full node rebuilds clean and links; pool-stratum selftest
passes; a scratch harness under RLIMIT_NPROC=1 confirms _beginthread
reports -1 and std::thread throws system_error, the premises of every
path above.
</content>
</entry>
<entry>
<title>Merge upstream v1.2.20 into the  fork</title>
<updated>2026-09-01T17:14:12+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-09-01T17:14:12+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=b4f0bc693ef7175702f82bcb2110b627922e8cbd'/>
<id>urn:sha1:b4f0bc693ef7175702f82bcb2110b627922e8cbd</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>net: bind INADDR_ANY only when this node advertises an onion endpoint</title>
<updated>2026-08-26T22:44:31+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-08-26T22:44:31+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=9b0535312a77a97b06837bbef51c1fe4b7f3b0da'/>
<id>urn:sha1:9b0535312a77a97b06837bbef51c1fe4b7f3b0da</id>
<content type='text'>
Upstream 1.2.19 made the P2P listener wildcard-bind unconditionally because
managed Tor forwards hidden-service traffic to 127.0.0.1, which a LAN-only
bind cannot receive. That also widened every non-Tor node to listen on VPN
tunnels, docker bridges and secondary NICs. The onion endpoint is always
configured (or the managed-Tor hidden service established) before StartNode,
so the predicate is ready when the bind happens.
</content>
</entry>
<entry>
<title>Merge upstream v1.2.19 into the  fork</title>
<updated>2026-08-26T22:41:53+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-08-26T22:41:53+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=bbe04dc8c96b4c4766d2771d3438233bf82687c0'/>
<id>urn:sha1:bbe04dc8c96b4c4766d2771d3438233bf82687c0</id>
<content type='text'>
Merges upstream tag v1.2.19 (9e39a50) into merge/upstream-1.2.18 (991853f).
Unlike the 1.2.15-&gt;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.
</content>
</entry>
<entry>
<title>Merge upstream v1.2.18 into the  fork</title>
<updated>2026-08-23T05:21:31+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-08-23T05:21:31+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=a9b4f8940907c2bf51e431172b628de89734a908'/>
<id>urn:sha1:a9b4f8940907c2bf51e431172b628de89734a908</id>
<content type='text'>
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-&lt;height&gt;", 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&lt;int&gt;()` sat two
   lines above our is_number-guarded version, and an unguarded get&lt;int&gt;() 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-&gt;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-&gt;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-&gt;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.
</content>
</entry>
<entry>
<title>mine: one pool session in participant mode, not one per core</title>
<updated>2026-08-23T04:37:36+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-08-19T22:46:53+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=c4c19bd894c8fc30c558e560e326aab538b23170'/>
<id>urn:sha1:c4c19bd894c8fc30c558e560e326aab538b23170</id>
<content type='text'>
StartMinerThreads() sizes its pool with MinerThreadCount(), which is right for
solo mining -- each thread hashes a template of its own -- and wrong for
participant mode, where every thread entering BitcoinMiner() is a whole miner:
its own Stratum session, its own payout address off the rotation, its own pool
of /hashthreads workers. The two counts multiplied instead of dividing.

Measured on the pool box with /hashthreads=2: a container with no cpuset logged
"Mining with 11 thread(s) of 12 core(s)" -- 11 sessions, 22 hash threads on 12
cores, 1058% CPU, load 22.68 -- while the same binary under --cpuset-cpus 10,11
logged one session and 199%. The second miner (8 cores, /hashthreads=1) ran 7
sessions and went 74C to 89C. No code differed between those runs. Only the
cpuset did, which means the cpuset was holding the thread count down by
accident and every deployment without one was oversubscribed.

Participant mode now starts exactly one session, so /hashthreads means what it
says instead of being multiplied by the core count. The worker count moves to
ParticipantHashThreadCount(), next to MinerThreadCount() where the two are
easy to confuse, so what is printed at startup and what actually hashes cannot
drift apart -- and it IS printed now. It was only ever visible under -debug,
which is how eleven sessions ran unnoticed.

Same host, same cpuset (0-3), same arguments, measured as a delta over 20 s
more than four minutes after start:

  before  399% CPU, 6-7 threads hot, 3 sessions
  after   201% CPU, exactly 2 threads at 99.9%, 1 session

Solo mining is untouched: /gen without /participant still starts
MinerThreadCount() threads.

(cherry picked from commit 45bc9ed5bc132712092f3e7902fef746c6cae898)
</content>
</entry>
<entry>
<title>net: stop serving the 2009 marketplace to anonymous peers</title>
<updated>2026-08-23T04:37:36+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-08-19T18:17:06+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=a7744058c49b1ba2c605f9e8380d9c3e8665bbc0'/>
<id>urn:sha1:a7744058c49b1ba2c605f9e8380d9c3e8665bbc0</id>
<content type='text'>
Four inherited handlers took attacker-controlled input and wrote to disk
with nothing authorising it. None of them had a caller left anywhere in
the tree, so all four were reachable only from the outside.

checkorder called GenerateNewKey() -- AddKey(), a wallet.dat write -- for
any peer that asked, and handed back the pubkey. Its comment claims the
cache keeps one key per ip, and on a 2009 network of routable addresses
it did. Not here: inbound peers arrive through a rendezvous tunnel and
are tagged by BtfMarkerAddr() over three random bytes, so addr.ip is
fresh every connection and the cache never hits. Reconnect in a loop and
the node mints a key per connection, none of them in any backup taken
beforehand, which for a pre-BIP32 wallet is how coins go missing. The map
holding them was static and never trimmed.

submitorder AddToWallet()'d a CWalletTx from any peer that sent one. It
had to be a valid transaction; it did not have to be ours.

review checked a signature the sender made with its own key, which proves
possession and authorises nothing, then appended to reviews.dat by
reading the whole vector, pushing one entry and writing it all back --
quadratic disk on an unbounded map&lt;string,string&gt; off the socket -- then
walked a link graph the same peer supplied and relayed the message to
every other node.

reply dispatched into CRequestTracker. Nothing ever called PushRequest,
so the table was permanently empty and the handler was a parser and
nothing else.

Removed with the subsystem behind them: market.cpp, CProduct, CReview,
CUser, CReviewDB, the advert publish templates and the subscribe channel
machinery, none of which had an inbound handler either. MSG_REVIEW,
MSG_PRODUCT and MSG_TABLE stay in the inv enum, unserved: CInv puts type
on the wire as an integer and MSG_TX and MSG_BLOCK are shared with every
other node.

Unknown commands are already ignored for extensibility, so peers still
running these lose nothing but an answer they never asked for. Verified
by a clean build of bitflash-node.

(cherry picked from commit ad9c393866c007a115f32d70a9017ce91066a7d1)
</content>
</entry>
<entry>
<title>size thread pools by the cores we are allowed, not the ones the host has</title>
<updated>2026-08-23T04:37:36+00:00</updated>
<author>
<name>bitflash-maintainer</name>
<email>noreply@bitflash.invalid</email>
</author>
<published>2026-08-13T17:29:10+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1q9geyrch3feyczz242sxjaa05yczv50dlpug082sjln2kl0wrnxst0nrfa/bitflash/commit/?id=ee7613be2f1d679c7ea6e3948c44110313d230b1'/>
<id>urn:sha1:ee7613be2f1d679c7ea6e3948c44110313d230b1</id>
<content type='text'>
std::thread::hardware_concurrency() reports the MACHINE's core count. Inside a
container that is the wrong number: with --cpuset-cpus 0-5 on a 12-core host it
still answers 12, and every caller that sized a pool from it oversubscribed 2x.

Measured on the pool box:
  - the pool node logged "Mining with 11 thread(s) of 12 core(s)" while
    confined to 6 CPUs
  - a burst miner launched with /hashthreads=2 filled the ~2 GB RandomX
    dataset with 12 threads, alongside the pool node in the same 8 GB container

AvailableCoreCount() asks sched_getaffinity what the cpuset actually permits,
which is the number all of these meant. It cannot make the dataset build slower:
the CPU budget is identical either way, so fewer threads on the same cores is
the same work without the thrash. Falls back to hardware_concurrency where the
call is unavailable, and never returns 0 because callers subtract from it.

Not the cause of the burst miner dying -- that was a corrupt Berkeley DB
environment in the worker datadir -- but it is why the failure landed on the
dataset build, and it is wrong on its own terms.

(cherry picked from commit b3bee16f3eef8982bff1174bd9d0a9c21473732c)
</content>
</entry>
</feed>
