diff options
Diffstat (limited to 'qa/rpc-tests/test_framework/mininode.py')
-rwxr-xr-x | qa/rpc-tests/test_framework/mininode.py | 212 |
1 files changed, 187 insertions, 25 deletions
diff --git a/qa/rpc-tests/test_framework/mininode.py b/qa/rpc-tests/test_framework/mininode.py index 6612b99b84..4548e2e7c0 100755 --- a/qa/rpc-tests/test_framework/mininode.py +++ b/qa/rpc-tests/test_framework/mininode.py @@ -28,7 +28,7 @@ import asyncore import time import sys import random -from binascii import hexlify, unhexlify +from .util import hex_str_to_bytes, bytes_to_hex_str from io import BytesIO from codecs import encode import hashlib @@ -46,6 +46,11 @@ MAX_BLOCK_SIZE = 1000000 COIN = 100000000 # 1 btc in satoshis +NODE_NETWORK = (1 << 0) +NODE_GETUTXO = (1 << 1) +NODE_BLOOM = (1 << 2) +NODE_WITNESS = (1 << 3) + # Keep our own socket map for asyncore, so that we can track disconnects # ourselves (to workaround an issue with closing an asyncore socket when # using select) @@ -63,6 +68,8 @@ mininode_lock = RLock() def sha256(s): return hashlib.new('sha256', s).digest() +def ripemd160(s): + return hashlib.new('ripemd160', s).digest() def hash256(s): return sha256(sha256(s)) @@ -133,7 +140,10 @@ def deser_vector(f, c): return r -def ser_vector(l): +# ser_function_name: Allow for an alternate serialization function on the +# entries in the vector (we use this for serializing the vector of transactions +# for a witness block). +def ser_vector(l, ser_function_name=None): r = b"" if len(l) < 253: r = struct.pack("B", len(l)) @@ -144,7 +154,10 @@ def ser_vector(l): else: r = struct.pack("<BQ", 255, len(l)) for i in l: - r += i.serialize() + if ser_function_name: + r += getattr(i, ser_function_name)() + else: + r += i.serialize() return r @@ -239,12 +252,12 @@ def ser_int_vector(l): # Deserialize from a hex string representation (eg from RPC) def FromHex(obj, hex_string): - obj.deserialize(BytesIO(unhexlify(hex_string.encode('ascii')))) + 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 hexlify(obj.serialize()).decode('ascii') + return bytes_to_hex_str(obj.serialize()) # Objects that map to bitcoind objects, which can be serialized/deserialized @@ -273,12 +286,16 @@ class CAddress(object): return "CAddress(nServices=%i ip=%s port=%i)" % (self.nServices, self.ip, self.port) +MSG_WITNESS_FLAG = 1<<30 class CInv(object): typemap = { 0: "Error", 1: "TX", - 2: "Block"} + 2: "Block", + 1|MSG_WITNESS_FLAG: "WitnessTx", + 2|MSG_WITNESS_FLAG : "WitnessBlock" + } def __init__(self, t=0, h=0): self.type = t @@ -362,7 +379,7 @@ class CTxIn(object): def __repr__(self): return "CTxIn(prevout=%s scriptSig=%s nSequence=%i)" \ - % (repr(self.prevout), hexlify(self.scriptSig), + % (repr(self.prevout), bytes_to_hex_str(self.scriptSig), self.nSequence) @@ -384,7 +401,67 @@ class CTxOut(object): def __repr__(self): return "CTxOut(nValue=%i.%08i scriptPubKey=%s)" \ % (self.nValue // COIN, self.nValue % COIN, - hexlify(self.scriptPubKey)) + bytes_to_hex_str(self.scriptPubKey)) + + +class CScriptWitness(object): + def __init__(self): + # stack is a vector of strings + self.stack = [] + + def __repr__(self): + return "CScriptWitness(%s)" % \ + (",".join([bytes_to_hex_str(x) for x in self.stack])) + + def is_null(self): + if self.stack: + return False + return True + + +class CTxinWitness(object): + def __init__(self): + self.scriptWitness = CScriptWitness() + + def deserialize(self, f): + self.scriptWitness.stack = deser_string_vector(f) + + def serialize(self): + return ser_string_vector(self.scriptWitness.stack) + + def __repr__(self): + return repr(self.scriptWitness) + + def is_null(self): + return self.scriptWitness.is_null() + + +class CTxWitness(object): + def __init__(self): + self.vtxinwit = [] + + def deserialize(self, f): + for i in range(len(self.vtxinwit)): + self.vtxinwit[i].deserialize(f) + + def serialize(self): + r = b"" + # This is different than the usual vector serialization -- + # we omit the length of the vector, which is required to be + # the same length as the transaction's vin vector. + for x in self.vtxinwit: + r += x.serialize() + return r + + def __repr__(self): + return "CTxWitness(%s)" % \ + (';'.join([repr(x) for x in self.vtxinwit])) + + def is_null(self): + for x in self.vtxinwit: + if not x.is_null(): + return False + return True class CTransaction(object): @@ -393,6 +470,7 @@ class CTransaction(object): self.nVersion = 1 self.vin = [] self.vout = [] + self.wit = CTxWitness() self.nLockTime = 0 self.sha256 = None self.hash = None @@ -401,32 +479,80 @@ class CTransaction(object): self.vin = copy.deepcopy(tx.vin) self.vout = copy.deepcopy(tx.vout) self.nLockTime = tx.nLockTime - self.sha256 = None - self.hash = None + self.sha256 = tx.sha256 + self.hash = tx.hash + self.wit = copy.deepcopy(tx.wit) def deserialize(self, f): self.nVersion = struct.unpack("<i", f.read(4))[0] self.vin = deser_vector(f, CTxIn) - self.vout = deser_vector(f, CTxOut) + flags = 0 + if len(self.vin) == 0: + flags = struct.unpack("<B", f.read(1))[0] + # Not sure why flags can't be zero, but this + # matches the implementation in bitcoind + if (flags != 0): + self.vin = deser_vector(f, CTxIn) + self.vout = deser_vector(f, CTxOut) + else: + self.vout = deser_vector(f, CTxOut) + if flags != 0: + self.wit.vtxinwit = [CTxinWitness()]*len(self.vin) + self.wit.deserialize(f) self.nLockTime = struct.unpack("<I", f.read(4))[0] self.sha256 = None self.hash = None - def serialize(self): + def serialize_without_witness(self): + r = b"" + r += struct.pack("<i", self.nVersion) + r += ser_vector(self.vin) + r += ser_vector(self.vout) + r += struct.pack("<I", self.nLockTime) + return r + + # Only serialize with witness when explicitly called for + def serialize_with_witness(self): + flags = 0 + if not self.wit.is_null(): + flags |= 1 r = b"" r += struct.pack("<i", self.nVersion) + if flags: + dummy = [] + r += ser_vector(dummy) + r += struct.pack("<B", flags) r += ser_vector(self.vin) r += ser_vector(self.vout) + if flags & 1: + if (len(self.wit.vtxinwit) != len(self.vin)): + # vtxinwit must have the same length as vin + self.wit.vtxinwit = self.wit.vtxinwit[:len(self.vin)] + for i in range(len(self.wit.vtxinwit), len(self.vin)): + self.wit.vtxinwit.append(CTxinWitness()) + r += self.wit.serialize() r += struct.pack("<I", self.nLockTime) return r + # Regular serialization is without witness -- must explicitly + # call serialize_with_witness to include witness data. + def serialize(self): + return self.serialize_without_witness() + + # Recalculate the txid (transaction hash without witness) def rehash(self): self.sha256 = None self.calc_sha256() - def calc_sha256(self): + # We will only cache the serialization without witness in + # self.sha256 and self.hash -- those are expected to be the txid. + def calc_sha256(self, with_witness=False): + if with_witness: + # Don't cache the result, just return it + return uint256_from_str(hash256(self.serialize_with_witness())) + if self.sha256 is None: - self.sha256 = uint256_from_str(hash256(self.serialize())) + self.sha256 = uint256_from_str(hash256(self.serialize_without_witness())) self.hash = encode(hash256(self.serialize())[::-1], 'hex_codec').decode('ascii') def is_valid(self): @@ -518,17 +644,17 @@ class CBlock(CBlockHeader): super(CBlock, self).deserialize(f) self.vtx = deser_vector(f, CTransaction) - def serialize(self): + def serialize(self, with_witness=False): r = b"" r += super(CBlock, self).serialize() - r += ser_vector(self.vtx) + if with_witness: + r += ser_vector(self.vtx, "serialize_with_witness") + else: + r += ser_vector(self.vtx) return r - def calc_merkle_root(self): - hashes = [] - for tx in self.vtx: - tx.calc_sha256() - hashes.append(ser_uint256(tx.sha256)) + # Calculate the merkle root given a vector of transaction hashes + def get_merkle_root(self, hashes): while len(hashes) > 1: newhashes = [] for i in range(0, len(hashes), 2): @@ -537,6 +663,24 @@ class CBlock(CBlockHeader): 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 calc_witness_merkle_root(self): + # For witness root purposes, the hash of the + # coinbase, with witness, is defined to be 0...0 + hashes = [ser_uint256(0)] + + for tx in self.vtx[1:]: + # Calculate the hashes with witness data + hashes.append(ser_uint256(tx.calc_sha256(True))) + + return self.get_merkle_root(hashes) + def is_valid(self): self.calc_sha256() target = uint256_from_compact(self.nBits) @@ -812,11 +956,16 @@ class msg_tx(object): self.tx.deserialize(f) def serialize(self): - return self.tx.serialize() + return self.tx.serialize_without_witness() def __repr__(self): return "msg_tx(tx=%s)" % (repr(self.tx)) +class msg_witness_tx(msg_tx): + + def serialize(self): + return self.tx.serialize_with_witness() + class msg_block(object): command = b"block" @@ -849,6 +998,12 @@ class msg_generic(object): def __repr__(self): return "msg_generic()" +class msg_witness_block(msg_block): + + def serialize(self): + r = self.block.serialize(with_witness=True) + return r + class msg_getaddr(object): command = b"getaddr" @@ -947,6 +1102,7 @@ class msg_sendheaders(object): def __repr__(self): return "msg_sendheaders()" + # getheaders message has # number of entries # vector of hashes @@ -1068,6 +1224,8 @@ class NodeConnCB(object): # tests; it causes message delivery to sleep for the specified time # before acquiring the global lock and delivering the next message. self.deliver_sleep_time = None + # Remember the services our peer has advertised + self.peer_services = None def set_deliver_sleep_time(self, value): with mininode_lock: @@ -1105,6 +1263,7 @@ class NodeConnCB(object): conn.ver_send = min(MY_VERSION, message.nVersion) if message.nVersion < 209: conn.ver_recv = conn.ver_send + conn.nServices = message.nServices def on_verack(self, conn, message): conn.ver_recv = conn.ver_send @@ -1135,6 +1294,7 @@ class NodeConnCB(object): def on_mempool(self, conn): pass def on_pong(self, conn, message): pass def on_feefilter(self, conn, message): pass + def on_sendheaders(self, conn, message): pass # More useful callbacks and functions for NodeConnCB's which have a single NodeConn class SingleNodeConnCB(NodeConnCB): @@ -1183,15 +1343,16 @@ class NodeConn(asyncore.dispatcher): b"getheaders": msg_getheaders, b"reject": msg_reject, b"mempool": msg_mempool, - b"feefilter": msg_feefilter + b"feefilter": msg_feefilter, + b"sendheaders": msg_sendheaders } MAGIC_BYTES = { "mainnet": b"\xf9\xbe\xb4\xd9", # mainnet "testnet3": b"\x0b\x11\x09\x07", # testnet3 - "regtest": b"\xfa\xbf\xb5\xda" # regtest + "regtest": b"\xfa\xbf\xb5\xda", # regtest } - def __init__(self, dstaddr, dstport, rpc, callback, net="regtest", services=1): + def __init__(self, dstaddr, dstport, rpc, callback, net="regtest", services=NODE_NETWORK): asyncore.dispatcher.__init__(self, map=mininode_socket_map) self.log = logging.getLogger("NodeConn(%s:%d)" % (dstaddr, dstport)) self.dstaddr = dstaddr @@ -1206,6 +1367,7 @@ class NodeConn(asyncore.dispatcher): self.network = net self.cb = callback self.disconnect = False + self.nServices = 0 # stuff version msg into sendbuf vt = msg_version() |