Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/shared/Angor.Shared.Tests/Services/RelayServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Angor.Shared.Models;
using Angor.Shared.Services;
using FluentAssertions;

namespace Angor.Test.Services;

/// <summary>
/// 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.
/// <see cref="RelayService.LookupLatestProjects{T}"/> uses <see cref="RelayService.LooksLikeJsonObject"/>
/// 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).
/// </summary>
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();
}
}
23 changes: 23 additions & 0 deletions src/shared/Angor.Shared/Services/RelayService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,17 @@ public void LookupLatestProjects<T>(Action<EventInfo<T>> 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<T>(ev.Content);
Expand Down Expand Up @@ -462,6 +473,18 @@ public Task<string> PublishAppSpecificDataAsync(string dTag, string content, str
return Task.FromResult(signed.Id);
}

/// <summary>
/// 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.
/// </summary>
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
Expand Down
Loading