Skip to content

agent: ConnectTarget type for connection link or SimpleX name#1796

Open
shumvgolove wants to merge 14 commits into
sh/smp-namespacefrom
sh/namespace-agent
Open

agent: ConnectTarget type for connection link or SimpleX name#1796
shumvgolove wants to merge 14 commits into
sh/smp-namespacefrom
sh/namespace-agent

Conversation

@shumvgolove
Copy link
Copy Markdown
Collaborator

No description provided.

Comment thread src/Simplex/Messaging/Agent/Protocol.hs Outdated
Comment on lines +1610 to +1612
nameStart =
() <$ A.satisfy (\c -> c == '@' || c == '#')
<|> () <$ "simplex:/name"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nameStart =
() <$ A.satisfy (\c -> c == '@' || c == '#')
<|> () <$ "simplex:/name"
nameStart = "@" <|> "#" <|> "simplex:/name"

Adds `ConnectTarget = CTLink AConnectionLink | CTName SimplexNameInfo`
in Agent/Protocol.hs next to AConnectionLink. The StrEncoding parser
gates the CTName branch on a `@`/`#`/`simplex:/name` discriminator so
that bare tokens (which SimplexNameInfo accepts for Markdown's sake)
cannot ambiguously match at this layer. JSON is a plain string via
strToJEncoding, mirroring AConnectionLink. No consumers yet.
Three cosmetic cleanups flagged in code review:
- Drop the vestigial () placeholder on decodesSuccessfully; use a
  prefix call instead of operator style.
- Use Data.Either.isLeft instead of a local one-liner.
- Add a symmetric CTLink JSON assertion to pin both branches of the
  wire shape, not just CTName.
Stored as TEXT via decodeLatin1 . strEncode and decoded via
fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8 — the
established pattern for typed TEXT columns in this codebase
(see RcvSwitchStatus, SndSwitchStatus, RatchetSyncState at
Agent/Protocol.hs:614-647). Lets simplex-chat carry the type
directly in DB tuples without a per-call decode helper.
The instance landed in the previous commit alongside ToField but has
zero consumers in either repo — chat-side row decoders use the
soft-degradation `decodeSimplexName` helper at the tuple level, never
this instance. `fromTextField_` raises ConversionFailed on parse
failure, which doesn't compose with the chat policy.

Keep ToField (used by parameter binding in name lookups). Leave a
comment explaining why FromField is absent so a future contributor
doesn't reintroduce it without thinking about the decode policy.
Client.hs: proxyResolveName wraps proxySMPCommand for RSLV/NAME.
Agent/Client.hs: resolveName routes via sendOrProxySMPCommand; direct
path throws TENoServerAuth since SResolver has no direct client role.
Agent.hs: resolveSimplexName takes resolver SMPServer + SimplexNameDomain,
looks up the TLD contract (mirrors hardcodedTldRegistries server-side),
and forwards the RSLV. TLDWeb is intentionally unmapped.
@shumvgolove shumvgolove force-pushed the sh/namespace-agent branch from 5cf3853 to 90de60e Compare June 4, 2026 15:02
RSLV was previously rejected by the server unless forwarded via PFWD,
making the agent's direct fallback unreachable. Relax the server-side
guard so RSLV is accepted both forwarded (preferred - hides client IP
from the resolver) and direct (faster, exposes client IP). Mode choice
is delegated to the client and the operator network config.

- Server: drop the forwarded-only check on SResolver in
  verifyQueueTransmission.
- Protocol: give SResolver a client role (SRMessaging) so SMPClient can
  connect in the Resolver role. checkRole accepts this because RSLV
  clients have no service binding (falls through to True).
- Client: add directResolveName mirroring proxyResolveName via
  sendProtocolCommand with no auth and no entity (RSLV is noAuthCmd).
- Agent: wire the direct path through sendOrProxySMPCommand so the
  PFWD-or-direct selection works the same as other commands.
Convergence on the Python resolver shape (PR #1795 `snrc-resolve.py`) so
a names router can be backed either by the direct-ETH-RPC resolver or by
the Python REST resolver without changing the wire format clients see.

Wire-level changes:

- Add `nickname`, `website`, `location`, `simplex.contact`,
  `simplex.channel`, `ETH`, `BTC`, `XMR`, `DOT`, `resolver` (SNRC
  contract address that produced the record); all but `name`, `owner`,
  `resolver` are optional.
- Drop `displayName` (now `name`), `channelLinks`, `contactLinks`,
  `adminAddress`, `adminEmail`, `expiry`, `isTest`.
- The wire NameRecord no longer carries `expiry`; clients trust the
  server's filter. Expiry checking moves into `decodeGetRecord`, which
  now takes a `nowSec :: Int64` argument (the placeholder remains, but
  the field-layout-aware decoder will apply the filter once it lands).
- Testnet status is derived from the queried TLD (`TLDTesting` vs
  `TLDSimplex`) rather than an in-record flag.

Other:

- ToJSON/FromJSON are hand-rolled because Aeson TH doesn't accommodate
  dot-keys (`simplex.contact`) or uppercase coin keys (`ETH`/`BTC`...).
- `NameLink` newtype is removed (no longer used internally); per-field
  byte caps are applied directly in the FromJSON parser.
- Update the canonical-encoding spec in protocol/simplex-messaging.md.
Adds tests/RSLVTests.hs covering the RSLV pipeline against a running SMP
server with a stub ethCall injected via newNamesEnvWith. The production
decodeGetRecord is a placeholder (returns Right Nothing for any non-
malformed buffer), so the success path is marked pendingWith until the
SNRC ABI codec ships; everything else - direct vs PFWD acceptance,
contract mismatch, unknown TLD, backend NotFound, transport error
mapping, and rslvDisabled - exercises the wiring end-to-end.

Adds a minimal test seam: newEnvWithNames / runSMPServerBlockingWithNames
that accept a pre-built NamesEnv and skip the real pingEndpoint probe.
The production newEnv / runSMPServerBlocking delegate through with
namesOverride = Nothing, so behaviour is unchanged outside tests.

7 active tests pass, 1 pending. The existing 42 SMPNamesTests still pass.
The TLD->NameOwner placeholder mapping was duplicated literal-for-literal
between Agent.hs (tldNameContract) and Server/Main.hs (hardcodedTldRegistries).
Lock-step bumps risked silent divergence. Extract into
Simplex.Messaging.SimplexName.Contracts.tldContract; both agent and server
read from there. Server-side per-operator TLD config (TldRegistries record,
lookupTldAddress, NamesConfig.tldRegistries) is removed entirely - it was
already inert post-b66d9730 (which dropped the INI keys).
Replaces the placeholder Right Nothing return with a real ABI decoder for
the assumed Solidity signature:

  function getRecord(bytes32 node) external view returns (
    string x10 fields, address owner, uint256 expiry
  )

Layout: 12 head slots (10 string tail offsets, owner address, uint256
expiry) followed by length-prefixed string data in declaration order.

Server-side expiry filter (nowSec passed by Names.hs:fetch) keeps the wire
NameRecord free of an expiry field. The on-chain value 0 means "never
expires" (reserved names); the caller can pass nowSec = 0 to disable the
filter in tests. nrResolver is populated from the contract address the
server's eth_call was sent to, since the ABI return doesn't carry it.
Zero owner remains the NotFound sentinel.

Drops the placeholder-warn IORef plumbing that surfaced the stub in logs;
the decoder is no longer a stub. Tests that used (32 * 8) sentinel buffers
move to (32 * 12) to match the new head size. Adds an encodeRecordAbi
helper in SMPNamesTests for end-to-end testing; both RSLVTests and the
agent ResolveNameTests reuse it for the success-path tests.

If the SNRC contract ships with a different return layout, this decoder
will need rework; the placeholder gave a documented MVP unblock until
that point.
The hand-rolled NameRecord ToJSON instance produced alphabetical keys
(via Aeson's KeyMap canonicalisation), while the hand-rolled toEncoding
preserved spec declaration order. The two paths were independent code
and could drift on the field set as well, silently breaking the
"byte-identical canonical encoding" requirement on the wire path.

Top-level TH-splice in Protocol.hs is blocked by dense forward refs
between the NameRecord declaration (~line 780) and BrokerMsg's NAME
constructor (~line 880). Extract NameRecord and NameOwner into two new
self-contained modules that support TH cleanly, alongside the existing
Simplex.Messaging.SimplexName tree.

NameRecord's ToJSON is now TH-derived with a custom fieldLabelModifier
covering dot-keys (simplex.contact / simplex.channel) and uppercase coin
keys (ETH/BTC/XMR/DOT). omitNothingFields is set to False to preserve
the previous wire shape (absent optionals emitted as JSON `null`).
FromJSON stays hand-rolled to enforce per-field UTF-8 byte-length caps
that TH cannot express.

Note: the canonical Value-encoding path (J.encode . J.toJSON) still
re-emits keys alphabetically because Aeson's KeyMap is internally
sorted; only the wire path (J.encode -> toEncoding) preserves the spec
order, and only that path is part of the protocol contract. The new
"toJSON and toEncoding agree on the field set" test pins the
no-drift-on-field-set invariant for future edits.

Protocol.hs re-exports both types so downstream callers are unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants