git / moonbite-network / explorer/demo_data.py · raw

"""Realistic fake sample data for DEMO_MODE.Provides a small in-memory blockchain (a handful of blocks + transactions)shaped exactly like the JSON that bigcoind's JSON-RPC returns, so everyexplorer page renders without a live chain."""import time# Number of blocks in the demo chain (tip height = _NUM_BLOCKS - 1)._NUM_BLOCKS = 12# Anchor the demo chain so its most recent block is ~now. Blocks are 150s# (2.5 min) apart, so the tip lands at the current time and older blocks step# back from there. This keeps relative times ("2m ago") fresh instead of the# whole chain drifting further into the past as real time passes._BASE_TIME = int(time.time()) - (_NUM_BLOCKS - 1) * 150def _h(n: int) -> str:    """Deterministic 64-hex 'hash' derived from an integer seed."""    import hashlib    return hashlib.sha256(f"bigcoin-demo-{n}".encode()).hexdigest()# --- Transactions --------------------------------------------------------# Each tx is shaped like getrawtransaction verbose=true output.def _coinbase_tx(height: int, reward: float, block_hash: str, confirmations: int):    txid = _h(1000 + height)    return {        "txid": txid,        "hash": txid,        "version": 2,        "size": 190,        "vsize": 163,        "weight": 652,        "locktime": 0,        "vin": [            {                "coinbase": f"03{height:06x}0442414c4c",                "sequence": 4294967295,            }        ],        "vout": [            {                "value": reward,                "n": 0,                "scriptPubKey": {                    "asm": "OP_DUP OP_HASH160 ... OP_EQUALVERIFY OP_CHECKSIG",                    "hex": "76a914" + _h(height)[:40] + "88ac",                    "reqSigs": 1,                    "type": "pubkeyhash",                    "addresses": [f"Big{_h(height)[:30]}"],                },            }        ],        "blockhash": block_hash,        "confirmations": confirmations,        "time": _BASE_TIME + height * 150,        "blocktime": _BASE_TIME + height * 150,    }def _payment_tx(seed: int, block_hash: str, height: int, confirmations: int,                inputs, outputs):    txid = _h(seed)    vin = []    for i, (prev_txid, vout_n, val) in enumerate(inputs):        vin.append(            {                "txid": prev_txid,                "vout": vout_n,                "scriptSig": {"asm": "3045...[sig]", "hex": "483045" + _h(seed + i)[:20]},                "sequence": 4294967295,                # Convenience field some explorers surface (prevout value/addr).                "prevout": {                    "value": val,                    "scriptPubKey": {                        "addresses": [f"Big{_h(seed + 500 + i)[:30]}"],                        "type": "pubkeyhash",                    },                },            }        )    vout = []    for n, (val, addr) in enumerate(outputs):        vout.append(            {                "value": val,                "n": n,                "scriptPubKey": {                    "asm": "OP_DUP OP_HASH160 ... OP_EQUALVERIFY OP_CHECKSIG",                    "hex": "76a914" + _h(seed + 900 + n)[:40] + "88ac",                    "reqSigs": 1,                    "type": "pubkeyhash",                    "addresses": [addr],                },            }        )    return {        "txid": txid,        "hash": txid,        "version": 2,        "size": 226,        "vsize": 226,        "weight": 904,        "locktime": 0,        "vin": vin,        "vout": vout,        "blockhash": block_hash,        "confirmations": confirmations,        "time": _BASE_TIME + height * 150 + 30,        "blocktime": _BASE_TIME + height * 150 + 30,    }def _build():    """Construct the demo chain. Returns (blocks_by_hash, blocks_by_height,    height_of_hash, txs_by_id, tip_height, mempool_txids)."""    num_blocks = _NUM_BLOCKS    tip_height = num_blocks - 1    blocks_by_hash = {}    blocks_by_height = {}    txs_by_id = {}    height_of_hash = {}    prev_hash = "0" * 64    reward = 50.0    for height in range(num_blocks):        block_hash = _h(height)        confirmations = tip_height - height + 1        cb = _coinbase_tx(height, reward, block_hash, confirmations)        txs_by_id[cb["txid"]] = cb        tx_list = [cb]        # Add a couple of payment txs to non-genesis blocks.        if height > 0:            p1 = _payment_tx(                seed=height * 10 + 1,                block_hash=block_hash,                height=height,                confirmations=confirmations,                inputs=[(_h(1000 + height - 1), 0, 50.0)],                outputs=[                    (12.5, f"Big{_h(height * 3 + 1)[:30]}"),                    (37.49, f"Big{_h(height * 3 + 2)[:30]}"),                ],            )            txs_by_id[p1["txid"]] = p1            tx_list.append(p1)        if height > 1:            p2 = _payment_tx(                seed=height * 10 + 2,                block_hash=block_hash,                height=height,                confirmations=confirmations,                inputs=[                    (_h(1000 + height - 2), 0, 50.0),                    (_h((height - 1) * 10 + 1), 1, 37.49),                ],                outputs=[                    (80.0, f"Big{_h(height * 5 + 7)[:30]}"),                    (7.485, f"Big{_h(height * 5 + 8)[:30]}"),                ],            )            txs_by_id[p2["txid"]] = p2            tx_list.append(p2)        txids = [t["txid"] for t in tx_list]        size = 285 + sum(t["size"] for t in tx_list)        block = {            "hash": block_hash,            "confirmations": confirmations,            "height": height,            "version": 536870912,            "versionHex": "20000000",            "merkleroot": _h(9000 + height),            "time": _BASE_TIME + height * 150,            "mediantime": _BASE_TIME + height * 150 - 75,            "nonce": 305419896 + height,            "bits": "1d00ffff",            "difficulty": 1.0 + height * 0.0003,            "chainwork": f"{'0' * 56}{(height + 1) * 65536:08x}",            "nTx": len(txids),            "previousblockhash": prev_hash if height > 0 else None,            "nextblockhash": _h(height + 1) if height < tip_height else None,            "strippedsize": size - 40,            "size": size,            "weight": size * 4,            "tx": txids,        }        if height == 0:            block.pop("previousblockhash")        blocks_by_hash[block_hash] = block        blocks_by_height[height] = block        height_of_hash[block_hash] = height        prev_hash = block_hash    # A couple of unconfirmed mempool transactions.    mempool_txids = []    for k in range(2):        mtx = _payment_tx(            seed=99000 + k,            block_hash="",            height=tip_height + 1,            confirmations=0,            inputs=[(_h(1000 + tip_height), 0, 50.0)],            outputs=[                (10.0 + k, f"Big{_h(88000 + k)[:30]}"),                (39.9 - k, f"Big{_h(77000 + k)[:30]}"),            ],        )        mtx["blockhash"] = ""        mtx["confirmations"] = 0        mtx.pop("blocktime", None)        txs_by_id[mtx["txid"]] = mtx        mempool_txids.append(mtx["txid"])    return {        "blocks_by_hash": blocks_by_hash,        "blocks_by_height": blocks_by_height,        "height_of_hash": height_of_hash,        "txs_by_id": txs_by_id,        "tip_height": tip_height,        "mempool_txids": mempool_txids,    }_DATA = _build()# --- RPC-shaped accessors ------------------------------------------------def getblockcount():    return _DATA["tip_height"]def getblockhash(height):    height = int(height)    blk = _DATA["blocks_by_height"].get(height)    if blk is None:        raise KeyError(f"Block height out of range: {height}")    return blk["hash"]def getblock(hash_or_height, verbosity=1):    key = hash_or_height    blk = _DATA["blocks_by_hash"].get(key)    if blk is None:        # Maybe a height was passed.        try:            blk = _DATA["blocks_by_height"].get(int(key))        except (ValueError, TypeError):            blk = None    if blk is None:        raise KeyError(f"Block not found: {hash_or_height}")    return dict(blk)def getrawtransaction(txid, verbose=True):    tx = _DATA["txs_by_id"].get(txid)    if tx is None:        raise KeyError(f"No such transaction: {txid}")    return dict(tx)def getblockchaininfo():    tip = _DATA["tip_height"]    tip_block = _DATA["blocks_by_height"][tip]    return {        "chain": "main",        "blocks": tip,        "headers": tip,        "bestblockhash": tip_block["hash"],        "difficulty": tip_block["difficulty"],        "mediantime": tip_block["mediantime"],        "verificationprogress": 1.0,        "initialblockdownload": False,        "chainwork": tip_block["chainwork"],        "size_on_disk": 1_234_567,        "pruned": False,    }def getmempoolinfo():    return {        "loaded": True,        "size": len(_DATA["mempool_txids"]),        "bytes": 452 * len(_DATA["mempool_txids"]),        "usage": 900 * len(_DATA["mempool_txids"]),        "maxmempool": 300_000_000,        "mempoolminfee": 0.00001000,        "minrelaytxfee": 0.00001000,    }def getrawmempool(verbose=False):    if verbose:        return {t: {"size": 226, "time": int(time.time())} for t in _DATA["mempool_txids"]}    return list(_DATA["mempool_txids"])def getnetworkinfo():    return {        "version": 250000,        "subversion": "/BigCoin:2.5.0/",        "protocolversion": 70015,        "connections": 8,        "connections_in": 3,        "connections_out": 5,        "networkactive": True,        "relayfee": 0.00001000,        "warnings": "",    }