Skip to content

Commit

Permalink
Add COutput::fSafe member for safe handling of unconfirmed outputs
Browse files Browse the repository at this point in the history
>>> adapted from bitcoin/bitcoin@af61d9f

This exposes a value computed in CWallet::AvailableCoins so it can used
for other things, like inclusion in listunspent output.
  • Loading branch information
random-zebra committed Jun 2, 2021
1 parent 75c8c6d commit 0201065
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 26 deletions.
2 changes: 1 addition & 1 deletion src/sapling/sapling_operation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ OperationResult SaplingOperation::loadUtxos(TxValues& txValues)
const auto* tx = wallet->GetWalletTx(outpoint.outPoint.hash);
if (!tx) continue;
nSelectedValue += tx->tx->vout[outpoint.outPoint.n].nValue;
selectedUTXOInputs.emplace_back(tx, outpoint.outPoint.n, 0, true, true);
selectedUTXOInputs.emplace_back(tx, outpoint.outPoint.n, 0, true, true, true);
}
return loadUtxos(txValues, selectedUTXOInputs, nSelectedValue);
}
Expand Down
3 changes: 2 additions & 1 deletion src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3713,11 +3713,12 @@ UniValue listunspent(const JSONRPCRequest& request)
CCoinControl coinControl;
coinControl.fAllowWatchOnly = nWatchonlyConfig == 2;

coinFilter.fOnlySafe = false;

UniValue results(UniValue::VARR);
std::vector<COutput> vecOutputs;

LOCK2(cs_main, pwallet->cs_wallet);
coinFilter.fOnlyConfirmed = false;
pwallet->AvailableCoins(&vecOutputs, &coinControl, coinFilter);
for (const COutput& out : vecOutputs) {
if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth)
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/test/wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ static void add_coin(std::unique_ptr<CWallet>& pwallet, const CAmount& nValue, i
if (fIsFromMe) {
wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1);
}
COutput output(wtx.get(), nInput, nAge, true, true);
COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
vCoins.push_back(output);
wtxn.emplace_back(std::move(wtx));
}
Expand Down
41 changes: 24 additions & 17 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2439,6 +2439,8 @@ void CWallet::GetAvailableP2CSCoins(std::vector<COutput>& vCoins) const {
if (fConflicted || nDepth < 0)
continue;

bool fSafe = pcoin->IsTrusted();

if (pcoin->tx->HasP2CSOutputs()) {
for (int i = 0; i < (int) pcoin->tx->vout.size(); i++) {
const auto &utxo = pcoin->tx->vout[i];
Expand All @@ -2451,7 +2453,7 @@ void CWallet::GetAvailableP2CSCoins(std::vector<COutput>& vCoins) const {
bool isMineSpendable = mine & ISMINE_SPENDABLE_DELEGATED;
if (mine & ISMINE_COLD || isMineSpendable)
// Depth and solvability members are not used, no need waste resources and set them for now.
vCoins.emplace_back(pcoin, i, 0, isMineSpendable, true);
vCoins.emplace_back(pcoin, i, 0, isMineSpendable, true, fSafe);
}
}
}
Expand All @@ -2463,9 +2465,9 @@ void CWallet::GetAvailableP2CSCoins(std::vector<COutput>& vCoins) const {
/**
* Test if the transaction is spendable.
*/
static bool CheckTXAvailabilityInternal(const CWalletTx* pcoin, bool fOnlyConfirmed, int& nDepth)
static bool CheckTXAvailabilityInternal(const CWalletTx* pcoin, bool fOnlySafe, int& nDepth)
{
if (fOnlyConfirmed && !pcoin->IsTrusted()) return false;
if (fOnlySafe && !pcoin->IsTrusted()) return false;
if (pcoin->GetBlocksToMaturity() > 0) return false;

nDepth = pcoin->GetDepthInMainChain();
Expand All @@ -2478,22 +2480,22 @@ static bool CheckTXAvailabilityInternal(const CWalletTx* pcoin, bool fOnlyConfir
}

// cs_main lock required
static bool CheckTXAvailability(const CWalletTx* pcoin, bool fOnlyConfirmed, int& nDepth)
static bool CheckTXAvailability(const CWalletTx* pcoin, bool fOnlySafe, int& nDepth)
{
AssertLockHeld(cs_main);
if (!CheckFinalTx(pcoin->tx)) return false;
return CheckTXAvailabilityInternal(pcoin, fOnlyConfirmed, nDepth);
return CheckTXAvailabilityInternal(pcoin, fOnlySafe, nDepth);
}

// cs_main lock NOT required
static bool CheckTXAvailability(const CWalletTx* pcoin,
bool fOnlyConfirmed,
bool fOnlySafe,
int& nDepth,
int nBlockHeight)
{
// Mimic CheckFinalTx without cs_main lock
if (!IsFinalTx(pcoin->tx, nBlockHeight + 1, GetAdjustedTime())) return false;
return CheckTXAvailabilityInternal(pcoin, fOnlyConfirmed, nDepth);
return CheckTXAvailabilityInternal(pcoin, fOnlySafe, nDepth);
}

bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet, std::string strTxHash, std::string strOutputIndex, std::string& strError)
Expand Down Expand Up @@ -2566,7 +2568,7 @@ bool CWallet::GetMasternodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey&
}

return GetVinAndKeysFromOutput(
COutput(wtx, nOutputIndex, nDepth, true, true),
COutput(wtx, nOutputIndex, nDepth, true, true, true),
txinRet,
pubKeyRet,
keyRet);
Expand Down Expand Up @@ -2639,12 +2641,14 @@ bool CWallet::AvailableCoins(std::vector<COutput>* pCoins, // --> populates

// Check if the tx is selectable
int nDepth;
if (!CheckTXAvailability(pcoin, coinsFilter.fOnlyConfirmed, nDepth, m_last_block_processed_height))
if (!CheckTXAvailability(pcoin, coinsFilter.fOnlySafe, nDepth, m_last_block_processed_height))
continue;

// Check min depth filtering requirements
if (nDepth < coinsFilter.minDepth) continue;

bool safeTx = pcoin->IsTrusted();

for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
const auto& output = pcoin->tx->vout[i];

Expand Down Expand Up @@ -2680,7 +2684,7 @@ bool CWallet::AvailableCoins(std::vector<COutput>* pCoins, // --> populates

// found valid coin
if (!pCoins) return true;
pCoins->emplace_back(pcoin, (int) i, nDepth, res.spendable, res.solvable);
pCoins->emplace_back(pcoin, (int) i, nDepth, res.spendable, res.solvable, safeTx);

// Checks the sum amount of all UTXO's.
if (coinsFilter.nMinimumSumAmount != 0) {
Expand All @@ -2701,11 +2705,11 @@ bool CWallet::AvailableCoins(std::vector<COutput>* pCoins, // --> populates
}
}

std::map<CTxDestination , std::vector<COutput> > CWallet::AvailableCoinsByAddress(bool fConfirmed, CAmount maxCoinValue, bool fIncludeColdStaking)
std::map<CTxDestination , std::vector<COutput> > CWallet::AvailableCoinsByAddress(bool fOnlySafe, CAmount maxCoinValue, bool fIncludeColdStaking)
{
CWallet::AvailableCoinsFilter coinFilter;
coinFilter.fIncludeColdStaking = true;
coinFilter.fOnlyConfirmed = fConfirmed;
coinFilter.fOnlySafe = fOnlySafe;
coinFilter.fIncludeColdStaking = fIncludeColdStaking;
coinFilter.nMaxOutValue = maxCoinValue;
std::vector<COutput> vCoins;
Expand Down Expand Up @@ -2810,7 +2814,7 @@ bool CWallet::StakeableCoins(std::vector<CStakeableOutput>* pCoins)
// found valid coin
if (!pCoins) return true;
if (!pindex) pindex = mapBlockIndex.at(pcoin->m_confirm.hashBlock);
pCoins->emplace_back(CStakeableOutput(pcoin, (int) index, nDepth, res.spendable, res.solvable, pindex));
pCoins->emplace_back(pcoin, (int) index, nDepth, pindex);
}
}
return (pCoins && !pCoins->empty());
Expand Down Expand Up @@ -4959,7 +4963,10 @@ const CWDestination* CAddressBookIterator::GetDestKey()
return &it->first;
}

CStakeableOutput::CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn,
const CBlockIndex*& _pindex) : COutput(txIn, iIn, nDepthIn, fSpendableIn, fSolvableIn),
pindex(_pindex) {}

