mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-09-12 00:26:38 +00:00
fix resolver v2 for subnames (#1867)
* fix resolver v2 for subnames * simplify doc
This commit is contained in:
@@ -155,6 +155,9 @@ Error bodies carry `name` and a fixed `error` code to branch on. Only
|
||||
label, so a hashed query cannot be answered with a name. See
|
||||
[Querying by labelhash](#querying-by-labelhash).
|
||||
|
||||
A subname reports the expiry and grace of the 2LD above it, since that is what
|
||||
bounds its lifetime. A subname nobody created reports as not registered.
|
||||
|
||||
### v1: `/resolve/<name>`
|
||||
|
||||
What routers before SMP v22 call. Its shape is unrelated to v2's: the record is
|
||||
@@ -213,7 +216,7 @@ expiry is what separates *never registered* from *registered and since
|
||||
released*.
|
||||
|
||||
A subname reports the status of the 2LD above it, which is only as good as the
|
||||
name it sits under.
|
||||
name it sits under. A subname nobody created answers 404 `unregistered`.
|
||||
|
||||
#### v1 errors
|
||||
|
||||
|
||||
@@ -722,13 +722,23 @@ def registration(name: str):
|
||||
# hashed query the registrar cannot name is refused rather than answered
|
||||
if rec["name"] is None:
|
||||
return 502, {"name": name, "error": "labelNotRecorded"}
|
||||
return 200, {
|
||||
"type": "registered",
|
||||
"expires": reg["expires"],
|
||||
"graceUntil": reg["graceEnds"],
|
||||
"reservedReason_": reg["reasonCode"],
|
||||
"nameRecord": rec,
|
||||
}
|
||||
# a subname inherits the 2LD's status, so only its node's owner says
|
||||
# whether anyone created it
|
||||
if len(name.split(".")) > 2 and rec["owner"] == ZERO_ADDR:
|
||||
# name_status reads pricing only when the name was already
|
||||
# unregistered, so read it here
|
||||
status = "unregistered"
|
||||
pricing = pricing_params(tld)
|
||||
if pricing:
|
||||
reg.update({k: v for k, v in pricing.items() if not k.startswith("_")})
|
||||
else:
|
||||
return 200, {
|
||||
"type": "registered",
|
||||
"expires": reg["expires"],
|
||||
"graceUntil": reg["graceEnds"],
|
||||
"reservedReason_": reg["reasonCode"],
|
||||
"nameRecord": rec,
|
||||
}
|
||||
if reg["reasonCode"]:
|
||||
return 200, {"type": "reserved", "reservedReason": reg["reasonCode"]}
|
||||
if status in ("unregistered", "expired"):
|
||||
@@ -781,8 +791,16 @@ def resolve(name: str):
|
||||
if resolver_addr == ZERO_ADDR:
|
||||
# A registered name always resolves: with no resolver set the record is
|
||||
# still returned with every field unset, so "taken until <date>" stays
|
||||
# answerable.
|
||||
# answerable. For a subname, no owner means nobody created it.
|
||||
owner = decode_address(eth_call(registry, selector("owner(bytes32)") + node_hex))
|
||||
if len(name.split(".")) > 2 and owner == ZERO_ADDR:
|
||||
return 404, {
|
||||
"name": name,
|
||||
**reg,
|
||||
"status": "unregistered",
|
||||
"error": "unregistered",
|
||||
"message": "this subname has never been created",
|
||||
}
|
||||
return 200, {
|
||||
"name": canonical_name(name) or name,
|
||||
"nickname": "",
|
||||
|
||||
@@ -834,11 +834,13 @@ class RegistrationV2Tests(unittest.TestCase):
|
||||
return ("0x" + snrc.encode_uint(0x20) + snrc.encode_uint(len(value))
|
||||
+ (value + b"\x00" * pad).hex())
|
||||
|
||||
def _chain(self, expires, reserved=0, oracle=None, label=b"acme"):
|
||||
def _chain(self, expires, reserved=0, oracle=None, label=b"acme", owner=None):
|
||||
"""The registry answers a zero resolver, so name_record returns the
|
||||
empty record a registered name still has. `label` is what the registrar
|
||||
recorded for the 2LD; b"" means it recorded none."""
|
||||
recorded for the 2LD; b"" means it recorded none. `owner` is the owner of
|
||||
the queried node; ZERO_ADDR means that node was never created."""
|
||||
oracle = self.ORACLE if oracle is None else oracle
|
||||
owner = self.OWNER if owner is None else owner
|
||||
|
||||
def eth_call(to, data):
|
||||
if data.startswith(snrc.selector("labelOf(uint256)")):
|
||||
@@ -858,7 +860,7 @@ class RegistrationV2Tests(unittest.TestCase):
|
||||
if data.startswith(snrc.selector("resolver(bytes32)")):
|
||||
return "0x" + snrc.encode_uint(0)
|
||||
if data.startswith(snrc.selector("owner(bytes32)")):
|
||||
return "0x" + snrc.encode_uint(int(self.OWNER, 16))
|
||||
return "0x" + snrc.encode_uint(int(owner, 16))
|
||||
return self.fail("unexpected call " + data[:10])
|
||||
|
||||
return eth_call
|
||||
@@ -971,6 +973,41 @@ class RegistrationV2Tests(unittest.TestCase):
|
||||
status, body = snrc.registration(hashed + ".testing")
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(body["nameRecord"]["name"], "acme.testing")
|
||||
def test_a_subname_that_exists_is_registered_with_its_parents_dates(self):
|
||||
expires = self.now + 3600
|
||||
snrc.eth_call = self._chain(expires)
|
||||
status, body = snrc.registration("sub.acme.testing")
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(body["type"], "registered")
|
||||
self.assertEqual(body["expires"], expires)
|
||||
self.assertEqual(body["nameRecord"]["name"], "sub.acme.testing")
|
||||
|
||||
def test_a_subname_nobody_created_is_not_registered(self):
|
||||
"""The registrar only tracks 2LDs, so the parent's registration says
|
||||
nothing about a child that was never created: its node has no owner."""
|
||||
snrc.eth_call = self._chain(self.now + 3600, owner=snrc.ZERO_ADDR)
|
||||
status, body = snrc.registration("sub.acme.testing")
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(body["type"], "available")
|
||||
|
||||
def test_a_2ld_is_not_subject_to_the_owner_check(self):
|
||||
"""Only a subname can be absent under a registered parent."""
|
||||
snrc.eth_call = self._chain(self.now + 3600, owner=snrc.ZERO_ADDR)
|
||||
_, body = snrc.registration("acme.testing")
|
||||
self.assertEqual(body["type"], "registered")
|
||||
def test_v1_does_not_report_an_uncreated_subname_as_registered(self):
|
||||
"""v1 has no availability, so the only honest answer is not-found. The
|
||||
2LD case is untouched: a registered name with no resolver still resolves."""
|
||||
snrc.eth_call = self._chain(self.now + 3600, owner=snrc.ZERO_ADDR)
|
||||
status, body = snrc.resolve("sub.acme.testing")
|
||||
self.assertEqual(status, 404)
|
||||
self.assertEqual(body["error"], "unregistered")
|
||||
|
||||
def test_v1_still_resolves_a_2ld_with_no_resolver_set(self):
|
||||
snrc.eth_call = self._chain(self.now + 3600, owner=snrc.ZERO_ADDR)
|
||||
status, body = snrc.resolve("acme.testing")
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(body["resolver"], snrc.ZERO_ADDR)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user