| Age | Commit message (Collapse) | Author |
|
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.
|
|
VERSION names every release artifact -- the AppImage, the windows zip and the
node binary. This tree is upstream v1.2.18 plus 24 commits of our own and a
payout ledger that is neither side's, so a binary built here must not be
mistakable for the upstream release of that number.
|
|
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.
|
|
The two dependency clones were `git clone --depth 1 <url>` with no
revision, which resolves to whatever the default branch points at the
moment the build happens to run.
For RandomX that is the proof of work. Its master is not frozen -- v2.0
changed the algorithm in March -- so two builds of the same Bitflash
commit, made weeks apart, can hash the same header differently. That is
not a supply-chain worry, it is a chain split delivered as a build
artifact, with no version recorded anywhere to explain it afterwards. For
libsecp256k1 it is signature verification, the rule deciding who may
spend.
Both now fetch a full commit id, which is the only form here that
verifies what it got, since git resolves the object by its own hash. The
RandomX pin is the commit this tree has been building and syncing
against, so today's behaviour is unchanged; secp256k1 is pinned to the
v0.8.0 release commit.
appimagetool came from the "continuous" tag, which upstream moves in
place on every push, fetched over the network and executed unverified.
Pinned to a numbered release and checked by digest before it is allowed
to run.
It also ran from /tmp on a fixed name, guarded by `[ ! -x
/tmp/appimagetool ]` -- an executable already sitting there is taken as
the tool, so any local account could leave one and have it run as
whoever built. The AppDir had the same shape one step later, through
symlinks on the rm -rf. Both moved under the build tree, which the build
already owns, and cleaned by `make clean`.
(cherry picked from commit 664aebca08d17a9dbae12c25f21cd8ef2abe7aa6)
|
|
direct onion peers, onion-only mode)
|
|
|
|
app icon)
|
|
|
|
|
|
|
|
* fuzz: add P2P message parser harness
* fuzz: avoid duplicate legacy header includes
* fuzz: seed the corpus with our network magic, not Bitcoin's
The two seed inputs began f5 be b6 d9, which is Bitcoin's message
start. Ours is bf 20 5c fd. CMessageHeader::IsValid rejects on that
field first, so both seeds died at the first check and the smoke run
exercised the reject path and nothing else.
libFuzzer would have to mutate its way onto four exact bytes before
reaching any parser, which does not happen in 128 runs. The job would
have gone green while covering none of what it was added to cover.
---------
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
Co-authored-by: Bitflash-sh <btf01@proton.me>
|
|
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
|
|
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
|
|
|
|
A block had no size limit, spending could move coins onto a key your recovery phrase cannot reproduce, and one peer could hold up the whole node with a message it never finished sending. All three are fixed here, and all three were found by auditing the tree rather than by anyone hitting them.
## Blocks now have a size limit — and this one is consensus
`CheckBlock()` measured a block against `MAX_SIZE`, the serializer's generic 32 MB guard. There was no `MAX_BLOCK_SIZE` anywhere in the tree: Bitcoin's 1 MB cap arrived in 0.3.x, after the 0.1.0 this project came from, so the inheritance was simply missing. A miner could produce a 32 MB block and every node would download, verify and store it.
The cap is now **1 MB**, chosen against the chain rather than tradition. Reading all 5709 blocks out of `blk0001.dat`:
| | |
|---|---|
| largest block ever mined | 34,290 bytes |
| median | 216 bytes |
| p99 | 14,451 bytes |
1 MB leaves **29× of headroom** over the largest block this chain has ever carried, so nothing existing is rejected. A node built with the rule then synced genesis → 5706 on the live network with zero rejections, which is the same test that once caught BIP30 rejecting a real block at height 859.
**Because it is consensus, it matters that you upgrade.** Until most nodes have it, a block above 1 MB would be accepted by old nodes and rejected by new ones — which is how a chain splits. Nobody is mining such blocks today; the gap is worth closing before somebody does.
## Spending could put your change on a key the phrase cannot recover
When a wallet spends, the leftover goes back to you as *change*. This tree sent change to **the same key as one of the coins being spent** — 0.1.0 behaviour, and a privacy problem long tracked as #23.
With recovery phrases it became a way to lose money. If the coin being spent sat on a key created *before* the seed — the wallet's original default address, an imported key, anything older than `-newphrase` — then the change went back to that same pre-seed key. So spending quietly moved your balance onto a key the twelve words do not reproduce, in a wallet you believed the words covered.
Change now comes from a newly derived, phrase-backed key. **If that derivation fails, the transaction is refused rather than created**, because a failed send is recoverable and unrecoverable change is not. Wallets with no seed keep the old behaviour exactly.
Restore also now scans past the pre-generated key-pool window before accepting an empty batch as the end, so a restore cannot stop short while used addresses sit just beyond it.
## One peer could hold up the whole node
`ProcessMessages` runs in a single thread that serves every peer. When a message header announced more bytes than had arrived, the old code consumed the header, inserted it back at the *front* of the receive buffer — an O(n) memmove on a buffer that could hold 256 MB — and then slept 100 ms **inside that shared thread**. A peer that announced a large message and dripped bytes could make every pass pay both costs, forever, while looking perfectly active to the inactivity reaper.
The parser now reads the header from a copy without consuming it, never sleeps in the shared thread, and disconnects a peer that sits on an incomplete message for two minutes. The wire cap drops from 256 MB to `MAX_SIZE`.
## The rendezvous holds more than one registration
A node's registration at a rendezvous relay was consumed by the first caller, and registering again *replaced* the previous one — so a node was reachable to exactly one peer at a time and unreachable for the whole gap between being paired and registering again. Measured on a live node: descriptors resolved 53 times out of 54, its own registrations paired 11 out of 12, and **half of every outbound dial failed**.
All three relays now keep a queue per node and pair with the oldest registration still alive. **This is already deployed** — you get it without upgrading. The matching node-side change, which parks spare registrations, is deliberately **not** in this release: it measured worse in the field than the code it replaced and will not ship until that is explained.
## Restoring from a phrase now looks far enough
Found by proving the change fix above with real coin, and caused by it.
Restoring derived forward in batches of a hundred and stopped at the first batch that turned up nothing. The wallet digs a wider gap than that by itself: a restore leaves the derivation counter at the depth it scanned, the key pool derives a hundred more, and change takes the index after those. So the address used *after* a restore can sit two hundred indices past the last one used before it, with nothing in between.
On a real wallet: receive, restore, spend once — the outputs land at indices 201 and 302. Restoring again reported
```
Restored: 1 transaction(s) across 201 derived addresses
```
and the spend was invisible, both the payment and the change. The money was derived and covered by the phrase; the search stopped short of it. Three quiet batches instead of one, and the same wallet reports `2 transaction(s) across 601 derived addresses`.
Change returning to an input's key used to hide this, because that key sat at a low index the scan had already passed. Fixing recoverability moved change out to where the search stopped looking, so the two had to be fixed together.
The gap is self-inflicted and grows with each restore, so a long enough history can still outrun a fixed number of quiet batches. `-restoredepth=N` looks as far as you ask. The structural fix is tracked separately.
## A headless node can finally spend
`SendMoney()` has been in this tree since 0.1.0 and only the window ever called it, so a node without a screen could be paid and could never pay — it could hold a balance with no way to move it. Found while trying to prove the change fix, which needs a spend.
```
bitflash -sendto=ADDRESS,AMOUNT
```
Refuses an address that does not decode, an amount that is zero, negative or not a number, and a wallet whose chain is not loaded — all before touching the wallet. A transaction created while the node is stopped is not broadcast until the node starts.
## Also
- `-help` printed nothing at all on Windows. This tree remaps `printf` to the debug-log writer, and a `-mwindows` binary has no console, so the one command whose job is to tell you something returned 0 in silence.
- The README described a program from several releases ago — deterministic restore listed as an open issue, mining mode described as forgotten between restarts, and every flag written in the slash form that MSYS2 rewrites into a path.
- Diagnostics now report why a socket close failed, per error code and per site, and flag a handle handed back while some part of the program still claims it.
## Downloads
| File | Platform | Notes |
|------|----------|-------|
| `Bitflash-1.2.14-windows.zip` | Windows x86_64 | Extract and run |
| `Bitflash-1.2.14-x86_64.AppImage` | Linux x86_64 | Desktop/GUI — `chmod +x` and run |
| `bitflash-node-1.2.14-x86_64` | Linux x86_64 | Headless server node, no libGL or FUSE |
Verify with `sha256sum -c SHA256SUMS`.
Everything in [v1.2.13](https://github.com/Bitflash-sh/bitflash/releases/tag/v1.2.13) is included.
|
|
Two things a node could do wrong without ever saying so: close a peer's socket twice, and hand out addresses a recovery phrase cannot reproduce.
## If you created a recovery phrase on 1.2.12, do this first
Run, with the same twelve words:
```
Bitflash.exe -restorephrase="your twelve words here"
```
1.2.12 installed the seed but left the wallet's visible address as the old random key, and left the old random key pool in place. The window said a recovery phrase existed while the next address the wallet handed out — a mining payout included — could be one the words cannot reproduce. Restoring with the same phrase repairs it: the pool is cleared, and the default address becomes a derived one.
Coins already sitting on pre-phrase addresses stay where they are. They are still yours, still spendable, and still **not** covered by the words — which is why `wallet.dat` backups keep mattering. The wallet now names that address `Your Address (created before the recovery phrase)` instead of leaving two entries both called `Your Address` with only one of them recoverable.
Thanks to **@mtg0001**, who found this within hours of 1.2.12 shipping (#102).
## A peer's socket was closed twice, and the second close hit whoever had the number
`CNode::Disconnect()` closed the socket and left the handle number in the object; the destructor then closed that same number again. There is no assignment of `INVALID_SOCKET` to that field anywhere in the tree, so the guard meant to prevent this never fired.
A double close is not a harmless no-op. This node opens hundreds of sockets a minute, and Windows reissues a handle number as soon as its last close lands — so the second close takes down a connection that now belongs to something else entirely.
The same omission left a closed handle in the `select()` set, where it makes `select()` fail on every iteration for as long as that node is in the list. The node then spends its socket loop in an error path instead of reading its peers. **That is the deaf-node behaviour this project has been chasing since 1.2.7**, and the `dropping node: its socket is no longer valid` line in the log is the recovery path, not the disease.
Measured, two nodes on the same machine and the same network, stopped at matched socket volume:
| | sockets opened | failed closes | `no longer valid` | peers held |
|---|---|---|---|---|
| without the fix | 552 | 4 | **741** | 11 |
| **with the fix** | 547 | **0** | **0** | **21** |
Half the peers, on the same machine, at the same minute. All 741 drops were the same peer, over and over — one dead handle in the set is enough.
## The socket leak was already fixed, and the way it was being measured was wrong
Earlier notes, including 1.2.12's, said sockets were still leaking. They were not. Windows lists a wildcard-bound socket **twice** in `Get-NetTCPConnection` — once for the binding (`0.0.0.0:port`, state `Bound`) and once for the connection — and 35 of one node's 38 `Bound` rows shared a local port with one of its own established connections. Counting those rows makes the number climb whenever the node talks to more peers, which is why the fix that worked looked like it had done nothing.
Counting **orphans** instead — a `Bound` row whose port appears nowhere else in that process — measured on three machines the same evening:
| node | version | uptime | orphans |
|---|---|---|---|
| 32-core miner | 1.2.10 | 26 h | **1262**, still taking on 30–90 an hour |
| second miner | 1.2.11 | 4h40m | 3 |
| test bench | 1.2.12 | 2 min | 3 |
The leak was real and **1.2.11 ended it**. If you are still on 1.2.10 or earlier, that alone is worth the upgrade.
## Also in this release
- The diagnostics report now says why a close failed, per error code and per site, totals what the node believes it is holding, and counts a handle handed out while some part of the program still claims it (#98, #100, #101).
- Every header is listed in both makefiles (#99). `sockcount.h` was missing, so editing it rebuilt some objects and not others and left two layouts of the same shared struct in one binary — a warning in the middle of a build log and nothing else.
## Downloads
| File | Platform | Notes |
|------|----------|-------|
| `Bitflash-1.2.13-windows.zip` | Windows x86_64 | Extract and run |
| `Bitflash-1.2.13-x86_64.AppImage` | Linux x86_64 | Desktop/GUI — `chmod +x` and run |
| `bitflash-node-1.2.13-x86_64` | Linux x86_64 | Headless server node, no libGL or FUSE |
Verify with `sha256sum -c SHA256SUMS`.
Everything in [v1.2.12](https://github.com/Bitflash-sh/bitflash/releases/tag/v1.2.12) is included.
|
|
Version bump for the 1.2.12 release. One line of Makefile; the release notes live here.
## The reason this release is not optional
**#87 — a node that was mining before 1.2.11 can come back as a relay and never say so.** The 1.2.11 mining rework left `fGenerateBitcoins` readable on its own: a wallet carrying that flag from an older build, with no mining mode stored beside it, loads as "not mining" and the window shows nothing wrong. It happened here, on a real node, and it was found only because somebody looked at the hashrate. Anyone running 1.2.11 who mined on an earlier build should assume they are affected.
## Recovery phrase (#93, #96)
Twelve words that bring a wallet back. Proved end to end before merging: a live wallet was paid on a derived address, `wallet.dat` was deleted, and the coin came back from the phrase alone.
- `-newphrase` creates a phrase, installs the seed, fills the key pool from it and shows the words once. It refuses when a seed already exists.
- `-restorephrase="WORDS"` installs the seed and walks forward in batches of 100, rescanning after each and stopping when a batch turns up nothing. `-restoredepth=N` to look further.
- `-newaddress` prints the next receiving address; `-showderived=N` lists what a phrase produces.
- The same two operations are reachable from the window.
**What the phrase does not cover, stated plainly:** keys that existed before the seed was installed — including the wallet's original default address — are not derived from it and do not come back from the words. The commands say this at the point of use. Keep the `wallet.dat` backup as well; the phrase is a second line, not a replacement.
The words are printed to the terminal only, never to `debug.log`.
## Wallet safety
- **#88** — `ScanForWalletTransactions` plus `-rescan`: the node can now walk the chain for coins a key owns but the wallet never saw. `-importwallet` scans too. Without this, restoring from a phrase would have derived the right keys and found nothing.
- **#89, #90** — a backup panel in the window, with the default backup written outside the directory it protects.
- **#95** — a scan with no chain data loaded is refused instead of reporting "no transactions found", which read as a verdict to somebody who had just lost a wallet.
## Tests and diagnostics
**#91** wallet key pool self-test, **#92** self-test output where the person running it can see it, **#94** MSYS2 argument conversion (use `-selftest=...`, not `/selftest=...`, under MSYS2). `make tests` runs the BIP32/BIP39 vectors and both wallet self-tests on Linux and Windows.
## Still not fixed
The socket accounting shipped in 1.2.11 is instrumentation, not a fix — sockets still leak. The three obvious causes have been measured and eliminated; the next step is reading a real node's diagnostic report, not another guess.
|
|
* Bump to 1.2.11
One change, and it is the reason a node on Windows kept collecting peers it
could not hear.
FD_SETSIZE was never defined in this tree, so it defaulted to 64 and select()
could watch the listen socket plus 63 peers -- everything past that stayed
connected and was never read again. Windows FD_SET stops adding once its array
is full and says nothing, so there was no error to notice. Because new
connections go on the end of vNodes, it was always the newest connection that
starved, including the ones dialed to fix connectivity.
Measured on two nodes: 277 and 90 CNode objects against 93 and 90 live
sockets, 316 and 355 sockets in a bound-but-never-connected state, and a status
bar reporting hundreds of peers for a node really talking to thirty.
1.2.11 raises FD_SETSIZE to 1024 on Windows, caps connections at 125, drops a
node stuck undeliverable after 60 seconds instead of holding it forever, stops
answering a failed select() with POSIX code that has no meaning on Windows, and
warns when there are more peers than select can watch.
* diag: let the node say what is wrong with it
Every hard problem in this project so far was found from outside the node --
sockets read over WMI, committed memory compared against a README, a grep over
somebody debug.log. In each case the node knew and had no way to say so.
One report, rendered identically for the GUI panel and the log: peers held vs
peers actually watched by select(), blocks received and how many arrived without
a parent, proof-of-work mode with the live miner thread count, and per peer the
age of the last message in each direction plus anything stuck unsent.
|
|
Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
|
|
1.2.9 shipped before the last seven merges landed, and one of them is holding
two live nodes hostage.
Measured on a 32-core node running 1.2.9 today: 65,134 MB committed, which is
30 RandomX datasets at 2,142 MB each -- one per miner thread, for a node that
advertises 2 GB. A 4-core node on the same build sits at 6,568 MB, three
datasets. #68 allocates the dataset once and closes the window where a thread
could mine against one that was not finished being filled.
The rest of the release:
- #67 orphans no longer stack above earned blocks in the GUI list
- #69 no submitting a solution for a parent that is no longer the tip
- #70 a ceiling on orphan blocks, and getblocks stops repeating itself
- #71 Berkeley DB cursors get closed instead of left to the handle
- #73 bounds on what a peer can make the node allocate
- #74 the recipient no longer collects the fee when a transaction retries
|
|
relay.nostr.band went down and stayed down. Every node running 1.2.8 or
earlier spends ten seconds of every discovery pass waiting for it and
writes an error line each time -- the seed logged 2029 of them. #64
replaces it with a relay that answers and backs off from any relay that
stops answering, so the next one to go down costs nothing.
|
|
Six changes since 1.2.7, and the reason not to sit on this is #58:
coinbase transactions were not unique, two of them overwrote older ones
on the main chain, and 100 BTF stopped being reachable. Nothing in how
they were built had changed since, so it could happen again at any time.
The height now goes into the coinbase on both paths that build one
(#59, #60), which makes a repeat impossible without any coordination.
Also in: a node now notices a peer it can no longer hear and stays
audible to peers that check the same way (#55); it announces its height
in the handshake and says out loud when it falls behind the network
(#56); script evaluation has ceilings on operation count and stack depth
(#57); the tail of the chain is verified at startup and a bad tip is
backed out of instead of being mined on (#61); and the wallet now
believes the chain about what it has already spent, which is what a
restored backup gets wrong (#62).
|
|
Rendezvous no longer waits forever for a dial (#51), keys can be
exported and imported as text (#49), and mining uses every core
instead of one (#52).
#51 is the reason not to sit on this: without it a node goes
silently deaf after a while and only a restart brings it back.
|
|
Wallet backup and clean shutdown (#46).
Two reasons not to sit on this: every Linux node aborts on the
way out instead of releasing wallet.dat, and until a node has
shut down cleanly once, the wallet it wrote cannot be moved
anywhere else.
|
|
Headless mining fix (#42), ARM64 build (#41), format-string
checking (#44), gitignore for key material (#43).
#42 is the reason this release exists: -gen has mined nothing on
headless nodes since v1.2.0.
|
|
Seeds (#36), headless node (#37), README (#35), wallet crash fix (#38).
|
|
The GUI build requires libGL at load time even when started with -nogui. The
dynamic loader resolves every dependency before main() runs, so the flag arrives
far too late -- on a clean server the binary dies with
error while loading shared libraries: libGL.so.1: cannot open shared object file
and nothing suggests it would otherwise have worked. A server is where a node
most naturally lives, so this is the deployment that mattered most and the one
that failed. The AppImage additionally needs FUSE, which minimal servers lack.
Bundling libGL into the AppImage would be the wrong fix: GL has to come from the
host's driver stack, and shipping our own breaks machines that have a real GPU.
So: a second binary, built from the same node objects without ImGui, GLFW or
OpenGL. `make linux-node` produces bitflash-node-<version>-x86_64, 2.5 MB
stripped, with no graphics or FUSE dependency at all. `make linux` now builds it
alongside the AppImage. Anyone wanting a window keeps using the AppImage.
Two small moves were needed. MainFrameRepaint is called from main.cpp and
net.cpp but defined in gui.cpp, so headless.cpp provides an empty one rather
than putting ifdefs at six call sites. DateTimeStr also lived in gui.cpp while
main.cpp logs block times with it -- nothing about formatting a date is GUI
work, so it moved to util.cpp and both builds share one definition.
Verified on Debian 13:
ldd bitflash-node | grep -iE 'libGL|glfw|X11' -> nothing
ldd bitflash | grep -iE 'libGL|glfw' -> libglfw.so.3, libGL.so.1, libGLdispatch.so.0
and at runtime, from an empty datadir:
btfseed: reached ygffz37...btf via 90.156.222.107:8434
rendezvous: registered at 90.156.222.107:8434, waiting for a dial
self-resolve OK (meeting_node=90.156.222.107:8434)
btfpeers: kept 15 of 15 descriptor(s)
first block at ~30s, 3182 blocks accepted, synced to the tip
The GUI builds on Linux and Windows were rebuilt and are unaffected.
|
|
Everything since 1.2.2 is about nodes being able to find and reach each other,
which is the problem this project actually had:
#30 rendezvous registration and waiting split apart. Registering used to
block until somebody dialled, so a node could not advertise a meeting
node until it had already been reached at one. This is the root cause
of the flaky connectivity, not dead relays or stale descriptors.
#26 a node no longer advertises a rendezvous it is not registered at
#25 .btf peers that answered are remembered and dialled first (36.3s -> 2.1s)
#32 peers exchange signed descriptors, so discovery survives a relay outage
#29 debug.log stopped dropping lines under concurrency
#28 /port, so two nodes can share a machine
|
|
|
|
|
|
The AppImage step converts src/rc/bitcoin.ico with PIL, but python3-pil
was never in deps-apt, so 'make linux' failed at packaging on a clean
Debian install -- the AppImage the README points Linux users at could
not actually be produced by following the documented steps.
Version bump only renames the produced artefacts; no rebuild needed.
|
|
* Add Linux GUI installer via Wine
* Fix launcher creation and add BTF_RELAY env var support
* Pool server, Linux GUI, unified binary, README update
* Root Makefile: make linux / make windows, cleaner README
* Remove headless.cpp
* Fix XPM color count mismatch causing toolbar image errors on Linux
* XPM icons: same code on Windows and Linux, no ifdefs around image loading
* Fix GTK icon crash and Linux printf format
* btfrv: 10s socket timeout on relay connects
* Faster peer discovery: more dials per pass, shorter backoff and sleep
* Parallel btf peer connections
* Fix GetMyExternalIP - was disabled, now uses modern IP echo services
* Fix tunnel disconnect on idle and restore external IP detection
* Filter stale Nostr descriptors
* Sort peers by freshness, fresh first
* Pool: add .btf Stratum listener — miners connect via rendezvous, no port forwarding needed
* btf pool: self-replacing listeners, one per relay, no queuing between miners
* Mining modes: solo/operator/participant — pool server on-demand, participant Stratum client
* Status bar: show external IP and .btf address
* README: clean rewrite, concept-first, minimal
* make linux produces AppImage
* make linux produces AppImage
* Pool dashboard: connect instructions, tabs, share bars
* Perf fixes: GetMyExternalIP threaded, btf listener counted
* Perf fixes: GetMyExternalIP threaded, btf listener counted
* Fix pool payouts: defer until coinbase maturity
* Pool: clean mode switching, IP warning
* Pool: clean mode switching
* Fix gPoolRunning: remove static
* AppImage: use real icon from bitcoin.ico
* Fix exit crash; Mining menu
* Fix exit crash; Mining menu
* Fix exit crash; Mining menu
* Fix exit crash; Mining menu
* Fix exit crash; Mining menu
* Windows build: use mingw32-make
* Windows build: simpler make invocation
* Add deps-windows target
* Windows build fixes
* Windows build fixes
* Windows makefile: fix obj dir
* Windows: output to repo root
* Windows: bundle DLLs into zip
* Replace wxWidgets with ImGui+GLFW: same build on Linux and Windows
* Fix %I64d that slipped back through rebase (main.h, net.h)
* MinGW: add _mkdir shim for non-MSVC Windows; fix %I64d
* Fix gPoolRunning extern outside ifndef _WIN32
* Remove -lboost_system: boost is header-only in this codebase
* Status bar: copy buttons for IP and .btf address
* Windows: add app icon via windres, strip wx stuff from rc file
* Clean GUI: single Options menu item, bigger font, better spacing, cleaner options dialog; update README
* GUI: embed Roboto font at 16px for clear readability on all platforms
* Windows: windres depends on obj dir target
* Fix balance hidden off-screen: put on own line below address bar
* Windows: inline mkdir in every rule, remove dir target that confused make
* Windows: move all target to top so it's the default rule
* Fix pool server not starting: always start when operator selected, re-sync dialog state on open
* Write debug.log to data directory (AppData/Roaming/Bitflash on Windows)
* Participant miner: use wallet address as username so payouts go to wallet
* Add .gitattributes to lock LF line endings, prevent CRLF drift
* Refactor pool mining to btf-only transport
* Persist pool settings and startup wiring
* Prune stale peers and expose pool queries
* Fix build regressions and add CLI mode/arg parity
* Fix rpc miner stats declaration order
* Centralize pool runtime extern declarations
* Separate operator/participant dashboards and restore peer stats
* Add live telemetry strip and clearer per-mode runtime metrics
* Reduce mining dashboard clutter and remove manual pool entry
* Count only authorized pool miners
* Seed initial pool job on startup
* Use a real share target for pool work
* Switch pool accounting to round-based shares
* Fix nostr pool announcer stats call sites
* Fix pool stats definition order
* Improve pool worker status display
* Add mutex include for participant status
* Replace ImClamp in dashboard layout
* Clarify participant work status
* Log participant status transitions
* Reduce peer discovery log noise
* Add participant status badge
* Track participant auth acknowledgements
* Log and validate pool job delivery
---------
Co-authored-by: Bitflash Pool Dev <pool@bitflash>
|