#!/usr/bin/env python3 # Copyright (c) 2010 ArtForz -- public domain half-a-node # Copyright (c) 2012 Jeff Garzik # Copyright (c) 2010-2020 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Bitcoin test framework primitive and message structures CBlock, CTransaction, CBlockHeader, CTxIn, CTxOut, etc....: data structures that should map to corresponding structures in bitcoin/primitives msg_block, msg_tx, msg_headers, etc.: data structures that represent network messages ser_*, deser_*: functions that handle serialization/deserialization. Classes use __slots__ to ensure extraneous attributes aren't accidentally added by tests, compromising their intended effect. """ from base64 import b32decode, b32encode import copy from collections import namedtuple import hashlib from io import BytesIO import random import socket import struct import time from test_framework.crypto.siphash import siphash256 from test_framework.util import assert_equal import dash_hash MAX_LOCATOR_SZ = 101 MAX_BLOCK_SIZE = 2000000 MAX_BLOOM_FILTER_SIZE = 36000 MAX_BLOOM_HASH_FUNCS = 50 COIN = 100000000 # 1 btc in satoshis MAX_MONEY = 21000000 * COIN BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out MAX_PROTOCOL_MESSAGE_LENGTH = 3 * 1024 * 1024 # Maximum length of incoming protocol messages MAX_HEADERS_UNCOMPRESSED_RESULT = 2000 # Number of headers sent in one getheaders result MAX_HEADERS_COMPRESSED_RESULT = 8000 # Number of headers2 sent in one getheaders2 result MAX_INV_SIZE = 50000 # Maximum number of entries in an 'inv' protocol message NODE_NETWORK = (1 << 0) NODE_BLOOM = (1 << 2) NODE_COMPACT_FILTERS = (1 << 6) NODE_NETWORK_LIMITED = (1 << 10) NODE_HEADERS_COMPRESSED = (1 << 11) MSG_TX = 1 MSG_BLOCK = 2 MSG_FILTERED_BLOCK = 3 MSG_GOVERNANCE_OBJECT = 17 MSG_GOVERNANCE_OBJECT_VOTE = 18 MSG_CMPCT_BLOCK = 20 MSG_TYPE_MASK = 0xffffffff >> 2 FILTER_TYPE_BASIC = 0 def sha256(s): return hashlib.sha256(s).digest() def hash256(s): return sha256(sha256(s)) def dashhash(s): return dash_hash.getPoWHash(s) def ser_compact_size(l): r = b"" if l < 253: r = struct.pack("B", l) elif l < 0x10000: r = struct.pack(">= 32 return rs def uint256_from_str(s): r = 0 t = struct.unpack("> 24) & 0xFF v = (c & 0xFFFFFF) << (8 * (nbytes - 3)) return v # deser_function_name: Allow for an alternate deserialization function on the # entries in the vector. def deser_vector(f, c, deser_function_name=None): nit = deser_compact_size(f) r = [] for _ in range(nit): t = c() if deser_function_name: getattr(t, deser_function_name)(f) else: t.deserialize(f) r.append(t) return r # ser_function_name: Allow for an alternate serialization function on the # entries in the vector (we use this for serializing addrv2 messages). def ser_vector(l, ser_function_name=None): r = ser_compact_size(len(l)) for i in l: if ser_function_name: r += getattr(i, ser_function_name)() else: r += i.serialize() return r def deser_uint256_vector(f): nit = deser_compact_size(f) r = [] for _ in range(nit): t = deser_uint256(f) r.append(t) return r def ser_uint256_vector(l): r = ser_compact_size(len(l)) for i in l: r += ser_uint256(i) return r def deser_dyn_bitset(f, bytes_based): if bytes_based: nb = deser_compact_size(f) n = nb * 8 else: n = deser_compact_size(f) nb = int((n + 7) / 8) b = f.read(nb) r = [] for i in range(n): r.append((b[int(i / 8)] & (1 << (i % 8))) != 0) return r def ser_dyn_bitset(l, bytes_based): n = len(l) nb = int((n + 7) / 8) r = [0] * nb for i in range(n): r[int(i / 8)] |= (1 if l[i] else 0) << (i % 8) if bytes_based: r = ser_compact_size(nb) + bytes(r) else: r = ser_compact_size(n) + bytes(r) return r def from_hex(obj, hex_string): """Deserialize from a hex string representation (e.g. from RPC) Note that there is no complementary helper like e.g. `to_hex` for the inverse operation. To serialize a message object to a hex string, simply use obj.serialize().hex()""" obj.deserialize(BytesIO(bytes.fromhex(hex_string))) return obj def tx_from_hex(hex_string): """Deserialize from hex string to a transaction object""" return from_hex(CTransaction(), hex_string) # Objects that map to dashd objects, which can be serialized/deserialized class CService: __slots__ = ("ip", "port") def __init__(self): self.ip = "" self.port = 0 def deserialize(self, f): self.ip = socket.inet_ntop(socket.AF_INET6, f.read(16)) self.port = struct.unpack(">H", f.read(2))[0] def serialize(self): r = b"" r += socket.inet_pton(socket.AF_INET6, self.ip) r += struct.pack(">H", self.port) return r def __repr__(self): return "CService(ip=%s port=%i)" % (self.ip, self.port) class CAddress: __slots__ = ("net", "ip", "nServices", "port", "time") # see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki NET_IPV4 = 1 NET_I2P = 5 ADDRV2_NET_NAME = { NET_IPV4: "IPv4", NET_I2P: "I2P" } ADDRV2_ADDRESS_LENGTH = { NET_IPV4: 4, NET_I2P: 32 } I2P_PAD = "====" def __init__(self): self.time = 0 self.nServices = 1 self.net = self.NET_IPV4 self.ip = "0.0.0.0" self.port = 0 def __eq__(self, other): return self.net == other.net and self.ip == other.ip and self.nServices == other.nServices and self.port == other.port and self.time == other.time def deserialize(self, f, *, with_time=True): """Deserialize from addrv1 format (pre-BIP155)""" if with_time: # VERSION messages serialize CAddress objects without time self.time = struct.unpack("H", f.read(2))[0] def serialize(self, *, with_time=True): """Serialize in addrv1 format (pre-BIP155)""" assert self.net == self.NET_IPV4 r = b"" if with_time: # VERSION messages serialize CAddress objects without time r += struct.pack("H", self.port) return r def deserialize_v2(self, f): """Deserialize from addrv2 format (BIP155)""" self.time = struct.unpack("H", f.read(2))[0] def serialize_v2(self): """Serialize in addrv2 format (BIP155)""" assert self.net in (self.NET_IPV4, self.NET_I2P) r = b"" r += struct.pack("H", self.port) return r def __repr__(self): return ("CAddress(nServices=%i net=%s addr=%s port=%i)" % (self.nServices, self.ADDRV2_NET_NAME[self.net], self.ip, self.port)) class CInv: __slots__ = ("hash", "type") typemap = { 0: "Error", MSG_TX: "TX", MSG_BLOCK: "Block", MSG_FILTERED_BLOCK: "filtered Block", MSG_GOVERNANCE_OBJECT: "Governance Object", MSG_GOVERNANCE_OBJECT_VOTE: "Governance Vote", MSG_CMPCT_BLOCK: "CompactBlock", } def __init__(self, t=0, h=0): self.type = t self.hash = h def deserialize(self, f): self.type = struct.unpack("> 16) & 0xffff self.vin = deser_vector(f, CTxIn) self.vout = deser_vector(f, CTxOut) self.nLockTime = struct.unpack(" 21000000 * COIN: return False return True # Calculate the virtual transaction size using # serialization size (does NOT use sigops). def get_vsize(self): return len(self.serialize()) def get_weight(self): return self.get_vsize() def __repr__(self): return "CTransaction(nVersion=%i vin=%s vout=%s nLockTime=%i)" \ % (self.nVersion, repr(self.vin), repr(self.vout), self.nLockTime) class CBlockHeader: __slots__ = ("hash", "hashMerkleRoot", "hashPrevBlock", "nBits", "nNonce", "nTime", "nVersion", "sha256") def __init__(self, header=None): if header is None: self.set_null() else: self.nVersion = header.nVersion self.hashPrevBlock = header.hashPrevBlock self.hashMerkleRoot = header.hashMerkleRoot self.nTime = header.nTime self.nBits = header.nBits self.nNonce = header.nNonce self.sha256 = header.sha256 self.hash = header.hash self.calc_sha256() def set_null(self): self.nVersion = 4 self.hashPrevBlock = 0 self.hashMerkleRoot = 0 self.nTime = 0 self.nBits = 0 self.nNonce = 0 self.sha256 = None self.hash = None def deserialize(self, f): self.nVersion = struct.unpack(" 1: newhashes = [] for i in range(0, len(hashes), 2): i2 = min(i+1, len(hashes)-1) newhashes.append(hash256(hashes[i] + hashes[i2])) hashes = newhashes return uint256_from_str(hashes[0]) def calc_merkle_root(self): hashes = [] for tx in self.vtx: tx.calc_sha256() hashes.append(ser_uint256(tx.sha256)) return self.get_merkle_root(hashes) def is_valid(self): self.calc_sha256() target = uint256_from_compact(self.nBits) if self.sha256 > target: return False for tx in self.vtx: if not tx.is_valid(): return False if self.calc_merkle_root() != self.hashMerkleRoot: return False return True def solve(self): self.rehash() target = uint256_from_compact(self.nBits) while self.sha256 > target: self.nNonce += 1 self.rehash() def __repr__(self): return "CBlock(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s nBits=%08x nNonce=%08x vtx=%s)" \ % (self.nVersion, self.hashPrevBlock, self.hashMerkleRoot, time.ctime(self.nTime), self.nBits, self.nNonce, repr(self.vtx)) class CompressibleBlockHeader: __slots__ = ("bitfield", "timeOffset", "nVersion", "hashPrevBlock", "hashMerkleRoot", "nTime", "nBits", "nNonce", "hash", "sha256") FLAG_VERSION_BIT_0 = 1 << 0 FLAG_VERSION_BIT_1 = 1 << 1 FLAG_VERSION_BIT_2 = 1 << 2 FLAG_PREV_BLOCK_HASH = 1 << 3 FLAG_TIMESTAMP = 1 << 4 FLAG_NBITS = 1 << 5 BITMASK_VERSION = FLAG_VERSION_BIT_0 | FLAG_VERSION_BIT_1 | FLAG_VERSION_BIT_2 def __init__(self, header=None): if header is None: self.set_null() else: self.bitfield = 0 self.timeOffset = 0 self.nVersion = header.nVersion self.hashPrevBlock = header.hashPrevBlock self.hashMerkleRoot = header.hashMerkleRoot self.nTime = header.nTime self.nBits = header.nBits self.nNonce = header.nNonce self.hash = None self.sha256 = None self.calc_sha256() def set_null(self): self.bitfield = 0 self.timeOffset = 0 self.nVersion = 0 self.hashPrevBlock = 0 self.hashMerkleRoot = 0 self.nTime = 0 self.nBits = 0 self.nNonce = 0 self.hash = None self.sha256 = None def deserialize(self, f): self.bitfield = struct.unpack(" 7: last_unique_versions.pop() @staticmethod def __mark_version_as_most_recent(last_unique_versions, version_idx): # Move version to the front of the list last_unique_versions.insert(0, last_unique_versions.pop(version_idx)) def compress(self, last_blocks, last_unique_versions): if not last_blocks: # First block, everything must be uncompressed self.bitfield &= (~CompressibleBlockHeader.BITMASK_VERSION) self.bitfield |= CompressibleBlockHeader.FLAG_PREV_BLOCK_HASH self.bitfield |= CompressibleBlockHeader.FLAG_TIMESTAMP self.bitfield |= CompressibleBlockHeader.FLAG_NBITS self.__save_version_as_most_recent(last_unique_versions) return # Compress version try: version_idx = last_unique_versions.index(self.nVersion) version_offset = len(last_unique_versions) - version_idx self.bitfield &= (~CompressibleBlockHeader.BITMASK_VERSION) self.bitfield |= (version_offset & CompressibleBlockHeader.BITMASK_VERSION) self.__mark_version_as_most_recent(last_unique_versions, version_idx) except ValueError: self.__save_version_as_most_recent(last_unique_versions) # We have the previous block last_block = last_blocks[-1] # Compress time self.timeOffset = self.nTime - last_block.nTime if self.timeOffset > 32767 or self.timeOffset < -32768: # Time diff overflows, we have to send it as 4 bytes (uncompressed) self.bitfield |= CompressibleBlockHeader.FLAG_TIMESTAMP # If nBits doesn't match previous block, we have to send it if self.nBits != last_block.nBits: self.bitfield |= CompressibleBlockHeader.FLAG_NBITS def uncompress(self, last_compressed_blocks, last_unique_versions): if not last_compressed_blocks: # First block header is always uncompressed self.__save_version_as_most_recent(last_unique_versions) return previous_block = last_compressed_blocks[-1] # Uncompress version version_idx = self.bitfield & self.BITMASK_VERSION if version_idx != 0: if version_idx <= len(last_unique_versions): self.nVersion = last_unique_versions[version_idx - 1] self.__mark_version_as_most_recent(last_unique_versions, version_idx - 1) else: self.__save_version_as_most_recent(last_unique_versions) # Uncompress prev block hash if not self.bitfield & self.FLAG_PREV_BLOCK_HASH: self.hashPrevBlock = previous_block.hash # Uncompress time if not self.bitfield & self.FLAG_TIMESTAMP: self.nTime = previous_block.nTime + self.timeOffset # Uncompress time bits if not self.bitfield & self.FLAG_NBITS: self.nBits = previous_block.nBits self.rehash() class PrefilledTransaction: __slots__ = ("index", "tx") def __init__(self, index=0, tx = None): self.index = index self.tx = tx def deserialize(self, f): self.index = deser_compact_size(f) self.tx = CTransaction() self.tx.deserialize(f) def serialize(self): r = b"" r += ser_compact_size(self.index) r += self.tx.serialize() return r def __repr__(self): return "PrefilledTransaction(index=%d, tx=%s)" % (self.index, repr(self.tx)) # This is what we send on the wire, in a cmpctblock message. class P2PHeaderAndShortIDs: __slots__ = ("header", "nonce", "prefilled_txn", "prefilled_txn_length", "shortids", "shortids_length") def __init__(self): self.header = CBlockHeader() self.nonce = 0 self.shortids_length = 0 self.shortids = [] self.prefilled_txn_length = 0 self.prefilled_txn = [] def deserialize(self, f): self.header.deserialize(f) self.nonce = struct.unpack("= 2: self.merkleRootQuorums = deser_uint256(f) if self.version >= 3: self.bestCLHeightDiff = deser_compact_size(f) self.bestCLSignature = f.read(96) self.lockedAmount = struct.unpack("= 2: r += ser_uint256(self.merkleRootQuorums) if self.version >= 3: r += ser_compact_size(self.bestCLHeightDiff) r += self.bestCLSignature r += struct.pack(" 0: self.vchData = f.read(size) self.nObjectType = struct.unpack(" 0: self.vchSig = f.read(size) def serialize(self): r = b"" r += ser_uint256(self.nParentHash) r += struct.pack(" 0: self.vchSig = f.read(size) def serialize(self): r = b"" r += self.masternodeOutpoint.serialize() r += ser_uint256(self.nParentHash) r += struct.pack(" class msg_headers: __slots__ = ("headers",) msgtype = b"headers" def __init__(self, headers=None): self.headers = headers if headers is not None else [] def deserialize(self, f): # comment in dashd indicates these should be deserialized as blocks blocks = deser_vector(f, CBlock) for x in blocks: self.headers.append(CBlockHeader(x)) def serialize(self): blocks = [CBlock(x) for x in self.headers] return ser_vector(blocks) def __repr__(self): return "msg_headers(headers=%s)" % repr(self.headers) # headers message has # class msg_headers2: __slots__ = ("headers",) msgtype = b"headers2" def __init__(self, headers=None): self.headers = headers if headers is not None else [] def deserialize(self, f): self.headers = deser_vector(f, CompressibleBlockHeader) last_unique_versions = [] for idx in range(len(self.headers)): self.headers[idx].uncompress(self.headers[:idx], last_unique_versions) def serialize(self): last_unique_versions = [] for idx in range(len(self.headers)): self.headers[idx].compress(self.headers[:idx], last_unique_versions) return ser_vector(self.headers) def __repr__(self): return "msg_headers2(headers=%s)" % repr(self.headers) class msg_merkleblock: __slots__ = ("merkleblock",) msgtype = b"merkleblock" def __init__(self, merkleblock=None): if merkleblock is None: self.merkleblock = CMerkleBlock() else: self.merkleblock = merkleblock def deserialize(self, f): self.merkleblock.deserialize(f) def serialize(self): return self.merkleblock.serialize() def __repr__(self): return "msg_merkleblock(merkleblock=%s)" % (repr(self.merkleblock)) class msg_filterload: __slots__ = ("data", "nHashFuncs", "nTweak", "nFlags") msgtype = b"filterload" def __init__(self, data=b'00', nHashFuncs=0, nTweak=0, nFlags=0): self.data = data self.nHashFuncs = nHashFuncs self.nTweak = nTweak self.nFlags = nFlags def deserialize(self, f): self.data = deser_string(f) self.nHashFuncs = struct.unpack("