CStakeableOutput::CStakeableOutput(const CWalletTx* txIn,
int iIn,
int nDepthIn,
const CBlockIndex*& _pindex) :
COutput(txIn, iIn, nDepthIn, true /*fSpendable*/, true/*fSolvable*/, true/*fSafe*/),
pindex(_pindex)
{}
24 changes: 18 additions & 6 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -766,15 +766,15 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
AvailableCoinsFilter() {}
AvailableCoinsFilter(bool _fIncludeDelegated,
bool _fIncludeColdStaking,
bool _fOnlyConfirmed,
bool _fOnlySafe,
bool _fOnlySpendable,
std::set<CTxDestination>* _onlyFilteredDest,
int _minDepth,
bool _fIncludeLocked = false,
CAmount _nMaxOutValue = 0) :
fIncludeDelegated(_fIncludeDelegated),
fIncludeColdStaking(_fIncludeColdStaking),
fOnlyConfirmed(_fOnlyConfirmed),
fOnlySafe(_fOnlySafe),
fOnlySpendable(_fOnlySpendable),
onlyFilteredDest(_onlyFilteredDest),
minDepth(_minDepth),
Expand All @@ -783,7 +783,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface

bool fIncludeDelegated{true};
bool fIncludeColdStaking{false};
bool fOnlyConfirmed{true};
bool fOnlySafe{true};
bool fOnlySpendable{false};
std::set<CTxDestination>* onlyFilteredDest{nullptr};
int minDepth{0};
Expand Down Expand Up @@ -1238,11 +1238,23 @@ class COutput
const CWalletTx* tx;
int i;
int nDepth;

/** Whether we have the private keys to spend this output */
bool fSpendable;

/** Whether we know how to spend this output, ignoring the lack of keys */
bool fSolvable;

COutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn) :
tx(txIn), i(iIn), nDepth(nDepthIn), fSpendable(fSpendableIn), fSolvable(fSolvableIn) {}
/**
* Whether this output is considered safe to spend. Unconfirmed transactions
* from outside keys and unconfirmed replacement transactions are considered
* unsafe and will not be used to fund new spending transactions.
*/
bool fSafe;

COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn) :
tx(txIn), i(iIn), nDepth(nDepthIn), fSpendable(fSpendableIn), fSolvable(fSolvableIn), fSafe(fSafeIn)
{}

CAmount Value() const { return tx->tx->vout[i].nValue; }
std::string ToString() const;
Expand All @@ -1253,7 +1265,7 @@ class CStakeableOutput : public COutput
public:
const CBlockIndex* pindex{nullptr};

CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn,
CStakeableOutput(const CWalletTx* txIn, int iIn, int nDepthIn,
const CBlockIndex*& pindex);

};
Expand Down

0 comments on commit 0201065

Please sign in to comment.