Skip to content

Commit

Permalink
Merge #2861: [Wallet] Locked sapling notes
Browse files Browse the repository at this point in the history
2d5d42b Functional test coverage + various test fix (Alessandro Rezzi)
66b6f9d Ability to lock shield notes in coincontrol + gui update (Alessandro Rezzi)
e19c631 Extended RPC coverage for locking shield notes (Alessandro Rezzi)
3d1c1b8 Avoid spending locked notes (Alessandro Rezzi)
dd311aa Add Locked sapling notes set (Alessandro Rezzi)

Pull request description:

  This PR implements the possibility to lock a sapling note, so the system will not try to spend it once a shield transaction is made. As for normal utxo locks are not saved on disk.

  In particular I have updated two RPCs:
   - listlockunspent will now also print sapling locked notes;
   - lockunspent now requires a third boolean parameter  "transparent" (true if you want to lock a normal utxo false if you want to lock a sapling note)

  I have also updated the coin control interface so users will be able to lock a note from the GUI.

ACKs for top commit:
  Liquid369:
    tACK 2d5d42b
  Fuzzbawls:
    ACK 2d5d42b

Tree-SHA512: ab5790a59137794f647c68a56f8d68aedc0282e9037772261d88fccf59db8b461082b595f9d78ec2452f3587bdb3f082aff061672fb3d45f3a2b7c76fa7824ac
  • Loading branch information
Fuzzbawls committed May 28, 2023
2 parents 13db0de + 2d5d42b commit 326321d
Show file tree
Hide file tree
Showing 20 changed files with 290 additions and 143 deletions.
66 changes: 23 additions & 43 deletions src/qt/coincontroldialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,17 +230,18 @@ void CoinControlDialog::buttonSelectAllClicked()

void CoinControlDialog::toggleItemLock(QTreeWidgetItem* item)
{
COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt());
if (model->isLockedCoin(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) {
model->unlockCoin(outpt);
uint256 hash = uint256S(item->text(COLUMN_TXHASH).toStdString());
int n = item->text(COLUMN_VOUT_INDEX).toUInt();
if (model->isLockedCoin(hash, n, fSelectTransparent)) {
model->unlockCoin(hash, n, fSelectTransparent);
item->setDisabled(false);
// restore cold-stake snowflake icon for P2CS which were previously locked
if (item->data(COLUMN_CHECKBOX, Qt::UserRole) == QString("Delegated"))
item->setIcon(COLUMN_CHECKBOX, QIcon("://ic-check-cold-staking-off"));
else
item->setIcon(COLUMN_CHECKBOX, QIcon());
} else {
model->lockCoin(outpt);
model->lockCoin(hash, n, fSelectTransparent);
item->setDisabled(true);
item->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/lock_closed"));
}
Expand All @@ -267,12 +268,6 @@ void CoinControlDialog::toggleCoinLock()
// Toggle lock state
void CoinControlDialog::buttonToggleLockClicked()
{
if (!fSelectTransparent) { // todo: implement locked notes
ui->pushButtonToggleLock->setChecked(false);
return;
}

// Works in list-mode only
ui->treeWidget->setEnabled(false);
toggleCoinLock();
ui->treeWidget->setEnabled(true);
Expand All @@ -289,7 +284,7 @@ void CoinControlDialog::showMenu(const QPoint& point)
// disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu
if (item->text(COLUMN_TXHASH).length() == 64) { // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode)
copyTransactionHashAction->setEnabled(true);
if (model->isLockedCoin(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) {
if (model->isLockedCoin(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt(), fSelectTransparent)) {
lockAction->setEnabled(false);
unlockAction->setEnabled(true);
} else {
Expand Down Expand Up @@ -340,12 +335,11 @@ void CoinControlDialog::copyTransactionHash()
// context menu action: lock coin
void CoinControlDialog::lockCoin()
{
if (!fSelectTransparent) return; // todo: implement locked notes
if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked)
contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);

COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt());
model->lockCoin(outpt);
uint256 txHash = uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString());
int n = contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt();
model->lockCoin(txHash, n, fSelectTransparent);
contextMenuItem->setDisabled(true);
contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/lock_closed"));
updateLabelLocked();
Expand All @@ -354,9 +348,9 @@ void CoinControlDialog::lockCoin()
// context menu action: unlock coin
void CoinControlDialog::unlockCoin()
{
if (!fSelectTransparent) return; // todo: implement locked notes
COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt());
model->unlockCoin(outpt);
uint256 txHash = uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString());
int n = contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt();
model->unlockCoin(txHash, n, fSelectTransparent);
contextMenuItem->setDisabled(false);
// restore cold-stake snowflake icon for P2CS which were previously locked
if (contextMenuItem->data(COLUMN_CHECKBOX, Qt::UserRole) == QString("Delegated"))
Expand Down Expand Up @@ -482,17 +476,12 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column)
// shows count of locked unspent outputs
void CoinControlDialog::updateLabelLocked()
{
if (fSelectTransparent) {
std::set<COutPoint> vOutpts = model->listLockedCoins();
if (!vOutpts.empty()) {
ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size()));
ui->labelLocked->setVisible(true);
} else
ui->labelLocked->setVisible(false);
} else {
int nLocked = fSelectTransparent ? model->listLockedCoins().size() : model->listLockedNotes().size();
if (nLocked > 0) {
ui->labelLocked->setText(tr("(%1 locked)").arg(nLocked));
ui->labelLocked->setVisible(true);
} else
ui->labelLocked->setVisible(false);
// TODO: implement locked notes functionality inside the wallet..
}
}

