diff --git a/configure.ac b/configure.ac index f5b2b2700..26a0a14fb 100644 --- a/configure.ac +++ b/configure.ac @@ -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/]) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 09592e360..9892ae0f7 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -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; diff --git a/src/libmw/include/mw/models/wallet/Coin.h b/src/libmw/include/mw/models/wallet/Coin.h index 7717aa89b..b73c68f7f 100644 --- a/src/libmw/include/mw/models/wallet/Coin.h +++ b/src/libmw/include/mw/models/wallet/Coin.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -10,30 +11,35 @@ MW_NAMESPACE /// -/// Change outputs will use the stealth address generated using index 2,000,000. +/// Change outputs will use the stealth address generated using index 0 /// static constexpr uint32_t CHANGE_INDEX{0}; /// -/// 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. /// static constexpr uint32_t PEGIN_INDEX{1}; +/// +/// Outputs sent to others will be marked with an address_index of 0xffffffff. +/// +static constexpr uint32_t UNKNOWN_INDEX{std::numeric_limits::max()}; + /// /// Represents an output owned by the wallet, and the keys necessary to spend it. /// 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 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 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 sender_key; + + // The StealthAddress the coin was sent to. + // This will only be populated when the coin has flag HAS_SENDER_INFO. + boost::optional 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); + } } }; diff --git a/src/libmw/include/mw/wallet/TxBuilder.h b/src/libmw/include/mw/wallet/TxBuilder.h index f3a772411..d89ccad75 100644 --- a/src/libmw/include/mw/wallet/TxBuilder.h +++ b/src/libmw/include/mw/wallet/TxBuilder.h @@ -21,6 +21,7 @@ class TxBuilder BlindingFactor total_blind; SecretKey total_key; std::vector outputs; + std::vector coins; }; public: @@ -29,7 +30,8 @@ public: const std::vector& recipients, const std::vector& pegouts, const boost::optional& pegin_amount, - const CAmount fee + const CAmount fee, + std::vector& output_coins ); private: diff --git a/src/libmw/src/models/tx/Output.cpp b/src/libmw/src/models/tx/Output.cpp index 6bb701d0a..d9825efbe 100644 --- a/src/libmw/src/models/tx/Output.cpp +++ b/src/libmw/src/models/tx/Output.cpp @@ -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{ diff --git a/src/libmw/src/wallet/Keychain.cpp b/src/libmw/src/wallet/Keychain.cpp index 47ac6e59b..d8b2faf61 100644 --- a/src/libmw/src/wallet/Keychain.cpp +++ b/src/libmw/src/wallet/Keychain.cpp @@ -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; } diff --git a/src/libmw/src/wallet/TxBuilder.cpp b/src/libmw/src/wallet/TxBuilder.cpp index b418364e8..6bdc18a4b 100644 --- a/src/libmw/src/wallet/TxBuilder.cpp +++ b/src/libmw/src/wallet/TxBuilder.cpp @@ -10,7 +10,8 @@ mw::Transaction::CPtr TxBuilder::BuildTx( const std::vector& recipients, const std::vector& pegouts, const boost::optional& pegin_amount, - const CAmount fee) + const CAmount fee, + std::vector& 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& re Blinds output_blinds; Blinds output_keys; std::vector 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 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) }; } diff --git a/src/libmw/test/framework/include/test_framework/models/TxOutput.h b/src/libmw/test/framework/include/test_framework/models/TxOutput.h index 87681209e..927d9674f 100644 --- a/src/libmw/test/framework/include/test_framework/models/TxOutput.h +++ b/src/libmw/test/framework/include/test_framework/models/TxOutput.h @@ -4,6 +4,7 @@ #include #include #include +#include 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; } diff --git a/src/libmw/test/tests/models/tx/Test_Output.cpp b/src/libmw/test/tests/models/tx/Test_Output.cpp index 132dded6d..50f7495c3 100644 --- a/src/libmw/test/tests/models/tx/Test_Output.cpp +++ b/src/libmw/test/tests/models/tx/Test_Output.cpp @@ -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(); diff --git a/src/libmw/test/tests/models/tx/Test_UTXO.cpp b/src/libmw/test/tests/models/tx/Test_UTXO.cpp index 9b202c08e..a937e2312 100644 --- a/src/libmw/test/tests/models/tx/Test_UTXO.cpp +++ b/src/libmw/test/tests/models/tx/Test_UTXO.cpp @@ -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); diff --git a/src/libmw/test/tests/wallet/Test_Keychain.cpp b/src/libmw/test/tests/wallet/Test_Keychain.cpp index 69f04da9b..5ccc8dd20 100644 --- a/src/libmw/test/tests/wallet/Test_Keychain.cpp +++ b/src/libmw/test/tests/wallet/Test_Keychain.cpp @@ -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(); diff --git a/src/mweb/mweb_transact.cpp b/src/mweb/mweb_transact.cpp index bfa649a16..43c9bf0f7 100644 --- a/src/mweb/mweb_transact.cpp +++ b/src/mweb/mweb_transact.cpp @@ -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 input_coins = GetInputCoins(selected_coins); + std::vector 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(); diff --git a/src/mweb/mweb_wallet.cpp b/src/mweb/mweb_wallet.cpp index aa6d5dcbf..f324b8d22 100644 --- a/src/mweb/mweb_wallet.cpp +++ b/src/mweb/mweb_wallet.cpp @@ -23,7 +23,7 @@ std::vector Wallet::RewindOutputs(const CTransaction& tx) bool Wallet::RewindOutput(const boost::variant& 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::variantGetStealthAddress(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& 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); diff --git a/src/mweb/mweb_wallet.h b/src/mweb/mweb_wallet.h index dd90dee47..947ce6bc1 100644 --- a/src/mweb/mweb_wallet.h +++ b/src/mweb/mweb_wallet.h @@ -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& coins); private: mw::Keychain::Ptr GetKeychain() const; diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 2946ba07c..69bd13296 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -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: diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index f72d9f56f..7f809cdff 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -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(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(""); } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 664e9b895..a8cdaf35a 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -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: { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 2d0880994..1dc6e4536 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -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) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6222cee70..1e8a04da5 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -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& 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(output.GetIndex()).n; @@ -2511,7 +2515,7 @@ std::map> 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(output_idx), coin)) { + if (GetCoin(boost::get(output_idx), coin) && coin.IsMine()) { result[address].emplace_back(MWOutput{coin, depth, boost::get(address), wtx}); } } else { @@ -2618,7 +2622,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const { if (idx.type() == typeid(mw::Hash)) { mw::Coin mweb_coin; - if (!GetCoin(boost::get(idx), mweb_coin)) { + if (!GetCoin(boost::get(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()); } diff --git a/test/functional/mweb_wallet_prehd.py b/test/functional/mweb_wallet_address.py similarity index 92% rename from test/functional/mweb_wallet_prehd.py rename to test/functional/mweb_wallet_address.py index 39058e73e..8bb35fc8f 100644 --- a/test/functional/mweb_wallet_prehd.py +++ b/test/functional/mweb_wallet_address.py @@ -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() diff --git a/test/functional/mweb_wallet_basic.py b/test/functional/mweb_wallet_basic.py new file mode 100644 index 000000000..5cfec6efe --- /dev/null +++ b/test/functional/mweb_wallet_basic.py @@ -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() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 3ad465f93..fa622e321 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -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',