smp-server: hardcode TldRegistries (drop registry_tld_* INI keys)

This commit is contained in:
sh
2026-06-01 12:12:00 +00:00
parent 1d394f56d2
commit b66d97307d
4 changed files with 29 additions and 43 deletions
+7 -9
View File
@@ -1465,15 +1465,13 @@ rslv = %s"RSLV" SP json-bytes ; json-bytes consumes the remainder of the trans
**Server-side validation.** The names router parses `name` as a fully-qualified
domain (TLD required — bare labels are rejected), extracts the TLD, and looks
up the expected SNRC contract address in its INI whitelist
(`registry_tld_simplex`, `registry_tld_testing`, `registry_tld_all`).
`registry_tld_all` is the catch-all used when no TLD-specific entry matches
the requested TLD (and the only entry that can resolve web domains). If no
whitelist entry matches the TLD, or if the client-supplied `contract` differs
from the configured address, the server replies with `ERR AUTH` without
contacting the chain. This lets one names router safely host multiple TLDs
(each backed by its own SNRC contract) and reject clients pointing at a
contract the operator doesn't run.
up the expected SNRC contract address in a whitelist hardcoded in the server
binary (TLD-specific addresses with an optional catch-all for unspecified
TLDs and web domains). If no whitelist entry matches the TLD, or if the
client-supplied `contract` differs from the configured address, the server
replies with `ERR AUTH` without contacting the chain. This lets one names
router safely host multiple TLDs (each backed by its own SNRC contract) and
reject clients pointing at a contract the operator doesn't run.
The names router responds with either a `NAME` response carrying the resolved
record, or `ERR AUTH` collapsing every failure mode (name not found, malformed
+1 -1
View File
@@ -754,7 +754,7 @@ instance J.ToJSON NameOwner where
instance J.FromJSON NameOwner where
parseJSON = J.withText "NameOwner" $ \t -> do
-- Accept "0x" and "0X" prefixes (matches Server/Main.hs:parseEthAddr via fromHex).
-- Accept "0x" and "0X" prefixes (matches the Server-side hex decoder).
let hex = fromMaybe t (T.stripPrefix "0x" t <|> T.stripPrefix "0X" t)
case BAE.convertFromBase BAE.Base16 (encodeUtf8 hex) of
Left e -> fail e
+18 -25
View File
@@ -77,9 +77,8 @@ import Simplex.Messaging.Server.Web (EmbeddedWebParams (..), WebHttpsParams (..)
import Simplex.Messaging.Server.MsgStore.Journal (JournalMsgStore (..), QStoreCfg (..), stmQueueStore)
import Simplex.Messaging.Server.MsgStore.Types (MsgStoreClass (..), SQSType (..), SMSType (..), newMsgStore)
import Network.URI (URI (..), URIAuth (..), parseAbsoluteURI)
import Simplex.Messaging.Protocol (mkNameOwner, NameOwner)
import Simplex.Messaging.Protocol (mkNameOwner)
import Simplex.Messaging.Server.Names (NamesConfig (..), RpcAuth (..), TldRegistries (..))
import Simplex.Messaging.Server.Names.Eth.RPC (fromHex)
import Simplex.Messaging.Server.QueueStore.Postgres.Config
import Simplex.Messaging.Server.StoreLog.ReadWrite (readQueueStore)
import Simplex.Messaging.Transport (supportedProxyClientSMPRelayVRange, alpnSupportedSMPHandshakes, supportedServerSMPRelayVRange)
@@ -807,11 +806,10 @@ readNamesConfig ini
| otherwise =
let rpcAuth_ = either (error . ("[NAMES] rpc_auth: " <>)) Just . parseRpcAuth =<< eitherToMaybe (lookupValue "NAMES" "rpc_auth" ini)
endpoint = requiredText "ethereum_endpoint"
registries = readTldRegistries
in Just
NamesConfig
{ ethereumEndpoint = either (error . ("[NAMES] ethereum_endpoint: " <>)) id (validateUrl endpoint rpcAuth_),
tldRegistries = registries,
tldRegistries = hardcodedTldRegistries,
rpcAuth = rpcAuth_,
rpcTimeoutMs = boundedIniInt 3000 100 60000 "rpc_timeout_ms",
rpcMaxResponseBytes = boundedIniInt 262144 1024 16777216 "rpc_max_response_bytes",
@@ -833,18 +831,22 @@ readNamesConfig ini
n | n >= floor_ && n <= ceiling_ -> n
| otherwise ->
error $ "[NAMES] " <> T.unpack key <> " must be in [" <> show floor_ <> ".." <> show ceiling_ <> "] (got " <> show n <> ")"
readTldRegistries =
let regs = TldRegistries
{ tldSimplex = optionalAddr "registry_tld_simplex",
tldTesting = optionalAddr "registry_tld_testing",
tldAll = optionalAddr "registry_tld_all"
}
in case (tldSimplex regs, tldTesting regs, tldAll regs) of
(Nothing, Nothing, Nothing) ->
error "[NAMES] at least one of registry_tld_simplex, registry_tld_testing, registry_tld_all is required"
_ -> regs
optionalAddr key =
either (error . (("[NAMES] " <> T.unpack key <> ": ") <>)) Just . parseEthAddr =<< eitherToMaybe (lookupValue "NAMES" key ini)
-- | Hardcoded SNRC contract whitelist. Placeholder addresses until the
-- launch contracts are deployed; replaced in code rather than INI so
-- operators can't accidentally point a names router at the wrong contract
-- during the bootstrap phase. The TldRegistries shape + lookup precedence
-- (TLD-specific then `tldAll` catch-all) is unchanged from the previous
-- INI-driven form.
hardcodedTldRegistries :: TldRegistries
hardcodedTldRegistries =
TldRegistries
{ tldSimplex = Just (placeholderAddr '\x11'),
tldTesting = Just (placeholderAddr '\x22'),
tldAll = Nothing
}
where
placeholderAddr c = either error id $ mkNameOwner (B.replicate 20 c)
-- | Validate the ethereum_endpoint URL:
-- * scheme must be http: or https:
@@ -913,15 +915,6 @@ validateUrl url auth_ = do
'0' : 'x' : rest -> all isHexDigit rest
lh -> not (null lh) && all isDigit lh
-- | Parse a 20-byte Ethereum address as text "0x[hex40]" or "[hex40]".
-- EIP-55 mixed-case checksum verification is a follow-up.
parseEthAddr :: Text -> Either String NameOwner
parseEthAddr t = do
bs <- fromHex (encodeUtf8 t)
if B.length bs == 20
then mkNameOwner bs
else Left "expected a 20-byte address (40 hex characters, optionally 0x-prefixed)"
-- | Parse an rpc_auth INI value. Scheme keyword is case-insensitive so
-- "Bearer <token>" / "BEARER <token>" (Caddy / RFC 7235 convention) work
-- as well as the lowercase form.
+3 -8
View File
@@ -168,14 +168,9 @@ iniFileContent cfgPath logPath opts host basicAuth controlPortPwds =
\# Central Reth via Caddy:\n\
\# ethereum_endpoint: https://eth.simplex.chat:443\n\
\# rpc_auth: basic <username>:<password>\n\
\# Per-TLD SNRC contract whitelist. At least one entry must be set.\n\
\# Each RSLV carries the contract address the client wants queried;\n\
\# the server only accepts it if it matches the address configured for\n\
\# that TLD (or registry_tld_all as catch-all for any unspecified TLD,\n\
\# including web domains).\n\
\# registry_tld_simplex: 0x<paste-your-contract-address>\n\
\# registry_tld_testing: 0x<paste-your-contract-address>\n\
\# registry_tld_all: 0x<paste-your-contract-address>\n\
\# The SNRC contract addresses are hardcoded in the server binary; each\n\
\# RSLV's contract field is verified against the binary's whitelist for\n\
\# the requested TLD. Operators do NOT configure registries here.\n\
\# rpc_timeout_ms: 3000\n\
\# rpc_max_response_bytes: 262144\n\
\# rpc_max_concurrency: 8\n\n\