Added "mwebheader" getdata type and CMerkleBlockWithMWEB response

pull/944/head
David Burkett 2 years ago committed by Loshan T
parent 0698e23d7a
commit 215edcfaa8

@ -178,3 +178,14 @@ uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch, std::ve
return uint256();
return hashMerkleRoot;
}
CMerkleBlockWithMWEB::CMerkleBlockWithMWEB(const CBlock& block)
{
hogex = block.GetHogEx();
assert(hogex != nullptr);
mweb_header = block.mweb_block.GetMWEBHeader();
assert(mweb_header != nullptr);
merkle = CMerkleBlock(block, std::set<uint256>{hogex->GetHash()});
}

@ -155,4 +155,53 @@ private:
CMerkleBlock(const CBlock& block, CBloomFilter* filter, const std::set<uint256>* txids);
};
/**
* Can be requested by MWEB light clients to retrieve and verify MWEB headers.
*
* Serializes as merkleblock + tx (HogEx) + MWEB header:
*
* Merkle Block
* - uint32 version (4 bytes)
* - uint256 prev_block (32 bytes)
* - uint256 merkle_root (32 bytes)
* - uint32 timestamp (4 bytes)
* - uint32 bits (4 bytes)
* - uint32 nonce (4 bytes)
* - uint32 total_transactions (4 bytes)
* - varint number of hashes (1-3 bytes)
* - uint256[] hashes in depth-first order (<= 32*N bytes)
* - varint number of bytes of flag bits (1-3 bytes)
* - byte[] flag bits, packed per 8 in a byte, least significant bit first (<= 2*N-1 bits)
*
* HogEx Transaction
* - CTransaction hogex transaction (? bytes)
*
* MWEB Header
* - int32 height (4 bytes)
* - uint256 output_root (32 bytes)
* - uint256 kernel_root (32 bytes)
* - uint256 leafset_root (32 bytes)
* - uint256 kernel_offset (32 bytes)
* - uint256 stealth_offset (32 bytes)
* - uint64 output_mmr_size (8 bytes)
* - uint64 kernel_mmr_size (8 bytes)
*
* NOTE: The class assumes that the given CBlock contains a hogex transaction and MWEB block data.
* If it does not, an assertion will be hit.
*/
class CMerkleBlockWithMWEB
{
private:
CMerkleBlock merkle;
CTransactionRef hogex;
mw::Header::CPtr mweb_header;
public:
CMerkleBlockWithMWEB() { }
CMerkleBlockWithMWEB(const CBlock& block);
SERIALIZE_METHODS(CMerkleBlockWithMWEB, obj) { READWRITE(obj.merkle, obj.hogex, obj.mweb_header); }
};
#endif // BITCOIN_MERKLEBLOCK_H

@ -1680,6 +1680,11 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
} else {
connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock));
}
} else if (inv.IsMsgMWEBHeader()) {
if (pblock->GetHogEx() != nullptr && !pblock->mweb_block.IsNull()) {
CMerkleBlockWithMWEB merkle_block_with_mweb(*pblock);
connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MWEBHEADER, merkle_block_with_mweb));
}
}
}

@ -46,6 +46,7 @@ const char *CFHEADERS="cfheaders";
const char *GETCFCHECKPT="getcfcheckpt";
const char *CFCHECKPT="cfcheckpt";
const char *WTXIDRELAY="wtxidrelay";
const char *MWEBHEADER="mwebheader";
} // namespace NetMsgType
/** All known message types. Keep this in the same order as the list of
@ -86,6 +87,7 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::GETCFCHECKPT,
NetMsgType::CFCHECKPT,
NetMsgType::WTXIDRELAY,
NetMsgType::MWEBHEADER,
};
const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
@ -176,6 +178,7 @@ std::string CInv::GetCommand() const
case MSG_BLOCK: return cmd.append(NetMsgType::BLOCK);
case MSG_FILTERED_BLOCK: return cmd.append(NetMsgType::MERKLEBLOCK);
case MSG_CMPCT_BLOCK: return cmd.append(NetMsgType::CMPCTBLOCK);
case MSG_MWEB_HEADER: return cmd.append(NetMsgType::MWEBHEADER);
default:
throw std::out_of_range(strprintf("CInv::GetCommand(): type=%d unknown type", type));
}

@ -260,6 +260,13 @@ extern const char* CFCHECKPT;
* @since protocol version 70016 as described by BIP 339.
*/
extern const char* WTXIDRELAY;
/**
* Contains a CMerkleBlockWithMWEB.
* Sent in response to a getdata message which requested a
* block using the inventory type MSG_MWEB_HEADER.
* @since protocol version 70017 as described by LIP-0007
*/
extern const char* MWEBHEADER;
}; // namespace NetMsgType
/* Get a vector of all valid message types (see above) */
@ -427,6 +434,7 @@ enum GetDataMsg : uint32_t {
// MSG_FILTERED_WITNESS_BLOCK = MSG_FILTERED_BLOCK | MSG_WITNESS_FLAG,
MSG_MWEB_BLOCK = MSG_WITNESS_BLOCK | MSG_MWEB_FLAG,
MSG_MWEB_TX = MSG_WITNESS_TX | MSG_MWEB_FLAG,
MSG_MWEB_HEADER = 8 | MSG_MWEB_FLAG, //!< Defined in LIP-0007
};
/** inv message data */
@ -451,6 +459,7 @@ public:
bool IsMsgCmpctBlk() const { return type == MSG_CMPCT_BLOCK; }
bool IsMsgWitnessBlk() const { return type == MSG_WITNESS_BLOCK; }
bool IsMsgMWEBBlk() const { return type == MSG_MWEB_BLOCK; }
bool IsMsgMWEBHeader() const { return type == MSG_MWEB_HEADER; }
// Combined-message helper methods
bool IsGenTxMsg() const
@ -459,7 +468,7 @@ public:
}
bool IsGenBlkMsg() const
{
return type == MSG_BLOCK || type == MSG_FILTERED_BLOCK || type == MSG_CMPCT_BLOCK || type == MSG_WITNESS_BLOCK || type == MSG_MWEB_BLOCK;
return type == MSG_BLOCK || type == MSG_FILTERED_BLOCK || type == MSG_CMPCT_BLOCK || type == MSG_WITNESS_BLOCK || type == MSG_MWEB_BLOCK || type == MSG_MWEB_HEADER;
}
uint32_t type;

