* Wallet fixes

* Fixing taproot testnet params
* Bump version to 0.21.2rc3
This commit is contained in:
David Burkett 2022-03-01 15:51:29 -05:00 committed by Loshan T
parent 4beb89b249
commit 149b4e5da5
22 changed files with 250 additions and 69 deletions

View File

@ -3,9 +3,9 @@ define(_CLIENT_VERSION_MAJOR, 0)
define(_CLIENT_VERSION_MINOR, 21)
define(_CLIENT_VERSION_REVISION, 2)
define(_CLIENT_VERSION_BUILD, 0)
define(_CLIENT_VERSION_RC, 2)
define(_CLIENT_VERSION_RC, 3)
define(_CLIENT_VERSION_IS_RELEASE, true)
define(_COPYRIGHT_YEAR, 2021)
define(_COPYRIGHT_YEAR, 2022)
define(_COPYRIGHT_HOLDERS,[The %s developers])
define(_COPYRIGHT_HOLDERS_SUBSTITUTION,[[Litecoin Core]])
AC_INIT([Litecoin Core],m4_join([.], _CLIENT_VERSION_MAJOR, _CLIENT_VERSION_MINOR, _CLIENT_VERSION_REVISION, m4_if(_CLIENT_VERSION_BUILD, [0], [], _CLIENT_VERSION_BUILD))m4_if(_CLIENT_VERSION_RC, [0], [], [rc]_CLIENT_VERSION_RC),[https://github.com/litecoin-project/litecoin/issues],[litecoin],[https://litecoin.org/])

View File

@ -207,8 +207,8 @@ public:
// Deployment of Taproot (BIPs 340-342)
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].bit = 2;
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartTime = 1619222400; // April 24th, 2021
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nStartHeight = 2225664; // March 2022
consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeoutHeight = 2435328; // 364 days later
// Deployment of MWEB (LIP-0002, LIP-0003, and LIP-0004)
consensus.vDeployments[Consensus::DEPLOYMENT_MWEB].bit = 4;

View File

@ -3,6 +3,7 @@
#include <mw/common/Macros.h>
#include <mw/models/crypto/BlindingFactor.h>
#include <mw/models/crypto/Commitment.h>
#include <mw/models/wallet/StealthAddress.h>
#include <amount.h>
#include <boost/optional.hpp>
@ -10,30 +11,35 @@
MW_NAMESPACE
/// <summary>
/// Change outputs will use the stealth address generated using index 2,000,000.
/// Change outputs will use the stealth address generated using index 0
/// </summary>
static constexpr uint32_t CHANGE_INDEX{0};
/// <summary>
/// Peg-in outputs will use the stealth address generated using index 2,000,000.
/// Peg-in outputs will use the stealth address generated using index 1.
/// </summary>
static constexpr uint32_t PEGIN_INDEX{1};
/// <summary>
/// Outputs sent to others will be marked with an address_index of 0xffffffff.
/// </summary>
static constexpr uint32_t UNKNOWN_INDEX{std::numeric_limits<uint32_t>::max()};
/// <summary>
/// Represents an output owned by the wallet, and the keys necessary to spend it.
/// </summary>
struct Coin : public Traits::ISerializable {
// Version byte to more easily support adding new fields to the object.
uint8_t version{0};
uint8_t version{1};
// Index of the subaddress this coin was received at.
uint32_t address_index;
uint32_t address_index{UNKNOWN_INDEX};
// The private key needed in order to spend the coin.
// May be empty for watch-only wallets.
boost::optional<SecretKey> key;
// The blinding factor needed in order to spend the coin.
// The blinding factor of the coin's output.
// May be empty for watch-only wallets.
boost::optional<BlindingFactor> blind;
@ -44,8 +50,18 @@ struct Coin : public Traits::ISerializable {
// The output's ID (hash).
mw::Hash output_id;
// The ephemeral private key used by the sender to create the output.
// This will only be populated when the coin has flag HAS_SENDER_INFO.
boost::optional<SecretKey> sender_key;
// The StealthAddress the coin was sent to.
// This will only be populated when the coin has flag HAS_SENDER_INFO.
boost::optional<StealthAddress> address;
bool IsChange() const noexcept { return address_index == CHANGE_INDEX; }
bool IsPegIn() const noexcept { return address_index == PEGIN_INDEX; }
bool IsMine() const noexcept { return address_index != UNKNOWN_INDEX; }
bool HasAddress() const noexcept { return !!address; }
IMPL_SERIALIZABLE(Coin, obj)
{
@ -55,6 +71,11 @@ struct Coin : public Traits::ISerializable {
READWRITE(obj.blind);
READWRITE(VARINT_MODE(obj.amount, VarIntMode::NONNEGATIVE_SIGNED));
READWRITE(obj.output_id);
if (obj.version >= 1) {
READWRITE(obj.sender_key);
READWRITE(obj.address);
}
}
};

View File

@ -21,6 +21,7 @@ class TxBuilder
BlindingFactor total_blind;
SecretKey total_key;
std::vector<Output> outputs;
std::vector<mw::Coin> coins;
};
public:
@ -29,7 +30,8 @@ public:
const std::vector<mw::Recipient>& recipients,
const std::vector<PegOutCoin>& pegouts,
const boost::optional<CAmount>& pegin_amount,
const CAmount fee
const CAmount fee,
std::vector<mw::Coin>& output_coins
);
private:

View File

@ -75,7 +75,7 @@ Output Output::Create(
Signature signature = Schnorr::Sign(sender_privkey.data(), sig_message);
if (blind_out != nullptr) {
*blind_out = blind;
*blind_out = mask.GetRawBlind();
}
return Output{

View File

@ -60,6 +60,7 @@ bool Keychain::RewindOutput(const Output& output, mw::Coin& coin) const
coin.blind = boost::make_optional(mask.GetRawBlind());
coin.amount = value;
coin.output_id = output.GetOutputID();
coin.address = wallet_addr;
return true;
}

View File

@ -10,7 +10,8 @@ mw::Transaction::CPtr TxBuilder::BuildTx(
const std::vector<mw::Recipient>& recipients,
const std::vector<PegOutCoin>& pegouts,
const boost::optional<CAmount>& pegin_amount,
const CAmount fee)
const CAmount fee,
std::vector<mw::Coin>& output_coins)
{
CAmount pegout_total = std::accumulate(
pegouts.cbegin(), pegouts.cend(), (CAmount)0,
@ -40,6 +41,7 @@ mw::Transaction::CPtr TxBuilder::BuildTx(
// Create outputs
TxBuilder::Outputs outputs = CreateOutputs(recipients);
output_coins = outputs.coins;
// Total kernel offset is split between raw kernel_offset and the kernel's blinding factor.
// sum(output.blind) - sum(input.blind) = kernel_offset + sum(kernel.blind)
@ -119,28 +121,36 @@ TxBuilder::Outputs TxBuilder::CreateOutputs(const std::vector<mw::Recipient>& re
Blinds output_blinds;
Blinds output_keys;
std::vector<Output> outputs;
std::transform(
recipients.cbegin(), recipients.cend(), std::back_inserter(outputs),
[&output_blinds, &output_keys](const mw::Recipient& recipient) {
BlindingFactor blind;
SecretKey ephemeral_key = SecretKey::Random();
Output output = Output::Create(
&blind,
ephemeral_key,
recipient.address,
recipient.amount
);
std::vector<mw::Coin> coins;
output_blinds.Add(blind);
output_keys.Add(ephemeral_key);
return output;
}
);
for (const mw::Recipient& recipient : recipients) {
BlindingFactor raw_blind;
SecretKey ephemeral_key = SecretKey::Random();
Output output = Output::Create(
&raw_blind,
ephemeral_key,
recipient.address,
recipient.amount
);
output_blinds.Add(Pedersen::BlindSwitch(raw_blind, recipient.amount));
output_keys.Add(ephemeral_key);
outputs.push_back(output);
mw::Coin coin;
coin.blind = raw_blind;
coin.amount = recipient.amount;
coin.output_id = output.GetOutputID();
coin.sender_key = ephemeral_key;
coin.address = recipient.address;
coins.push_back(coin);
}
return TxBuilder::Outputs{
output_blinds.Total(),
SecretKey(output_keys.Total().data()),
std::move(outputs)
std::move(outputs),
std::move(coins)
};
}

View File

@ -4,6 +4,7 @@
#include <mw/models/tx/Output.h>
#include <mw/models/wallet/StealthAddress.h>
#include <mw/crypto/Bulletproofs.h>
#include <mw/crypto/Pedersen.h>
TEST_NAMESPACE
@ -18,10 +19,11 @@ public:
const StealthAddress& receiver_addr,
const uint64_t amount)
{
BlindingFactor blinding_factor;
Output output = Output::Create(&blinding_factor, sender_privkey, receiver_addr, amount);
BlindingFactor raw_blind;
Output output = Output::Create(&raw_blind, sender_privkey, receiver_addr, amount);
BlindingFactor blind_switch = Pedersen::BlindSwitch(raw_blind, amount);
return TxOutput{ std::move(blinding_factor), amount, std::move(output) };
return TxOutput{std::move(blind_switch), amount, std::move(output)};
}
const BlindingFactor& GetBlind() const noexcept { return m_blindingFactor; }

View File

@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(Create)
receiver_subaddr,
amount
);
Commitment expected_commit = Commitment::Blinded(blind, amount);
Commitment expected_commit = Commitment::Switch(blind, amount);
// Verify bulletproof
ProofData proof_data = output.BuildProofData();

View File

@ -20,7 +20,7 @@ BOOST_AUTO_TEST_CASE(TxUTXO)
StealthAddress::Random(),
amount
);
Commitment commit = Commitment::Blinded(blind, amount);
Commitment commit = Commitment::Switch(blind, amount);
int32_t blockHeight = 20;
mmr::LeafIndex leafIndex = mmr::LeafIndex::At(5);

View File

@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(Create)
receiver_subaddr,
amount
);
Commitment expected_commit = Commitment::Blinded(blind, amount);
Commitment expected_commit = Commitment::Switch(blind, amount);
// Verify bulletproof
ProofData proof_data = output.BuildProofData();

