Repository navigation
Expand file tree
/
Copy path12_monero_block.py
More file actions
79 lines (64 loc) · 3.24 KB
/
Copy path12_monero_block.py
File metadata and controls
79 lines (64 loc) · 3.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# SPDX-License-Identifier: MIT
"""12 - Explore a Monero block and see how much a transaction hides.
Needs a running monerod (stagenet by default, no wallet). Set MONEROD to point elsewhere.
Reads a block and up to three of its transactions, then prints what is visible on chain
versus what RingCT, ring signatures, and stealth addresses keep hidden. The counterpart
to the Bitcoin transaction decoder in recipe 10, where every amount and address is in the
clear.
MONEROD=http://127.0.0.1:38081 python 12_monero_block.py [height]
"""
import sys
import json
import datetime
import requests
from config import MONEROD
def rpc(method, params=None):
payload = {"jsonrpc": "2.0", "id": "0", "method": method, "params": params or {}}
r = requests.post(f"{MONEROD}/json_rpc", json=payload, timeout=15)
r.raise_for_status()
return r.json()["result"]
def get_transactions(hashes):
# A raw endpoint, not json_rpc. decode_as_json returns a parsed view of each tx.
r = requests.post(f"{MONEROD}/get_transactions",
json={"txs_hashes": hashes, "decode_as_json": True}, timeout=15)
r.raise_for_status()
return r.json().get("txs", [])
def short(h):
return f"{h[:12]}...{h[-6:]}"
def xmr(atomic):
# Monero amounts are atomic units (1 XMR = 1e12). Amounts inside a tx read 0 (hidden).
return f"{atomic / 1e12:,.12f}"
try:
height = int(sys.argv[1]) if len(sys.argv) > 1 else rpc("get_block_count")["count"] - 1
block = rpc("get_block", {"height": height})
header = block["block_header"]
tx_hashes = block.get("tx_hashes", [])
when = datetime.datetime.fromtimestamp(header["timestamp"], datetime.timezone.utc)
print(f"block height : {header['height']:,}")
print(f"timestamp : {when.strftime('%Y-%m-%d %H:%M:%S')} UTC")
print(f"transactions : {header['num_txes']:,}")
print(f"miner reward : {xmr(header['reward'])} XMR")
if not tx_hashes:
print()
print("this block is empty: it holds only the miner (coinbase) transaction,")
print("so there is nothing here to hide.")
else:
if len(tx_hashes) > 3:
print(f"\n(showing 3 of {len(tx_hashes):,} transactions)")
for tx in get_transactions(tx_hashes[:3]):
j = json.loads(tx["as_json"])
vin = j.get("vin", [])
# Ring size is how many decoy outputs each real input hides among.
ring = len(vin[0]["key"]["key_offsets"]) if vin and "key" in vin[0] else 0
fee = j.get("rct_signatures", {}).get("txnFee", 0)
print(f"\ntx {short(tx['tx_hash'])}")
print(f" ring size : {ring}")
print(f" inputs : {len(vin):,}")
print(f" outputs : {len(j.get('vout', [])):,}")
print(f" fee : {xmr(fee)} XMR")
print("\nwhat a Monero block reveals")
print(" visible : that a transaction happened, its ring size, input and output counts, the fee")
print(" hidden : amounts (RingCT), the real sender (ring signatures), the real recipient (stealth addresses)")
print(" bitcoin : recipe 10 decodes a Bitcoin tx and shows every amount and address in the clear")
except requests.exceptions.ConnectionError:
sys.exit(f"could not reach monerod at {MONEROD}. start it with: monerod --stagenet")