// serialized int size
Expand Down Expand Up @@ -726,16 +715,13 @@ void CoinControlDialog::loadAvailableCoin(bool treeMode,
// vout index
itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(outIndex));

// disable locked coins (!TODO: implement locked notes)
bool isLockedCoin{false};
if (fSelectTransparent) {
isLockedCoin = model->isLockedCoin(txhash, outIndex);
if (isLockedCoin) {
--nSelectableInputs;
coinControl->UnSelect({txhash, outIndex}); // just to be sure
itemOutput->setDisabled(true);
itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/lock_closed"));
}
isLockedCoin = model->isLockedCoin(txhash, outIndex, fSelectTransparent);
if (isLockedCoin) {
--nSelectableInputs;
coinControl->UnSelect({txhash, outIndex}); // just to be sure
itemOutput->setDisabled(true);
itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/lock_closed"));
}

// set checkbox
Expand Down Expand Up @@ -841,12 +827,6 @@ void CoinControlDialog::updateView()
// sort view
sortView(sortColumn, sortOrder);
ui->treeWidget->setEnabled(true);

// TODO: Remove this once note locking is functional
// Hide or show locking button and context menu items
lockAction->setVisible(fSelectTransparent);
unlockAction->setVisible(fSelectTransparent);
ui->pushButtonToggleLock->setVisible(fSelectTransparent);
}

void CoinControlDialog::refreshDialog()
Expand Down
6 changes: 3 additions & 3 deletions src/qt/pivx/mnmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "masternode.h"
#include "masternodeman.h"
#include "net.h" // for validateMasternodeIP
#include "primitives/transaction.h"
#include "qt/bitcoinunits.h"
#include "qt/optionsmodel.h"
#include "qt/pivx/guitransactionsutils.h"
Expand Down Expand Up @@ -408,7 +409,7 @@ CMasternodeConfig::CMasternodeEntry* MNModel::createLegacyMN(COutPoint& collater
auto ret_mn_entry = masternodeConfig.add(alias, serviceAddr+":"+port, mnKeyString, txID, indexOutStr);

// Lock collateral output
walletModel->lockCoin(collateralOut);
walletModel->lockCoin(collateralOut.hash, collateralOut.n);
return ret_mn_entry;
}

