From 288978a181cdd20befe349aa9eb6bf5483c52353 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Thu, 10 Sep 2026 14:05:09 +0200 Subject: [PATCH] derive NameRegistration JSON the same way on every build sumTypeJSON switches to the _owsf form on swift builds, but this JSON is the RNAME payload and the resolver's HTTP contract, so a swift client and a Linux relay would disagree on every field. taggedObjectJSON is what it already resolves to everywhere else. The label modifier keeps the reservedReason_ collision escape out of the API, as AgentWorkersDetails does: both arms now say reservedReason. --- protocol/simplex-messaging.md | 4 ++-- scripts/resolver/service/snrc-resolve.py | 2 +- src/Simplex/Messaging/Names/Record.hs | 10 ++++++++-- tests/SMPNamesTests.hs | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/protocol/simplex-messaging.md b/protocol/simplex-messaging.md index 2394cc8f2..fbff6566f 100644 --- a/protocol/simplex-messaging.md +++ b/protocol/simplex-messaging.md @@ -1549,7 +1549,7 @@ seconds since the Unix epoch. Lengths are characters. |---|---|---|---| | `expires` | `registered` | number | absent only from a v20/v21 router, which sent the record alone | | `graceUntil` | `registered` | number | after `expires`; until here only the owner may renew. Absent on the same condition as `expires` | -| `reservedReason_` | `registered` | string | a reason word, present only when the name is held back as well | +| `reservedReason` | `registered` | string | a reason word, present only when the name is held back as well | | `nameRecord` | `registered` | object | the record, schema below | | `pricing` | `available` | object | `registrationPrices`, `basePrice` and `minLabelLength`, below | | `reservedReason` | `reserved` | string | a reason word | @@ -1566,7 +1566,7 @@ JSON and in a client's own API. `available` alone means registrable: a name the registry holds back answers `reserved` instead, so a client has no flags to combine. A reservation on a name -that *is* registered is carried in `reservedReason_`, and is why that name will +that *is* registered is carried in `reservedReason`, and is why that name will not free up when it expires. There is no separate answer for an auction. A name past its grace period answers diff --git a/scripts/resolver/service/snrc-resolve.py b/scripts/resolver/service/snrc-resolve.py index 87a1745b8..6ba95b85e 100755 --- a/scripts/resolver/service/snrc-resolve.py +++ b/scripts/resolver/service/snrc-resolve.py @@ -744,7 +744,7 @@ def registration(name: str): "type": "registered", "expires": reg["expires"], "graceUntil": reg["graceEnds"], - "reservedReason_": reg["reasonCode"], + "reservedReason": reg["reasonCode"], "nameRecord": name_record(name), } if reg["reasonCode"]: diff --git a/src/Simplex/Messaging/Names/Record.hs b/src/Simplex/Messaging/Names/Record.hs index c9f31f490..5a76c3445 100644 --- a/src/Simplex/Messaging/Names/Record.hs +++ b/src/Simplex/Messaging/Names/Record.hs @@ -23,7 +23,7 @@ import Data.Int (Int64) import Data.Map.Strict (Map) import Data.Text (Text) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, taggedObjectJSON) import Simplex.Messaging.SystemTime (SystemSeconds) -- | Resolved name record returned by the names role. JSON keys match the @@ -132,4 +132,10 @@ oldRegistration nameRecord = $(JQ.deriveJSON defaultJSON ''NamePricing) -$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "NR") ''NameRegistration) +-- taggedObjectJSON, not sumTypeJSON: this JSON is the RNAME payload and the +-- resolver contract, so it must not vary with the swift build flag. The label +-- modifier keeps the reservedReason_ collision escape out of the API. +$( JQ.deriveJSON + (taggedObjectJSON $ dropPrefix "NR") {J.fieldLabelModifier = takeWhile (/= '_')} + ''NameRegistration + ) diff --git a/tests/SMPNamesTests.hs b/tests/SMPNamesTests.hs index 44d0118e7..26857ef95 100644 --- a/tests/SMPNamesTests.hs +++ b/tests/SMPNamesTests.hs @@ -54,7 +54,7 @@ testNameRecord = -- from the Haskell value: the literal JSON is the contract with the resolver. registeredBody :: NameRecord -> LB.ByteString registeredBody nameRec = - "{\"type\":\"registered\",\"expires\":1813853483,\"graceUntil\":1821629483,\"reservedReason_\":null,\"nameRecord\":" <> J.encode nameRec <> "}" + "{\"type\":\"registered\",\"expires\":1813853483,\"graceUntil\":1821629483,\"reservedReason\":null,\"nameRecord\":" <> J.encode nameRec <> "}" availableBody :: LB.ByteString availableBody = "{\"type\":\"available\",\"pricing\":{\"registrationPrices\":{\"3\":12793,\"4\":3198},\"basePrice\":100,\"minLabelLength\":3}}" @@ -177,7 +177,7 @@ availabilitySpec = do J.encode NRRTrademark `shouldBe` "\"trademark\"" where heldBackBody = - "{\"type\":\"registered\",\"expires\":1813853483,\"graceUntil\":1821629483,\"reservedReason_\":\"internal\",\"nameRecord\":" <> J.encode testNameRecord <> "}" + "{\"type\":\"registered\",\"expires\":1813853483,\"graceUntil\":1821629483,\"reservedReason\":\"internal\",\"nameRecord\":" <> J.encode testNameRecord <> "}" answers body a = withResolverServer (resolveResp status200 body) $ \port _ -> do env <- newNamesEnv (testNamesConfig port)