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#3# linearize-data.py: Construct a linear, no-fork version of the chain.4#5# Copyright (c) 2013-present The Bitcoin Core developers6# Distributed under the MIT software license, see the accompanying7# file COPYING or http://www.opensource.org/licenses/mit-license.php.8#10import struct11import re12import os13import os.path14import sys15import hashlib16import datetime17import time18import glob19from collections import namedtuple21settings = {}23def calc_hash_str(blk_hdr):24blk_hdr_hash = hashlib.sha256(hashlib.sha256(blk_hdr).digest()).digest()25return blk_hdr_hash[::-1].hex()27def get_blk_dt(blk_hdr):28members = struct.unpack("<I", blk_hdr[68:68+4])29nTime = members[0]30dt = datetime.datetime.fromtimestamp(nTime)31dt_ym = datetime.datetime(dt.year, dt.month, 1)32return (dt_ym, nTime)34# When getting the list of block hashes, undo any byte reversals.35def get_block_hashes(settings):36blkindex = []37with open(settings['hashlist'], "r") as f:38for line in f:39line = line.rstrip()40if settings['rev_hash_bytes'] == 'true':41line = bytes.fromhex(line)[::-1].hex()42blkindex.append(line)44print("Read " + str(len(blkindex)) + " hashes")46return blkindex48# The block map shouldn't give or receive byte-reversed hashes.49def mkblockmap(blkindex):50blkmap = {}51for height,hash in enumerate(blkindex):52blkmap[hash] = height53return blkmap55# This gets the first block file ID that exists from the input block56# file directory.57def getFirstBlockFileId(block_dir_path):58# First, this sets up a pattern to search for block files, for59# example 'blkNNNNN.dat'.60blkFilePattern = os.path.join(block_dir_path, "blk[0-9][0-9][0-9][0-9][0-9].dat")62# This search is done with glob63blkFnList = glob.glob(blkFilePattern)65if len(blkFnList) == 0:66print("blocks not pruned - starting at 0")67return 068# We then get the lexicographic minimum, which should be the first69# block file name.70firstBlkFilePath = min(blkFnList)71firstBlkFn = os.path.basename(firstBlkFilePath)73# now, the string should be ['b','l','k','N','N','N','N','N','.','d','a','t']74# So get the ID by choosing: 3 4 5 6 775# The ID is not necessarily 0 if this is a pruned node.76blkId = int(firstBlkFn[3:8])77return blkId79def read_xor_key(blocks_path):80NUM_XOR_BYTES = 8 # From InitBlocksdirXorKey::xor_key.size()81try:82xor_filename = os.path.join(blocks_path, "xor.dat")83with open(xor_filename, "rb") as xor_file:84return xor_file.read(NUM_XOR_BYTES)85# support also blockdirs created with pre-v28 versions, where no xor key exists yet86except FileNotFoundError:87return bytes([0] * NUM_XOR_BYTES)89# Block header and extent on disk90BlockExtent = namedtuple('BlockExtent', ['fn', 'offset', 'inhdr', 'blkhdr', 'size'])92class BlockDataCopier:93def __init__(self, settings, blkindex, blkmap):94self.settings = settings95self.blkindex = blkindex96self.blkmap = blkmap98# Get first occurring block file id - for pruned nodes this99# will not necessarily be 0100self.inFn = getFirstBlockFileId(self.settings['input'])101self.inF = None102self.outFn = 0103self.outsz = 0104self.outF = None105self.outFname = None106self.blkCountIn = 0107self.blkCountOut = 0108self.xor_key = read_xor_key(self.settings['input'])110self.lastDate = datetime.datetime(2000, 1, 1)111self.highTS = 1408893517 - 315360000112self.timestampSplit = False113self.fileOutput = True114self.setFileTime = False115self.maxOutSz = settings['max_out_sz']116if 'output' in settings:117self.fileOutput = False118if settings['file_timestamp'] != 0:119self.setFileTime = True120if settings['split_timestamp'] != 0:121self.timestampSplit = True122# Extents and cache for out-of-order blocks123self.blockExtents = {}124self.outOfOrderData = {}125self.outOfOrderSize = 0 # running total size for items in outOfOrderData127def read_xored(self, f, size):128offset = f.tell()129data = bytearray(f.read(size))130for i in range(len(data)):131data[i] ^= self.xor_key[(i + offset) % len(self.xor_key)]132return bytes(data)134def writeBlock(self, inhdr, blk_hdr, rawblock):135blockSizeOnDisk = len(inhdr) + len(blk_hdr) + len(rawblock)136if not self.fileOutput and ((self.outsz + blockSizeOnDisk) > self.maxOutSz):137self.outF.close()138if self.setFileTime:139os.utime(self.outFname, (int(time.time()), self.highTS))140self.outF = None141self.outFname = None142self.outFn = self.outFn + 1143self.outsz = 0145(blkDate, blkTS) = get_blk_dt(blk_hdr)146if self.timestampSplit and (blkDate > self.lastDate):147print("New month " + blkDate.strftime("%Y-%m") + " @ " + self.hash_str)148self.lastDate = blkDate149if self.outF:150self.outF.close()151if self.setFileTime:152os.utime(self.outFname, (int(time.time()), self.highTS))153self.outF = None154self.outFname = None155self.outFn = self.outFn + 1156self.outsz = 0158if not self.outF:159if self.fileOutput:160self.outFname = self.settings['output_file']161else:162self.outFname = os.path.join(self.settings['output'], "blk%05d.dat" % self.outFn)163print("Output file " + self.outFname)164self.outF = open(self.outFname, "wb")166self.outF.write(inhdr)167self.outF.write(blk_hdr)168self.outF.write(rawblock)169self.outsz = self.outsz + len(inhdr) + len(blk_hdr) + len(rawblock)171self.blkCountOut = self.blkCountOut + 1172if blkTS > self.highTS:173self.highTS = blkTS175if (self.blkCountOut % 1000) == 0:176print('%i blocks scanned, %i blocks written (of %i, %.1f%% complete)' %177(self.blkCountIn, self.blkCountOut, len(self.blkindex), 100.0 * self.blkCountOut / len(self.blkindex)))179def inFileName(self, fn):180return os.path.join(self.settings['input'], "blk%05d.dat" % fn)182def fetchBlock(self, extent):183'''Fetch block contents from disk given extents'''184with open(self.inFileName(extent.fn), "rb") as f:185f.seek(extent.offset)186return self.read_xored(f, extent.size)188def copyOneBlock(self):189'''Find the next block to be written in the input, and copy it to the output.'''190extent = self.blockExtents.pop(self.blkCountOut)191if self.blkCountOut in self.outOfOrderData:192# If the data is cached, use it from memory and remove from the cache193rawblock = self.outOfOrderData.pop(self.blkCountOut)194self.outOfOrderSize -= len(rawblock)195else: # Otherwise look up data on disk196rawblock = self.fetchBlock(extent)198self.writeBlock(extent.inhdr, extent.blkhdr, rawblock)200def run(self):201while self.blkCountOut < len(self.blkindex):202if not self.inF:203fname = self.inFileName(self.inFn)204print("Input file " + fname)205try:206self.inF = open(fname, "rb")207except IOError:208print("Premature end of block data")209return211inhdr = self.read_xored(self.inF, 8)212if (not inhdr or (inhdr[0] == "\0")):213self.inF.close()214self.inF = None215self.inFn = self.inFn + 1216continue218inMagic = inhdr[:4]219if (inMagic != self.settings['netmagic']):220# Seek backwards 7 bytes (skipping the first byte in the previous search)221# and continue searching from the new position if the magic bytes are not222# found.223self.inF.seek(-7, os.SEEK_CUR)224continue225inLenLE = inhdr[4:]226su = struct.unpack("<I", inLenLE)227inLen = su[0] - 80 # length without header228blk_hdr = self.read_xored(self.inF, 80)229inExtent = BlockExtent(self.inFn, self.inF.tell(), inhdr, blk_hdr, inLen)231self.hash_str = calc_hash_str(blk_hdr)232if self.hash_str not in blkmap:233# Because blocks can be written to files out-of-order as of 0.10, the script234# may encounter blocks it doesn't know about. Treat as debug output.235if settings['debug_output'] == 'true':236print("Skipping unknown block " + self.hash_str)237self.inF.seek(inLen, os.SEEK_CUR)238continue240blkHeight = self.blkmap[self.hash_str]241self.blkCountIn += 1243if self.blkCountOut == blkHeight:244# If in-order block, just copy245rawblock = self.read_xored(self.inF, inLen)246self.writeBlock(inhdr, blk_hdr, rawblock)248# See if we can catch up to prior out-of-order blocks249while self.blkCountOut in self.blockExtents:250self.copyOneBlock()
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.