diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 30b2b85d6..7d3123f76 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -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 diff --git a/src/libmw/include/mw/interfaces/chain_interface.h b/src/libmw/include/mw/interfaces/chain_interface.h deleted file mode 100644 index c4b24c1da..000000000 --- a/src/libmw/include/mw/interfaces/chain_interface.h +++ /dev/null @@ -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 -#include - -MW_NAMESPACE - -/// -/// Interface for iterating sequentially blocks in the chain. -/// -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; -}; - -/// -/// Interface for accessing blocks in the chain. -/// -class IChain -{ -public: - using Ptr = std::shared_ptr; - - virtual ~IChain() = default; - - virtual std::unique_ptr NewIterator() = 0; -}; - -END_NAMESPACE // mw \ No newline at end of file diff --git a/src/libmw/include/mw/models/crypto/SecretKey.h b/src/libmw/include/mw/models/crypto/SecretKey.h index 00e84e35f..479ba30d0 100644 --- a/src/libmw/include/mw/models/crypto/SecretKey.h +++ b/src/libmw/include/mw/models/crypto/SecretKey.h @@ -49,6 +49,7 @@ public: // Getters // const BigInt& 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& vec() const { return m_value.vec(); } std::array array() const noexcept { return m_value.ToArray(); } diff --git a/src/libmw/test/tests/wallet/Test_Keychain.cpp b/src/libmw/test/tests/wallet/Test_Keychain.cpp new file mode 100644 index 000000000..69f04da9b --- /dev/null +++ b/src/libmw/test/tests/wallet/Test_Keychain.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include + +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() \ No newline at end of file diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 3f8fbfff2..41015685e 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -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; }, }; diff --git a/src/validation.cpp b/src/validation.cpp index 0a578fca8..365feae4c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -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 diff --git a/src/wallet/test/scriptpubkeyman_tests.cpp b/src/wallet/test/scriptpubkeyman_tests.cpp index d71a47189..757b5ced0 100644 --- a/src/wallet/test/scriptpubkeyman_tests.cpp +++ b/src/wallet/test/scriptpubkeyman_tests.cpp @@ -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()); diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 52fbf8f90..efae7ed9b 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -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 diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index 6ba7d97fe..486d38e08 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -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") diff --git a/test/functional/mining_getblocktemplate_longpoll.py b/test/functional/mining_getblocktemplate_longpoll.py index 2adafb1fd..d7959cea9 100755 --- a/test/functional/mining_getblocktemplate_longpoll.py +++ b/test/functional/mining_getblocktemplate_longpoll.py @@ -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") diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py index 1426fdaac..bb4820232 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -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 diff --git a/test/functional/mweb_basic.py b/test/functional/mweb_basic.py index 7d19d6250..5b7eb90cb 100644 --- a/test/functional/mweb_basic.py +++ b/test/functional/mweb_basic.py @@ -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) diff --git a/test/functional/mweb_mining.py b/test/functional/mweb_mining.py new file mode 100644 index 000000000..17f1e1904 --- /dev/null +++ b/test/functional/mweb_mining.py @@ -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() diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 027907e41..8d50d6f40 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -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. diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 6b7214f03..b2f1a3604 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -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): diff --git a/test/functional/test_framework/ltc_util.py b/test/functional/test_framework/ltc_util.py index 15b90b7b7..ebb00bd9d 100644 --- a/test/functional/test_framework/ltc_util.py +++ b/test/functional/test_framework/ltc_util.py @@ -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']) \ No newline at end of file + 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 \ No newline at end of file diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 97ec4242e..839f6feee 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -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(" 0 and self.vtx[-1].hogex: + has_mweb = struct.unpack(" 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() \ No newline at end of file diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py index 318a43870..d07458b10 100755 --- a/test/functional/test_framework/script_util.py +++ b/test/functional/test_framework/script_util.py @@ -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 diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 850ebbde6..63ef71ac2 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -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',