diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/nostr.cpp | 56 |
1 files changed, 45 insertions, 11 deletions
diff --git a/src/nostr.cpp b/src/nostr.cpp index b3e4a7c..3f43302 100644 --- a/src/nostr.cpp +++ b/src/nostr.cpp | |||
| @@ -1057,9 +1057,14 @@ static void HandlePoolAnnouncement(const json& ev) | |||
| 1057 | 1057 | ||
| 1058 | // Resolve a `.btf` address via a relay: fetch the descriptor published by that | 1058 | // Resolve a `.btf` address via a relay: fetch the descriptor published by that |
| 1059 | // address's key and verify it self-certifies under the decoded pubkey. | 1059 | // address's key and verify it self-certifies under the decoded pubkey. |
| 1060 | // fExchangeOk reports whether the relay actually finished the conversation | ||
| 1061 | // (an EOSE arrived) as opposed to the socket dying mid-read. The caller pools | ||
| 1062 | // this socket for reuse, and only a completed exchange leaves it in a state | ||
| 1063 | // the next caller can trust. | ||
| 1060 | static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, | 1064 | static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, |
| 1061 | btf::Descriptor& out) | 1065 | btf::Descriptor& out, bool& fExchangeOk) |
| 1062 | { | 1066 | { |
| 1067 | fExchangeOk = false; | ||
| 1063 | unsigned char pk[32]; | 1068 | unsigned char pk[32]; |
| 1064 | if (!btf::ParseAddress(btfAddr, pk)) | 1069 | if (!btf::ParseAddress(btfAddr, pk)) |
| 1065 | return false; | 1070 | return false; |
| @@ -1070,26 +1075,42 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, | |||
| 1070 | json req = json::array({ "REQ", "btf-resolve", filter }); | 1075 | json req = json::array({ "REQ", "btf-resolve", filter }); |
| 1071 | if (!ws.SendText(req.dump())) | 1076 | if (!ws.SendText(req.dump())) |
| 1072 | return false; | 1077 | return false; |
| 1078 | bool fFound = false; | ||
| 1073 | for (;;) | 1079 | for (;;) |
| 1074 | { | 1080 | { |
| 1075 | string msg; | 1081 | string msg; |
| 1076 | if (!ws.RecvText(msg)) | 1082 | if (!ws.RecvText(msg)) |
| 1077 | break; | 1083 | return fFound; // socket died mid-exchange; fExchangeOk stays false |
| 1078 | json j; | 1084 | json j; |
| 1079 | try { j = json::parse(msg); } catch (...) { continue; } | 1085 | try { j = json::parse(msg); } catch (...) { continue; } |
| 1080 | if (!j.is_array() || j.empty() || !j[0].is_string()) continue; | 1086 | if (!j.is_array() || j.empty() || !j[0].is_string()) continue; |
| 1081 | string t = j[0].get<string>(); | 1087 | string t = j[0].get<string>(); |
| 1088 | // Every relay message names the subscription it belongs to. This socket | ||
| 1089 | // is pooled and reused, so a message left over from an earlier exchange | ||
| 1090 | // is the ordinary case here, not an exotic one -- and an unmatched EOSE | ||
| 1091 | // ends this query before it has begun, which is indistinguishable from | ||
| 1092 | // "that descriptor does not exist". Check the id. | ||
| 1093 | if (j.size() >= 2 && j[1].is_string() && j[1].get<string>() != "btf-resolve") | ||
| 1094 | continue; | ||
| 1082 | if (t == "EVENT" && j.size() >= 3) | 1095 | if (t == "EVENT" && j.size() >= 3) |
| 1083 | { | 1096 | { |
| 1084 | const json& ev = j[2]; | 1097 | const json& ev = j[2]; |
| 1085 | if (ev.contains("content") && | 1098 | if (!fFound && ev.contains("content") && |
| 1086 | btf::VerifyDescriptor(ctx, ev["content"].get<string>(), pk, out)) | 1099 | btf::VerifyDescriptor(ctx, ev["content"].get<string>(), pk, out)) |
| 1087 | return true; | 1100 | fFound = true; |
| 1101 | // Deliberately keep reading to EOSE rather than returning here. | ||
| 1102 | // Returning early leaves the rest of the relay's answer queued on a | ||
| 1103 | // socket that goes straight back into the pool, and the next caller | ||
| 1104 | // reads our leftovers as its own -- one message out of step, for | ||
| 1105 | // the life of the process. | ||
| 1088 | } | 1106 | } |
| 1089 | else if (t == "EOSE") | 1107 | else if (t == "EOSE") |
| 1108 | { | ||
| 1109 | fExchangeOk = true; // relay said "that is all": socket is clean | ||
| 1090 | break; | 1110 | break; |
| 1111 | } | ||
| 1091 | } | 1112 | } |
| 1092 | return false; | 1113 | return fFound; |
| 1093 | } | 1114 | } |
| 1094 | 1115 | ||
| 1095 | // Resolve a `.btf` address into its rendezvous coordinates by querying the | 1116 | // Resolve a `.btf` address into its rendezvous coordinates by querying the |
| @@ -1108,12 +1129,22 @@ bool BtfResolve(const std::string& btfAddr, std::string& meetingHostPort, | |||
| 1108 | if (!lease.Connected()) | 1129 | if (!lease.Connected()) |
| 1109 | continue; | 1130 | continue; |
| 1110 | btf::Descriptor d; | 1131 | btf::Descriptor d; |
| 1111 | if (!ResolveDescriptor(lease.Ws(), g_nostrKey.ctx, btfAddr, d)) | 1132 | bool fExchangeOk = false; |
| 1133 | if (!ResolveDescriptor(lease.Ws(), g_nostrKey.ctx, btfAddr, d, fExchangeOk)) | ||
| 1112 | { | 1134 | { |
| 1113 | lease.fOk = true; // exchange completed, just no match | 1135 | // Only keep the pooled socket if the relay actually finished |
| 1136 | // the conversation. This used to say "exchange completed, just | ||
| 1137 | // no match" on every failure -- including a dead socket -- so | ||
| 1138 | // ~CRelayLease never closed it and fConnected was never | ||
| 1139 | // cleared. A relay connection that died was then pinned in the | ||
| 1140 | // pool, reported healthy, and reused for every later lookup, | ||
| 1141 | // which is why "no Nostr descriptor found" persisted for a | ||
| 1142 | // descriptor that was demonstrably published, and why only a | ||
| 1143 | // restart cleared it. | ||
| 1144 | lease.fOk = fExchangeOk; | ||
| 1114 | continue; | 1145 | continue; |
| 1115 | } | 1146 | } |
| 1116 | lease.fOk = true; | 1147 | lease.fOk = fExchangeOk; |
| 1117 | if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") | 1148 | if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") |
| 1118 | continue; // descriptor predates the owner's relay config | 1149 | continue; // descriptor predates the owner's relay config |
| 1119 | if (!HexDecode(d.enc, enc_pub, 32)) | 1150 | if (!HexDecode(d.enc, enc_pub, 32)) |
| @@ -1761,14 +1792,17 @@ void ThreadNostrSeed(void* parg) | |||
| 1761 | PublishDescriptor(ws, key); | 1792 | PublishDescriptor(ws, key); |
| 1762 | Sleep(700); // let the relay store the replaceable event | 1793 | Sleep(700); // let the relay store the replaceable event |
| 1763 | btf::Descriptor d; | 1794 | btf::Descriptor d; |
| 1764 | if (ResolveDescriptor(ws, key.ctx, key.BtfAddress(), d)) | 1795 | bool fExchangeOk = false; |
| 1796 | if (ResolveDescriptor(ws, key.ctx, key.BtfAddress(), d, fExchangeOk)) | ||
| 1765 | { | 1797 | { |
| 1766 | LogPrint("nostr", "Nostr: .btf self-resolve OK via %s (meeting_node=%s, enc=%s...)\n", | 1798 | LogPrint("nostr", "Nostr: .btf self-resolve OK via %s (meeting_node=%s, enc=%s...)\n", |
| 1767 | pszNostrRelays[i], d.meeting_node.c_str(), d.enc.substr(0, 16).c_str()); | 1799 | pszNostrRelays[i], d.meeting_node.c_str(), d.enc.substr(0, 16).c_str()); |
| 1768 | lease.fOk = true; | 1800 | lease.fOk = fExchangeOk; |
| 1769 | break; | 1801 | break; |
| 1770 | } | 1802 | } |
| 1771 | lease.fOk = true; | 1803 | // Same rule as BtfResolve: a socket only goes back in the pool if |
| 1804 | // the relay finished the conversation on it. | ||
| 1805 | lease.fOk = fExchangeOk; | ||
| 1772 | } | 1806 | } |
| 1773 | CATCH_PRINT_EXCEPTION("btf self-test") | 1807 | CATCH_PRINT_EXCEPTION("btf self-test") |
| 1774 | } | 1808 | } |