mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-utils.git
synced 2026-11-05 16:51:07 +00:00
light-client/zagros.json and light-client/people-zagros.json are what the wallet's embedded light client starts from. The full specifications carry the raw genesis storage, about 3 MB each. These carry only the genesis state root, which identifies the chain: - The relay specification adds a checkpoint (lightSyncState) exported by the relay node through sync_state_genSyncSpec. It is 7.3 KB. - People has no checkpoint, because the relay chain's finality covers it. It is 732 bytes. Boot nodes put the /wss addresses on 443 first and keep the plain TCP ones. scripts/light_client_specs.py writes both files. It checks every state root by rebuilding the genesis header and comparing its hash with the chain's genesis hash. A root with one byte changed is refused. The checkpoint is a trust anchor: a client that starts from a false one can follow a false chain. It reaches the wallet inside the APK or through signed/targets.json, never from peers. Targets are re-signed (version 3, 689 files). The secret scan lets through the four public chain-data keys these files carry: stateRootHash, finalizedBlockHeader, babeEpochChanges and grandpaAuthoritySet.
113 lines
4.6 KiB
Python
Executable File
113 lines
4.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Chain specifications for the wallet's light client, written to light-client/.
|
|
|
|
The full specifications carry the raw genesis storage, about 3 MB each. The light client needs
|
|
none of it: the genesis state root identifies the chain, and the relay chain starts from a
|
|
checkpoint (lightSyncState) that the relay node exports with sync_state_genSyncSpec. What is
|
|
written here is that trimmed form, with the boot nodes below.
|
|
|
|
The checkpoint is a trust anchor: a client that starts from a false one can follow a false
|
|
chain. It is taken from our own relay node and reaches the wallet only inside the APK or through
|
|
signed/targets.json, never from peers. Every state root is checked against its chain's genesis
|
|
hash before anything is written.
|
|
|
|
light_client_specs.py --relay-rpc URL --people-rpc URL --people-spec FILE
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import sys
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
OUT = REPO / "light-client"
|
|
|
|
# Secure WebSockets on 443 first: they look like any HTTPS WebSocket where plain libp2p TCP is
|
|
# filtered. The TCP addresses stay for networks that filter nothing.
|
|
ZAGROS_BOOT_NODES = [
|
|
"/dns/zagros-bootnode-0.pezkuwichain.io/tcp/443/wss/p2p/12D3KooWHKMr39ubvsigZXdVJzr7nnMp5YTzMywKuTzPDo3Xf1tZ",
|
|
"/dns/zagros-bootnode-1.pezkuwichain.io/tcp/443/wss/p2p/12D3KooWH6tzPuoFeyf8n6gNmus9Q3CdEGEVUvbduUnPTqJcLRLC",
|
|
"/dns/zagros-bootnode-0.pezkuwichain.io/tcp/30333/p2p/12D3KooWHKMr39ubvsigZXdVJzr7nnMp5YTzMywKuTzPDo3Xf1tZ",
|
|
"/dns/zagros-bootnode-1.pezkuwichain.io/tcp/30333/p2p/12D3KooWH6tzPuoFeyf8n6gNmus9Q3CdEGEVUvbduUnPTqJcLRLC",
|
|
]
|
|
PEOPLE_ZAGROS_BOOT_NODES = [
|
|
"/dns/zagros-people-bootnode.pezkuwichain.io/tcp/443/wss/p2p/12D3KooWLnDS5p5bvMzfco2BR4arKRbtpqoZwRvLKgCdzgVpuEQG",
|
|
"/ip4/217.77.6.126/tcp/30535/p2p/12D3KooWLnDS5p5bvMzfco2BR4arKRbtpqoZwRvLKgCdzgVpuEQG",
|
|
"/ip4/164.68.110.49/tcp/30535/p2p/12D3KooW9rHfLv4za95C4fPGTUzNk531zRrjaXYJY3pijMEYd3xB",
|
|
]
|
|
|
|
|
|
def rpc(url: str, method: str, params: list):
|
|
request = urllib.request.Request(
|
|
url,
|
|
json.dumps(
|
|
{"jsonrpc": "2.0", "id": 1, "method": method, "params": params}
|
|
).encode(),
|
|
{"Content-Type": "application/json"},
|
|
)
|
|
with urllib.request.urlopen(request, timeout=120) as response:
|
|
reply = json.load(response)
|
|
if "error" in reply:
|
|
sys.exit(f"{url} {method}: {reply['error']}")
|
|
return reply["result"]
|
|
|
|
|
|
def genesis_state_root(url: str) -> str:
|
|
"""The genesis state root, checked by rebuilding the genesis header and hashing it."""
|
|
genesis_hash = rpc(url, "chain_getBlockHash", [0])
|
|
header = rpc(url, "chain_getHeader", [genesis_hash])
|
|
if header["digest"]["logs"]:
|
|
sys.exit(f"{url}: genesis header has digest logs; this check assumes none")
|
|
encoded = (
|
|
bytes(32) # parent hash
|
|
+ b"\x00" # compact block number 0
|
|
+ bytes.fromhex(header["stateRoot"][2:])
|
|
+ bytes.fromhex(header["extrinsicsRoot"][2:])
|
|
+ b"\x00" # empty digest
|
|
)
|
|
rebuilt = "0x" + hashlib.blake2b(encoded, digest_size=32).hexdigest()
|
|
if rebuilt != genesis_hash:
|
|
sys.exit(f"{url}: rebuilt genesis hash {rebuilt} is not {genesis_hash}")
|
|
return header["stateRoot"]
|
|
|
|
|
|
def write(name: str, spec: dict) -> None:
|
|
path = OUT / name
|
|
path.write_text(json.dumps(spec, indent=2) + "\n")
|
|
print(f"{path.relative_to(REPO)}: {path.stat().st_size} bytes")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
parser.add_argument("--relay-rpc", required=True)
|
|
parser.add_argument("--people-rpc", required=True)
|
|
parser.add_argument(
|
|
"--people-spec", required=True, help="full People chain specification"
|
|
)
|
|
args = parser.parse_args()
|
|
OUT.mkdir(exist_ok=True)
|
|
|
|
relay = rpc(args.relay_rpc, "sync_state_genSyncSpec", [True])
|
|
if not relay.get("lightSyncState"):
|
|
sys.exit(f"{args.relay_rpc}: the exported specification has no checkpoint")
|
|
relay["genesis"] = {"stateRootHash": genesis_state_root(args.relay_rpc)}
|
|
relay["bootNodes"] = ZAGROS_BOOT_NODES
|
|
|
|
# Teyrchains have no checkpoint: the relay chain's finality covers them.
|
|
people = json.loads(Path(args.people_spec).read_text())
|
|
if people.get("relay_chain") != relay["id"]:
|
|
sys.exit(
|
|
f"People names relay chain {people.get('relay_chain')!r}, not {relay['id']!r}"
|
|
)
|
|
people["genesis"] = {"stateRootHash": genesis_state_root(args.people_rpc)}
|
|
people["bootNodes"] = PEOPLE_ZAGROS_BOOT_NODES
|
|
|
|
write("zagros.json", relay)
|
|
write("people-zagros.json", people)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|