upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash-sh <btf01@proton.me>2026-07-24 14:46:49 -0300
committerBitflash-sh <btf01@proton.me>2026-07-24 14:46:49 -0300
commite2bc68de1af27d3459878f43c40061d0c30d09d7 (patch)
tree40bffb13573ff9fe4a3c114883d10f533aa7e803
parent2b10d4a64fd17ad6320055935d76ab6beca3b9b3 (diff)
Automatic relay discovery over Nostr + call-to-action
Relay operators announce their relay with -announcerelay=host:port; every node discovers announced relays (kind 38502) and adds them to its failover list, so volunteer relays join with no maintainer edit or approval. Anti-spam cap + validation; relays are trustless (echan makes a bad relay unable to read/MITM). README: automatic relay-volunteer guide, 'don't let the network die' section.
-rw-r--r--README.md50
-rw-r--r--src/headless.cpp3
-rw-r--r--src/net.cpp15
-rw-r--r--src/nostr.cpp110
-rw-r--r--src/nostr.h6
-rw-r--r--src/ui.cpp4
6 files changed, 170 insertions, 18 deletions
diff --git a/README.md b/README.md
index 5f1c2bb..d5be586 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,24 @@ promise back.
49 49
50--- 50---
51 51
52<div align="center">
53
54## ๐Ÿ”ฅ Don't let the network die โ€” run a node
55
56A cryptocurrency is only as alive as the people running it. Every node that mines,
57relays, and stays online makes Bitflash **faster, freer, and impossible to shut down.**
58No companies. No servers to trust. No gatekeepers โ€” just people, one CPU each.
59
60### Download it. Run it. Keep it alive.
61
62**[โฌ‡๏ธ Windows](../../releases/latest)** &nbsp;ยท&nbsp; **[๐Ÿง Linux](#-linux-node--miner)** &nbsp;ยท&nbsp; **[๐Ÿ›ฐ๏ธ Run a relay](#-become-a-relay-volunteer--automatically)**
63
64*The network needs you online. Close this tab and it's one node weaker.*
65
66</div>
67
68---
69
52## โœจ Features 70## โœจ Features
53 71
54| | | 72| | |
@@ -138,31 +156,33 @@ source, then the `bitflash-node` binary. Debian/Ubuntu.
138Built with **MSYS2 UCRT64** (g++ 16, wxWidgets 3.2, OpenSSL 3, Berkeley DB). See 156Built with **MSYS2 UCRT64** (g++ 16, wxWidgets 3.2, OpenSSL 3, Berkeley DB). See
139[`build.sh`](build.sh) and `src/makefile.mingw`. 157[`build.sh`](build.sh) and `src/makefile.mingw`.
140 158
141### ๐Ÿ›ฐ๏ธ Become a relay volunteer 159### ๐Ÿ›ฐ๏ธ Become a relay volunteer โ€” automatically
142 160
143Relays are the meeting points that let nodes behind CGNAT find each other. More 161Relays are the meeting points that let nodes behind CGNAT find each other. More
144relays, spread across more places, make the network **faster** and **harder to 162relays, in more places, make the network **faster** and **harder to knock
145knock offline**. If you have a VPS or box with a public IP, you can run one and 163offline**. If you have a VPS with a public IP, you can add one โ€” and **the whole
146directly strengthen Bitflash: 164network discovers it automatically. No approval, no gatekeeper, no waiting.**
147 165
148**1. Run the relay โ€” one command:** 166**1. Run the relay โ€” one command:**
149```bash 167```bash
150sudo bash relay/install-bitflash-relay.sh 8434 168sudo bash relay/install-bitflash-relay.sh 8434
151``` 169```
152It builds a tiny, dependency-free daemon, applies basic anti-flood firewall 170Builds a tiny, dependency-free daemon, applies anti-flood firewall rules, and
153rules, and installs it as a `systemd` service (auto-restarts, survives reboot). 171installs it as a `systemd` service (auto-restarts, survives reboot).
154 172
155**2. Open TCP port `8434`** in your firewall / cloud panel. 173**2. Open TCP port `8434`** in your firewall / cloud panel.
156 174
157**3. Get it into the network.** Send your relay's `IP:8434` by 175**3. Announce it** by running a node that advertises your relay:
158[opening an issue](../../issues) (or a pull request adding it to the seed list 176```bash
159in `src/nostr.cpp`). A maintainer reviews it and includes it in the **seed list** 177./bitflash-node -gen -announcerelay=YOUR_PUBLIC_IP:8434
160of the next release โ€” then every node starts using it automatically, with 178```
161failover if any relay goes down. 179That node publishes your relay over Nostr; every other node discovers it within
180a minute and adds it to its failover list. **Done โ€” you're part of the backbone.**
162 181
163> ๐Ÿ”’ A relay **never sees your traffic** โ€” it only forwards end-to-end-encrypted 182> ๐Ÿ”’ A relay is **trustless** โ€” it only forwards end-to-end-encrypted bytes and
164> bytes and pairs peers by their `.btf` key. It holds no keys and reads nothing. 183> pairs peers by their `.btf` key. It **cannot read, tamper with, or MITM** your
165> A 1 GB VPS is plenty, and every relay you add makes the whole network tougher. 184> traffic; a malicious relay can at most refuse to forward, and nodes simply fail
185> over. That's exactly why anyone can run one safely. A 1 GB VPS is plenty.
166 186
167<details> 187<details>
168<summary>Run the Linux node 24/7 (systemd)</summary> 188<summary>Run the Linux node 24/7 (systemd)</summary>
@@ -214,7 +234,7 @@ systemctl daemon-reload && systemctl enable --now bitflash-node
214- [x] Headless Linux node/miner 234- [x] Headless Linux node/miner
215- [x] Fair-launch, stable-parameter relaunch 235- [x] Fair-launch, stable-parameter relaunch
216- [x] Redundant relays across regions with automatic failover 236- [x] Redundant relays across regions with automatic failover
217- [ ] Automatic relay discovery over Nostr (self-announcing volunteer relays) 237- [x] Automatic relay discovery over Nostr (self-announcing volunteer relays)
218- [ ] Optional direct IPv6 transport (opt-in, for public backbone nodes) 238- [ ] Optional direct IPv6 transport (opt-in, for public backbone nodes)
219- [ ] One-click installers 239- [ ] One-click installers
220 240
diff --git a/src/headless.cpp b/src/headless.cpp
index 646476f..cdb8ac9 100644
--- a/src/headless.cpp
+++ b/src/headless.cpp
@@ -80,6 +80,7 @@ int main(int argc, char* argv[])
80 " -datadir=<dir> data directory (default ~/.bitflash)\n" 80 " -datadir=<dir> data directory (default ~/.bitflash)\n"
81 " -port=<n> P2P listen port (default 8433)\n" 81 " -port=<n> P2P listen port (default 8433)\n"
82 " -rvrelay=host:port rendezvous meeting relay (has a seed default)\n" 82 " -rvrelay=host:port rendezvous meeting relay (has a seed default)\n"
83 " -announcerelay=h:p announce a relay you run so nodes discover it\n"
83 " -connectbtf=<a>.btf keep a connection to a specific .btf peer\n" 84 " -connectbtf=<a>.btf keep a connection to a specific .btf peer\n"
84 " -solomine mine without waiting for a peer (testing)\n" 85 " -solomine mine without waiting for a peer (testing)\n"
85 " -debug verbose logging\n", 86 " -debug verbose logging\n",
@@ -96,6 +97,8 @@ int main(int argc, char* argv[])
96 addrProxy = CAddress(mapArgs["/proxy"].c_str()); 97 addrProxy = CAddress(mapArgs["/proxy"].c_str());
97 if (mapArgs.count("/rvrelay")) 98 if (mapArgs.count("/rvrelay"))
98 vBtfMeetingRelays = { mapArgs["/rvrelay"] }; 99 vBtfMeetingRelays = { mapArgs["/rvrelay"] };
100 if (mapArgs.count("/announcerelay"))
101 strBtfAnnounceRelay = mapArgs["/announcerelay"];
99 if (mapArgs.count("/connectbtf")) 102 if (mapArgs.count("/connectbtf"))
100 strBtfConnect = mapArgs["/connectbtf"]; 103 strBtfConnect = mapArgs["/connectbtf"];
101 if (mapArgs.count("/debug")) 104 if (mapArgs.count("/debug"))
diff --git a/src/net.cpp b/src/net.cpp
index 659de05..31afaf5 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -442,18 +442,27 @@ void ThreadBtfAccept(void* parg)
442 Sleep(5000); 442 Sleep(5000);
443 } 443 }
444 size_t iRelay = 0; 444 size_t iRelay = 0;
445 bool fPicked = false;
445 loop 446 loop
446 { 447 {
447 if (fShutdown) 448 if (fShutdown)
448 return; 449 return;
449 if (vBtfMeetingRelays.empty()) 450 // Curated seeds + relays discovered from Nostr announcements.
451 vector<string> relays = BtfAllRelays();
452 if (relays.empty())
450 { 453 {
451 Sleep(10000); // no meeting relay configured 454 Sleep(10000); // no meeting relay configured
452 continue; 455 continue;
453 } 456 }
457 // Random start spreads nodes across all relays (incl. volunteer ones).
458 if (!fPicked)
459 {
460 iRelay = (size_t)GetRand(relays.size());
461 fPicked = true;
462 }
454 // Stick to the current relay while it works; on failure, fail over to 463 // Stick to the current relay while it works; on failure, fail over to
455 // the next seed so a DDoS'd/blocked relay IP can't keep us offline. 464 // the next one so a DDoS'd/blocked relay IP can't keep us offline.
456 string strMeeting = vBtfMeetingRelays[iRelay % vBtfMeetingRelays.size()]; 465 string strMeeting = relays[iRelay % relays.size()];
457 size_t colon = strMeeting.rfind(':'); 466 size_t colon = strMeeting.rfind(':');
458 if (colon == string::npos) 467 if (colon == string::npos)
459 { 468 {
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 59817f5..44aec56 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -31,6 +31,13 @@ static const int BTF_DESC_KIND = 38501;
31// the abandoned test .btf descriptors lingering on the relays become invisible. 31// the abandoned test .btf descriptors lingering on the relays become invisible.
32static const char* BTF_DESC_DTAG = "btf-descriptor-2"; 32static const char* BTF_DESC_DTAG = "btf-descriptor-2";
33 33
34// Relay auto-discovery: a relay operator announces their relay as an addressable
35// event (kind 38502) so every node learns it automatically -- no manual
36// seed-list edit, no maintainer approval. Volunteer relays just work.
37static const int BTF_RELAY_KIND = 38502;
38static const char* BTF_RELAY_DTAG = "btf-relay-2";
39static const size_t BTF_MAX_DISCOVERED_RELAYS = 50; // anti-spam cap
40
34// Nostr network/scope for Bitflash. Events are replaceable (NIP-78). 41// Nostr network/scope for Bitflash. Events are replaceable (NIP-78).
35static const int NOSTR_KIND = 30078; 42static const int NOSTR_KIND = 30078;
36static const char* NOSTR_DTAG = "bitflash-mainnet-2"; 43static const char* NOSTR_DTAG = "bitflash-mainnet-2";
@@ -82,6 +89,48 @@ void BtfSetActiveRelay(const string& relay)
82 strBtfActiveRelay = relay; 89 strBtfActiveRelay = relay;
83} 90}
84 91
92// A relay this node advertises on Nostr for others to discover (/announcerelay).
93string strBtfAnnounceRelay;
94// Relays learned from other operators' Nostr announcements.
95static set<string> g_discoveredRelays;
96static CCriticalSection cs_discoveredRelays;
97
98// Basic sanity: "host:port" with a numeric 1..65535 port and a plausible host.
99static bool LooksLikeRelay(const string& r)
100{
101 size_t colon = r.rfind(':');
102 if (colon == string::npos || colon == 0 || colon + 1 >= r.size())
103 return false;
104 if (r.size() > 100)
105 return false;
106 for (size_t i = colon + 1; i < r.size(); i++)
107 if (!isdigit((unsigned char)r[i]))
108 return false;
109 int port = atoi(r.c_str() + colon + 1);
110 if (port <= 0 || port > 65535)
111 return false;
112 // host: allow letters, digits, dot, dash, colon (IPv6 not supported yet)
113 for (size_t i = 0; i < colon; i++)
114 {
115 char c = r[i];
116 if (!(isalnum((unsigned char)c) || c == '.' || c == '-'))
117 return false;
118 }
119 return true;
120}
121
122// Every relay to try: curated seeds first (reliable bootstrap), then the ones
123// discovered from Nostr announcements (volunteer relays).
124vector<string> BtfAllRelays()
125{
126 vector<string> all = vBtfMeetingRelays;
127 CRITICAL_BLOCK(cs_discoveredRelays)
128 foreach(const string& r, g_discoveredRelays)
129 if (find(all.begin(), all.end(), r) == all.end())
130 all.push_back(r);
131 return all;
132}
133
85 134
86// 135//
87// Utilities 136// Utilities
@@ -560,6 +609,21 @@ static void PublishDescriptor(CWebSocket& ws, CNostrKey& key)
560 } 609 }
561} 610}
562 611
612// Announce our relay (from /announcerelay=host:port) so every node discovers it
613// automatically -- volunteer relays join the network with no maintainer step.
614static void PublishRelayAnnouncement(CWebSocket& ws, CNostrKey& key)
615{
616 if (strBtfAnnounceRelay.empty())
617 return;
618 json tags = json::array({ json::array({ "d", BTF_RELAY_DTAG }) });
619 json ev;
620 if (BuildSignedEvent(key, BTF_RELAY_KIND, tags, strBtfAnnounceRelay, ev))
621 {
622 json pub = json::array({ "EVENT", ev });
623 ws.SendText(pub.dump());
624 }
625}
626
563// Resolve a `.btf` address via a relay: fetch the descriptor published by that 627// Resolve a `.btf` address via a relay: fetch the descriptor published by that
564// address's key and verify it self-certifies under the decoded pubkey. 628// address's key and verify it self-certifies under the decoded pubkey.
565static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, 629static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr,
@@ -654,6 +718,9 @@ static bool SeedFromRelay(CNostrKey& key, const string& relay)
654 // Publish our self-certifying .btf descriptor (rendezvous discovery) 718 // Publish our self-certifying .btf descriptor (rendezvous discovery)
655 PublishDescriptor(ws, key); 719 PublishDescriptor(ws, key);
656 720
721 // If we run a relay, announce it so others discover it automatically
722 PublishRelayAnnouncement(ws, key);
723
657 // Subscribe to receive the announcements of other nodes 724 // Subscribe to receive the announcements of other nodes
658 json filter = json::object(); 725 json filter = json::object();
659 filter["kinds"] = json::array({ NOSTR_KIND }); 726 filter["kinds"] = json::array({ NOSTR_KIND });
@@ -736,6 +803,49 @@ static bool SeedFromRelay(CNostrKey& key, const string& relay)
736 } 803 }
737 } 804 }
738 } 805 }
806
807 // Discover volunteer relays announced by other operators (kind 38502). Nodes
808 // add these to their failover list automatically, so anyone can strengthen
809 // the network without a maintainer editing the seed list. Bad/dead relays
810 // just fail to pair and get skipped -- and a relay is trustless anyway (it
811 // only forwards echan ciphertext and can't read or MITM the traffic).
812 ws.SendText(json::array({ "CLOSE", "btf-disc" }).dump());
813 {
814 json rfilter = json::object();
815 rfilter["kinds"] = json::array({ BTF_RELAY_KIND });
816 rfilter["#d"] = json::array({ BTF_RELAY_DTAG });
817 rfilter["limit"] = 200;
818 json rreq = json::array({ "REQ", "btf-relays", rfilter });
819 if (ws.SendText(rreq.dump()))
820 {
821 for (;;)
822 {
823 string msg;
824 if (!ws.RecvText(msg))
825 break;
826 json j;
827 try { j = json::parse(msg); } catch (...) { continue; }
828 if (!j.is_array() || j.empty()) continue;
829 string type = j[0].get<string>();
830 if (type == "EVENT" && j.size() >= 3)
831 {
832 const json& ev = j[2];
833 if (!ev.contains("content") || !ev.contains("kind")) continue;
834 if (ev["kind"].get<int>() != BTF_RELAY_KIND) continue;
835 string r = ev["content"].get<string>();
836 if (!LooksLikeRelay(r)) continue;
837 bool fNew = false;
838 CRITICAL_BLOCK(cs_discoveredRelays)
839 if (g_discoveredRelays.size() < BTF_MAX_DISCOVERED_RELAYS)
840 fNew = g_discoveredRelays.insert(r).second;
841 if (fNew)
842 printf("Nostr: discovered relay %s\n", r.c_str());
843 }
844 else if (type == "EOSE")
845 break;
846 }
847 }
848 }
739 return true; 849 return true;
740} 850}
741 851
diff --git a/src/nostr.h b/src/nostr.h
index 41e59df..b878db9 100644
--- a/src/nostr.h
+++ b/src/nostr.h
@@ -32,6 +32,12 @@ std::string BtfActiveRelay();
32// Record the relay this node just registered its service at (thread-safe). 32// Record the relay this node just registered its service at (thread-safe).
33void BtfSetActiveRelay(const std::string& relay); 33void BtfSetActiveRelay(const std::string& relay);
34 34
35// If set (via /announcerelay=host:port), this node announces that relay on
36// Nostr so other nodes discover it automatically -- no manual seed-list edit.
37extern std::string strBtfAnnounceRelay;
38// Every relay to try: curated seeds + relays discovered from Nostr announcements.
39std::vector<std::string> BtfAllRelays();
40
35// Copy this node's .btf identity (lazily loading or generating it): the x-only 41// Copy this node's .btf identity (lazily loading or generating it): the x-only
36// pubkey (= the .btf address) and the x25519 secret for the end-to-end channel. 42// pubkey (= the .btf address) and the x25519 secret for the end-to-end channel.
37bool BtfGetIdentity(unsigned char pubkey[32], unsigned char enc_sk[32]); 43bool BtfGetIdentity(unsigned char pubkey[32], unsigned char enc_sk[32]);
diff --git a/src/ui.cpp b/src/ui.cpp
index cb1a94d..311a78e 100644
--- a/src/ui.cpp
+++ b/src/ui.cpp
@@ -2974,6 +2974,10 @@ bool CMyApp::OnInit2()
2974 if (mapArgs.count("/rvrelay")) 2974 if (mapArgs.count("/rvrelay"))
2975 vBtfMeetingRelays = { mapArgs["/rvrelay"] }; 2975 vBtfMeetingRelays = { mapArgs["/rvrelay"] };
2976 2976
2977 // Announce a relay you run so other nodes discover it automatically.
2978 if (mapArgs.count("/announcerelay"))
2979 strBtfAnnounceRelay = mapArgs["/announcerelay"];
2980
2977 // Keep an anonymous outbound connection to a specific .btf peer. 2981 // Keep an anonymous outbound connection to a specific .btf peer.
2978 if (mapArgs.count("/connectbtf")) 2982 if (mapArgs.count("/connectbtf"))
2979 strBtfConnect = mapArgs["/connectbtf"]; 2983 strBtfConnect = mapArgs["/connectbtf"];