@ -9,7 +9,7 @@
* network protocol versioning
*/
static const int PROTOCOL_VERSION = 70016;
static const int PROTOCOL_VERSION = 70017;
//! initial proto version, to be increased after version/verack negotiation
static const int INIT_PROTO_VERSION = 209;
@ -38,6 +38,9 @@ static const int INVALID_CB_NO_BAN_VERSION = 70015;
//! "wtxidrelay" command for wtxid-based relay starts with this version
static const int WTXID_RELAY_VERSION = 70016;
//! "mwebheader" command for light client MWEB support starts with this version
static const int MWEB_SYNC_VERSION = 70017;
// Make sure that none of the values above collide with
// `SERIALIZE_TRANSACTION_NO_WITNESS` or `ADDRV2_FORMAT`.

@ -8,7 +8,7 @@ from test_framework.blocktools import (create_coinbase, NORMAL_GBT_REQUEST_PARAM
from test_framework.messages import (CBlock, MWEBBlock)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
from test_framework.ltc_util import create_hogex, get_mweb_header_tip, setup_mweb_chain
from test_framework.ltc_util import create_hogex, get_mweb_header, setup_mweb_chain
class MWEBMiningTest(BitcoinTestFramework):
def set_test_params(self):
@ -28,7 +28,7 @@ class MWEBMiningTest(BitcoinTestFramework):
next_height = int(gbt["height"])
# Build MWEB block
mweb_header = get_mweb_header_tip(node)
mweb_header = get_mweb_header(node)
mweb_header.height = next_height
mweb_header.rehash()
mweb_block = MWEBBlock(mweb_header)
@ -46,7 +46,7 @@ class MWEBMiningTest(BitcoinTestFramework):
block.nBits = int(gbt["bits"], 16)
block.nNonce = 0
block.vtx = vtx
block.mweb_block = mweb_block.serialize().hex()
block.mweb_block = mweb_block
block.hashMerkleRoot = block.calc_merkle_root()
# Call getblocktemplate with the block proposal

@ -0,0 +1,108 @@
#!/usr/bin/env python3
# Copyright (c) 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.
"""
Test LIP-0007
1. Test getdata 'mwebheader' *before* MWEB activation
2. Test getdata 'mwebheader' *after* MWEB activation
"""
from test_framework.messages import (
CBlockHeader,
CInv,
Hash,
hash256,
msg_getdata,
MSG_MWEB_HEADER,
)
from test_framework.p2p import P2PInterface, p2p_lock
from test_framework.script import MAX_SCRIPT_ELEMENT_SIZE
from test_framework.test_framework import BitcoinTestFramework
from test_framework.ltc_util import FIRST_MWEB_HEIGHT, get_hogex_tx, get_mweb_header
from test_framework.util import assert_equal
# Can be used to mimic a light client requesting MWEB data from a full node
class MockLightClient(P2PInterface):
def __init__(self):
super().__init__()
self.merkle_blocks_with_mweb = {}
self.block_headers = {}
def request_mweb_header(self, block_hash):
want = msg_getdata([CInv(MSG_MWEB_HEADER, int(block_hash, 16))])
self.send_message(want)
def on_mwebheader(self, message):
self.merkle_blocks_with_mweb[message.header_hash()] = message.merkleblockwithmweb
def on_block(self, message):
message.block.calc_sha256()
self.block_headers[Hash(message.block.sha256)] = CBlockHeader(message.block)
class MWEBP2PTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.extra_args = [['-whitelist=noban@127.0.0.1']] # immediate tx relay
self.num_nodes = 1
def run_test(self):
node = self.nodes[0]
light_client = node.add_p2p_connection(MockLightClient())
self.log.info("Generate pre-MWEB blocks")
pre_mweb_block_hash = node.generate(FIRST_MWEB_HEIGHT - 1)[-1]
self.log.info("Request mweb_header for pre-MWEB block '{}'".format(pre_mweb_block_hash))
light_client.request_mweb_header(pre_mweb_block_hash)
self.log.info("Activate MWEB")
node.sendtoaddress(node.getnewaddress(address_type='mweb'), 1)
post_mweb_block_hash = node.generate(1)[0]
self.log.info("Request mweb_header for block '{}'".format(post_mweb_block_hash))
light_client.request_mweb_header(post_mweb_block_hash)
self.log.info("Waiting for mweb_header")
light_client.wait_for_mwebheader(post_mweb_block_hash, 5)
light_client.wait_for_block(int(post_mweb_block_hash, 16), 5)
self.log.info("Assert results")
# Before MWEB activation, no merkle block should be returned
assert pre_mweb_block_hash not in light_client.merkle_blocks_with_mweb
# After MWEB activation, the requested merkle block should be returned
assert post_mweb_block_hash in light_client.merkle_blocks_with_mweb
merkle_block_with_mweb = light_client.merkle_blocks_with_mweb[post_mweb_block_hash]
# Check block header is correct
assert Hash.from_hex(post_mweb_block_hash) in light_client.block_headers
block_header = light_client.block_headers[Hash.from_hex(post_mweb_block_hash)]
assert_equal(block_header, merkle_block_with_mweb.merkle.header)
# Check MWEB header is correct
mweb_header = get_mweb_header(node, post_mweb_block_hash)
assert_equal(mweb_header, merkle_block_with_mweb.mweb_header)
# Check HogEx transaction is correct
hogex_tx = get_hogex_tx(node, post_mweb_block_hash)
assert_equal(hogex_tx, merkle_block_with_mweb.hogex)
# Check Merkle tree
merkle_tree = merkle_block_with_mweb.merkle.txn
assert_equal(3, merkle_tree.nTransactions)
assert_equal(2, len(merkle_tree.vHash))
left_hash = Hash(merkle_tree.vHash[0])
right_hash = Hash(merkle_tree.vHash[1])
assert_equal(Hash.from_hex(hogex_tx.hash), right_hash)
right_branch_bytes = hash256(right_hash.serialize() + right_hash.serialize())
merkle_root_bytes = hash256(left_hash.serialize() + right_branch_bytes)
assert_equal(Hash.from_byte_arr(merkle_root_bytes), Hash(block_header.hashMerkleRoot))
if __name__ == '__main__':
MWEBP2PTest().main()

@ -6,17 +6,22 @@
import os
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, MWEBHeader
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, MWEBHeader
from test_framework.util import get_datadir_path, initialize_datadir, satoshi_round
from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, hogaddr_script
from test_framework.test_node import TestNode
FIRST_MWEB_HEIGHT = 432 # Height of the first block to contain an MWEB if using default regtest params
"""Create a txout with a given amount and scriptPubKey
Mines coins as needed.
confirmed - txouts created will be confirmed in the blockchain;
unconfirmed otherwise.
Returns the 'COutPoint' of the UTXO
"""
def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
fee = 1*COIN
@ -56,26 +61,59 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
return COutPoint(int(txid, 16), 0)
def setup_mweb_chain(node):
"""Generates all pre-MWEB blocks, pegs 1 coin into the MWEB,
then mines the first MWEB block which includes that pegin.
mining_node - The node to use to generate blocks
"""
def setup_mweb_chain(mining_node):
# Create all pre-MWEB blocks
node.generate(431)
mining_node.generate(FIRST_MWEB_HEIGHT - 1)
# Pegin some coins
node.sendtoaddress(node.getnewaddress(address_type='mweb'), 1)
mining_node.sendtoaddress(mining_node.getnewaddress(address_type='mweb'), 1)
# Create some blocks - activate MWEB
node.generate(1)
mining_node.generate(1)
def get_hog_addr_txout(node):
best_block = node.getblock(node.getbestblockhash(), 2)
hogex_tx = best_block['tx'][-1] # TODO: Should validate that the tx is marked as a hogex tx
hog_addr = hogex_tx['vout'][0]
"""Retrieves the HogEx transaction for the block.
return CTxOut(int(hog_addr['value'] * COIN), hog_addr['scriptPubKey'])
node - The node to use to lookup the transaction.
block_hash - The block to retrieve the HogEx for. If not provided, the best block hash will be used.
def get_mweb_header_tip(node):
best_block = node.getblock(node.getbestblockhash(), 2)
Returns the HogEx as a 'CTransaction'
"""
def get_hogex_tx(node, block_hash = None):
block_hash = block_hash or node.getbestblockhash()
hogex_tx_id = node.getblock(block_hash)['tx'][-1]
hogex_tx = FromHex(CTransaction(), node.getrawtransaction(hogex_tx_id, False, block_hash)) # TODO: Should validate that the tx is marked as a hogex tx
hogex_tx.rehash()
return hogex_tx
"""Retrieves the HogAddr for a block.
node - The node to use to lookup the HogAddr.
block_hash - The block to retrieve the HogAddr for. If not provided, the best block hash will be used.
Returns the HogAddr as a 'CTxOut'
"""
def get_hog_addr_txout(node, block_hash = None):
return get_hogex_tx(node, block_hash).vout[0]
"""Retrieves the MWEB header for a block.
node - The node to use to lookup the MWEB header.
block_hash - The block to retrieve the MWEB header for. If not provided, the best block hash will be used.
Returns the MWEB header as an 'MWEBHeader' or None if the block doesn't contain MWEB data.
"""
def get_mweb_header(node, block_hash = None):
block_hash = block_hash or node.getbestblockhash()
best_block = node.getblock(block_hash, 2)
if not 'mweb' in best_block:
return None
@ -83,19 +121,26 @@ def get_mweb_header_tip(node):
mweb_header.from_json(best_block['mweb'])
return mweb_header
def create_hogex(node, mweb_hash):
best_block = node.getblock(node.getbestblockhash(), 2)
"""Creates a HogEx transaction that commits to the provided MWEB hash.
# TODO: In the future, this should support passing in pegins and pegouts.
hogex_tx = best_block['tx'][-1] # TODO: Should validate that the tx is marked as a hogex tx
hog_addr = hogex_tx['vout'][0]
node - The node to use to lookup the latest block.
mweb_hash - The block to retrieve the MWEB header for. If not provided, the best block hash will be used.
Returns the built HogEx transaction as a 'CTransaction'
"""
def create_hogex(node, mweb_hash):
hogex_tx = get_hogex_tx(node)
tx = CTransaction()
tx.vin = [CTxIn(COutPoint(int(hogex_tx['txid'], 16), 0))]
tx.vout = [CTxOut(int(hog_addr['value'] * COIN), hogaddr_script(mweb_hash))]
tx.vin = [CTxIn(COutPoint(hogex_tx.sha256, 0))]
tx.vout = [CTxOut(hogex_tx.vout[0].nValue, hogaddr_script(mweb_hash.to_byte_arr()))]
tx.hogex = True
tx.rehash()
return tx
""" Create a non-HD wallet from a temporary v15.1.0 node.
Returns the path of the wallet.dat.

@ -34,7 +34,7 @@ from test_framework.siphash import siphash256
from test_framework.util import hex_str_to_bytes, assert_equal
MIN_VERSION_SUPPORTED = 60001
MY_VERSION = 70016 # past wtxid relay
MY_VERSION = 70017 # past wtxid relay
MY_SUBVERSION = b"/python-p2p-tester:0.0.3/"
MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37)
@ -71,6 +71,7 @@ MSG_TYPE_MASK = 0xffffffff >> 3
MSG_WITNESS_TX = MSG_TX | MSG_WITNESS_FLAG
MSG_MWEB_BLOCK = MSG_BLOCK | MSG_WITNESS_FLAG | MSG_MWEB_FLAG
MSG_MWEB_TX = MSG_WITNESS_TX | MSG_MWEB_FLAG
MSG_MWEB_HEADER = 8 | MSG_MWEB_FLAG
FILTER_TYPE_BASIC = 0
@ -142,6 +143,52 @@ def uint256_from_compact(c):
return v
def deser_fixed_bytes(f, size):
r = []
for i in range(size):
r.append(struct.unpack("B", f.read(1))[0])
return r
def ser_fixed_bytes(u, size):
rs = b""
for i in range(size):
rs += struct.pack("B", u[i])
#rs += struct.pack("B", u & 0xFF)
#u >>= 8
return rs
def deser_pubkey(f):
r = 0
for i in range(33):
t = struct.unpack("B", f.read(1))[0]
r += t << (i * 8)
return r
def ser_pubkey(u):
rs = b""
for _ in range(33):
rs += struct.pack("B", u & 0xFF)
u >>= 8
return rs
def deser_signature(f):
r = 0
for i in range(64):
t = struct.unpack("B", f.read(1))[0]
r += t << (i * 8)
return r
def ser_signature(u):
rs = b""
for _ in range(64):
rs += struct.pack("B", u & 0xFF)
u >>= 8
return rs
# deser_function_name: Allow for an alternate deserialization function on the
# entries in the vector.
def deser_vector(f, c, deser_function_name=None):
@ -307,6 +354,7 @@ class CInv:
MSG_FILTERED_BLOCK: "filtered Block",
MSG_CMPCT_BLOCK: "CompactBlock",
MSG_WTX: "WTX",
MSG_MWEB_HEADER: "MWEB Header"
}
def __init__(self, t=0, h=0):
@ -647,6 +695,8 @@ class CTransaction:
return "CTransaction(nVersion=%i vin=%s vout=%s wit=%s nLockTime=%i)" \
% (self.nVersion, repr(self.vin), repr(self.vout), repr(self.wit), self.nLockTime)
def __eq__(self, other):
return isinstance(other, CTransaction) and repr(self) == repr(other)
class CBlockHeader:
__slots__ = ("hash", "hashMerkleRoot", "hashPrevBlock", "nBits", "nNonce",
@ -723,6 +773,9 @@ class CBlockHeader:
% (self.nVersion, self.hashPrevBlock, self.hashMerkleRoot,
time.ctime(self.nTime), self.nBits, self.nNonce)
def __eq__(self, other):
return isinstance(other, CBlockHeader) and repr(self) == repr(other)
BLOCK_HEADER_SIZE = len(CBlockHeader().serialize())
assert_equal(BLOCK_HEADER_SIZE, 80)
@ -1871,8 +1924,55 @@ class msg_cfcheckpt:
import blake3 as BLAKE3
def hex_reverse(h):
return "".join(reversed([h[i:i+2] for i in range(0, len(h), 2)]))
class Hash:
__slots__ = ("val")
def __init__(self, val = 0):
self.val = val
@classmethod
def from_hex(cls, hex_str):
return cls(int(hex_str, 16))
@classmethod
def from_rev_hex(cls, hex_str):
return cls(int(hex_reverse(hex_str), 16))
@classmethod
def from_byte_arr(cls, b):
return cls(deser_uint256(BytesIO(b)))
def to_hex(self):
return self.serialize().hex()
def to_byte_arr(self):
return hex_str_to_bytes(self.to_hex())
@classmethod
def deserialize(cls, f):
return cls(deser_uint256(f))
def serialize(self):
return ser_uint256(self.val)
def __hash__(self):
return hash(self.val)
def __str__(self):
return repr(self)
def __repr__(self):
return "Hash(val=0x{:x})".format(self.val)
def __eq__(self, other):
return isinstance(other, Hash) and self.val == other.val
def blake3(s):
return BLAKE3.blake3(s).hexdigest()
return Hash.from_rev_hex(BLAKE3.blake3(s).hexdigest())
def ser_varint(n):
r = b""
@ -1904,7 +2004,7 @@ def ser_mweb_block(b):
if b == None:
return struct.pack("B", 0)
else:
return struct.pack("B", 1) + hex_str_to_bytes(b)
return struct.pack("B", 1) + b.serialize()
def deser_mweb_block(f):
has_mweb = struct.unpack("B", f.read(1))[0]
@ -1919,14 +2019,260 @@ def ser_mweb_tx(t):
if t == None:
return struct.pack("B", 0)
else:
return struct.pack("B", 1) + hex_str_to_bytes(t)
return struct.pack("B", 1) + t.serialize()
def deser_mweb_tx(f):
has_mweb = struct.unpack("B", f.read(1))[0]
if has_mweb == 1:
return binascii.hexlify(f.read())
mweb_tx = MWEBTransaction()
return mweb_tx.deserialize(f)
else:
return None
class MWEBInput:
__slots__ = ("features", "output_id", "commitment", "input_pubkey",
"output_pubkey", "extradata", "signature", "hash")
def __init__(self):
self.features = 0
self.output_id = Hash()
self.commitment = None
self.input_pubkey = None
self.output_pubkey = None
self.extradata = None
self.signature = None
self.hash = None
def deserialize(self, f):
self.features = f.read(1)
self.output_id = Hash.deserialize(f)
self.commitment = deser_pubkey(f)
self.output_pubkey = deser_pubkey(f)
if self.features & 1:
self.input_pubkey = deser_pubkey(f)
self.extradata = None
if self.features & 2:
self.extradata = deser_fixed_bytes(f, deser_compact_size(f))
self.signature = deser_signature(f)
self.rehash()
def serialize(self):
r = b""
r += struct.pack("B", self.features)
r += ser_uint256(self.output_id)
r += ser_pubkey(self.commitment)
r += ser_pubkey(self.output_pubkey)
if self.features & 1:
r += ser_pubkey(self.input_pubkey)
if self.features & 2:
r += ser_compact_size(self.extradata)
r += ser_fixed_bytes(self.extradata, len(self.extradata))#extradata
r += ser_signature(self.signature)
return r
def rehash(self):
self.hash = blake3(self.serialize())
return self.hash.to_hex()
class MWEBOutputMessage:
__slots__ = ("features", "key_exchange_pubkey", "view_tag", "masked_value",
"masked_nonce", "extradata", "hash")
def __init__(self):
self.features = 0
self.key_exchange_pubkey = None
self.view_tag = 0
self.masked_value = None
self.masked_nonce = None
self.extradata = None
self.hash = None
def deserialize(self, f):
self.features = struct.unpack("B", f.read(1))[0]
if self.features & 1:
self.key_exchange_pubkey = deser_pubkey(f)
self.view_tag = struct.unpack("B", f.read(1))[0]
self.masked_value = struct.unpack("<q", f.read(8))[0]
self.masked_nonce = deser_fixed_bytes(f, 16)
self.extradata = None
if self.features & 2:
self.extradata = deser_fixed_bytes(f, deser_compact_size(f))
self.rehash()
def serialize(self):
r = b""
r += struct.pack("B", self.features)
if self.features & 1:
r += ser_pubkey(self.key_exchange_pubkey)
r += struct.pack("B", self.view_tag)
r += struct.pack("<q", self.masked_value)
r += ser_fixed_bytes(self.masked_nonce, 16)
if self.features & 2:
r += ser_compact_size(self.extradata)
r += ser_fixed_bytes(self.extradata, len(self.extradata))
return r
def rehash(self):
self.hash = blake3(self.serialize())
return self.hash.to_hex()
class MWEBOutput:
__slots__ = ("commitment", "sender_pubkey", "receiver_pubkey", "message",
"proof", "signature", "hash")
def __init__(self):
self.commitment = None
self.sender_pubkey = None
self.receiver_pubkey = None
self.message = MWEBOutputMessage()
self.proof = None
self.signature = None
self.hash = None
def deserialize(self, f):
self.commitment = deser_pubkey(f)
self.sender_pubkey = deser_pubkey(f)
self.receiver_pubkey = deser_pubkey(f)
self.message.deserialize(f)
self.proof = deser_fixed_bytes(f, 675)
self.signature = deser_signature(f)
self.rehash()
def serialize(self):
r = b""
r += ser_pubkey(self.commitment)
r += ser_pubkey(self.sender_pubkey)
r += ser_pubkey(self.receiver_pubkey)
r += self.message.serialize()
r += ser_fixed_bytes(self.proof, 675)
r += ser_signature(self.signature)
return r
def rehash(self):
self.hash = blake3(self.serialize())
return self.hash.to_hex()
class MWEBKernel:
__slots__ = ("features", "fee", "pegin", "pegouts", "lock_height",
"stealth_excess", "extradata", "excess", "signature", "hash")
def __init__(self):
self.features = 0
self.fee = None
self.pegin = None
self.pegouts = None
self.lock_height = None
self.stealth_excess = None
self.extradata = None
self.excess = None
self.signature = None
self.hash = None
def deserialize(self, f):
self.features = struct.unpack("B", f.read(1))[0]
self.fee = None
if self.features & 1:
self.fee = deser_varint(f)
self.pegin = None
if self.features & 2:
self.pegin = deser_varint(f)
self.pegouts = None
if self.features & 4:
self.pegouts = []# TODO: deser_vector(f, CPegout)
self.lock_height = None
if self.features & 8:
self.lock_height = deser_varint(f)
self.stealth_excess = None
if self.features & 16:
self.stealth_excess = Hash.deserialize(f)
self.extradata = None
if self.features & 32:
self.extradata = f.read(deser_compact_size(f))
self.excess = deser_pubkey(f)
self.signature = deser_signature(f)
self.rehash()
def serialize(self):
r = b""
r += struct.pack("B", self.features)
if self.features & 1:
r += ser_varint(self.fee)
if self.features & 2:
r += ser_varint(self.pegin)
if self.features & 4:
r += ser_vector(self.pegouts)
if self.features & 8:
r += ser_varint(self.lock_height)
if self.features & 16:
r += self.stealth_excess.serialize()
if self.features & 32:
r += ser_compact_size(len(self.extradata))
for i in range(len(self.extradata)):
struct.pack("B", self.extradata[i])
r += ser_pubkey(self.excess)
r += ser_signature(self.signature)
return r
def rehash(self):
self.hash = blake3(self.serialize())
return self.hash.to_hex()
def __repr__(self):
return "MWEBKernel(features=%d, excess=%s)" % (self.features, repr(self.excess))
class MWEBTxBody:
__slots__ = ("inputs", "outputs", "kernels")
def __init__(self):
self.inputs = []
self.outputs = []
self.kernels = []
def deserialize(self, f):
self.inputs = deser_vector(f, MWEBInput)
self.outputs = deser_vector(f, MWEBOutput)
self.kernels = deser_vector(f, MWEBKernel)
def serialize(self):
r = b""
r += ser_vector(self.inputs)
r += ser_vector(self.outputs)
r += ser_vector(self.kernels)
return r
def __repr__(self):
return "MWEBTxBody(inputs=%s, outputs=%d, kernels=%s)" % (repr(self.inputs), repr(self.outputs), repr(self.kernels))
class MWEBTransaction:
__slots__ = ("kernel_offset", "stealth_offset", "body", "hash")
def __init__(self):
self.kernel_offset = Hash()
self.stealth_offset = Hash()
self.body = MWEBTxBody()
self.hash = None
def deserialize(self, f):
self.kernel_offset = Hash.deserialize(f)
self.stealth_offset = Hash.deserialize(f)
self.body.deserialize(f)
self.rehash()
def serialize(self):
r = b""
r += self.kernel_offset.serialize()
r += self.stealth_offset.serialize()
r += self.body.serialize()
return r
def rehash(self):
self.hash = blake3(self.serialize())
return self.hash.to_hex()
def __repr__(self):
return "MWEBTransaction(kernel_offset=%s, stealth_offset=%d, body=%s, hash=%s)" % (repr(self.kernel_offset), repr(self.stealth_offset), repr(self.body), repr(self.hash))
class MWEBHeader:
__slots__ = ("height", "output_root", "kernel_root", "leafset_root",
@ -1934,33 +2280,33 @@ class MWEBHeader:
def __init__(self):
self.height = 0
self.output_root = 0
self.kernel_root = 0
self.leafset_root = 0
self.kernel_offset = 0
self.stealth_offset = 0
self.output_root = Hash()
self.kernel_root = Hash()
self.leafset_root = Hash()
self.kernel_offset = Hash()
self.stealth_offset = Hash()
self.num_txos = 0
self.num_kernels = 0
self.hash = None
def from_json(self, mweb_json):
self.height = mweb_json['height']
self.output_root = hex_str_to_bytes(mweb_json['output_root'])
self.kernel_root = hex_str_to_bytes(mweb_json['kernel_root'])
self.leafset_root = hex_str_to_bytes(mweb_json['leaf_root'])
self.kernel_offset = hex_str_to_bytes(mweb_json['kernel_offset'])
self.stealth_offset = hex_str_to_bytes(mweb_json['stealth_offset'])
self.output_root = Hash.from_rev_hex(mweb_json['output_root'])
self.kernel_root = Hash.from_rev_hex(mweb_json['kernel_root'])
self.leafset_root = Hash.from_rev_hex(mweb_json['leaf_root'])
self.kernel_offset = Hash.from_rev_hex(mweb_json['kernel_offset'])
self.stealth_offset = Hash.from_rev_hex(mweb_json['stealth_offset'])
self.num_txos = mweb_json['num_txos']
self.num_kernels = mweb_json['num_kernels']
self.rehash()
def deserialize(self, f):
self.height = deser_varint(f)
self.output_root = deser_uint256(f)
self.kernel_root = deser_uint256(f)
self.leafset_root = deser_uint256(f)
self.kernel_offset = deser_uint256(f)
self.stealth_offset = deser_uint256(f)
self.output_root = Hash.deserialize(f)
self.kernel_root = Hash.deserialize(f)
self.leafset_root = Hash.deserialize(f)
self.kernel_offset = Hash.deserialize(f)
self.stealth_offset = Hash.deserialize(f)
self.num_txos = deser_varint(f)
self.num_kernels = deser_varint(f)
self.rehash()
@ -1968,46 +2314,94 @@ class MWEBHeader:
def serialize(self):
r = b""
r += ser_varint(self.height)
r += self.output_root
r += self.kernel_root
r += self.leafset_root
r += self.kernel_offset
r += self.stealth_offset
r += self.output_root.serialize()
r += self.kernel_root.serialize()
r += self.leafset_root.serialize()
r += self.kernel_offset.serialize()
r += self.stealth_offset.serialize()
r += ser_varint(self.num_txos)
r += ser_varint(self.num_kernels)
return r
def rehash(self):
self.hash = blake3(self.serialize())
return self.hash
return self.hash.to_hex()
def __repr__(self):
return "MWEBHeader(height=%s, hash=%s)" % (repr(self.height), repr(self.hash))
return ("MWEBHeader(height=%s, output_root=%s, kernel_root=%s, leafset_root=%s, kernel_offset=%s, stealth_offset=%s, num_txos=%d, num_kernels=%d, hash=%s)" %
(repr(self.height), repr(self.output_root), repr(self.kernel_root), repr(self.leafset_root), repr(self.kernel_offset), repr(self.stealth_offset), self.num_txos, self.num_kernels, repr(self.hash)))
def __eq__(self, other):
return isinstance(other, MWEBHeader) and self.hash == other.hash
# TODO: Finish this class
class MWEBBlock:
__slots__ = ("header", "inputs", "outputs", "kernels")
__slots__ = ("header", "body")
def __init__(self, header = MWEBHeader(), inputs = [], outputs = [], kernels = []):
def __init__(self, header = MWEBHeader()):
self.header = copy.deepcopy(header)
self.inputs = inputs
self.outputs = outputs
self.kernels = kernels
self.body = MWEBTxBody()
def deserialize(self, f):
self.header.deserialize(f)
#self.inputs = deser_vector(f, MWEBInput)
#self.outputs = deser_vector(f, MWEBOutput)
#self.kernels = deser_vector(f, MWEBKernel)
self.body.deserialize(f)
self.rehash()
def serialize(self):
r = b""
r += self.header.serialize()
r += ser_vector(self.inputs)
r += ser_vector(self.outputs)
r += ser_vector(self.kernels)
r += self.body.serialize()
return r
def rehash(self):
return self.header.rehash()
return self.header.rehash()
def __repr__(self):
return "MWEBBlock(header=%s, body=%s)" % (repr(self.header), repr(self.body))
class CMerkleBlockWithMWEB:
__slots__ = ("merkle", "hogex", "mweb_header")
def __init__(self):
self.merkle = CMerkleBlock()
self.hogex = CTransaction()
self.mweb_header = MWEBHeader()
def deserialize(self, f):
self.merkle.deserialize(f)
self.hogex.deserialize(f)
self.mweb_header.deserialize(f)
def serialize(self):
r = b""
r += self.merkle.serialize()
r += self.hogex.serialize_with_mweb()
r += self.mweb_header.serialize()
return r
def __repr__(self):
return "CMerkleBlockWithMWEB(merkle=%s, hogex=%s, mweb_header=%s)" % (repr(self.merkle), repr(self.hogex), repr(self.mweb_header))
class msg_mwebheader:
__slots__ = ("merkleblockwithmweb",)
msgtype = b"mwebheader"
def __init__(self, merkleblockwithmweb=None):
if merkleblockwithmweb is None:
self.merkleblockwithmweb = CMerkleBlockWithMWEB()
else:
self.merkleblockwithmweb = merkleblockwithmweb
def deserialize(self, f):
self.merkleblockwithmweb.deserialize(f)
def serialize(self):
return self.merkleblockwithmweb.serialize()
def header_hash(self):
self.merkleblockwithmweb.merkle.header.rehash()
return self.merkleblockwithmweb.merkle.header.hash
def __repr__(self):
return "msg_mwebheader(merkleblockwithmweb=%s)" % (repr(self.merkleblockwithmweb))

@ -54,6 +54,7 @@ from test_framework.messages import (
msg_inv,
msg_mempool,
msg_merkleblock,
msg_mwebheader,
msg_notfound,
msg_ping,
msg_pong,
@ -102,6 +103,7 @@ MESSAGEMAP = {
b"inv": msg_inv,
b"mempool": msg_mempool,
b"merkleblock": msg_merkleblock,
b"mwebheader": msg_mwebheader,
b"notfound": msg_notfound,
b"ping": msg_ping,
b"pong": msg_pong,
@ -390,6 +392,7 @@ class P2PInterface(P2PConnection):
def on_getdata(self, message): pass
def on_getheaders(self, message): pass
def on_headers(self, message): pass
def on_hogexheader(self, message): pass
def on_mempool(self, message): pass
def on_merkleblock(self, message): pass
def on_notfound(self, message): pass
@ -475,6 +478,18 @@ class P2PInterface(P2PConnection):
self.wait_until(test_function, timeout=timeout)
def wait_for_mwebheader(self, blockhash, timeout=60):
"""Waits for an mwebheader message
The hash of the block header must match the provided blockhash"""
def test_function():
last_mwebheader = self.last_message.get('mwebheader')
if not last_mwebheader:
return False
return last_mwebheader.merkleblockwithmweb.merkle.header.rehash() == int(blockhash, 16)
self.wait_until(test_function, timeout=timeout)
def wait_for_getdata(self, hash_list, timeout=60):
"""Waits for a getdata message.

@ -248,6 +248,7 @@ BASE_SCRIPTS = [
'mweb_basic.py',
'mweb_mining.py',
'mweb_reorg.py',
'mweb_p2p.py',
'mweb_pegout_all.py',
'mweb_node_compatibility.py',
'mweb_wallet_address.py',

Loading…
Cancel
Save