* 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:
parent
7b6dabd36a
commit
91a8c4e6ae
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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(); }
|
||||
|
||||
106
src/libmw/test/tests/wallet/Test_Keychain.cpp
Normal file
106
src/libmw/test/tests/wallet/Test_Keychain.cpp
Normal 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()
|
||||
@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
63
test/functional/mweb_mining.py
Normal file
63
test/functional/mweb_mining.py
Normal 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()
|
||||
@ -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.
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
@ -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()
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user