TeachMeBitcoin

Custom Python UTXO Reorg Simulator

From TeachMeBitcoin, the free encyclopedia ⏱️ 3 min read

Building a UTXO & Mempool Reorg Simulator in Python

To understand how a node's internal state changes during a reorganization, we can build a simulator that tracks a UTXO Database and a Mempool. This script demonstrates how transactions are "undone" and moved back to the mempool when a block is disconnected.


💻 1. The UTXO Reorg Simulator Source Code

Save the following Python script as utxo_reorg_sim.py.

class UTXOState:
    def __init__(self):
        # Format: { "txid:vout": "owner" }
        self.utxo_set = {
            "genesis:0": "Alice (50 BTC)"
        }
        self.mempool = []
        self.block_history = []

    def connect_block(self, block):
        print(f"\n[+] Connecting Block {block['height']} ({block['id']})")

        # 1. Remove consumed inputs from UTXO set
        for tx in block['txs']:
            if tx['id'] == "coinbase": continue # Skip coinbase input check

            input_key = f"{tx['input_id']}:{tx['input_vout']}"
            if input_key in self.utxo_set:
                del self.utxo_set[input_key]
                print(f"    ├─ Consumed Input: {input_key}")

            # Remove from mempool if present
            self.mempool = [m for m in self.mempool if m['id'] != tx['id']]

        # 2. Add new outputs to UTXO set
        for tx in block['txs']:
            for i, out in enumerate(tx['outputs']):
                output_key = f"{tx['id']}:{i}"
                self.utxo_set[output_key] = f"{out['owner']} ({out['amount']} BTC)"
                print(f"    ├─ Created UTXO: {output_key}")

        self.block_history.append(block)

    def disconnect_block(self):
        if not self.block_history: return
        block = self.block_history.pop()

        print(f"\n[-] Disconnecting Block {block['height']} ({block['id']})")

        # 1. Remove outputs created by this block
        for tx in block['txs']:
            for i in range(len(tx['outputs'])):
                output_key = f"{tx['id']}:{i}"
                if output_key in self.utxo_set:
                    del self.utxo_set[output_key]
                    print(f"    ├─ Removed UTXO: {output_key}")

        # 2. Restore inputs consumed by this block
        for tx in block['txs']:
            if tx['id'] == "coinbase": continue
            input_key = f"{tx['input_id']}:{tx['input_vout']}"
            # In a real node, we'd look up the original owner/amount in undo data
            self.utxo_set[input_key] = "Restored from Undo Data"
            print(f"    ├─ Restored Input: {input_key}")

            # 3. Move transactions back to mempool
            self.mempool.append(tx)
            print(f"    ├─ Moved TX {tx['id']} back to Mempool")

    def show_state(self):
        print("\n=== CURRENT NODE STATE ===")
        print(f"[*] UTXO Set: {list(self.utxo_set.keys())}")
        print(f"[*] Mempool:  {[m['id'] for m in self.mempool]}")

if __name__ == "__main__":
    node = UTXOState()

    # Block 1: Alice sends to Bob
    block1 = {
        "height": 1, "id": "B1",
        "txs": [
            {"id": "tx1", "input_id": "genesis", "input_vout": 0, "outputs": [{"owner": "Bob", "amount": 10}]}
        ]
    }

    # Block 2A: Bob sends to Charlie
    block2a = {
        "height": 2, "id": "B2A",
        "txs": [
            {"id": "tx2", "input_id": "tx1", "input_vout": 0, "outputs": [{"owner": "Charlie", "amount": 9}]}
        ]
    }

    node.connect_block(block1)
    node.connect_block(block2a)
    node.show_state()

    # --- TRIGGER REORG ---
    # Node finds out Block 2A is invalid or on a shorter chain
    print("\n" + "!"*30 + "\n! TRIGGERING REORGANIZATION !\n" + "!"*30)
    node.disconnect_block()
    node.show_state()

🛠️ 2. Key Takeaways

  1. State Reversal: Notice how tx2 is completely wiped from the UTXO set and Bob's input (tx1:0) is magically restored.
  2. Mempool Fluidity: The transaction tx2 isn't lost. It sits in the mempool, waiting for the node to find a new winning Block 2 (or 3) that can include it.
  3. Consistency: This process ensures that no matter how many forks happen, every node that reaches the same block height on the same chain will have an identical UTXO database.
☕ Help support TeachMeBitcoin

TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!