Skip to content

Commit

Permalink
* Prevent MWEB txs from being accepted to mempool before activation
Browse files Browse the repository at this point in the history
* Add 'mweb' rule to getblocktemplate request
* Add 'mweb' field to getblocktemplate reply
* Build out MWEB serialization for better functional test coverage
  • Loading branch information
DavidBurkett authored and losh11 committed May 8, 2022
1 parent 7b6dabd commit 91a8c4e
Show file tree
Hide file tree
Showing 19 changed files with 411 additions and 76 deletions.
3 changes: 2 additions & 1 deletion src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 0 additions & 42 deletions src/libmw/include/mw/interfaces/chain_interface.h

This file was deleted.

1 change: 1 addition & 0 deletions src/libmw/include/mw/models/crypto/SecretKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class secret_key_t : public Traits::ISerializable
// 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(); }
Expand Down
106 changes: 106 additions & 0 deletions src/libmw/test/tests/wallet/Test_Keychain.cpp
Original file line number Diff line number Diff line change
@@ -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()
18 changes: 14 additions & 4 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
},
},
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
},
};
Expand Down
5 changes: 5 additions & 0 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/wallet/test/scriptpubkeyman_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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());
Expand Down
6 changes: 3 additions & 3 deletions test/functional/feature_segwit.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def run_test(self):

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
Expand Down Expand Up @@ -211,7 +211,7 @@ def run_test(self):

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
Expand Down Expand Up @@ -268,7 +268,7 @@ def run_test(self):
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
Expand Down
6 changes: 3 additions & 3 deletions test/functional/mining_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -132,7 +132,7 @@ def assert_submitblock(block, result_str_1, result_str_2=None):
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")
Expand Down Expand Up @@ -165,7 +165,7 @@ def assert_submitblock(block, result_str_1, result_str_2=None):
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")
Expand Down
8 changes: 4 additions & 4 deletions test/functional/mining_getblocktemplate_longpoll.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -35,9 +35,9 @@ def run_test(self):
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")
Expand Down
4 changes: 2 additions & 2 deletions test/functional/mining_prioritisetransaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ def run_test(self):
# 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

Expand Down
17 changes: 14 additions & 3 deletions test/functional/mweb_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,23 @@ def skip_test_if_missing_module(self):
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)
Expand Down
Loading

0 comments on commit 91a8c4e

Please sign in to comment.