#!/usr/bin/env python3 # Copyright (c) 2010 ArtForz -- public domain half-a-node # Copyright (c) 2012 Jeff Garzik # Copyright (c) 2010-2017 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 codecs import encode import copy from collections import namedtuple import hashlib from io import BytesIO import random import socket import struct import time from test_framework.siphash import siphash256 from test_framework.util import hex_str_to_bytes import dash_hash MIN_VERSION_SUPPORTED = 60001 MY_VERSION = 70223 # ADDRV2_PROTO_VERSION MY_SUBVERSION = b"/python-mininode-tester:0.0.3%s/" MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37) MAX_LOCATOR_SZ = 101 MAX_BLOCK_SIZE = 1000000 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 NODE_NETWORK = (1 << 0) NODE_GETUTXO = (1 << 1) 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_CMPCT_BLOCK = 20 MSG_TYPE_MASK = 0xffffffff >> 2 FILTER_TYPE_BASIC = 0 # Serialization/deserialization tools def sha256(s): return hashlib.new('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 i 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 i 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 # Deserialize from a hex string representation (eg from RPC) def FromHex(obj, hex_string): obj.deserialize(BytesIO(hex_str_to_bytes(hex_string))) return obj # Convert a binary-serializable object to hex (eg for submission via RPC) def ToHex(obj): return obj.serialize().hex() # 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 ADDRV2_NET_NAME = { NET_IPV4: "IPv4" } ADDRV2_ADDRESS_LENGTH = { NET_IPV4: 4 } def __init__(self): self.time = 0 self.nServices = 1 self.net = self.NET_IPV4 self.ip = "0.0.0.0" self.port = 0 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 == self.NET_IPV4 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", 1: "TX", 2: "Block", 20: "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 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 = 1 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) def serialize(self): r = b"" r += struct.pack("= 2: r += ser_uint256(self.merkleRootQuorums) return r class CSimplifiedMNListEntry: __slots__ = ("proRegTxHash", "confirmedHash", "service", "pubKeyOperator", "keyIDVoting", "isValid") def __init__(self): self.set_null() def set_null(self): self.proRegTxHash = 0 self.confirmedHash = 0 self.service = CService() self.pubKeyOperator = b'\x00' * 48 self.keyIDVoting = 0 self.isValid = False def deserialize(self, f): self.proRegTxHash = deser_uint256(f) self.confirmedHash = deser_uint256(f) self.service.deserialize(f) self.pubKeyOperator = f.read(48) self.keyIDVoting = f.read(20) self.isValid = struct.unpack(" 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("= 70001: # Relay field is optional for version 70001 onwards try: self.nRelay = struct.unpack(" class msg_headers: __slots__ = ("headers",) command = 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",) command = 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_sendcmpct: __slots__ = ("announce", "version") command = b"sendcmpct" def __init__(self): self.announce = False self.version = 1 def deserialize(self, f): self.announce = struct.unpack("