Files
pezkuwi-wallet-utils/scripts/check_pezkuwi_integrity.py
T
Satoshi Qazi Muhammed b5f5af2cce fix(chains): strip every keyed provider URL, and make the gate fail closed
The first pass matched only `infura.io/.../v3/<lowercase hex>`, so it missed
`https://...infura.io/v3/<key>`, upper-case keys and hosts, and Nova's
OnFinality `?apikey=` URLs (Moonriver, and the Hades testnet in v20/v21
preConfigured). preConfigured was copied from Nova without the filter, and the
integrity check passed if grep itself failed.

- one predicate, is_keyed(), in sync_from_nova.py: Infura /v3/ ids and
  apikey/api_key/key/token query values, case-insensitive; {PLACEHOLDER}
  values are allowed
- preConfigured details are filtered after the copy; a chain left with no
  node loses its details file and its index entry (Hades, v20/v21 only)
- rewritten files keep their own indent, so the diff is the data change only
- check 5 imports is_keyed() and checks every string in every JSON file
  under chains/; an unreadable file or an empty tree is an error
- check 3 reports a grep error instead of passing on it
2026-10-17 19:58:49 +04:00

158 lines
6.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Refuse the five regressions this repo has already had.
Each check below exists because the thing it forbids happened, was fixed, and in two
cases came back. They are cheap; the failures they catch are not.
Run from the repository root. Exits non-zero on the first category that fails, after
reporting every problem it found.
"""
import json
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
APP_CHAINS = ROOT / "chains" / "v22" / "android" / "chains.json"
AH = "Pezkuwi Asset Hub"
errors: list[str] = []
def load(path: Path):
with path.open(encoding="utf-8") as fh:
return json.load(fh)
def asset_hub_assets(doc):
for chain in doc:
if chain.get("name") == AH:
yield from chain.get("assets", [])
def extras(asset):
return asset.get("typeExtras") or {}
# ── 1. Only assets that exist on chain may be declared ───────────────────────
#
# 1001/1002/1003 were declared as DOT/ETH/BTC with icons pointing at Nova's own repo —
# copied from a template, never created on Pezkuwi Asset Hub. Queried 2026-08-11 against
# wss://asset-hub-rpc.pezkuwichain.io, assets.asset() returns None for all three; only
# 1 (PEZ) and 1000 (wUSDT) are Live. Removed twice, back twice.
GHOST = {"1001", "1002", "1003"}
for name in ("chains/chains.json",
"chains/v22/android/chains.json",
"chains/v22/android/chains_minimal.json"):
path = ROOT / name
if not path.exists():
continue
bad = [a.get("symbol") for a in asset_hub_assets(load(path))
if str(extras(a).get("assetId", "")) in GHOST]
if bad:
errors.append(
f"{name}: declares Asset Hub assets that do not exist on chain: {bad}"
)
# ── 2. Sufficient assets must say so ─────────────────────────────────────────
#
# Absent, LocalToDomainChainMapper resolves isSufficient to false, the wallet's
# DeadRecipientValidation runs at ERROR level, and every transfer to an account holding
# no HEZ is refused — which is every fresh deposit address, so nobody can fund an
# exchange account. Both assets are sufficient=true on chain.
if APP_CHAINS.exists():
declared = {
str(extras(a).get("assetId"))
for a in asset_hub_assets(load(APP_CHAINS))
if extras(a).get("isSufficient") is True
}
missing = {"1", "1000"} - declared
if missing:
errors.append(
f"{APP_CHAINS.name}: Asset Hub assets {sorted(missing)} must declare "
f"isSufficient: true — the chain says they are sufficient"
)
# ── 3. Assets are served from a branch that is actually served ───────────────
#
# 252 icon references once pointed at pending/post-fix-release. Every file they named
# also existed on master, so the dependency bought nothing — and would have broken
# silently the day that branch was tidied away.
grep = subprocess.run(
[
"grep",
"-rlE",
r"pezkuwi-wallet-utils/(pending|feature|fix|test)/",
str(ROOT / "chains"),
],
capture_output=True,
text=True,
)
if grep.returncode not in (0, 1):
errors.append(f"grep for working-branch asset URLs failed: {grep.stderr.strip()}")
if grep.stdout.strip():
files = grep.stdout.strip().splitlines()
errors.append(
f"{len(files)} file(s) under chains/ reference a working branch for "
f"assets; use master, e.g. {Path(files[0]).name}"
)
# ── 4. A published fixture matches its source ────────────────────────────────
#
# sync_from_nova.py does not copy tests/, so these two are kept in step by hand and
# nothing noticed if they drifted.
for fixture in ("pezkuwi_assets_for_testBalance.json",):
src = ROOT / "pezkuwi-overlay" / "tests" / fixture
pub = ROOT / "tests" / fixture
if src.exists() and pub.exists() and src.read_bytes() != pub.read_bytes():
errors.append(
f"tests/{fixture} has drifted from pezkuwi-overlay/tests/{fixture}"
)
# ── 5. No third-party API key in a URL ───────────────────────────────────────
#
# Nova's configs shipped Nova's own provider keys (Infura project ids, OnFinality
# apikeys) inside node URLs, and every sync copied them into ours. Our builds fill
# {INFURA_API_KEY}-style placeholders from our own secrets; a keyed URL is someone
# else's quota and dies when they rotate it. The predicate is the one the sync
# uses, and every string in every JSON file is checked, so a file that does not
# parse is a failure, not a pass.
sys.path.insert(0, str(ROOT / "scripts"))
from sync_from_nova import is_keyed # noqa: E402
keyed_files = []
json_files = sorted(p for p in (ROOT / "chains").rglob("*.json"))
if not json_files:
errors.append("chains/ has no JSON files; the key check would have checked nothing")
for path in json_files:
try:
doc = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError) as exc:
errors.append(f"{path.relative_to(ROOT)} could not be read for the key check: {exc}")
continue
stack = [doc]
while stack:
node = stack.pop()
if isinstance(node, dict):
stack.extend(node.values())
elif isinstance(node, list):
stack.extend(node)
elif isinstance(node, str) and is_keyed(node):
keyed_files.append(path)
break
if keyed_files:
errors.append(
f"{len(keyed_files)} file(s) under chains/ embed a provider key in a URL; "
f"use a {{PLACEHOLDER}}, e.g. {keyed_files[0].relative_to(ROOT)}"
)
if errors:
for e in errors:
print(f"::error::{e}")
print(f"\n{len(errors)} integrity problem(s).", file=sys.stderr)
sys.exit(1)
print("Pezkuwi integrity: on-chain assets only, sufficiency declared, "
"assets served from master, fixture in step with its source, "
"no third-party key in a node URL.")