Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2024-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Tool to convert a compact-serialized UTXO set to a SQLite3 database.7The input UTXO set can be generated by Bitcoin Core with the `dumptxoutset` RPC:8$ bitcoin-cli dumptxoutset ~/utxos.dat latest10The created database contains a table `utxos` with the following schema:11(txid TEXT, vout INT, value INT, coinbase INT, height INT, scriptpubkey TEXT)13If --txid=raw or --txid=rawle is specified, txid will be BLOB instead;14if --spk=raw, then scriptpubkey will be BLOB instead.15"""16import argparse17import os18import sqlite319import sys20import time23UTXO_DUMP_MAGIC = b'utxo\xff'24UTXO_DUMP_VERSION = 225NET_MAGIC_BYTES = {26b"\xf9\xbe\xb4\xd9": "Mainnet",27b"\x0a\x03\xcf\x40": "Signet",28b"\x0b\x11\x09\x07": "Testnet3",29b"\x1c\x16\x3f\x28": "Testnet4",30b"\xfa\xbf\xb5\xda": "Regtest",31}34def read_varint(f):35"""Equivalent of `ReadVarInt()` (see serialization module)."""36n = 037while True:38dat = f.read(1)[0]39n = (n << 7) | (dat & 0x7f)40if (dat & 0x80) > 0:41n += 142else:43return n46def read_compactsize(f):47"""Equivalent of `ReadCompactSize()` (see serialization module)."""48n = f.read(1)[0]49if n == 253:50n = int.from_bytes(f.read(2), "little")51elif n == 254:52n = int.from_bytes(f.read(4), "little")53elif n == 255:54n = int.from_bytes(f.read(8), "little")55return n58def decompress_amount(x):59"""Equivalent of `DecompressAmount()` (see compressor module)."""60if x == 0:61return 062x -= 163e = x % 1064x //= 1065n = 066if e < 9:67d = (x % 9) + 168x //= 969n = x * 10 + d70else:71n = x + 172while e > 0:73n *= 1074e -= 175return n78def decompress_script(f):79"""Equivalent of `DecompressScript()` (see compressor module)."""80size = read_varint(f) # sizes 0-5 encode compressed script types81if size == 0: # P2PKH82return bytes([0x76, 0xa9, 20]) + f.read(20) + bytes([0x88, 0xac])83elif size == 1: # P2SH84return bytes([0xa9, 20]) + f.read(20) + bytes([0x87])85elif size in (2, 3): # P2PK (compressed)86return bytes([33, size]) + f.read(32) + bytes([0xac])87elif size in (4, 5): # P2PK (uncompressed)88compressed_pubkey = bytes([size - 2]) + f.read(32)89return bytes([65]) + decompress_pubkey(compressed_pubkey) + bytes([0xac])90else: # others (bare multisig, segwit etc.)91size -= 692assert size <= 10000, f"too long script with size {size}"93return f.read(size)96def decompress_pubkey(compressed_pubkey):97"""Decompress pubkey by calculating y = sqrt(x^3 + 7) % p98(see functions `secp256k1_eckey_pubkey_parse` and `secp256k1_ge_set_xo_var`).99"""100P = 2**256 - 2**32 - 977 # secp256k1 field size101assert len(compressed_pubkey) == 33 and compressed_pubkey[0] in (2, 3)102x = int.from_bytes(compressed_pubkey[1:], 'big')103rhs = (x**3 + 7) % P104y = pow(rhs, (P + 1)//4, P) # get sqrt using Tonelli-Shanks algorithm (for p % 4 = 3)105assert pow(y, 2, P) == rhs, f"pubkey is not on curve ({compressed_pubkey.hex()})"106tag_is_odd = compressed_pubkey[0] == 3107y_is_odd = (y & 1) == 1108if tag_is_odd != y_is_odd: # fix parity (even/odd) if necessary109y = P - y110return bytes([4]) + x.to_bytes(32, 'big') + y.to_bytes(32, 'big')113def main():114parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)115parser.add_argument('infile', help='filename of compact-serialized UTXO set (input)')116parser.add_argument('outfile', help='filename of created SQLite3 database (output)')117parser.add_argument('--verbose', action='store_true', help='show details about each UTXO')118parser.add_argument('--spk', choices=['hex', 'raw'], default='hex', help='encode scriptPubKey as hex or raw bytes')119parser.add_argument('--txid', choices=['hex', 'raw', 'rawle'], default='hex', help='encode txid as hex, raw bytes (sha256 byteorder), or reversed raw bytes (little endian)')120args = parser.parse_args()122if not os.path.exists(args.infile):123print(f"Error: provided input file '{args.infile}' doesn't exist.")124sys.exit(1)126if os.path.exists(args.outfile):127print(f"Error: provided output file '{args.outfile}' already exists.")128sys.exit(1)130spk_hex = (args.spk == 'hex')131txid_hex = (args.txid == 'hex')132txid_reverse = (args.txid != 'raw')134# create database table135txid_fmt = "TEXT" if txid_hex else "BLOB"136spk_fmt = "TEXT" if spk_hex else "BLOB"137con = sqlite3.connect(args.outfile)138con.execute(f"CREATE TABLE utxos(txid {txid_fmt}, vout INT, value INT, coinbase INT, height INT, scriptpubkey {spk_fmt})")140# read metadata (magic bytes, version, network magic, block hash, UTXO count)141f = open(args.infile, 'rb')142magic_bytes = f.read(5)143version = int.from_bytes(f.read(2), 'little')144network_magic = f.read(4)145block_hash = f.read(32)146num_utxos = int.from_bytes(f.read(8), 'little')147if magic_bytes != UTXO_DUMP_MAGIC:148print(f"Error: provided input file '{args.infile}' is not an UTXO dump.")149sys.exit(1)150if version != UTXO_DUMP_VERSION:151print(f"Error: provided input file '{args.infile}' has unknown UTXO dump version {version} "152f"(only version {UTXO_DUMP_VERSION} supported)")153sys.exit(1)154network_string = NET_MAGIC_BYTES.get(network_magic, f"unknown network ({network_magic.hex()})")155print(f"UTXO Snapshot for {network_string} at block hash "156f"{block_hash[::-1].hex()[:32]}..., contains {num_utxos} coins")158start_time = time.time()159write_batch = []160coins_per_hash_left = 0161prevout_hash = None162max_height = 0164for coin_idx in range(1, num_utxos+1):165# read key (COutPoint)166if coins_per_hash_left == 0: # read next prevout hash167prevout_hash = f.read(32)168coins_per_hash_left = read_compactsize(f)169prevout_index = read_compactsize(f)170# read value (Coin)171code = read_varint(f)172height = code >> 1173is_coinbase = code & 1174amount = decompress_amount(read_varint(f))175scriptpubkey = decompress_script(f)177scriptpubkey_write = scriptpubkey.hex() if spk_hex else scriptpubkey178txid_write = prevout_hash[::-1] if txid_reverse else prevout_hash179txid_write = txid_write.hex() if txid_hex else txid_write180write_batch.append((txid_write, prevout_index, amount, is_coinbase, height, scriptpubkey_write))181if height > max_height:182max_height = height183coins_per_hash_left -= 1185if args.verbose:186print(f"Coin {coin_idx}/{num_utxos}:")187print(f" prevout = {prevout_hash[::-1].hex()}:{prevout_index}")188print(f" amount = {amount}, height = {height}, coinbase = {is_coinbase}")189print(f" scriptPubKey = {scriptpubkey.hex()}\n")191if coin_idx % (16*1024) == 0 or coin_idx == num_utxos:192# write utxo batch to database193con.executemany("INSERT INTO utxos VALUES(?, ?, ?, ?, ?, ?)", write_batch)194con.commit()195write_batch.clear()197if coin_idx % (1024*1024) == 0:198elapsed = time.time() - start_time199print(f"{coin_idx} coins converted [{coin_idx/num_utxos*100:.2f}%], " +200f"{elapsed:.3f}s passed since start")201con.close()203print(f"TOTAL: {num_utxos} coins written to {args.outfile}, snapshot height is {max_height}.")204if f.read(1) != b'': # EOF should be reached by now205print(f"WARNING: input file {args.infile} has not reached EOF yet!")206sys.exit(1)209if __name__ == '__main__':210main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.