* Prevent MWEB txs from being accepted to mempool before activation

* Add 'mweb' rule to getblocktemplate request
* Add 'mweb' field to getblocktemplate reply
* Build out MWEB serialization for better functional test coverage
This commit is contained in:
David Burkett 2022-02-25 12:53:19 -05:00 committed by Loshan T
parent 7b6dabd36a
commit 91a8c4e6ae
19 changed files with 411 additions and 76 deletions

View File

@ -347,7 +347,8 @@ BITCOIN_TESTS += \
libmw/test/tests/node/Test_BlockValidator.cpp \
libmw/test/tests/node/Test_CoinsView.cpp \
libmw/test/tests/node/Test_MineChain.cpp \
libmw/test/tests/node/Test_Reorg.cpp
libmw/test/tests/node/Test_Reorg.cpp \
libmw/test/tests/wallet/Test_Keychain.cpp
test_test_litecoin_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
test_test_litecoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(EVENT_CFLAGS) $(LIBMW_CPPFLAGS) -Ilibmw/test/framework/include

View File

@ -1,42 +0,0 @@
#pragma once
// Copyright (c) 2018-2020 David Burkett
// Copyright (c) 2020 The Litecoin Developers
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php.
#include <mw/common/Macros.h>
#include <mw/models/block/Block.h>
MW_NAMESPACE
/// <summary>
/// Interface for iterating sequentially blocks in the chain.
/// </summary>
class IChainIterator
{
public:
virtual ~IChainIterator() = default;
virtual void Next() noexcept = 0;
virtual bool Valid() const noexcept = 0;
virtual uint64_t GetHeight() const = 0;
virtual mw::Header::CPtr GetHeader() const = 0;
virtual mw::Block::CPtr GetBlock() const = 0;
};
/// <summary>
/// Interface for accessing blocks in the chain.
/// </summary>
class IChain
{
public:
using Ptr = std::shared_ptr<mw::IChain>;
virtual ~IChain() = default;
virtual std::unique_ptr<IChainIterator> NewIterator() = 0;
};
END_NAMESPACE // mw

View File

@ -49,6 +49,7 @@ public:
// Getters
//
const BigInt<NUM_BYTES>& GetBigInt() const { return m_value; }
std::string ToHex() const noexcept { return m_value.ToHex(); }
bool IsNull() const noexcept { return m_value.IsZero(); }
const std::vector<uint8_t>& vec() const { return m_value.vec(); }
std::array<uint8_t, 32> array() const noexcept { return m_value.ToArray(); }

View File

