#!/usr/bin/env python3 """Mainnet miners: copies of eddycoin_wallets_v2_kits/_src miner files (src/) -> patched build sources (winbuild/). Counted exact replacements. Denylist regenerated from old_key_denylist.json (env EDDY_DENYLIST; old test-network addresses).""" import hashlib, json, os, shutil, sys H = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SRC, OUT = os.path.join(H, "src"), os.path.join(H, "winbuild") DENY = os.environ.get("EDDY_DENYLIST", "old_key_denylist.json") os.makedirs(OUT, exist_ok=True) def rep(t, old, new, n=1, name=""): c = t.count(old) assert c == n, "%s: expected %d x %r, found %d" % (name, n, old[:70], c) return t.replace(old, new) # ---------- eddycoin_old_keys.py (regenerated, mainnet only) ---------- d = json.load(open(DENY)) pk = sorted(set(d["pubkey_sha256"])); ad = sorted(set(d["addresses"])); sup = sorted(set(d.get("superseded_v2_addresses", []))) assert set(sup) <= set(ad) assert all(len(x) == 64 and all(c in "0123456789abcdef" for c in x) for x in pk) assert all(len(x) == 45 and x.startswith("eddy1") for x in ad) open(os.path.join(OUT, "eddycoin_old_keys.py"), "w").write( '''# -*- coding: utf-8 -*- """OLD Eddycoin testnet keys (generated from old_key_denylist.json, version %s, %d pubkey hashes, %d addresses). Public data only. MAINNET miner build: these addresses are never accepted as payout address.""" import hashlib PUBKEY_SHA256 = frozenset(%r) ADDRESSES = frozenset(%r) # v2 friend wallets without recovery words, superseded by the v3 wallets (subset of ADDRESSES) SUPERSEDED = frozenset(%r) MSG_OLD = "%%s is an OLD Eddycoin testnet address. It cannot be used on the new Eddycoin mainnet - enter the address of your NEW wallet." MSG_SUPERSEDED = "%%s: This is a superseded wallet without recovery words - use your new wallet." def refusal(address): """user-facing text why this payout address is refused, or None if it is allowed""" a = str(address or "").strip().lower() if a in SUPERSEDED: return MSG_SUPERSEDED %% a if a in ADDRESSES: return MSG_OLD %% a return None def is_mainnet(network): return True # this build only ever mines on the new mainnet def is_old(address="", pubkey_hex=""): if address and str(address).strip().lower() in ADDRESSES: return True try: return bool(pubkey_hex) and hashlib.sha256(bytes.fromhex(pubkey_hex)).hexdigest() in PUBKEY_SHA256 except ValueError: return False ''' % (d.get("version"), len(pk), len(ad), pk, ad, sup)) # ---------- eddycoin_chain.py: exact network id, never a testnet id ---------- t = open(os.path.join(SRC, "eddycoin_chain.py")).read() t = rep(t, ' c["active"] = bool(c["server"]) and c["chain"] != PENDING["chain"]\n', ' # mainnet build: a testnet id (eddycoin-testnet*) or an empty id can never activate the miners\n' ' c["active"] = bool(c["server"]) and bool(c["chain"]) and c["chain"] != PENDING["chain"] and not c["chain"].lower().startswith("eddycoin-testnet")\n', name="chain") t = rep(t, '''def network_ok(name): """a server/node answer belongs to the configured chain""" return str(name or "") == NETWORK if ACTIVE else False''', '''def network_ok(name): """a server/node answer belongs to the configured chain: EXACT match with the network id in eddycoin_chain.json (no prefix match, so no eddycoin-testnet* server is ever accepted)""" return ACTIVE and isinstance(name, str) and name == NETWORK''', name="chain") t = rep(t, "NETWORK = CHAIN[\"chain\"]\n", "NETWORK = CHAIN[\"chain\"]\n# mainnet chain id (GENESIS_DESIGN.md): block headers start with EDDY1--BLOCK (replay protection vs the old testnet's EDDY--BLOCK)\nCHAIN_ID = \"EDDY1\"\n", name="chain") open(os.path.join(OUT, "eddycoin_chain.py"), "w").write(t) GATE = ''' def network_gate(srv, out, stop_ev, ok_set): """Mainnet build: before any work is requested from a server it must report EXACTLY the network id from eddycoin_chain.json in /api/state. Anything else (old testnet, other chains, no answer) is refused.""" if srv.cur in ok_set: return True r = api(srv.cur, "/api/state", timeout=10) net = r.get("network") if isinstance(r, dict) else None if _CH.network_ok(net) and isinstance(r.get("tips"), dict): ok_set.add(srv.cur); out(" network check OK: %s is on %s" % (srv.cur, NETWORK)); return True why = ("network '%s'" % net) if net else str(r.get("error") or r.get("reason") or "no Eddycoin answer")[:100] out(" REFUSED %s: %s - this miner only mines on the network '%s'. Retrying in 30 s." % (srv.cur, why, NETWORK)) if len(srv.servers) > 1: srv.i = (srv.i + 1) % len(srv.servers); srv.st["server"] = srv.cur stop_ev.wait(30) return False def mainnet_refusal(a): """reason why this miner must not start, or None""" if not _CH.ACTIVE: return "The new Eddycoin chain is not live yet (eddycoin_chain.json: %s). Nothing is mined and no server is contacted until it is activated." % _CH.NETWORK if _OLD.is_old(a.wallet or ""): return _OLD.refusal(a.wallet) or "refused payout address" return None ''' for fn, chain_word in (("eddycoin_echain_cpu_miner.py", "E-Chain CPU"), ("eddycoin_dchain_gpu_miner.py", "D-Chain GPU")): t = open(os.path.join(SRC, fn)).read() t = rep(t, "import eddycoin_chain as _CH # single switch: eddycoin_chain.json\n", "import eddycoin_chain as _CH # single switch: eddycoin_chain.json\nimport eddycoin_old_keys as _OLD # old testnet addresses (refused as payout)\n", name=fn) t = rep(t, '"user-agent": "eddycoin-miner/2 (test coin)"', '"user-agent": "eddycoin-miner/mainnet-1"', name=fn) t = rep(t, 'HTTP_WARNING = "plain http:// is unencrypted - fine for a test-coin node, but others on the network could read or change the traffic"', 'HTTP_WARNING = "plain http:// is unencrypted - fine for your own PC or home network, but others on the network could read or change the traffic"', name=fn) # gate functions after probe_server t = rep(t, "\n\nclass ServerList:", GATE + "\n\nclass ServerList:", name=fn) # CLI: refuse old payout address t = rep(t, ' a.server = a.servers[0]\n if not is_addr(a.wallet or ""):', ' a.server = a.servers[0]\n if is_addr(a.wallet or "") and _OLD.is_old(a.wallet):\n' ' sys.exit(_OLD.refusal(a.wallet) or "refused payout address")\n' ' if not is_addr(a.wallet or ""):', name=fn) t = rep(t, "real double-SHA-256, test coin only (no real money)\" % (srv.cur" if "cpu" in fn else "real OpenCL double-SHA-256, test coin only (no real money)\" % (srv.cur", ("real double-SHA-256, network %s\" % (NETWORK, srv.cur" if "cpu" in fn else "real OpenCL double-SHA-256, network %s\" % (NETWORK, srv.cur"), name=fn) t = rep(t, 'LIVE on %s - real', 'LIVE on %s - real', name=fn) t = t.replace('miner - LIVE on %s - real double-SHA-256, network %s" % (NETWORK, srv.cur', 'miner - LIVE - network %s - on %s - real double-SHA-256" % (NETWORK, srv.cur') t = t.replace('miner - LIVE on %s - real OpenCL double-SHA-256, network %s" % (NETWORK, srv.cur', 'miner - LIVE - network %s - on %s - real OpenCL double-SHA-256" % (NETWORK, srv.cur') # live(): refuse before any server contact; gate every server; check network in every work answer t = rep(t, " st = st if st is not None else {}\n srv = ServerList(", " st = st if st is not None else {}\n why = mainnet_refusal(a)\n if why:\n out(why); st[\"last\"] = \"not started\"; return\n srv = ServerList(", name=fn) t = rep(t, " nshare = 0\n", " nshare = 0\n net_ok = set() # servers that passed the exact network check\n", name=fn) t = rep(t, " w = srv.call(wurl())\n if \"y_candidate\" not in w:", " if not network_gate(srv, out, stop_ev, net_ok):\n continue\n" " w = srv.call(wurl())\n" " if srv.cur not in net_ok:\n continue # failover happened inside call(): check the new server first\n" " if isinstance(w, dict) and w.get(\"network\") is not None and w.get(\"network\") != NETWORK:\n" " net_ok.discard(srv.cur); out(\" REFUSED work from %s: network '%s' (expected '%s')\" % (srv.cur, w.get(\"network\"), NETWORK)); stop_ev.wait(30); continue\n" " if \"y_candidate\" not in w:", name=fn) t = rep(t, " head_miner = P[\"address\"] if P else a.wallet\n", " if P and _OLD.is_old(P[\"address\"]):\n" " net_ok.discard(srv.cur); out(\" REFUSED pool %s: its pool address is an OLD testnet key\" % srv.cur); stop_ev.wait(30); continue\n" " head_miner = P[\"address\"] if P else a.wallet\n", name=fn) if "cpu" in fn: t = rep(t, " prefix = header_prefix(w[\"height\"], w[\"prev\"], w[\"y_candidate\"], head_miner, t, w[\"bits\"])\n", " prefix = _CH.CHAIN_ID + header_prefix(w[\"height\"], w[\"prev\"], w[\"y_candidate\"], head_miner, t, w[\"bits\"])[4:] # EDDY- -> EDDY1-\n", name=fn) else: t = rep(t, " prefix = \"EDDY-%s-BLOCK|v1|%d|%s|%s|%s|%d|%d|\" % (CHAIN, w[\"height\"]", " prefix = _CH.CHAIN_ID + \"-%s-BLOCK|v1|%d|%s|%s|%s|%d|%d|\" % (CHAIN, w[\"height\"]", name=fn) t = rep(t, " if \"y_candidate\" not in w:\n", " if isinstance(w, dict) and w.get(\"header_format\") and not str(w[\"header_format\"]).startswith(_CH.CHAIN_ID + \"-\"):\n" " net_ok.discard(srv.cur); out(\" REFUSED work from %s: header format %s is not chain %s\" % (srv.cur, str(w[\"header_format\"])[:20], _CH.CHAIN_ID)); stop_ev.wait(30); continue\n" " if \"y_candidate\" not in w:\n", name=fn) # --max-blocks: live mode ignored it -> now counts accepted blocks/shares in live mode (only if given); offline default stays 500 t = rep(t, 'ap.add_argument("--max-blocks", type=int, default=500, help="stop after this many blocks (default 500)")', 'ap.add_argument("--max-blocks", type=int, default=None, help="stop after this many accepted blocks / pool shares (live: no limit unless given; offline default 500)")', name=fn) t = rep(t, " while time.time() < deadline and len(blocks) < a.max_blocks:", " while time.time() < deadline and len(blocks) < (a.max_blocks or 500):", name=fn) t = rep(t, " while (end is None or time.time() < end) and not stop_ev.is_set():\n", " while (end is None or time.time() < end) and not stop_ev.is_set():\n" " if getattr(a, \"max_blocks\", None) and acc >= a.max_blocks:\n" " out(\" --max-blocks %d reached - stopping\" % a.max_blocks); break\n", name=fn) open(os.path.join(OUT, fn), "w").write(t) # ---------- GUI ---------- t = open(os.path.join(SRC, "eddycoin_miner_gui.py")).read() t = rep(t, '"""Shared tkinter GUI for the Eddycoin CPU (E-Chain) and GPU (D-Chain) miners. Test coin only, no real money."""', '"""Shared tkinter GUI for the Eddycoin CPU (E-Chain) and GPU (D-Chain) miners - new Eddycoin mainnet build.\nMIT License, Copyright (c) 2026 Nakamoto Vein."""', name="gui") t = rep(t, 'HTTP_WARNING = "plain http:// is unencrypted - fine for a test-coin node, but others on the network could read or change the traffic"', 'HTTP_WARNING = "plain http:// is unencrypted - fine for your own PC or home network, but others on the network could read or change the traffic"', name="gui") t = rep(t, """ if ADDR_RE.match(a) and _OLD.is_mainnet(_CH.NETWORK) and _OLD.is_old(a): messagebox.showerror(title, "%s is an OLD Eddycoin testnet address. It cannot be used on the new Eddycoin mainnet - enter the address of your NEW wallet." % a); return""", """ if ADDR_RE.match(a) and _OLD.is_old(a): messagebox.showerror(title, _OLD.refusal(a) or "Refused payout address."); return""", name="gui") t = rep(t, ''' if not ADDR_RE.match(a): messagebox.showerror(title, "Enter your wallet address (eddy1 + 40 hex). Create a wallet in Eddycoin Wallet or on %s/live." % default_server); return''', ''' if not ADDR_RE.match(a): messagebox.showerror(title, "Enter your payout wallet address (eddy1 + 40 hex characters). Create a wallet in Eddycoin Wallet and copy its address."); return if not _CH.ACTIVE: messagebox.showinfo(title, "The new Eddycoin chain is not live yet, so there is nothing to mine.\\n\\nYour address is saved. Mining starts once eddycoin_chain.json holds the new chain's values (see README.txt)."); save_settings(); return''', name="gui") open(os.path.join(OUT, "eddycoin_miner_gui.py"), "w").write(t) for f in sorted(os.listdir(OUT)): if f.endswith(".py"): compile(open(os.path.join(OUT, f)).read(), f, "exec") print(hashlib.sha256(open(os.path.join(OUT, f), "rb").read()).hexdigest()[:12], f) print("denylist: %d pubkey hashes, %d addresses (%d superseded v2)" % (len(pk), len(ad), len(sup)))