"""Beanstalk teardown: read the two exploit transactions straight from Ethereum.

Read-only JSON-RPC calls to a public archive endpoint; no keys, nothing signed or sent. Addresses and hashes are
the ones CertiK published in its reconstruction. Prints and saves onchain_cp0844.json:
  1. each transaction's block, sender and target;
  2. each block's timestamp, and the gap between the proposal and the execution;
  3. whether the BIP-18 execution target (CertiK's "Malicious Proposal" address) held any contract code at the
     block the proposal was filed, and at the block it was executed (Veridise: "an address holding no code at
     submission time").
Usage: python onchain_cp0844.py [rpc_url ...]   (tries each endpoint until one answers archive queries)
"""
import datetime, json, pathlib, sys, urllib.request

HERE = pathlib.Path(__file__).resolve().parent
ENDPOINTS = sys.argv[1:] or ["https://ethereum-rpc.publicnode.com", "https://eth.llamarpc.com",
                             "https://eth.drpc.org", "https://1rpc.io/eth"]
TX_PROPOSE = "0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f"
TX_EXECUTE = "0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7"
BEANSTALK = "0xc1e088fc1323b20bcbee9bd1b9fc9546db5624c5"
PROPOSAL_TARGET = "0xe5ecf73603d98a0128f05ed30506ac7a663dbb69"


def rpc(url, method, params):
    body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode()
    req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json",
                                                           "User-Agent": "onchain_cp0844"})
    with urllib.request.urlopen(req, timeout=30) as r:
        out = json.loads(r.read().decode())
    if "error" in out:
        raise RuntimeError(out["error"])
    return out["result"]


def main():
    last_err = None
    for url in ENDPOINTS:
        try:
            res = {"endpoint": url, "read_at": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")}
            for name, h in (("propose", TX_PROPOSE), ("execute", TX_EXECUTE)):
                tx = rpc(url, "eth_getTransactionByHash", [h])
                rc = rpc(url, "eth_getTransactionReceipt", [h])
                blk = rpc(url, "eth_getBlockByNumber", [tx["blockNumber"], False])
                ts = int(blk["timestamp"], 16)
                res[name] = {"hash": h, "block": int(tx["blockNumber"], 16), "from": tx["from"], "to": tx["to"],
                             "status": int(rc["status"], 16), "gas_used": int(rc["gasUsed"], 16), "logs": len(rc["logs"]),
                             "timestamp": ts,
                             "utc": datetime.datetime.fromtimestamp(ts, datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")}
            gap = res["execute"]["timestamp"] - res["propose"]["timestamp"]
            res["gap_seconds"] = gap
            res["gap_hms"] = str(datetime.timedelta(seconds=gap))
            for label, blockno in (("at_propose_block", res["propose"]["block"]), ("at_execute_block_minus_1", res["execute"]["block"] - 1),
                                   ("at_execute_block", res["execute"]["block"])):
                code = rpc(url, "eth_getCode", [PROPOSAL_TARGET, hex(blockno)])
                res.setdefault("proposal_target_code_bytes", {})[label] = (len(code) - 2) // 2
            (HERE / "onchain_cp0844.json").write_text(json.dumps(res, indent=1), encoding="utf-8")
            print(json.dumps(res, indent=1))
            return 0
        except Exception as e:  # try the next public endpoint
            last_err = f"{url}: {type(e).__name__}: {str(e)[:160]}"
            print("endpoint failed ->", last_err, file=sys.stderr)
    print("no endpoint answered:", last_err, file=sys.stderr)
    return 1


if __name__ == "__main__":
    sys.exit(main())
