Skip to content

net: compiled-in bootstrap seeds, so a first run does not need a relay - #36

Merged
Bitflash-sh merged 2 commits into
mainfrom
feat/btf-seeds
Jul 29, 2026
Merged

net: compiled-in bootstrap seeds, so a first run does not need a relay#36
Bitflash-sh merged 2 commits into
mainfrom
feat/btf-seeds

Conversation

@Bitflash-sh

@Bitflash-sh Bitflash-sh commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Completes the discovery plan: seeds to be born, peer exchange to live, cache to be reborn. The last two shipped in 1.2.3; this is the first.

The gap

A node with no cache has only the Nostr relays. If they are down, blocked, or just slow, a first run has no way into the network — and that is precisely the moment a new user has no patience.

Design

A seed entry is an address and an encryption key. Deliberately no meeting node.

The rendezvous relay pairs on the service's public key, and a .btf address is that key, so a client finds a seed by trying the known relays in turn. Recording which relay a seed was on would rot the moment it failed over — the exact failure chased through #26 and #30. A seed with no such field cannot go stale that way.

Seeds carry no authority. They serve the same signed, self-certifying descriptors as any peer, so a hostile seed can stall a bootstrap but cannot forge a peer or feed a false chain.

The seed

One entry: a dedicated node in Almaty, running beside a rendezvous relay on the same host. No wallet balance, no mining, -nogui, memory-capped by systemd so it can never be the reason the relay dies. It exists only to answer a first dial.

Tested

Fresh datadir, no flags, compiled list only:

[net] btfpeers: no cache yet, falling back to seeds
[net] btfseed: trying 1 seed(s) across 3 relay(s)
[net] btfseed: reached ygffz37...btf via 90.156.222.107:8434
[net] btfseed: 1 seed(s) answered
[net] btfpeers: kept 2 of 2 descriptor(s) from ...

5.2 seconds from nothing to a peer, against 36s for a cold start going through the relays. The seed then handed over two more peers by exchange, which the cache keeps for next time — all three layers in one run.

/btfseed=ADDRESS:ENCHEX (repeatable) appends at runtime, for testing and for private networks.

Build green on MSYS2 UCRT64.

A node with no cache has only the Nostr relays. If they are down, blocked or
slow, a first run has no way into the network at all -- and that is the one
moment a user has no patience for. Seeds are the floor under that.

A seed entry is an address and an encryption key. Deliberately no meeting node:
the rendezvous relay pairs on the service's public key, and a `.btf` address is
that key, so a client can find a seed by trying the known relays in turn.
Recording which relay a seed was on would rot the moment it failed over, which
is exactly the failure this project spent weeks chasing -- advertising a
rendezvous somebody is not registered at.

Seeds carry no authority. They serve the same signed, self-certifying
descriptors as any peer, so a hostile seed can stall a bootstrap but cannot
forge a peer or feed a false chain.

The compiled list is empty. Which nodes become permanent public bootstrap
targets is a decision for whoever runs the network, not a default, and it is
close to irreversible once binaries are distributed. Empty is valid: discovery
falls back to Nostr exactly as before. /btfseed=ADDRESS:ENCHEX (repeatable)
appends at runtime, for testing and for private networks.

Tested against the live network, clean datadir, no cache, three seeds carrying
no meeting node:

  btfpeers: no cache yet, falling back to seeds
  btfseed: trying 3 seed(s) across 3 relay(s)
  btfseed: reached q5jxyzn...btf via 90.156.222.107:8434
  btfseed: 3 seed(s) answered
Dedicated node, no wallet balance, no mining, running beside the rendezvous
relay on the same host. It exists only to answer a first dial.

Fresh datadir, no flags, compiled list only:

  btfpeers: no cache yet, falling back to seeds
  btfseed: trying 1 seed(s) across 3 relay(s)
  btfseed: reached ygffz37...btf via 90.156.222.107:8434
  btfseed: 1 seed(s) answered
  btfpeers: kept 2 of 2 descriptor(s)

5.2 seconds from nothing to a peer, against 36s for a cold start that had to go
through the relays. The seed then handed over two more peers by exchange, which
the cache keeps for next time.
@Bitflash-sh
Bitflash-sh merged commit 3b0d4df into main Jul 29, 2026
Bitflash-sh added a commit that referenced this pull request Jul 29, 2026
Seeds (#36), headless node (#37), README (#35), wallet crash fix (#38).
Vic-Nas added a commit to Vic-Nas/bitflash-mod that referenced this pull request Jul 30, 2026
…RM64 build (port of Bitflash-sh#41); gitignore: cover key files (port of Bitflash-sh#43)

## Bootstrap seeds (Bitflash-sh#36)

A node with an empty cache has only the Nostr relays for discovery --
if those are down, blocked, or just slow, a first run has no way into
the network at all, which is exactly the moment a user has the least
patience. Seeds are the floor under that: a compiled-in list of
long-lived peers, tried automatically only when the peer cache is
empty.

A seed entry is deliberately just an address and an encryption key,
no meeting node -- the rendezvous relay pairs on the service's public
key, and a .btf address is that key, so a seed is findable by walking
the known relays. Recording which relay a seed was on would rot the
moment it failed over, which is the exact "advertising a rendezvous
nobody is registered at" failure this project has chased before.
Seeds carry no special authority: they hand out the same signed,
self-certifying descriptors any peer does, so a hostile seed can
stall a bootstrap but can't forge a peer or feed a false chain.

The compiled list here is empty, deliberately -- upstream shipped
their own dedicated bootstrap node's address in their version of this
commit, but that's their infrastructure, not something this fork has
any relationship to or authority to point users at. /btfseed=
ADDRESS:ENCHEX (repeatable, CLI-only) covers testing and private
networks without needing a compiled entry.

Adapted the hex-decode call to our own BtfPeerCacheHexToBytes rather
than a generic HexToBytes upstream doesn't have under that name.

## ARM64 build failure (Bitflash-sh#41)

ByteReverse(word32) in sha.h used the x86 bswap instruction via
inline asm, guarded only by #if defined(__GNUC__) -- true on every
architecture GCC/Clang target, so on aarch64 the assembler rejected
the instruction outright. The 64-bit ByteReverse right below it
already had the correct guard (&& defined(__x86_64__)); the 32-bit
one just never got it. Fails the build on Apple Silicon, ARM servers,
or a Raspberry Pi -- all plausible headless-node hosts.

## .gitignore gaps (Bitflash-sh#43)

*.dat/wallet.dat were covered, but btf_enc.key (this node's .btf
identity) and nostr.key (its Nostr signing key) are not .dat files
and are exactly as sensitive -- already listed explicitly in our
.gitignore from an earlier session, but walletrpc.token (bearer
authority to spend from the wallet over the wallet API) was missing
entirely, along with a general *.key catch-all and patterns for key
backup archives, mempool dumps, and stray .patch/.rej/.orig files. A
node run with its data directory inside a working tree -- the obvious
thing to do while developing -- would otherwise leave all of this
untracked but committable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant