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# Copyright (c) 2022 Pieter Wuille2# Distributed under the MIT software license, see the accompanying3# file LICENSE or http://www.opensource.org/licenses/mit-license.php.5"""6This module provides the ASNEntry and ASMap classes.7"""9import copy10import ipaddress11import random12import unittest13from collections.abc import Callable, Iterable14from enum import Enum15from functools import total_ordering16from typing import Optional, Union, overload18def net_to_prefix(net: Union[ipaddress.IPv4Network,ipaddress.IPv6Network]) -> list[bool]:19"""20Convert an IPv4 or IPv6 network to a prefix represented as a list of bits.22IPv4 ranges are remapped to their IPv4-mapped IPv6 range (::ffff:0:0/96).23"""24num_bits = net.prefixlen25netrange = int.from_bytes(net.network_address.packed, 'big')27# Map an IPv4 prefix into IPv6 space.28if isinstance(net, ipaddress.IPv4Network):29num_bits += 9630netrange += 0xffff0000000032# Strip unused bottom bits.33assert (netrange & ((1 << (128 - num_bits)) - 1)) == 034return [((netrange >> (127 - i)) & 1) != 0 for i in range(num_bits)]36def prefix_to_net(prefix: list[bool]) -> Union[ipaddress.IPv4Network,ipaddress.IPv6Network]:37"""The reverse operation of net_to_prefix."""38# Convert to number39netrange = sum(b << (127 - i) for i, b in enumerate(prefix))40num_bits = len(prefix)41assert num_bits <= 12843# Return IPv4 range if in ::ffff:0:0/9644if num_bits >= 96 and (netrange >> 32) == 0xffff:45return ipaddress.IPv4Network((netrange & 0xffffffff, num_bits - 96), True)47# Return IPv6 range otherwise.48return ipaddress.IPv6Network((netrange, num_bits), True)50# Shortcut for (prefix, ASN) entries.51ASNEntry = tuple[list[bool], int]53# Shortcut for (prefix, old ASN, new ASN) entries.54ASNDiff = tuple[list[bool], int, int]56class _VarLenCoder:57"""58A class representing a custom variable-length binary encoder/decoder for59integers. Each object represents a different coder, with different parameters60minval and clsbits.62The encoding is easiest to describe using an example. Let's say minval=100 and63clsbits=[4,2,2,3]. In that case:64- x in [100..115]: encoded as [0] + [4-bit BE encoding of (x-100)].65- x in [116..119]: encoded as [1,0] + [2-bit BE encoding of (x-116)].66- x in [120..123]: encoded as [1,1,0] + [2-bit BE encoding of (x-120)].67- x in [124..131]: encoded as [1,1,1] + [3-bit BE encoding of (x-124)].69In general, every number is encoded as:70- First, k "1"-bits, where k is the class the number falls in (there is one class71per element of clsbits).72- Then, a "0"-bit, unless k is the highest class, in which case there is nothing.73- Lastly, clsbits[k] bits encoding in big endian the position in its class that74number falls into.75- Every class k consists of 2^clsbits[k] consecutive integers. k=0 starts at minval,76other classes start one past the last element of the class before it.77"""79def __init__(self, minval: int, clsbits: list[int]):80"""Construct a new _VarLenCoder."""81self._minval = minval82self._clsbits = clsbits83self._maxval = minval + sum(1 << b for b in clsbits) - 185def can_encode(self, val: int) -> bool:86"""Check whether value val is in the range this coder supports."""87return self._minval <= val <= self._maxval89def encode(self, val: int, ret: list[int]) -> None:90"""Append encoding of val onto integer list ret."""92assert self._minval <= val <= self._maxval93val -= self._minval94bits = 095for k, bits in enumerate(self._clsbits):96if val >> bits:97# If the value will not fit in class k, subtract its range from v,98# emit a "1" bit and continue with the next class.99val -= 1 << bits100ret.append(1)101else:102if k + 1 < len(self._clsbits):103# Unless we're in the last class, emit a "0" bit.104ret.append(0)105break106# And then encode v (now the position within the class) in big endian.107ret.extend((val >> (bits - 1 - b)) & 1 for b in range(bits))109def encode_size(self, val: int) -> int:110"""Compute how many bits are needed to encode val."""111assert self._minval <= val <= self._maxval112val -= self._minval113ret = 0114bits = 0115for k, bits in enumerate(self._clsbits):116if val >> bits:117val -= 1 << bits118ret += 1119else:120ret += k + 1 < len(self._clsbits)121break122return ret + bits124def decode(self, stream, bitpos) -> tuple[int,int]:125"""Decode a number starting at bitpos in stream, returning value and new bitpos."""126val = self._minval127bits = 0128for k, bits in enumerate(self._clsbits):129bit = 0130if k + 1 < len(self._clsbits):131bit = stream[bitpos]132bitpos += 1133if not bit:134break135val += 1 << bits136for i in range(bits):137bit = stream[bitpos]138bitpos += 1139val += bit << (bits - 1 - i)140return val, bitpos142# Variable-length encoders used in the binary asmap format.143_CODER_INS = _VarLenCoder(0, [0, 0, 1])144_CODER_ASN = _VarLenCoder(1, list(range(15, 25)))145_CODER_MATCH = _VarLenCoder(2, list(range(1, 9)))146_CODER_JUMP = _VarLenCoder(17, list(range(5, 31)))148class _Instruction(Enum):149"""One instruction in the binary asmap format."""150# A return instruction, encoded as [0], returns a constant ASN. It is followed by151# an integer using the ASN encoding.152RETURN = 0153# A jump instruction, encoded as [1,0] inspects the next unused bit in the input154# and either continues execution (if 0), or skips a specified number of bits (if 1).155# It is followed by an integer, and then two subprograms. The integer uses jump encoding156# and corresponds to the length of the first subprogram (so it can be skipped).157JUMP = 1158# A match instruction, encoded as [1,1,0] inspects 1 or more of the next unused bits159# in the input with its argument. If they all match, execution continues. If they do160# not, failure (represented by 0) is returned. If a default instruction has been executed before, instead161# of failure the default instruction's argument is returned. It is followed by an162# integer in match encoding, and a subprogram. That value is at least 2 bits and at163# most 9 bits. An n-bit value signifies matching (n-1) bits in the input with the lower164# (n-1) bits in the match value.165MATCH = 2166# A default instruction, encoded as [1,1,1] sets the default variable to its argument,167# and continues execution. It is followed by an integer in ASN encoding, and a subprogram.168DEFAULT = 3169# Not an actual instruction, but a way to encode the empty program that fails. In the170# encoder, it is used more generally to represent the failure case inside MATCH instructions,171# which may (if used inside the context of a DEFAULT instruction) actually correspond to172# a successful return. In this usage, they're always converted to an actual MATCH or RETURN173# before the top level is reached (see make_default below).174END = 4176class _BinNode:177"""A class representing a (node of) the parsed binary asmap format."""179@overload180def __init__(self, ins: _Instruction): ...181@overload182def __init__(self, ins: _Instruction, arg1: int): ...183@overload184def __init__(self, ins: _Instruction, arg1: "_BinNode", arg2: "_BinNode"): ...185@overload186def __init__(self, ins: _Instruction, arg1: int, arg2: "_BinNode"): ...188def __init__(self, ins: _Instruction, arg1=None, arg2=None):189"""190Construct a new asmap node. Possibilities are:191- _BinNode(_Instruction.RETURN, asn)192- _BinNode(_Instruction.JUMP, node_0, node_1)193- _BinNode(_Instruction.MATCH, val, node)194- _BinNode(_Instruction.DEFAULT, asn, node)195- _BinNode(_Instruction.END)196"""197self.ins = ins198self.arg1 = arg1199self.arg2 = arg2200if ins == _Instruction.RETURN:201assert isinstance(arg1, int)202assert arg2 is None203self.size = _CODER_INS.encode_size(ins.value) + _CODER_ASN.encode_size(arg1)204elif ins == _Instruction.JUMP:205assert isinstance(arg1, _BinNode)206assert isinstance(arg2, _BinNode)207self.size = (_CODER_INS.encode_size(ins.value) + _CODER_JUMP.encode_size(arg1.size) +208arg1.size + arg2.size)209elif ins == _Instruction.DEFAULT:210assert isinstance(arg1, int)211assert isinstance(arg2, _BinNode)212self.size = _CODER_INS.encode_size(ins.value) + _CODER_ASN.encode_size(arg1) + arg2.size213elif ins == _Instruction.MATCH:214assert isinstance(arg1, int)215assert isinstance(arg2, _BinNode)216self.size = (_CODER_INS.encode_size(ins.value) + _CODER_MATCH.encode_size(arg1)217+ arg2.size)218elif ins == _Instruction.END:219assert arg1 is None220assert arg2 is None221self.size = 0222else:223assert False225@staticmethod226def make_end() -> "_BinNode":227"""Constructor for a _BinNode with just an END instruction."""228return _BinNode(_Instruction.END)230@staticmethod231def make_leaf(val: int) -> "_BinNode":232"""Constructor for a _BinNode of just a RETURN instruction."""233assert val is not None and val > 0234return _BinNode(_Instruction.RETURN, val)236@staticmethod237def make_branch(node0: "_BinNode", node1: "_BinNode") -> "_BinNode":238"""239Construct a _BinNode corresponding to running either the node0 or node1 subprogram,240based on the next input bit. It exploits shortcuts that are possible in the encoding,241and uses either a JUMP, MATCH, or END instruction.242"""243if node0.ins == _Instruction.END and node1.ins == _Instruction.END:244return node0245if node0.ins == _Instruction.END:246if node1.ins == _Instruction.MATCH and node1.arg1 <= 0xFF:247return _BinNode(node1.ins, node1.arg1 + (1 << node1.arg1.bit_length()), node1.arg2)248return _BinNode(_Instruction.MATCH, 3, node1)249if node1.ins == _Instruction.END:250if node0.ins == _Instruction.MATCH and node0.arg1 <= 0xFF:
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.