diff --git a/src/shared/Angor.Shared.Tests/Services/RelayServiceTests.cs b/src/shared/Angor.Shared.Tests/Services/RelayServiceTests.cs new file mode 100644 index 000000000..5e660835b --- /dev/null +++ b/src/shared/Angor.Shared.Tests/Services/RelayServiceTests.cs @@ -0,0 +1,59 @@ +using Angor.Shared.Models; +using Angor.Shared.Services; +using FluentAssertions; + +namespace Angor.Test.Services; + +/// +/// Kind 3030 is a draft/custom Nostr kind, not officially reserved, so public relays can contain +/// unrelated non-JSON content published by other applications under the same kind number. +/// uses +/// to cheaply skip that noise (logged at Debug) instead of attempting a full JSON parse that fails +/// with a JsonException on every such event (previously logged at Warning with a full stack trace). +/// +public class RelayServiceTests +{ + [Fact] + public void LooksLikeJsonObject_ReturnsTrue_ForSerializedProjectInfo() + { + var serializer = new Serializer(); + var json = serializer.Serialize(new ProjectInfo + { + FounderKey = "founder", + FounderRecoveryKey = "recovery", + ProjectIdentifier = "angor1test", + NostrPubKey = "abc123", + NetworkName = "Testnet" + }); + + RelayService.LooksLikeJsonObject(json).Should().BeTrue(); + } + + [Fact] + public void LooksLikeJsonObject_ReturnsTrue_WhenContentHasLeadingWhitespace() + { + RelayService.LooksLikeJsonObject(" \n{\"version\":2}").Should().BeTrue(); + } + + [Fact] + public void LooksLikeJsonObject_ReturnsFalse_ForForeignHexBlobContent() + { + // Real content observed from an unrelated Nostr event that reused kind 3030 on a public relay + // (0x-prefixed hex blob), which previously threw a JsonException at byte position 1. + const string foreignContent = + "0xdf71595bf0776ae54a15c87844c79d0cff46f6fab1739a084cf04f50d4a700b6dbe5978a67ccfd02451918e5e1747939b8936fef48f37ac439e6d878af7b6de"; + + RelayService.LooksLikeJsonObject(foreignContent).Should().BeFalse(); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("[1,2,3]")] + [InlineData("\"just a string\"")] + [InlineData("12345")] + public void LooksLikeJsonObject_ReturnsFalse_ForNonObjectContent(string content) + { + RelayService.LooksLikeJsonObject(content).Should().BeFalse(); + } +} diff --git a/src/shared/Angor.Shared/Services/RelayService.cs b/src/shared/Angor.Shared/Services/RelayService.cs index e0cf50657..937f2e053 100644 --- a/src/shared/Angor.Shared/Services/RelayService.cs +++ b/src/shared/Angor.Shared/Services/RelayService.cs @@ -324,6 +324,17 @@ public void LookupLatestProjects(Action> onResponseAction, Actio if (ev?.Content == null || ev.Id == null) return; + // Kind 3030 is a draft/custom kind, not an officially-reserved Nostr event kind, so + // other applications on shared public relays (e.g. relay.damus.io, nos.lol) can and do + // reuse the same kind number for unrelated, non-JSON payloads. Skip those quietly + // instead of logging a warning for every foreign event; only genuinely JSON-shaped + // content that still fails to map to T is worth a warning. + if (!LooksLikeJsonObject(ev.Content)) + { + _logger.LogDebug("Skipping non-JSON content for Nostr event {EventId} (kind {Kind} reused by another application)", ev.Id, (int)Nip3030NostrKind); + return; + } + try { var projectInfo = _serializer.Deserialize(ev.Content); @@ -462,6 +473,18 @@ public Task PublishAppSpecificDataAsync(string dTag, string content, str return Task.FromResult(signed.Id); } + /// + /// Cheaply checks whether content is shaped like a JSON object (starts with '{') without + /// attempting a full parse. Kind 3030 is not an officially-reserved Nostr event kind, so relays + /// can contain unrelated, non-JSON content published by other applications under the same kind + /// number; this lets us skip that noise without the cost/verbosity of a failed deserialization. + /// + internal static bool LooksLikeJsonObject(string content) + { + var span = content.AsSpan().Trim(); + return span.Length > 0 && span[0] == '{'; + } + private static NostrEvent GetNip3030NostrEvent(string content, string? projectIdentifier = null) { // https://github.com/block-core/nips/blob/peer-to-peer-decentralized-funding/3030.md