test(lifecycle): probe electrumx with the protocol, not a bare socket
Both electrumx tests did socket.create_connection(("127.0.0.1", 50001)) and
called it proof the service was up. It is not: that is the HOST-published
port, and podman's port forwarder completes the TCP handshake even when
nothing inside the container is listening.
The cost was real. Both tests were green while mempool-api sat in a hard
ECONNREFUSED loop against electrumx:50001 from inside archy-net — the
operator's "mempool doesn't connect to bitcoin". Measured side by side:
127.0.0.1:50001 from the host OPEN, electrumx:50001 from a sibling container
REFUSED.
The cause is not a misconfiguration — mempool-api's CORE_RPC_HOST,
ELECTRUM_HOST and ports are all correct. electrumx has not finished its
initial sync (DB height 959,774 vs chain tip 961,706, ~1,932 blocks, 8d 14h
elapsed) and ElectrumX does not serve sessions until it has caught up.
Now both tests do a server.version round-trip and require a JSON result, so a
forwarder that accepts-and-drops fails. Verified against the live node: the
new probe FAILS today where the old one passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6c41852c56
commit
791e6e5ed9
@@ -53,11 +53,29 @@ teardown_file() {
|
||||
fi
|
||||
|
||||
# Same probe required-stack.bats uses — divergence flags a real regression.
|
||||
# It is a real Electrum round-trip, not a bare connect(): podman's port
|
||||
# forwarder accepts the TCP handshake on the host-published port even when
|
||||
# nothing inside the container is serving, which kept this test green for
|
||||
# days while mempool-api could not reach electrumx at all. See the longer
|
||||
# note in required-stack.bats.
|
||||
run python3 - <<'PY'
|
||||
import socket
|
||||
s = socket.create_connection(("127.0.0.1", 50001), 3)
|
||||
import json, socket
|
||||
s = socket.create_connection(("127.0.0.1", 50001), 5)
|
||||
s.settimeout(10)
|
||||
s.sendall((json.dumps({"id": 0, "method": "server.version",
|
||||
"params": ["archy-gate", "1.4"]}) + "\n").encode())
|
||||
buf = b""
|
||||
while b"\n" not in buf:
|
||||
chunk = s.recv(4096)
|
||||
if not chunk:
|
||||
raise SystemExit("electrumx closed the connection without replying "
|
||||
"— listening but not serving (still syncing?)")
|
||||
buf += chunk
|
||||
s.close()
|
||||
print("ok")
|
||||
resp = json.loads(buf.split(b"\n")[0])
|
||||
if "result" not in resp:
|
||||
raise SystemExit(f"electrumx returned no result: {resp}")
|
||||
print("ok", resp["result"])
|
||||
PY
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@@ -133,13 +133,34 @@ bitcoin_json() {
|
||||
fi
|
||||
}
|
||||
|
||||
@test "electrumx TCP port accepts connections" {
|
||||
@test "electrumx answers the Electrum protocol (not just an open socket)" {
|
||||
skip_if_not_installed electrumx
|
||||
# A bare connect() to the HOST-published port proves nothing: podman's port
|
||||
# forwarder accepts the TCP handshake even when nothing inside the container
|
||||
# is listening. On 2026-08-09 this test was green while mempool-api was in a
|
||||
# hard ECONNREFUSED loop against electrumx:50001 from inside archy-net —
|
||||
# electrumx was still doing its initial sync (DB height 959,774 vs chain tip
|
||||
# 961,706) and ElectrumX does not serve sessions until it has caught up.
|
||||
# So do a real protocol round-trip: the forwarder cannot fake a reply.
|
||||
run python3 - <<'PY'
|
||||
import socket
|
||||
s = socket.create_connection(("127.0.0.1", 50001), 3)
|
||||
import json, socket
|
||||
s = socket.create_connection(("127.0.0.1", 50001), 5)
|
||||
s.settimeout(10)
|
||||
req = json.dumps({"id": 0, "method": "server.version",
|
||||
"params": ["archy-gate", "1.4"]}) + "\n"
|
||||
s.sendall(req.encode())
|
||||
buf = b""
|
||||
while b"\n" not in buf:
|
||||
chunk = s.recv(4096)
|
||||
if not chunk:
|
||||
raise SystemExit("electrumx closed the connection without replying "
|
||||
"— it is listening but not serving (still syncing?)")
|
||||
buf += chunk
|
||||
s.close()
|
||||
print("ok")
|
||||
resp = json.loads(buf.split(b"\n")[0])
|
||||
if "result" not in resp:
|
||||
raise SystemExit(f"electrumx returned no result: {resp}")
|
||||
print("ok", resp["result"])
|
||||
PY
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user