@ -0,0 +1,106 @@
// Copyright (c) 2021 The Litecoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <mw/crypto/Blinds.h>
#include <mw/crypto/Bulletproofs.h>
#include <mw/crypto/Hasher.h>
#include <mw/crypto/Schnorr.h>
#include <mw/crypto/SecretKeys.h>
#include <mw/models/tx/Output.h>
#include <mw/models/wallet/StealthAddress.h>
#include <test_framework/Deserializer.h>
#include <test_framework/TestMWEB.h>
BOOST_FIXTURE_TEST_SUITE(TestKeychain, MWEBTestingSetup)
BOOST_AUTO_TEST_CASE(Create)
{
// Generate receiver master keys
SecretKey a = SecretKey::Random();
SecretKey b = SecretKey::Random();
PublicKey A = PublicKey::From(a);
// Generate receiver sub-address (i = 10)
SecretKey b_i = SecretKeys::From(b)
.Add(Hasher().Append(A).Append(10).Append(a).hash())
.Total();
StealthAddress receiver_subaddr(
PublicKey::From(b_i).Mul(a),
PublicKey::From(b_i)
);
// Build output
uint64_t amount = 1'234'567;
BlindingFactor blind;
SecretKey sender_key = SecretKey::Random();
Output output = Output::Create(
&blind,
sender_key,
receiver_subaddr,
amount
);
Commitment expected_commit = Commitment::Blinded(blind, amount);
// Verify bulletproof
ProofData proof_data = output.BuildProofData();
BOOST_REQUIRE(proof_data.commitment == expected_commit);
BOOST_REQUIRE(proof_data.pRangeProof == output.GetRangeProof());
BOOST_REQUIRE(Bulletproofs::BatchVerify({ output.BuildProofData() }));
// Verify sender signature
SignedMessage signed_msg = output.BuildSignedMsg();
BOOST_REQUIRE(signed_msg.GetPublicKey() == PublicKey::From(sender_key));
BOOST_REQUIRE(Schnorr::BatchVerify({ signed_msg }));
// Verify Output ID
mw::Hash expected_id = Hasher()
.Append(output.GetCommitment())
.Append(output.GetSenderPubKey())
.Append(output.GetReceiverPubKey())
.Append(output.GetOutputMessage().GetHash())
.Append(output.GetRangeProof()->GetHash())
.Append(output.GetSignature())
.hash();
BOOST_REQUIRE(output.GetOutputID() == expected_id);
// Getters
BOOST_REQUIRE(output.GetCommitment() == expected_commit);
//
// Test Restoring Output
//
{
// Check view tag
BOOST_REQUIRE(Hashed(EHashTag::TAG, output.Ke().Mul(a))[0] == output.GetViewTag());
// Make sure B belongs to wallet
SecretKey t = Hashed(EHashTag::DERIVE, output.Ke().Mul(a));
BOOST_REQUIRE(receiver_subaddr.B() == output.Ko().Div(Hashed(EHashTag::OUT_KEY, t)));
SecretKey r = Hashed(EHashTag::BLIND, t);
uint64_t value = output.GetMaskedValue() ^ *((uint64_t*)Hashed(EHashTag::VALUE_MASK, t).data());
BigInt<16> n = output.GetMaskedNonce() ^ BigInt<16>(Hashed(EHashTag::NONCE_MASK, t).data());
BOOST_REQUIRE(Commitment::Switch(r, value) == output.GetCommitment());
// Calculate Carol's sending key 's' and check that s*B ?= Ke
SecretKey s = Hasher(EHashTag::SEND_KEY)
.Append(receiver_subaddr.A())
.Append(receiver_subaddr.B())
.Append(value)
.Append(n)
.hash();
BOOST_REQUIRE(output.Ke() == receiver_subaddr.B().Mul(s));
// Make sure receiver can generate the spend key
SecretKey spend_key = SecretKeys::From(b_i)
.Mul(Hashed(EHashTag::OUT_KEY, t))
.Total();
BOOST_REQUIRE(output.GetReceiverPubKey() == PublicKey::From(spend_key));
}
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -534,6 +534,7 @@ static RPCHelpMan getblocktemplate()
{"rules", RPCArg::Type::ARR, RPCArg::Optional::NO, "A list of strings",
{
{"segwit", RPCArg::Type::STR, RPCArg::Optional::NO, "(literal) indicates client side segwit support"},
{"mweb", RPCArg::Type::STR, RPCArg::Optional::NO, "(literal) indicates client side MWEB support"},
{"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "other client side supported softfork deployment"},
},
},
@ -589,11 +590,12 @@ static RPCHelpMan getblocktemplate()
{RPCResult::Type::NUM_TIME, "curtime", "current timestamp in " + UNIX_EPOCH_TIME},
{RPCResult::Type::STR, "bits", "compressed target of next block"},
{RPCResult::Type::NUM, "height", "The height of the next block"},
{RPCResult::Type::STR, "default_witness_commitment", /* optional */ true, "a valid witness commitment for the unmodified block template"}
{RPCResult::Type::STR, "default_witness_commitment", /* optional */ true, "a valid witness commitment for the unmodified block template"},
{RPCResult::Type::STR, "mweb", /* optional */ true, "the MWEB block serialized as hex"}
}},
RPCExamples{
HelpExampleCli("getblocktemplate", "'{\"rules\": [\"segwit\"]}'")
+ HelpExampleRpc("getblocktemplate", "{\"rules\": [\"segwit\"]}")
HelpExampleCli("getblocktemplate", "'{\"rules\": [\"mweb\", \"segwit\"]}'")
+ HelpExampleRpc("getblocktemplate", "{\"rules\": [\"mweb\", \"segwit\"]}")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
@ -729,7 +731,10 @@ static RPCHelpMan getblocktemplate()
throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the segwit rule set (call with {\"rules\": [\"segwit\"]})");
}
// MW: TODO - Handle deployment rule
// GBT must be called with 'mweb' set in the rules
if (setClientRules.count("mweb") != 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the segwit rule set (call with {\"rules\": [\"mweb\"]})");
}
// Update block
static CBlockIndex* pindexPrev;
@ -905,6 +910,11 @@ static RPCHelpMan getblocktemplate()
result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment));
}
const auto& mweb_block = pblocktemplate->block.mweb_block;
if (!mweb_block.IsNull()) {
result.pushKV("mweb", HexStr(mweb_block.m_block->Serialized()));
}
return result;
},
};

