extends SceneTree
## Layer 2 — Peer B: DHT Discoverer + Connector
##
## Matches JS test pattern: both sides wait for full round-trip.
## 1. join_dht → bootstrap into local testnet
## 2. lookup_topic → discover Peer A
## 3. Wait for dht_peer_found signal
## 4. connect_to_peer(remote_pk) → DHT handshake + holepunch
## 5. Wait for peer_connected
## 6. Exchange packets (A sends → B echoes → A receives → A sends "done" → B receives)
const TIMEOUT := 30.0
const STATUS_FILE := "/tmp/layer2_peer_b_status.txt"
const POLL_HZ := 0.05
var _peer: Object
var _done := false
var _connected := false
var _remote_pk := PackedByteArray()
var _connect_attempted := false
var _sent_reply := false
func _init() -> void:
_write_status("STARTING")
_start.call_deferred()
func _start() -> void:
# Read bootstrap
var f := FileAccess.open("/tmp/layer_bootstrap.txt", FileAccess.READ)
if f == null:
_fail("Cannot read bootstrap")
return
var bootstrap := PackedStringArray(f.get_as_text().strip_edges().split(","))
f.close()
print("[B] bootstrap: %s" % str(bootstrap))
# Single peer — DHT mode
_peer = ClassDB.instantiate(&"HyperswarmMultiplayerPeer")
var kp: Dictionary = _peer.generate_dht_keypair()
var pk: PackedByteArray = kp["public_key"]
var sk: PackedByteArray = kp["secret_key"]
print("[B] dht pk: %s" % pk.hex_encode().left(16))
# Connect signals
_peer.peer_connected.connect(func(id: int) -> void:
print("[B] peer_connected: id=%d" % id)
_connected = true
)
_peer.dht_peer_found.connect(func(found_pk: PackedByteArray) -> void:
print("[B] dht_peer_found: pk=%s" % found_pk.hex_encode().left(16))
if _remote_pk.is_empty():
_remote_pk = found_pk
)
# Join DHT
var err = _peer.join_dht(pk, sk, bootstrap)
if err != OK:
_fail("join_dht: %d" % err)
return
# Wait for DHT to become active
var elapsed := 0.0
while elapsed < 10.0:
_peer.poll()
if _peer.get_dht_status().get("active", false):
break
await create_timer(POLL_HZ).timeout
elapsed += POLL_HZ
if not _peer.get_dht_status().get("active", false):
_fail("DHT not active after 10s")
return
print("[B] DHT active")
# Lookup topic to discover Peer A
var topic := "test-p2p-layer2".sha256_buffer()
err = _peer.lookup_topic(topic)
if err != OK:
_fail("lookup_topic: %d" % err)
return
_write_status("DISCOVERING")
print("[B] looking up topic, waiting for peers...")
# Main poll loop — matches JS pattern: wait for full round-trip
elapsed = 0.0
while elapsed < TIMEOUT and not _done:
_peer.poll()
# Once we discover Peer A, connect to them
if not _remote_pk.is_empty() and not _connect_attempted:
_connect_attempted = true
print("[B] connecting to peer: %s" % _remote_pk.hex_encode().left(16))
err = _peer.connect_to_peer(_remote_pk)
if err != OK:
_fail("connect_to_peer: %d" % err)
return
_write_status("CONNECTING")
# Once connected, handle packet exchange
if _connected:
while _peer.get_available_packet_count() > 0:
var pkt: PackedByteArray = _peer.get_packet()
var text := pkt.get_string_from_utf8()
print("[B] received: '%s'" % text)
if text == "hello from A" and not _sent_reply:
# Echo back (JS pattern: server echoes)
_sent_reply = true
var msg := "hello from B".to_utf8_buffer()
_peer.set_target_peer(1)
_peer.set_transfer_mode(MultiplayerPeer.TRANSFER_MODE_RELIABLE)
_peer.put_packet(msg)
print("[B] sent 'hello from B'")
elif text == "done":
# A confirmed receipt — full round-trip complete
print("[B] received 'done' — full round-trip confirmed")
_write_status("PASS")
print("[B] SUCCESS — P2P packet exchange complete")
_done = true
break
await create_timer(POLL_HZ).timeout
elapsed += POLL_HZ
if not _done:
var diag := "remote_pk=%s connected=%s attempted=%s reply_sent=%s" % [
"found" if not _remote_pk.is_empty() else "none",
str(_connected), str(_connect_attempted), str(_sent_reply)]
_fail("Timeout after %.1fs (%s)" % [elapsed, diag])
else:
# Flush remaining then close
for i in range(10):
_peer.poll()
await create_timer(POLL_HZ).timeout
_peer.close()
quit()
func _write_file(path: String, content: String) -> void:
var wf := FileAccess.open(path, FileAccess.WRITE)
wf.store_string(content)
wf.close()
func _write_status(status: String) -> void:
_write_file(STATUS_FILE, status)
func _fail(msg: String) -> void:
print("[B] FAIL: %s" % msg)
_write_status("FAIL: %s" % msg)
if _peer:
_peer.close()
quit()