upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/main.cpp
AgeCommit message (Collapse)Author
2026-08-19mine: one pool session in participant mode, not one per corebitflash-maintainer
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)
2026-08-19net: stop serving the 2009 marketplace to anonymous peersbitflash-maintainer
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<string,string> 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)
2026-08-19size thread pools by the cores we are allowed, not the ones the host hasbitflash-maintainer
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)
2026-08-19net: do not consume a header until the body is there, and cap what one can claimbitflash-maintainer
Upstream #109's message-stall rework, hand-merged so it keeps the progress timing this tree already had. The old path consumed the header, found the payload short, inserted the header back at the front of vRecv, then Sleep(100) inside the shared message thread. A peer that announces a large payload and drips bytes made every pass pay an O(n) insert and a 100 ms stall -- one peer slowing the thread that serves all of them. Now the header is read from a copy and only skipped once the whole body has arrived, so there is nothing to put back and nothing to sleep for. Kept OURS, not upstream's, on the timeout: upstream restarts its clock only when the (size, command) pair changes, which drops a peer sending one big message slowly but honestly. This times the STALL -- the clock restarts on any progress -- which is the distinction that matters on a slow link. Also from #109: a header could announce 256 MB while serialize.h refuses anything over MAX_SIZE (32 MB), so the check admitted an allocation the parser would reject anyway and burned a peer slot for two minutes doing it. MAX_PROTOCOL_MESSAGE_SIZE ties the two together. 8x less a peer can make us hold, and this node takes public Stratum. And #103's other half: ~CNode closed its socket with a raw closesocket(), invisible to the accounting, and left hSocket set afterwards -- which is how a descriptor number that now belongs to somebody else gets closed a second time. (cherry picked from commit 2f25e2e07985e9bc732cadfbb3c9ac233895a814)
2026-08-19consensus: cap block size at 1 MB, and stop assembling blocks we would rejectbitflash-maintainer
CheckBlock() validated block size against MAX_SIZE, the serializer's generic 32 MB guard, not a block rule. MAX_BLOCK_SIZE did not exist in this tree at all: 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. Upstream #111 (from #110 by mtg0001). The cap is a tightening, so a node that does not upgrade is the permissive one. Measured upstream over 5709 blocks: largest 34,290 bytes, median 216, so 1 MB rejects nothing that exists. Upstream shipped the validation half only. Both places that ASSEMBLE a block still filled to MAX_SIZE/2 (16 MB), which was consistent against a 32 MB validation cap and is inverted by a 1 MB one -- the node would solve a block and then reject it itself. Fixed both: main.cpp CreateNewBlock, the node's own miner rpc.cpp RebuildJob, the POOL's job builder -- the one that matters here, because every miner on the pool would be working a template the network rejects, and this pool is ~90% of hashrate Half the cap in both, preserving the original margin: each test runs against a transaction that is already counted, so the loop can overshoot by one. (cherry picked from commit a3be9f04903447fa2741aff4eb529fe45f916877)
2026-08-19pool: refuse jobs when no peer has reported a height for 20 minutesbitflash-maintainer
The peer-height gate replaced a tip-age check that was wrong in both directions. It is a strict improvement and it is still blind in the one case that matters most. When the node goes deaf -- peers still in vNodes, nothing arriving -- no peer announces anything, GetPeerMedianHeight() returns -1, and the fallback mines our best chain. That fallback is right for a node that has not been told yet and wrong for a node that has stopped being able to hear, and a height nobody sent cannot tell those apart. A wedged node is exactly a node with no peer height information, so the gate was silent for the whole of heights 3966-4085: 120 blocks, 5890.50 BTF voided. Time can tell them apart. GetPeerHeightAgeSecs() measures how long since any peer reported a height, counting from first use rather than the epoch so a starting node does not refuse jobs before its first handshake. Past 20 minutes the pool stops issuing jobs and says so unconditionally in the log. /poolmineblind overrides it: without an escape hatch a genuinely isolated pool that is 90% of the hashrate could never restart its own chain, which is the deadlock the tip-age check used to cause. Also stop nStartingHeight from going stale. It was a snapshot taken once during the version handshake and never touched again, so on a long-lived connection the median is built from heights the peers had hours ago, and the behind-the-network check drifts toward 'everyone is behind us' and quietly stops firing. A peer that hands us a block is demonstrably at least at that block's height. (cherry picked from commit 0800c18faf703b4acde1b131d0b742d37a86d92b)
2026-08-19payout: say what actually failed instead of blaming transaction sizebitflash-maintainer
SendMoney's "oversized transaction that requires a transaction fee of 0.00" is not an oversized transaction and there is no fee. That branch is taken when nValue + nFeeRequired > GetBalance() -- an empty spendable balance -- and the fee reads 0.00 because CreateTransaction failed before setting one. 3,267 of these in debug.log sent me looking for a transaction-size problem that does not exist. Report what is short, by how much, and against what balance. Name COINBASE_SPENDABLE_DEPTH in main.h and derive GetBlocksToMaturity from it, because the distinction it captures is genuinely surprising: CMerkleTx::GetCredit values a mined coinbase at zero until COINBASE_MATURITY+20 deep, not COINBASE_MATURITY. I first changed the payout gate to that deeper value, on the theory that each payout was firing 20 blocks before its own coinbase could be spent. Measuring it refuted that: across 934 payments, 96% succeeded at depth 100, the moment they were queued, which is only possible because a payout is funded from whatever coin is already mature rather than from its own block. The gate stays at COINBASE_MATURITY, with a comment recording the measurement so the next person does not re-make the same change. The real shortfall is structural and not addressed here: the pool pays out ~99% of each block's subsidy while only matured coinbases are spendable, so it runs with almost no buffer, and any orphaned block it queued a payout for drains that buffer permanently. The 24h retry loop absorbs this -- 0 payouts forfeited -- but the buffer is what needs fixing. (cherry picked from commit e28df01f90d621b0cd818f28d70a20b8ab5d1a8d)
2026-08-19participant: honor explicit /hashthreads=1 (floor of 2 only in auto mode)bitflash-maintainer
The min-2-threads floor applied even when the user explicitly asked for 1 thread, so /hashthreads=1 silently spawned 2. (cherry picked from commit a67b9e84f4f7607d307081b4a9873f5d9e78cf8a)
2026-08-19operator: /noannounce flag -- serve miners without publishing pool announcementsbitflash-maintainer
Descriptor (38501) still published so miners can resolve the pool; only the public pool listing event (38503) is suppressed. (cherry picked from commit 17bbdae1ec97945c37c1d5b2486e5c01a4d7ad87)
2026-08-19pool: sync-gate job creation, retry failed payouts; widen nostr relay setbitflash-maintainer
- RebuildJob: no jobs with zero peers or a tip older than 30min; mining unsynced produced orphaned blocks and payouts that can never be paid (observed in production: 8 orphan 'found blocks') - payout exec: on SendMoney failure requeue with 60s backoff, up to 30 attempts, instead of silently forfeiting the payment - nostr: add oxtr.dev, nos.social, fmt.wiz.biz, current.fyi, nostr.bg (12 relays total) for announcement redundancy (cherry picked from commit f1986cd7e4a6f69469a2256eb96ff03f3f146792)
2026-08-04public commitbitflash-coin