View File

@ -576,6 +576,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return false; // state filled in by CheckTransaction
}
// MWEB: Don't accept MWEB transactions before activation.
if (tx.HasMWEBTx() && !IsMWEBEnabled(::ChainActive().Tip(), args.m_chainparams.GetConsensus())) {
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "mweb-before-activation");
}
// MWEB: Check MWEB tx
if (!MWEB::Node::CheckTransaction(tx, state)) {
return false; // state filled in by CheckTransaction

View File

@ -59,8 +59,8 @@ BOOST_AUTO_TEST_CASE(StealthAddresses)
// Check generated MWEB keychain
mw::Keychain::Ptr mweb_keychain = keyman.GetMWEBKeychain();
BOOST_CHECK(mweb_keychain != nullptr);
BOOST_CHECK(mweb_keychain->GetSpendSecret().GetBigInt().ToHex() == "2396e5c33b07dfa2d9e70da1dcbdad0ad2399e5672ff2d4afbe3b20bccf3ba1b");
BOOST_CHECK(mweb_keychain->GetScanSecret().GetBigInt().ToHex() == "918271168655385e387907612ee09d755be50c4685528f9f53eabae380ecba97");
BOOST_CHECK(mweb_keychain->GetSpendSecret().ToHex() == "2396e5c33b07dfa2d9e70da1dcbdad0ad2399e5672ff2d4afbe3b20bccf3ba1b");
BOOST_CHECK(mweb_keychain->GetScanSecret().ToHex() == "918271168655385e387907612ee09d755be50c4685528f9f53eabae380ecba97");
// Check "change" (idx=0) address is USED
StealthAddress change_address = mweb_keychain->GetStealthAddress(0);
@ -80,6 +80,7 @@ BOOST_AUTO_TEST_CASE(StealthAddresses)
// Check first receive (idx=2) address is UNUSED
StealthAddress receive_address = mweb_keychain->GetStealthAddress(2);
BOOST_CHECK(EncodeDestination(receive_address) == "ltcmweb1qq0yq03ewm830ugmkkvrvjmyyeslcpwk8ayd7k27qx63sryy6kx3ksqm3k6jd24ld3r5dp5lzx7rm7uyxfujf8sn7v4nlxeqwrcq6k6xxwqdc6tl3");
BOOST_CHECK(keyman.IsMine(receive_address) == ISMINE_SPENDABLE);
CPubKey receive_pubkey(receive_address.B().vec());
BOOST_CHECK(keyman.GetAllReserveKeys().find(receive_pubkey.GetID()) != keyman.GetAllReserveKeys().end());

View File

@ -103,7 +103,7 @@ class SegWitTest(BitcoinTestFramework):
self.log.info("Verify sigops are counted in GBT with pre-BIP141 rules before the fork")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']})
tmpl = self.nodes[0].getblocktemplate({'rules': ['mweb', 'segwit']})
assert tmpl['sizelimit'] == 1000000
assert 'weightlimit' not in tmpl
assert tmpl['sigoplimit'] == 20000
@ -211,7 +211,7 @@ class SegWitTest(BitcoinTestFramework):
self.log.info("Verify sigops are counted in GBT with BIP141 rules after the fork")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']})
tmpl = self.nodes[0].getblocktemplate({'rules': ['mweb', 'segwit']})
assert tmpl['sizelimit'] >= 3999577 # actual maximum size is lower due to minimum mandatory non-witness data
assert tmpl['weightlimit'] == 4000000
assert tmpl['sigoplimit'] == 80000
@ -268,7 +268,7 @@ class SegWitTest(BitcoinTestFramework):
assert txid3 in self.nodes[0].getrawmempool()
# Check that getblocktemplate includes all transactions.
template = self.nodes[0].getblocktemplate({"rules": ["segwit"]})
template = self.nodes[0].getblocktemplate({"rules": ["mweb", "segwit"]})
template_txids = [t['txid'] for t in template['transactions']]
assert txid1 in template_txids
assert txid2 in template_txids

View File

@ -38,7 +38,7 @@ def assert_template(node, block, expect, rehash=True):
rsp = node.getblocktemplate(template_request={
'data': block.serialize().hex(),
'mode': 'proposal',
'rules': ['segwit'],
'rules': ['mweb', 'segwit'],
})
assert_equal(rsp, expect)
@ -132,7 +132,7 @@ class MiningTest(BitcoinTestFramework):
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
'data': block.serialize()[:-1].hex(),
'mode': 'proposal',
'rules': ['segwit'],
'rules': ['mweb', 'segwit'],
})
self.log.info("getblocktemplate: Test duplicate transaction")
@ -165,7 +165,7 @@ class MiningTest(BitcoinTestFramework):
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
'data': bad_block_sn.hex(),
'mode': 'proposal',
'rules': ['segwit'],
'rules': ['mweb', 'segwit'],
})
self.log.info("getblocktemplate: Test bad bits")

