upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
5 files changed, 135 insertions, 3 deletions
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"];