Files
pezkuwi-wallet-utils/scripts/check_pezkuwi_integrity.py
T
Satoshi Qazi Muhammed 7f0bb6a3d3 fix(chains): drop node URLs that embed Nova's Infura keys
Nova's configs carry Nova's own Infura project keys inside node URLs, and
every sync copied them into ours: Ethereum had four keyed Infura nodes and
Base one, across 43 published files. Our Android build already fills
`{INFURA_API_KEY}` from our own secret; the keyed URLs used another
organisation's quota and would break for every user when Nova rotates them.

- sync_from_nova.py strips nodes whose URL embeds an Infura key, and drops a
  chain left with no node (only Ethereum - Goerli, a retired testnet, in the
  dev files); merge-chains.py applies the same filter
- the published chain files are rewritten with that filter; nothing else in
  them changes (each file round-trips byte for byte through json.dump)
- check_pezkuwi_integrity.py refuses a keyed Infura URL under chains/
2026-10-17 19:51:58 +04:00

136 lines
5.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.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 node URL ──────────────────────────────────
#
# Nova's configs shipped Nova's own Infura project keys inside node URLs, and
# every sync copied them into ours. Our builds fill {INFURA_API_KEY} from our
# own secret; a keyed URL is someone else's quota and dies when they rotate it.
grep = subprocess.run(
["grep", "-rlE", r"infura\.io/.*/v3/[0-9a-f]{32}", str(ROOT / "chains")],
capture_output=True,
text=True,
)
if grep.stdout.strip():
files = grep.stdout.strip().splitlines()
errors.append(
f"{len(files)} file(s) under chains/ embed an Infura key in a node URL; "
f"use {{INFURA_API_KEY}}, e.g. {Path(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.")