View File

@ -17,14 +17,14 @@ class LongpollThread(threading.Thread):
def __init__(self, node):
threading.Thread.__init__(self)
# query current longpollid
template = node.getblocktemplate({'rules': ['segwit']})
template = node.getblocktemplate({'rules': ['mweb', 'segwit']})
self.longpollid = template['longpollid']
# create a new connection to the node, we can't use the same
# connection from two threads
self.node = get_rpc_proxy(node.url, 1, timeout=600, coveragedir=node.coverage_dir)
def run(self):
self.node.getblocktemplate({'longpollid': self.longpollid, 'rules': ['segwit']})
self.node.getblocktemplate({'longpollid': self.longpollid, 'rules': ['mweb', 'segwit']})
class GetBlockTemplateLPTest(BitcoinTestFramework):
def set_test_params(self):
@ -35,9 +35,9 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
self.log.info("Warning: this test will take about 70 seconds in the best case. Be patient.")
self.log.info("Test that longpollid doesn't change between successive getblocktemplate() invocations if nothing else happens")
self.nodes[0].generate(10)
template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
template = self.nodes[0].getblocktemplate({'rules': ['mweb', 'segwit']})
longpollid = template['longpollid']
template2 = self.nodes[0].getblocktemplate({'rules': ['segwit']})
template2 = self.nodes[0].getblocktemplate({'rules': ['mweb', 'segwit']})
assert template2['longpollid'] == longpollid
self.log.info("Test that longpoll waits if we do nothing")

View File

@ -146,10 +146,10 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
# getblocktemplate to (eventually) return a new block.
mock_time = int(time.time())
self.nodes[0].setmocktime(mock_time)
template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
template = self.nodes[0].getblocktemplate({'rules': ['mweb', 'segwit']})
self.nodes[0].prioritisetransaction(txid=tx_id, fee_delta=-int(self.relayfee*COIN))
self.nodes[0].setmocktime(mock_time+10)
new_template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
new_template = self.nodes[0].getblocktemplate({'rules': ['mweb', 'segwit']})
assert template != new_template

