Implement NIP-66 relay discovery publishing
This commit is contained in:
114
test/parrhesia/nip66_test.exs
Normal file
114
test/parrhesia/nip66_test.exs
Normal file
@@ -0,0 +1,114 @@
|
||||
defmodule Parrhesia.Nip66Test do
|
||||
use Parrhesia.IntegrationCase, async: false, sandbox: true
|
||||
|
||||
alias Parrhesia.API.Events
|
||||
alias Parrhesia.API.RequestContext
|
||||
alias Parrhesia.NIP66
|
||||
alias Parrhesia.Protocol.EventValidator
|
||||
alias Parrhesia.Web.Listener
|
||||
alias Parrhesia.Web.RelayInfo
|
||||
|
||||
setup do
|
||||
previous_nip66 = Application.get_env(:parrhesia, :nip66)
|
||||
previous_relay_url = Application.get_env(:parrhesia, :relay_url)
|
||||
|
||||
on_exit(fn ->
|
||||
Application.put_env(:parrhesia, :nip66, previous_nip66)
|
||||
Application.put_env(:parrhesia, :relay_url, previous_relay_url)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "publish_snapshot stores monitor and discovery events for the configured relay" do
|
||||
identity_path = unique_identity_path()
|
||||
relay_url = "ws://127.0.0.1:4413/relay"
|
||||
|
||||
Application.put_env(:parrhesia, :relay_url, relay_url)
|
||||
|
||||
Application.put_env(:parrhesia, :nip66,
|
||||
enabled: true,
|
||||
publish_interval_seconds: 600,
|
||||
publish_monitor_announcement?: true,
|
||||
timeout_ms: 2_500,
|
||||
checks: [:open, :read, :nip11],
|
||||
geohash: "u33dc1",
|
||||
targets: [
|
||||
%{
|
||||
listener: :public,
|
||||
relay_url: relay_url,
|
||||
topics: ["marmot"],
|
||||
relay_type: "PublicInbox"
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
probe_fun = fn _target, _probe_opts, _publish_opts ->
|
||||
{:ok,
|
||||
%{
|
||||
checks: [:open, :read, :nip11],
|
||||
metrics: %{rtt_open_ms: 12, rtt_read_ms: 34},
|
||||
relay_info: nil,
|
||||
relay_info_body: nil
|
||||
}}
|
||||
end
|
||||
|
||||
assert {:ok, [monitor_event, discovery_event]} =
|
||||
NIP66.publish_snapshot(
|
||||
path: identity_path,
|
||||
now: 1_700_000_000,
|
||||
context: %RequestContext{},
|
||||
probe_fun: probe_fun
|
||||
)
|
||||
|
||||
assert monitor_event["kind"] == 10_166
|
||||
assert discovery_event["kind"] == 30_166
|
||||
assert :ok = EventValidator.validate(monitor_event)
|
||||
assert :ok = EventValidator.validate(discovery_event)
|
||||
|
||||
assert {:ok, stored_events} =
|
||||
Events.query(
|
||||
[%{"ids" => [monitor_event["id"], discovery_event["id"]]}],
|
||||
context: %RequestContext{}
|
||||
)
|
||||
|
||||
assert Enum.sort(Enum.map(stored_events, & &1["kind"])) == [10_166, 30_166]
|
||||
|
||||
assert Enum.any?(discovery_event["tags"], &(&1 == ["d", relay_url]))
|
||||
assert Enum.any?(discovery_event["tags"], &(&1 == ["n", "clearnet"]))
|
||||
assert Enum.any?(discovery_event["tags"], &(&1 == ["T", "PublicInbox"]))
|
||||
assert Enum.any?(discovery_event["tags"], &(&1 == ["t", "marmot"]))
|
||||
assert Enum.any?(discovery_event["tags"], &(&1 == ["rtt-open", "12"]))
|
||||
assert Enum.any?(discovery_event["tags"], &(&1 == ["rtt-read", "34"]))
|
||||
assert Enum.any?(discovery_event["tags"], &(&1 == ["N", "66"]))
|
||||
assert Enum.any?(discovery_event["tags"], &(&1 == ["R", "!payment"]))
|
||||
|
||||
relay_info = JSON.decode!(discovery_event["content"])
|
||||
assert relay_info["self"] == discovery_event["pubkey"]
|
||||
assert 66 in relay_info["supported_nips"]
|
||||
end
|
||||
|
||||
test "relay info only advertises NIP-66 when the publisher is enabled" do
|
||||
listener = Listener.from_opts(listener: %{id: :public, bind: %{port: 4413}})
|
||||
|
||||
Application.put_env(:parrhesia, :nip66, enabled: false)
|
||||
refute 66 in RelayInfo.document(listener)["supported_nips"]
|
||||
|
||||
Application.put_env(:parrhesia, :nip66, enabled: true)
|
||||
assert 66 in RelayInfo.document(listener)["supported_nips"]
|
||||
end
|
||||
|
||||
defp unique_identity_path do
|
||||
path =
|
||||
Path.join(
|
||||
System.tmp_dir!(),
|
||||
"parrhesia_nip66_identity_#{System.unique_integer([:positive, :monotonic])}.json"
|
||||
)
|
||||
|
||||
on_exit(fn ->
|
||||
_ = File.rm(path)
|
||||
end)
|
||||
|
||||
path
|
||||
end
|
||||
end
|
||||
70
test/parrhesia/protocol/event_validator_nip66_test.exs
Normal file
70
test/parrhesia/protocol/event_validator_nip66_test.exs
Normal file
@@ -0,0 +1,70 @@
|
||||
defmodule Parrhesia.Protocol.EventValidatorNip66Test do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Parrhesia.Protocol.EventValidator
|
||||
|
||||
test "accepts valid kind 30166 relay discovery events" do
|
||||
event = valid_discovery_event()
|
||||
|
||||
assert :ok = EventValidator.validate(event)
|
||||
end
|
||||
|
||||
test "rejects kind 30166 discovery events without d tags" do
|
||||
event = valid_discovery_event(%{"tags" => [["N", "11"]]})
|
||||
|
||||
assert {:error, :missing_nip66_d_tag} = EventValidator.validate(event)
|
||||
end
|
||||
|
||||
test "accepts valid kind 10166 monitor announcements" do
|
||||
event = valid_monitor_announcement()
|
||||
|
||||
assert :ok = EventValidator.validate(event)
|
||||
end
|
||||
|
||||
test "rejects kind 10166 monitor announcements without frequency tags" do
|
||||
event = valid_monitor_announcement(%{"tags" => [["c", "open"]]})
|
||||
|
||||
assert {:error, :missing_nip66_frequency_tag} = EventValidator.validate(event)
|
||||
end
|
||||
|
||||
defp valid_discovery_event(overrides \\ %{}) do
|
||||
base_event = %{
|
||||
"pubkey" => String.duplicate("1", 64),
|
||||
"created_at" => System.system_time(:second),
|
||||
"kind" => 30_166,
|
||||
"tags" => [
|
||||
["d", "wss://relay.example.com/relay"],
|
||||
["n", "clearnet"],
|
||||
["N", "11"],
|
||||
["R", "!payment"],
|
||||
["R", "auth"],
|
||||
["t", "marmot"],
|
||||
["rtt-open", "12"]
|
||||
],
|
||||
"content" => "{}",
|
||||
"sig" => String.duplicate("2", 128)
|
||||
}
|
||||
|
||||
event = Map.merge(base_event, overrides)
|
||||
Map.put(event, "id", EventValidator.compute_id(event))
|
||||
end
|
||||
|
||||
defp valid_monitor_announcement(overrides \\ %{}) do
|
||||
base_event = %{
|
||||
"pubkey" => String.duplicate("3", 64),
|
||||
"created_at" => System.system_time(:second),
|
||||
"kind" => 10_166,
|
||||
"tags" => [
|
||||
["frequency", "900"],
|
||||
["timeout", "open", "5000"],
|
||||
["c", "open"],
|
||||
["c", "nip11"]
|
||||
],
|
||||
"content" => "",
|
||||
"sig" => String.duplicate("4", 128)
|
||||
}
|
||||
|
||||
event = Map.merge(base_event, overrides)
|
||||
Map.put(event, "id", EventValidator.compute_id(event))
|
||||
end
|
||||
end
|
||||
48
test/parrhesia/tasks/nip66_publisher_test.exs
Normal file
48
test/parrhesia/tasks/nip66_publisher_test.exs
Normal file
@@ -0,0 +1,48 @@
|
||||
defmodule Parrhesia.Tasks.Nip66PublisherTest do
|
||||
use Parrhesia.IntegrationCase, async: false
|
||||
|
||||
alias Parrhesia.API.Events
|
||||
alias Parrhesia.API.Identity
|
||||
alias Parrhesia.API.RequestContext
|
||||
alias Parrhesia.Tasks.Nip66Publisher
|
||||
|
||||
test "publishes a NIP-66 snapshot when ticked" do
|
||||
path =
|
||||
Path.join(
|
||||
System.tmp_dir!(),
|
||||
"parrhesia_nip66_worker_#{System.unique_integer([:positive, :monotonic])}.json"
|
||||
)
|
||||
|
||||
on_exit(fn ->
|
||||
_ = File.rm(path)
|
||||
end)
|
||||
|
||||
probe_fun = fn _target, _probe_opts, _publish_opts ->
|
||||
{:ok, %{checks: [:open], metrics: %{rtt_open_ms: 8}, relay_info: nil, relay_info_body: nil}}
|
||||
end
|
||||
|
||||
worker =
|
||||
start_supervised!(
|
||||
{Nip66Publisher,
|
||||
name: nil,
|
||||
interval_ms: 60_000,
|
||||
path: path,
|
||||
now: 1_700_000_123,
|
||||
probe_fun: probe_fun,
|
||||
config: [enabled: true, publish_interval_seconds: 600, targets: [%{listener: :public}]]}
|
||||
)
|
||||
|
||||
send(worker, :tick)
|
||||
_ = :sys.get_state(worker)
|
||||
|
||||
{:ok, %{pubkey: pubkey}} = Identity.get(path: path)
|
||||
|
||||
assert {:ok, events} =
|
||||
Events.query(
|
||||
[%{"authors" => [pubkey], "kinds" => [10_166, 30_166]}],
|
||||
context: %RequestContext{}
|
||||
)
|
||||
|
||||
assert Enum.sort(Enum.map(events, & &1["kind"])) == [10_166, 30_166]
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user