diff --git a/doc/release-notes-7635.md b/doc/release-notes-7635.md new file mode 100644 index 000000000000..bfa23986c039 --- /dev/null +++ b/doc/release-notes-7635.md @@ -0,0 +1,16 @@ +Wallet changes +-------------- + +- Unlocking an output with `lockunspent` (or through the Dash-Qt coin control + dialog) now also opts that output out of the automatic masternode-collateral + and dust-protection locks. Previously those automatic locks were reapplied on + every wallet load, which silently undid the unlock and left the output + unspendable with no indication why. Locking the output again hands it back to + the automatic protection, as long as that lock is persistent: `lockunspent` + writes a lock to the wallet file only when asked to, and a memory-only lock + leaves the decision standing. Registering the output as a masternode + collateral also ends the opt-out and locks the output again, because the + decision was made about an ordinary coin and does not carry over to live + collateral. The opt-out is stored in the wallet file; an older Dash Core + release reading the same wallet ignores the record and reapplies the automatic + locks as it did before. (#7635) diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index dee4fedcf332..6eed5c854bec 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -224,6 +224,14 @@ class Wallet //! Unlock the provided coins in a single batch. virtual bool unlockCoins(const std::vector& outputs) = 0; + //! Lock a coin because the user asked for it, handing it back to the automatic + //! masternode-collateral and dust locks. Use lockCoin() for anything else. + virtual bool lockCoinByUser(const COutPoint& output, bool write_to_db) = 0; + + //! Unlock a coin because the user asked for it, opting it out of those automatic + //! locks. Use unlockCoin() to release an internal or transient hold. + virtual bool unlockCoinByUser(const COutPoint& output) = 0; + //! Set dust protection threshold (does not lock anything by itself). virtual void setDustProtectionThreshold(CAmount threshold) = 0; diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 487693a498a3..40f0de651408 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -312,7 +312,7 @@ void CoinControlDialog::lockCoin() contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); COutPoint outpt(uint256S(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toUInt()); - model->wallet().lockCoin(outpt, /*write_to_db=*/true); + model->wallet().lockCoinByUser(outpt, /*write_to_db=*/true); contextMenuItem->setDisabled(true); contextMenuItem->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED)); updateLabelLocked(); @@ -322,7 +322,7 @@ void CoinControlDialog::lockCoin() void CoinControlDialog::unlockCoin() { COutPoint outpt(uint256S(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toUInt()); - model->wallet().unlockCoin(outpt); + model->wallet().unlockCoinByUser(outpt); contextMenuItem->setDisabled(false); contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); updateLabelLocked(); diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index a9421e3793c7..b5d3381a7ef5 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -491,7 +491,7 @@ void TransactionView::unlockDust() // Create the outpoint and unlock COutPoint outpoint(hash, outputIdx); - model->wallet().unlockCoin(outpoint); + model->wallet().unlockCoinByUser(outpoint); // Refresh the transaction view to update the display model->getTransactionTableModel()->refreshWallet(true); diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index e76b07e6059c..44b292f4567b 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -40,51 +40,6 @@ #include #include -static CMutableTransaction CreateSpendTx(const ChainstateManager& chainman, SimpleUTXOMap& utxos, const CScript& scriptPayout, CAmount amount, const CKey& coinbaseKey) -{ - CMutableTransaction tx; - const auto spent = FundTransaction(chainman, tx, utxos, scriptPayout, amount); - SignTransaction(tx, spent, coinbaseKey); - return tx; -} - -static COutPoint GetCollateralOutpoint(const CMutableTransaction& tx) -{ - for (size_t i = 0; i < tx.vout.size(); ++i) { - if (tx.vout[i].nValue == dmn_types::Regular.collat_amount) { - return COutPoint(tx.GetHash(), i); - } - } - return COutPoint(); -} - -// ProRegTx that references a pre-existing collateral output instead of funding the collateral inline. -static CMutableTransaction CreateProRegTxExternalCollateral(const ChainstateManager& chainman, SimpleUTXOMap& utxos, int port, const COutPoint& collateralOutpoint, const CScript& scriptPayout, const CKey& ownerKey, const CBLSSecretKey& operatorKey, const CKey& collateralKey, const CKey& coinbaseKey) -{ - CProRegTx proTx; - proTx.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false); - proTx.netInfo = NetInfoInterface::MakeNetInfo(proTx.nVersion); - BOOST_CHECK_EQUAL(proTx.netInfo->AddEntry(NetInfoPurpose::CORE_P2P, strprintf("1.1.1.1:%d", port)), - NetInfoStatus::Success); - proTx.collateralOutpoint = collateralOutpoint; - proTx.keyIDOwner = ownerKey.GetPubKey().GetID(); - proTx.pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load()); - proTx.keyIDVoting = ownerKey.GetPubKey().GetID(); - proTx.scriptPayout = scriptPayout; - - CMutableTransaction tx; - tx.nVersion = 3; - tx.nType = TRANSACTION_PROVIDER_REGISTER; - // The collateral is external (referenced via collateralOutpoint), so this tx only needs to fund a fee. - const auto spent = FundTransaction(chainman, tx, utxos, scriptPayout, /*amount=*/1 * COIN); - proTx.inputsHash = CalcTxInputsHash(CTransaction(tx)); - CMessageSigner::SignMessage(proTx.MakeSignString(), proTx.vchSig, collateralKey); - SetTxPayload(tx, proTx); - SignTransaction(tx, spent, coinbaseKey); - - return tx; -} - static CMutableTransaction CreateProUpServTx(const ChainstateManager& chainman, SimpleUTXOMap& utxos, const uint256& proTxHash, const CBLSSecretKey& operatorKey, int port, const CScript& scriptOperatorPayout, const CKey& coinbaseKey, uint16_t version = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false)) { diff --git a/src/test/util/masternode.cpp b/src/test/util/masternode.cpp index 0551591a2f94..277049747a0f 100644 --- a/src/test/util/masternode.cpp +++ b/src/test/util/masternode.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include