View File

@ -88,18 +88,29 @@ bool Transact::CreateTx(
if (change_amount < 0) {
return false;
}
StealthAddress change_address = mweb_wallet->GetStealthAddress(mw::CHANGE_INDEX);
StealthAddress change_address;
if (!mweb_wallet->GetStealthAddress(mw::CHANGE_INDEX, change_address)) {
return false;
}
receivers.push_back(mw::Recipient{change_amount, change_address});
}
// Create transaction
std::vector<mw::Coin> input_coins = GetInputCoins(selected_coins);
std::vector<mw::Coin> output_coins;
transaction.mweb_tx = TxBuilder::BuildTx(
input_coins,
receivers,
pegouts,
pegin_amount,
mweb_fee);
mweb_fee,
output_coins
);
if (!output_coins.empty()) {
mweb_wallet->SaveToWallet(output_coins);
}
// Update pegin output
auto pegins = transaction.mweb_tx.GetPegIns();

View File

@ -23,7 +23,7 @@ std::vector<mw::Coin> Wallet::RewindOutputs(const CTransaction& tx)
bool Wallet::RewindOutput(const boost::variant<mw::Block::CPtr, mw::Transaction::CPtr>& parent,
const mw::Hash& output_id, mw::Coin& coin)
{
if (GetCoin(output_id, coin)) {
if (GetCoin(output_id, coin) && coin.IsMine()) {
return true;
}
@ -61,14 +61,29 @@ bool Wallet::RewindOutput(const boost::variant<mw::Block::CPtr, mw::Transaction:
bool Wallet::IsChange(const StealthAddress& address) const
{
return IsSupported() && address == GetStealthAddress(mw::CHANGE_INDEX);
StealthAddress change_addr;
return GetStealthAddress(mw::CHANGE_INDEX, change_addr) && change_addr == address;
}
StealthAddress Wallet::GetStealthAddress(const uint32_t index) const
bool Wallet::GetStealthAddress(const mw::Coin& coin, StealthAddress& address) const
{
if (coin.HasAddress()) {
address = *coin.address;
return true;
}
return GetStealthAddress(coin.address_index, address);
}
bool Wallet::GetStealthAddress(const uint32_t index, StealthAddress& address) const
{
mw::Keychain::Ptr keychain = GetKeychain();
assert(keychain != nullptr);
return keychain->GetStealthAddress(index);
if (!keychain || index == mw::UNKNOWN_INDEX) {
return false;
}
address = keychain->GetStealthAddress(index);
return true;
}
void Wallet::LoadToWallet(const mw::Coin& coin)
@ -76,6 +91,15 @@ void Wallet::LoadToWallet(const mw::Coin& coin)
m_coins[coin.output_id] = coin;
}
void Wallet::SaveToWallet(const std::vector<mw::Coin>& coins)
{
WalletBatch batch(m_pWallet->GetDatabase());
for (const mw::Coin& coin : coins) {
m_coins[coin.output_id] = coin;
batch.WriteMWEBCoin(coin);
}
}
bool Wallet::GetCoin(const mw::Hash& output_id, mw::Coin& coin) const
{
auto iter = m_coins.find(output_id);

View File

@ -37,9 +37,11 @@ public:
const mw::Hash& output_id,
mw::Coin& coin
);
StealthAddress GetStealthAddress(const uint32_t index) const;
bool GetStealthAddress(const mw::Coin& coin, StealthAddress& address) const;
bool GetStealthAddress(const uint32_t index, StealthAddress& address) const;
void LoadToWallet(const mw::Coin& coin);
void SaveToWallet(const std::vector<mw::Coin>& coins);
private:
mw::Keychain::Ptr GetKeychain() const;

View File

@ -184,11 +184,15 @@ void ReceiveCoinsDialog::on_receiveButton_clicked()
tr("Could not unlock wallet."),
QMessageBox::Ok, QMessageBox::Ok);
break;
case AddressTableModel::EditStatus::KEY_GENERATION_FAILURE:
QMessageBox::critical(this, windowTitle(),
tr("Could not generate new %1 address").arg(QString::fromStdString(FormatOutputType(address_type))),
QMessageBox::Ok, QMessageBox::Ok);
case AddressTableModel::EditStatus::KEY_GENERATION_FAILURE: {
QString message = tr("Could not generate new %1 address.").arg(QString::fromStdString(FormatOutputType(address_type)));
if (address_type == OutputType::MWEB) {
message += tr("\nTry upgrading your wallet.");
}
QMessageBox::critical(this, windowTitle(), message, QMessageBox::Ok, QMessageBox::Ok);
break;
}
// These aren't valid return values for our action
case AddressTableModel::EditStatus::INVALID_ADDRESS:
case AddressTableModel::EditStatus::DUPLICATE_ADDRESS:

View File

@ -275,7 +275,12 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
// Reserve pegout keys and set pegout addresses
for (SendCoinsRecipient& rcp : recipients) {
if (rcp.type == SendCoinsRecipient::MWEB_PEGIN) {
rcp.address = QString::fromStdString(EncodeDestination(model->wallet().getPeginAddress()));
StealthAddress pegin_address;
if (!model->wallet().getPeginAddress(pegin_address)) {
fNewRecipientAllowed = true;
return false;
}
rcp.address = QString::fromStdString(EncodeDestination(pegin_address));
} else if (rcp.type == SendCoinsRecipient::MWEB_PEGOUT) {
CTxDestination dest;
auto reserved_dest = model->wallet().reserveNewDestination(dest);
@ -1015,8 +1020,10 @@ void SendCoinsDialog::mwebPegInButtonClicked(bool checked)
ui->pushButtonMWEBPegOut->setChecked(false);
SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
if (checked) {
entry->setPegInAddress(EncodeDestination(model->wallet().getPeginAddress()));
StealthAddress pegin_address;
if (checked && model->wallet().getPeginAddress(pegin_address)) {
entry->setPegInAddress(EncodeDestination(pegin_address));
} else {
entry->setPegInAddress("");
}

View File

@ -180,7 +180,12 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
switch (rcp.type) {
case SendCoinsRecipient::MWEB_PEGIN: {
coin_control_copy.fPegIn = true;
receiver = m_wallet->getPeginAddress();
StealthAddress pegin_address;
if (!m_wallet->getPeginAddress(pegin_address)) {
return TransactionCreationFailed;
}
receiver = pegin_address;
break;
}
case SendCoinsRecipient::MWEB_PEGOUT: {

View File

@ -1099,10 +1099,10 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param
if (nDepth < nMinDepth)
continue;
for (const CTxOut& txout : wtx.tx->vout)
for (const CTxOutput& txout : wtx.tx->GetOutputs())
{
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
if (!pwallet->ExtractOutputDestination(txout, address))
continue;
if (has_filtered_address && !(filtered_address == address)) {
@ -1114,7 +1114,7 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param
continue;
tallyitem& item = mapTally[address];
item.nAmount += txout.nValue;
item.nAmount += pwallet->GetValue(txout);
item.nConf = std::min(item.nConf, nDepth);
item.txids.push_back(wtx.GetHash());
if (mine & ISMINE_WATCH_ONLY)

View File

@ -1351,7 +1351,7 @@ isminetype CWallet::IsMine(const CTxInput& input) const
AssertLockHeld(cs_wallet);
if (input.IsMWEB()) {
mw::Coin coin;
return GetCoin(input.ToMWEB(), coin) ? ISMINE_SPENDABLE : ISMINE_NO;
return GetCoin(input.ToMWEB(), coin) && coin.IsMine() ? ISMINE_SPENDABLE : ISMINE_NO;
}
const CWalletTx* prev = FindPrevTx(input);
@ -1372,7 +1372,7 @@ CAmount CWallet::GetDebit(const CTxInput& input, const isminefilter& filter) con
LOCK(cs_wallet);
if (input.IsMWEB()) {
mw::Coin coin;
if ((filter & ISMINE_SPENDABLE) && GetCoin(input.ToMWEB(), coin)) {
if ((filter & ISMINE_SPENDABLE) && GetCoin(input.ToMWEB(), coin) && coin.IsMine()) {
return coin.amount;
}
@ -1397,7 +1397,7 @@ isminetype CWallet::IsMine(const CTxOutput& output) const
AssertLockHeld(cs_wallet);
if (output.IsMWEB()) {
mw::Coin coin;
return GetCoin(output.ToMWEB(), coin) ? ISMINE_SPENDABLE : ISMINE_NO;
return GetCoin(output.ToMWEB(), coin) && coin.IsMine() ? ISMINE_SPENDABLE : ISMINE_NO;
}
return IsMine(DestinationAddr(output.GetScriptPubKey()));
@ -1433,7 +1433,7 @@ bool CWallet::IsChange(const CTxOutput& output) const
if (output.IsMWEB()) {
mw::Coin coin;
if (GetCoin(output.ToMWEB(), coin)) {
return coin.address_index == mw::CHANGE_INDEX;
return coin.IsChange();
}
return false;
@ -1494,7 +1494,7 @@ bool CWallet::IsFromMe(const CTransaction& tx) const
CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const
{
CAmount nDebit = 0;
for (const CTxIn& txin : tx.vin)
for (const CTxInput& txin : tx.GetInputs())
{
nDebit += GetDebit(txin, filter);
if (!MoneyRange(nDebit))
@ -2061,7 +2061,7 @@ CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter
{
auto& amount = m_amounts[type];
if (recalculate || !amount.m_cached[filter]) {
amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter));
amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter)); // MW: TODO - Need to support partial mweb info
m_is_cache_empty = false;
}
return amount.m_value[filter];
@ -2456,7 +2456,11 @@ void CWallet::AvailableCoins(std::vector<COutputCoin>& vCoins, bool fOnlySafe, c
continue;
}
StealthAddress address = mweb_wallet->GetStealthAddress(coin.address_index);
StealthAddress address;
if (!mweb_wallet->GetStealthAddress(coin, address)) {
continue;
}
vCoins.push_back(MWOutput{coin, nDepth, address, &wtx});
} else {
size_t i = boost::get<COutPoint>(output.GetIndex()).n;
@ -2511,7 +2515,7 @@ std::map<CTxDestination, std::vector<COutputCoin>> CWallet::ListCoins() const
if (ExtractOutputDestination(FindNonChangeParentOutput(*wtx->tx, output_idx), address)) {
if (output_idx.type() == typeid(mw::Hash)) {
mw::Coin coin;
if (GetCoin(boost::get<mw::Hash>(output_idx), coin)) {
if (GetCoin(boost::get<mw::Hash>(output_idx), coin) && coin.IsMine()) {
result[address].emplace_back(MWOutput{coin, depth, boost::get<StealthAddress>(address), wtx});
}
} else {
@ -2618,7 +2622,7 @@ bool CWallet::SelectCoins(const std::vector<COutputCoin>& vAvailableCoins, const
{
if (idx.type() == typeid(mw::Hash)) {
mw::Coin mweb_coin;
if (!GetCoin(boost::get<mw::Hash>(idx), mweb_coin)) {
if (!GetCoin(boost::get<mw::Hash>(idx), mweb_coin) || !mweb_coin.IsMine()) {
return false;
}
@ -4331,7 +4335,12 @@ bool CWallet::ExtractOutputDestination(const CTxOutput& output, CTxDestination&
return false;
}
dest = mweb_wallet->GetStealthAddress(coin.address_index);
StealthAddress address;
if (!mweb_wallet->GetStealthAddress(coin, address)) {
return false;
}
dest = address;
return true;
} else {
return ExtractDestination(output.GetScriptPubKey(), dest);
@ -4346,7 +4355,13 @@ bool CWallet::ExtractDestinationScript(const CTxOutput& output, DestinationAddr&
return false;
}
dest = mweb_wallet->GetStealthAddress(coin.address_index);
StealthAddress address;
if (!mweb_wallet->GetStealthAddress(coin, address)) {
return false;
}
dest = address;
return true;
} else {
dest = DestinationAddr(output.GetScriptPubKey());
}

View File

@ -30,7 +30,7 @@ from test_framework.util import (
assert_raises_rpc_error,
)
class MWEBWalletPreHDTest(BitcoinTestFramework):
class MWEBWalletAddressTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
@ -56,8 +56,13 @@ class MWEBWalletPreHDTest(BitcoinTestFramework):
self.start_nodes()
self.import_deterministic_coinbase_privkeys()
def run_test(self):
self.test_prehd_wallet()
# TODO: self.test_blank_wallet()
# TODO: self.test_keys_disabled()
def test_prehd_wallet(self):
self.nodes[0].generatetoaddress(101, self.nodes[0].getnewaddress())
node_master = self.nodes[0]
@ -82,4 +87,4 @@ class MWEBWalletPreHDTest(BitcoinTestFramework):
assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", node_master.getnewaddress, address_type='mweb')
if __name__ == '__main__':
MWEBWalletPreHDTest().main()
MWEBWalletAddressTest().main()

View File

@ -0,0 +1,71 @@
#!/usr/bin/env python3
# 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.
"""Basic MWEB Wallet test"""
from test_framework.test_framework import BitcoinTestFramework
from test_framework.ltc_util import setup_mweb_chain
from test_framework.util import assert_equal
class MWEBWalletBasicTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [['-whitelist=noban@127.0.0.1'],[]] # immediate tx relay
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
node0 = self.nodes[0]
node1 = self.nodes[1]
self.log.info("Setting up MWEB chain")
setup_mweb_chain(node0)
self.sync_all()
self.log.info("Send to node1 mweb address")
n1_addr0 = node1.getnewaddress(address_type='mweb')
tx1_id = node0.sendtoaddress(n1_addr0, 25)
self.sync_mempools()
self.log.info("Verify node0's wallet lists the transaction as spent")
n0_tx1 = node0.gettransaction(txid=tx1_id)
assert_equal(n0_tx1['confirmations'], 0)
assert n0_tx1['amount'] == -25
assert n0_tx1['fee'] < 0 and n0_tx1['fee'] > -0.1
self.log.info("Verify node1's wallet receives the unconfirmed transaction")
n1_addr0_coins = node1.listreceivedbyaddress(minconf=0, address_filter=n1_addr0)
assert_equal(len(n1_addr0_coins), 1)
assert n1_addr0_coins[0]['amount'] == 25
assert n1_addr0_coins[0]['confirmations'] == 0
assert node1.getbalances()['mine']['untrusted_pending'] == 25
assert node1.getbalances()['mine']['trusted'] == 0
self.log.info("Mine the next block")
node0.generate(1)
self.sync_blocks()
self.log.info("Verify node1's wallet lists the transaction as confirmed")
n1_addr0_coins = node1.listunspent(addresses=[n1_addr0])
assert_equal(len(n1_addr0_coins), 1)
assert n1_addr0_coins[0]['amount'] == 25
assert n1_addr0_coins[0]['confirmations'] == 1
assert node1.getbalances()['mine']['untrusted_pending'] == 0
assert node1.getbalances()['mine']['trusted'] == 25
self.log.info("Verify node0's wallet lists the transaction as confirmed")
n0_tx1 = node0.gettransaction(txid=tx1_id)
assert_equal(n0_tx1['confirmations'], 1)
assert n0_tx1['amount'] == -25
assert n0_tx1['fee'] < 0 and n0_tx1['fee'] > -0.1
# TODO: Conflicting txs
# TODO: Duplicate hash
if __name__ == '__main__':
MWEBWalletBasicTest().main()

View File

@ -250,7 +250,8 @@ BASE_SCRIPTS = [
'mweb_reorg.py',
'mweb_pegout_all.py',
'mweb_node_compatibility.py',
'mweb_wallet_prehd.py',
'mweb_wallet_address.py',
'mweb_wallet_basic.py',
'rpc_uptime.py',
'wallet_resendwallettransactions.py',
'wallet_resendwallettransactions.py --descriptors',