mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
Read the wire version the network actually sends
luxfi/zap v1.2.6 — the version luxfi/node ships — emits wire version 2 by default, and Go's Parse accepts 1 and 2 alike. This layer accepted only 1, so every frame luxd sends was refused at the header. The module header already says the codec constants come from zap.wire "so they can never drift", and the canonical zap.wire had this right: VERSION1, VERSION2, and a parse that admits both. The drift was here — the guard was re-implemented locally against the single emitted VERSION instead of the accepted pair. Source both constants from the canonical wire and admit what it admits, so the claim in the header is true rather than aspirational. Add the vectors that would have caught it. The suite round-tripped this layer against itself; these are bytes captured from github.com/luxfi/zap v1.2.6 by calling EncodeNodeIDHandshake, so they fail if this layer and the Go module ever disagree. They also pin the accepted set exactly — 0 and 3 refused — because admitting a frame the network refuses is as wrong as refusing one it accepts, and pin that our handshake equals Go's in every byte but the version. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -26,7 +26,13 @@ from zap import wire as _zw
|
||||
# Codec constants are sourced from the canonical wire so they can never drift.
|
||||
ZAP_MAGIC = _zw.MAGIC # b"ZAP\x00"
|
||||
HEADER_SIZE = _zw.HEADER_SIZE # 16
|
||||
VERSION = _zw.VERSION # 1
|
||||
# Both accepted versions come from the canonical wire, so the set this layer
|
||||
# admits is the set ``zap.wire.parse`` admits — which is the set Go's
|
||||
# ``zap.Parse`` admits. luxd emits VERSION2 by default, so a reader that
|
||||
# recognises only VERSION1 cannot read the live network.
|
||||
VERSION1 = _zw.VERSION1 # 1 — legacy schema
|
||||
VERSION2 = _zw.VERSION2 # 2 — current; what luxd emits
|
||||
VERSION = _zw.VERSION # the version this layer emits
|
||||
ALIGNMENT = _zw.ALIGNMENT # 8
|
||||
MAX_MESSAGE_SIZE = 10 * 1024 * 1024 # 10 MB
|
||||
|
||||
@@ -76,8 +82,10 @@ class Message:
|
||||
if data[:4] != ZAP_MAGIC:
|
||||
raise ValueError(f"Bad ZAP magic: {data[:4]!r}")
|
||||
ver = struct.unpack_from("<H", data, 4)[0]
|
||||
if ver != VERSION:
|
||||
raise ValueError(f"Unsupported ZAP version: {ver} (expected {VERSION})")
|
||||
if ver not in (VERSION1, VERSION2):
|
||||
raise ValueError(
|
||||
f"Unsupported ZAP version: {ver} (expected {VERSION1} or {VERSION2})"
|
||||
)
|
||||
return cls(data)
|
||||
|
||||
@property
|
||||
|
||||
@@ -8,6 +8,8 @@ from hanzo_zap.wire import (
|
||||
ZAP_MAGIC,
|
||||
HEADER_SIZE,
|
||||
VERSION,
|
||||
VERSION1,
|
||||
VERSION2,
|
||||
MSG_TYPE_CLOUD,
|
||||
REQ_FLAG_REQ,
|
||||
REQ_FLAG_RESP,
|
||||
@@ -43,7 +45,11 @@ def test_zap_magic():
|
||||
|
||||
|
||||
def test_version():
|
||||
assert VERSION == 1
|
||||
assert VERSION1 == 1
|
||||
assert VERSION2 == 2
|
||||
# What this layer emits. Reading is wider than writing: see
|
||||
# test_reads_the_go_oracles_v2_handshake.
|
||||
assert VERSION == VERSION1
|
||||
|
||||
|
||||
def test_msg_type_cloud():
|
||||
@@ -338,3 +344,57 @@ async def test_full_message_through_frames():
|
||||
assert peer == "frame-test-node"
|
||||
finally:
|
||||
await cleanup()
|
||||
|
||||
|
||||
# ── Conformance to the Go oracle ─────────────────────────────────────────
|
||||
#
|
||||
# Go is the network. These are not hand-written vectors: they are the output
|
||||
# of github.com/luxfi/zap v1.2.6 — the version luxfi/node ships — captured by
|
||||
# calling zap.EncodeNodeIDHandshake. luxd's default builder emits VERSION2, so
|
||||
# a reader that admits only VERSION1 cannot read the live network at all.
|
||||
|
||||
GO_HANDSHAKE_V2 = bytes.fromhex(
|
||||
"5a41500002000000100000005000000068616e7a6f2d6e6f646500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000"
|
||||
)
|
||||
GO_HANDSHAKE_NODEID_V2 = bytes.fromhex(
|
||||
"5a4150000200000010000000500000004e6f646549442d415572454270666e6f595569436348465851646a3764415a363453396d534d5a73000000000000000000000000000000000000000028000000"
|
||||
)
|
||||
|
||||
|
||||
def test_reads_the_go_oracles_v2_handshake():
|
||||
"""The regression test for the break: luxd emits VERSION2."""
|
||||
for wire, want in (
|
||||
(GO_HANDSHAKE_V2, "hanzo-node"),
|
||||
(GO_HANDSHAKE_NODEID_V2, "NodeID-AUrEBpfnoYUiCcHFXQdj7dAZ64S9mSMZs"),
|
||||
):
|
||||
assert len(wire) == 80
|
||||
msg = Message.parse(wire)
|
||||
assert msg.version == VERSION2
|
||||
assert parse_handshake(msg) == want
|
||||
|
||||
|
||||
def test_version_acceptance_matches_go():
|
||||
"""Go's Parse accepts exactly {1, 2}. Not 0, not 3.
|
||||
|
||||
Accepting a frame the network refuses is as wrong as refusing one it
|
||||
accepts, so the bounds are exact rather than merely permissive.
|
||||
"""
|
||||
for version, accepted in ((0, False), (1, True), (2, True), (3, False)):
|
||||
wire = bytearray(GO_HANDSHAKE_V2)
|
||||
wire[4] = version
|
||||
if accepted:
|
||||
assert Message.parse(bytes(wire)).version == version
|
||||
else:
|
||||
with pytest.raises(ValueError):
|
||||
Message.parse(bytes(wire))
|
||||
|
||||
|
||||
def test_payload_is_byte_identical_to_go():
|
||||
"""Our handshake equals Go's in every byte but the version.
|
||||
|
||||
That is what makes flipping the emitter to VERSION2 a one-byte change
|
||||
rather than a re-encoding, and it proves the arena layout agrees.
|
||||
"""
|
||||
want = bytearray(GO_HANDSHAKE_V2)
|
||||
want[4] = VERSION1
|
||||
assert build_handshake("hanzo-node") == bytes(want)
|
||||
|
||||
Reference in New Issue
Block a user