Expand Down Expand Up @@ -498,8 +499,7 @@ bool MNModel::removeLegacyMN(const std::string& alias_to_remove, const std::stri
rename(pathConfigFile, pathNewConfFile);

// Unlock collateral
COutPoint collateralOut(uint256S(tx_id), out_index);
walletModel->unlockCoin(collateralOut);
walletModel->unlockCoin(uint256S(tx_id), out_index);
// Remove alias
masternodeConfig.remove(alias_to_remove);
return true;
Expand Down
27 changes: 14 additions & 13 deletions src/qt/pivx/send.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "qt/pivx/send.h"
#include "qt/pivx/forms/ui_send.h"
#include "addresstablemodel.h"
#include "clientmodel.h"
#include "coincontrol.h"
#include "openuridialog.h"
#include "operationresult.h"
#include "optionsmodel.h"
#include "qt/pivx/addnewcontactdialog.h"
#include "qt/pivx/forms/ui_send.h"
#include "qt/pivx/guitransactionsutils.h"
#include "qt/pivx/loadingdialog.h"
#include "qt/pivx/optionbutton.h"
#include "qt/pivx/qtutils.h"
#include "qt/pivx/sendchangeaddressdialog.h"
#include "qt/pivx/optionbutton.h"
#include "qt/pivx/sendconfirmdialog.h"
#include "qt/pivx/guitransactionsutils.h"
#include "qt/pivx/loadingdialog.h"
#include "clientmodel.h"
#include "optionsmodel.h"
#include "operationresult.h"
#include "addresstablemodel.h"
#include "coincontrol.h"
#include "qt/walletmodel.h"
#include "script/standard.h"
#include "openuridialog.h"

#define REQUEST_PREPARE_TX 1
#define REQUEST_REFRESH_BALANCE 2
Expand Down Expand Up @@ -160,14 +161,14 @@ void SendWidget::refreshAmounts()
} else {
interfaces::WalletBalances balances = walletModel->GetWalletBalances();
if (isTransparent) {
totalAmount = balances.balance - balances.shielded_balance - walletModel->getLockedBalance() - total;
totalAmount = balances.balance - balances.shielded_balance - walletModel->getLockedBalance(isTransparent) - total;
if (!fDelegationsChecked) {
totalAmount -= balances.delegate_balance;
}
// show delegated balance if exist
delegatedBalance = balances.delegate_balance;
} else {
totalAmount = balances.shielded_balance - total;
totalAmount = balances.shielded_balance - total - walletModel->getLockedBalance(isTransparent);
}
titleTotalRemaining = tr("Unlocked remaining");
}
Expand Down Expand Up @@ -702,7 +703,7 @@ void SendWidget::onShieldCoinsClicked()
}

auto balances = walletModel->GetWalletBalances();
CAmount availableBalance = balances.balance - balances.shielded_balance - walletModel->getLockedBalance();
CAmount availableBalance = balances.balance - balances.shielded_balance - walletModel->getLockedBalance(true);
if (availableBalance > 0) {

// Calculate the required fee first. TODO future: Unify this code with the code in coincontroldialog into the model.
Expand Down
2 changes: 1 addition & 1 deletion src/qt/pivx/topbar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ void TopBar::updateBalances(const interfaces::WalletBalances& newBalance)
// Locked balance. //TODO move this to the signal properly in the future..
CAmount nLockedBalance = 0;
if (walletModel) {
nLockedBalance = walletModel->getLockedBalance();
nLockedBalance = walletModel->getLockedBalance(true) + walletModel->getLockedBalance(false);
}
ui->labelTitle1->setText(nLockedBalance > 0 ? tr("Available (Locked included)") : tr("Available"));

Expand Down
25 changes: 17 additions & 8 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ CAmount WalletModel::getMinColdStakingAmount() const
return MIN_COLDSTAKING_AMOUNT;
}

CAmount WalletModel::getLockedBalance() const
CAmount WalletModel::getLockedBalance(bool isTransparent) const
{
return wallet->GetLockedCoins();
return isTransparent ? wallet->GetLockedCoins() : wallet->GetLockedShieldCoins();
}

bool WalletModel::haveWatchOnly() const
Expand Down Expand Up @@ -1112,22 +1112,25 @@ void WalletModel::listCoins(std::map<ListCoinsKey, std::vector<ListCoinsValue>>&
}
}

bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const
bool WalletModel::isLockedCoin(uint256 hash, unsigned int n, bool isTransparent) const
{
LOCK(wallet->cs_wallet);
return wallet->IsLockedCoin(hash, n);
if (isTransparent)
return wallet->IsLockedCoin(hash, n);
else
return wallet->IsLockedNote(SaplingOutPoint(hash, n));
}

void WalletModel::lockCoin(COutPoint& output)
void WalletModel::lockCoin(uint256 hash, unsigned int n, bool isTransparent)
{
LOCK(wallet->cs_wallet);
wallet->LockCoin(output);
isTransparent ? wallet->LockCoin(COutPoint(hash, n)) : wallet->LockNote(SaplingOutPoint(hash, n));
}

void WalletModel::unlockCoin(COutPoint& output)
void WalletModel::unlockCoin(uint256 hash, unsigned int n, bool isTransparent)
{
LOCK(wallet->cs_wallet);
wallet->UnlockCoin(output);
isTransparent ? wallet->UnlockCoin(COutPoint(hash, n)) : wallet->UnlockNote(SaplingOutPoint(hash, n));
}

std::set<COutPoint> WalletModel::listLockedCoins()
Expand All @@ -1136,6 +1139,12 @@ std::set<COutPoint> WalletModel::listLockedCoins()
return wallet->ListLockedCoins();
}

std::set<SaplingOutPoint> WalletModel::listLockedNotes()
{
LOCK(wallet->cs_wallet);
return wallet->ListLockedNotes();
}

void WalletModel::loadReceiveRequests(std::vector<std::string>& vReceiveRequests)
{
vReceiveRequests = wallet->GetDestValues("rr"); // receive request
Expand Down
10 changes: 6 additions & 4 deletions src/qt/walletmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class WalletModel : public QObject

CAmount getBalance(const CCoinControl* coinControl = nullptr, bool fIncludeDelegated = true, bool fUnlockedOnly = false, bool fIncludeShielded = true) const;
CAmount getUnlockedBalance(const CCoinControl* coinControl = nullptr, bool fIncludeDelegated = true, bool fIncludeShielded = true) const;
CAmount getLockedBalance() const;
CAmount getLockedBalance(bool isTransparent) const;
bool haveWatchOnly() const;
CAmount getDelegatedBalance() const;

Expand Down Expand Up @@ -339,10 +339,12 @@ class WalletModel : public QObject
void listCoins(std::map<ListCoinsKey, std::vector<ListCoinsValue>>& mapCoins) const;
void listAvailableNotes(std::map<ListCoinsKey, std::vector<ListCoinsValue>>& mapCoins) const;

bool isLockedCoin(uint256 hash, unsigned int n) const;
void lockCoin(COutPoint& output);
void unlockCoin(COutPoint& output);
bool isLockedCoin(uint256 hash, unsigned int n, bool isTransparent = true) const;
void lockCoin(uint256 hash, unsigned int n, bool isTransparent = true);
void unlockCoin(uint256 hash, unsigned int n, bool isTransparent = true);

std::set<COutPoint> listLockedCoins();
std::set<SaplingOutPoint> listLockedNotes();

void loadReceiveRequests(std::vector<std::string>& vReceiveRequests);
bool saveReceiveRequest(const std::string& sAddress, const int64_t nId, const std::string& sRequest);
Expand Down
3 changes: 2 additions & 1 deletion src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ static const CRPCConvertParam vRPCConvertParams[] = {
{ "listunspent", 4, "query_options" },
{ "listunspent", 5, "include_unsafe" },
{ "lockunspent", 0, "unlock" },
{ "lockunspent", 1, "transactions" },
{ "lockunspent", 1, "transparent" },
{ "lockunspent", 2, "transactions" },
{ "logging", 0, "include" },
{ "logging", 1, "exclude" },
{ "mnbudgetvote", 4, "legacy" },
Expand Down
Loading

0 comments on commit 326321d

Please sign in to comment.