View File

@ -16,12 +16,23 @@ class MWEBBasicTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def run_test(self):
self.log.info("Create all pre-MWEB blocks")
self.nodes[0].generate(431)
self.log.info("Create all but one pre-MWEB blocks")
self.nodes[0].generate(430)
self.log.info("Pegin some coins")
addr0 = self.nodes[0].getnewaddress(address_type='mweb')
self.nodes[0].sendtoaddress(addr0, 10)
pegin1_txid = self.nodes[0].sendtoaddress(addr0, 10)
self.log.info("Ensure pegin transaction was not accepted to mempool, and abandon it")
assert_equal(set(self.nodes[0].getrawmempool()), set())
self.nodes[0].abandontransaction(pegin1_txid)
self.log.info("Generate final pre-MWEB block and pegin, ensuring tx is accepted to mempool")
self.nodes[0].generate(1)
pegin2_txid = self.nodes[0].sendtoaddress(addr0, 10)
self.sync_all();
assert_equal(set(self.nodes[0].getrawmempool()), {pegin2_txid})
assert_equal(set(self.nodes[1].getrawmempool()), {pegin2_txid})
self.log.info("Create some blocks - activate MWEB")
self.nodes[0].generate(10)

View File

@ -0,0 +1,63 @@
#!/usr/bin/env python3
# Copyright (c) 2014-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 mining RPCs for MWEB blocks"""
from test_framework.blocktools import (create_coinbase, NORMAL_GBT_REQUEST_PARAMS)
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
class MWEBMiningTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
self.supports_cli = False
def run_test(self):
node = self.nodes[0]
self.log.info("Setup MWEB chain")
setup_mweb_chain(node)
# Call getblocktemplate
node.generatetoaddress(1, node.get_deterministic_priv_key().address)
gbt = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
next_height = int(gbt["height"])
# Build MWEB block
mweb_header = get_mweb_header_tip(node)
mweb_header.height = next_height
mweb_header.rehash()
mweb_block = MWEBBlock(mweb_header)
# Build coinbase and HogEx txs
coinbase_tx = create_coinbase(height=next_height)
hogex_tx = create_hogex(node, mweb_header.hash)
vtx = [coinbase_tx, hogex_tx]
# Build block proposal
block = CBlock()
block.nVersion = gbt["version"]
block.hashPrevBlock = int(gbt["previousblockhash"], 16)
block.nTime = gbt["curtime"]
block.nBits = int(gbt["bits"], 16)
block.nNonce = 0
block.vtx = vtx
block.mweb_block = mweb_block.serialize().hex()
block.hashMerkleRoot = block.calc_merkle_root()
# Call getblocktemplate with the block proposal
self.log.info("getblocktemplate: Test valid block")
rsp = node.getblocktemplate(template_request={
'data': block.serialize().hex(),
'mode': 'proposal',
'rules': ['mweb', 'segwit'],
})
assert_equal(rsp, None)
if __name__ == '__main__':
MWEBMiningTest().main()

View File

@ -571,7 +571,7 @@ class SegWitTest(BitcoinTestFramework):
txid = int(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1), 16)
for node in [self.nodes[0], self.nodes[2]]:
gbt_results = node.getblocktemplate({"rules": ["segwit"]})
gbt_results = node.getblocktemplate({"rules": ["mweb", "segwit"]})
if node == self.nodes[2]:
# If this is a non-segwit node, we should not get a witness
# commitment.

View File

@ -55,7 +55,7 @@ TIME_GENESIS_BLOCK = 1296688602
# From BIP141
WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed"
NORMAL_GBT_REQUEST_PARAMS = {"rules": ["segwit"]}
NORMAL_GBT_REQUEST_PARAMS = {"rules": ["mweb", "segwit"]}
def create_block(hashprev=None, coinbase=None, ntime=None, *, version=None, tmpl=None, txlist=None):

