Files
node/test/accept.py
zeekayandHanzo Dev 95532567e1 zood v0.1.3: the history RPC luxd serves
cevm 1e8e42c answers what the Go archive answers:
- eth_getBlockReceipts, which the explorer's indexer reads;
- the uncle, count and by-index methods, headers and fee history;
- web3_sha3;
- Go's rules for eth_getLogs and eth_gasPrice;
- debug tracing with go-ethereum's callTracer, byte-equal to Go on all 811
  zoo-mainnet transactions, and off unless the C-Chain config's eth-apis
  names it.

The acceptance now also holds eth_getBlockReceipts to the transaction count
(811 on mainnet, 84 on testnet) and debug_* to off at the public door. All
three runs pass on AWS.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 23:46:58 -07:00

121 lines
4.5 KiB
Python

# Copyright (C) 2026, Zoo Labs Foundation. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause-Eco
#
# A Zoo network's history, asked for through the door a client uses.
#
# python3 accept.py NETWORK HOST:PORT [--alone] [VALIDATOR_LOG...]
#
# Waits for the node to reach the export's tip, then checks what luxfi/state's
# export and the Go archive say: the block hashes, the transactions, the chain
# id, and that the chain is Zoo's and not the C-Chain.
import json
import sys
import time
import urllib.error
import urllib.request
# What each network's export and its Go archive say.
NETWORKS = {
"mainnet": {
"number": "200200", "chain": "0x30e08", "tip": 799, "txs": 811,
"blocks": {
0: "0x7c548af47de27560779ccc67dda32a540944accc71dac3343da3b9cd18f14933",
400: "0xa8f981809d9f9f5d3f4e381b68af76f384a5d023a1b8f3f7b52726c14f242585",
799: "0xd6f92941bb2ac91dfd2443f32b0d93f2730e6cc81ddb2edd3f35720a9e1f72b8",
},
},
"testnet": {
"number": "200201", "chain": "0x30e09", "tip": 84, "txs": 84,
"blocks": {
0: "0x0652fb2fde1460544a5893e5eba5095ff566861cbc87fcb1c73be2b81d6d1979",
42: "0x8aa48eac58059ddd70133c507fbe2c9573ece034f255cba087394a78706cdf81",
84: "0x7077c4e52ace2a56dd1189b2aaf91ba9ec338d86c5b4530b0ac98a8ff6d8ba0c",
},
},
}
args = sys.argv[1:]
NET = NETWORKS[args.pop(0)]
HOST = args.pop(0)
ALONE = "--alone" in args # a committee of one: it must refuse transactions
LOGS = [a for a in args if a != "--alone"]
TIP = NET["tip"]
WANT = NET["blocks"]
TXS = NET["txs"]
failed = 0
def check(ok, what):
global failed
print((" ok " if ok else " FAIL ") + what, flush=True)
if not ok:
failed += 1
def call(path, method, params):
body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode()
req = urllib.request.Request(f"http://{HOST}{path}", data=body,
headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=10) as r:
return r.status, json.load(r)
except urllib.error.HTTPError as e:
return e.code, None
rpc = "/v1/chain/zoo/rpc"
deadline = time.time() + 600
while True:
try:
status, body = call(rpc, "eth_blockNumber", [])
if status == 200 and body.get("result") == hex(TIP):
break
except OSError:
pass
for log in LOGS:
with open(log, errors="replace") as f:
if "cannot start" in f.read():
sys.exit(f"a validator did not start: {log}")
if time.time() > deadline:
sys.exit(f"zood did not reach block {TIP}")
time.sleep(2)
print(f"zood at block {TIP} on {HOST}")
status, body = call(rpc, "eth_chainId", [])
check(status == 200 and body.get("result") == NET["chain"], f"eth_chainId is {NET['chain']}")
for n, h in WANT.items():
status, body = call(rpc, "eth_getBlockByNumber", [hex(n), False])
got = (body or {}).get("result") or {}
check(got.get("hash") == h, f"block {n} is {h[:12]}… (got {str(got.get('hash'))[:12]}…)")
txs = 0
receipts = 0
for n in range(1, TIP + 1):
_, body = call(rpc, "eth_getBlockByNumber", [hex(n), False])
txs += len(((body or {}).get("result") or {}).get("transactions") or [])
_, body = call(rpc, "eth_getBlockReceipts", [hex(n)])
receipts += len((body or {}).get("result") or [])
check(txs == TXS, f"blocks 1..{TIP} carry {TXS} transactions (got {txs})")
check(receipts == TXS, f"and eth_getBlockReceipts answers {TXS} receipts for them (got {receipts})")
status, body = call(f"/v1/chain/{NET['number']}/rpc", "eth_chainId", [])
check(status == 200 and body.get("result") == NET["chain"],
f"/v1/chain/{NET['number']} is the same chain")
status, _ = call("/v1/chain/c/rpc", "eth_chainId", [])
check(status == 404, f"/v1/chain/c is not Zoo's (got {status})")
_, body = call(rpc, "admin_importChain", ["/nonexistent"])
check(((body or {}).get("error") or {}).get("code") == -32601, "admin_* is off")
_, body = call(rpc, "debug_traceTransaction", ["0x" + "00" * 32, {"tracer": "callTracer"}])
check(((body or {}).get("error") or {}).get("code") == -32601, "debug_* is off")
if ALONE:
# A committee of one certifies nothing, so it takes nothing it cannot land.
_, body = call(rpc, "eth_sendRawTransaction", ["0x00"])
msg = (((body or {}).get("error") or {}).get("message") or "")
check("accepts no transactions" in msg, "a transaction is refused, with the reason")
print("PASS" if failed == 0 else "FAIL", flush=True)
sys.exit(1 if failed else 0)