View File

@ -4,10 +4,9 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Random assortment of utility functions"""
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round
from test_framework.script_util import DUMMY_P2WPKH_SCRIPT
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, MWEBHeader
from test_framework.util import satoshi_round
from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, hogaddr_script
"""Create a txout with a given amount and scriptPubKey
@ -70,4 +69,27 @@ def get_hog_addr_txout(node):
hogex_tx = best_block['tx'][-1] # TODO: Should validate that the tx is marked as a hogex tx
hog_addr = hogex_tx['vout'][0]
return CTxOut(hog_addr['value'], hog_addr['scriptPubKey'])
return CTxOut(int(hog_addr['value'] * COIN), hog_addr['scriptPubKey'])
def get_mweb_header_tip(node):
best_block = node.getblock(node.getbestblockhash(), 2)
if not 'mweb' in best_block:
return None
mweb_header = MWEBHeader()
mweb_header.from_json(best_block['mweb'])
return mweb_header
def create_hogex(node, mweb_hash):
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]
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.hogex = True
tx.rehash()
return tx

View File

@ -18,6 +18,7 @@ ser_*, deser_*: functions that handle serialization/deserialization.
Classes use __slots__ to ensure extraneous attributes aren't accidentally added
by tests, compromising their intended effect.
"""
import binascii
from codecs import encode
import copy
import hashlib
@ -489,7 +490,7 @@ class CTxWitness:
class CTransaction:
__slots__ = ("hash", "nLockTime", "nVersion", "sha256", "vin", "vout",
"wit")
"wit", "mweb_tx", "hogex")
def __init__(self, tx=None):
if tx is None:
@ -500,6 +501,8 @@ class CTransaction:
self.nLockTime = 0
self.sha256 = None
self.hash = None
self.mweb_tx = None
self.hogex = False
else:
self.nVersion = tx.nVersion
self.vin = copy.deepcopy(tx.vin)
@ -508,6 +511,8 @@ class CTransaction:
self.sha256 = tx.sha256
self.hash = tx.hash
self.wit = copy.deepcopy(tx.wit)
self.mweb_tx = tx.mweb_tx
self.hogex = tx.hogex
def deserialize(self, f):
self.nVersion = struct.unpack("<i", f.read(4))[0]
@ -522,11 +527,17 @@ class CTransaction:
self.vout = deser_vector(f, CTxOut)
else:
self.vout = deser_vector(f, CTxOut)
if flags != 0:
if flags & 1:
self.wit.vtxinwit = [CTxInWitness() for _ in range(len(self.vin))]
self.wit.deserialize(f)
else:
self.wit = CTxWitness()
if flags & 8:
self.mweb_tx = deser_mweb_tx(f)
if self.mweb_tx == None:
self.hogex = True
self.nLockTime = struct.unpack("<I", f.read(4))[0]
self.sha256 = None
self.hash = None
@ -544,6 +555,8 @@ class CTransaction:
flags = 0
if not self.wit.is_null():
flags |= 1
if self.hogex or self.mweb_tx != None:
flags |= 8
r = b""
r += struct.pack("<i", self.nVersion)
if flags:
@ -560,6 +573,8 @@ class CTransaction:
self.wit.vtxinwit.append(CTxInWitness())
r += self.wit.serialize()
r += struct.pack("<I", self.nLockTime)
if flags & 8:
r += ser_mweb_tx(self.mweb_tx)
return r
# Regular serialization is with witness -- must explicitly
@ -682,23 +697,32 @@ BLOCK_HEADER_SIZE = len(CBlockHeader().serialize())
assert_equal(BLOCK_HEADER_SIZE, 80)
class CBlock(CBlockHeader):
__slots__ = ("vtx",)
__slots__ = ("vtx", "mweb_block")
def __init__(self, header=None):
super().__init__(header)
self.vtx = []
self.mweb_block = None
def deserialize(self, f):
super().deserialize(f)
self.vtx = deser_vector(f, CTransaction)
if len(self.vtx) > 0 and self.vtx[-1].hogex:
has_mweb = struct.unpack("<B", f.read(1))[0]
if has_mweb == 1:
self.mweb_block = MWEBBlock()
self.mweb_block.deserialize(f)
def serialize(self, with_witness=True):
def serialize(self, with_witness=True, with_mweb=True):
r = b""
r += super().serialize()
if with_witness:
r += ser_vector(self.vtx, "serialize_with_witness")
else:
r += ser_vector(self.vtx, "serialize_without_witness")
if with_mweb and len(self.vtx) > 0 and self.vtx[-1].hogex:
r += ser_mweb_block(self.mweb_block)
return r
# Calculate the merkle root given a vector of transaction hashes
@ -1795,3 +1819,132 @@ class msg_cfcheckpt:
def __repr__(self):
return "msg_cfcheckpt(filter_type={:#x}, stop_hash={:x})".format(
self.filter_type, self.stop_hash)
"""------------MWEB------------"""
import blake3 as BLAKE3
def blake3(s):
return BLAKE3.blake3(s).hexdigest()
def ser_varint(n):
r = b""
l=0;
while True:
t = (n & 0x7F) | (0x80, 0x00)[l == 0]
r = struct.pack("B", t) + r
if n <= 0x7F:
break
n = (n >> 7) - 1;
l = l + 1
return r
def deser_varint(f):
return 0 # TODO: deser_varint
def ser_mweb_block(b):
if b == None:
return struct.pack("B", 0)
else:
return struct.pack("B", 1) + hex_str_to_bytes(b)
def ser_mweb_tx(t):
if t == None:
return struct.pack("B", 0)
else:
return struct.pack("B", 1) + hex_str_to_bytes(t)
def deser_mweb_tx(f):
has_mweb = struct.unpack("B", f.read(1))[0]
if has_mweb == 1:
return binascii.hexlify(f.read())
else:
return None
class MWEBHeader:
__slots__ = ("height", "output_root", "kernel_root", "leafset_root",
"kernel_offset", "stealth_offset", "num_txos", "num_kernels", "hash")
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.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.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.num_txos = deser_varint(f)
self.num_kernels = deser_varint(f)
self.rehash()
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 += ser_varint(self.num_txos)
r += ser_varint(self.num_kernels)
return r
def rehash(self):
self.hash = blake3(self.serialize())
return self.hash
def __repr__(self):
return "MWEBHeader(height=%s, hash=%s)" % (repr(self.height), repr(self.hash))
# TODO: Finish this class
class MWEBBlock:
__slots__ = ("header", "inputs", "outputs", "kernels")
def __init__(self, header = MWEBHeader(), inputs = [], outputs = [], kernels = []):
self.header = copy.deepcopy(header)
self.inputs = inputs
self.outputs = outputs
self.kernels = kernels
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.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)
return r
def rehash(self):
return self.header.rehash()

View File

@ -68,6 +68,9 @@ def script_to_p2sh_p2wsh_script(script, main = False):
p2shscript = CScript([OP_0, sha256(script)])
return script_to_p2sh_script(p2shscript, main)
def hogaddr_script(mweb_hash, main = False):
return program_to_witness_script(8, mweb_hash, main)
def check_key(key):
if isinstance(key, str):
key = hex_str_to_bytes(key) # Assuming this is hex string

View File

@ -246,8 +246,9 @@ BASE_SCRIPTS = [
'feature_dersig.py',
'feature_cltv.py',
'mweb_basic.py',
'mweb_mining.py',
'mweb_reorg.py',
'mweb_pegout_all.py'
'mweb_pegout_all.py',
'rpc_uptime.py',
'wallet_resendwallettransactions.py',
'wallet_resendwallettransactions.py --descriptors',