mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 20:12:57 +01:00
Merge 84deba5456
into dd96032e12
This commit is contained in:
commit
491d17833a
15
doc/release-notes-23065.md
Normal file
15
doc/release-notes-23065.md
Normal file
@ -0,0 +1,15 @@
|
||||
Notable changes
|
||||
===============
|
||||
|
||||
Updated RPCs
|
||||
------------
|
||||
|
||||
- `lockunspent` now optionally takes a third parameter, `persistent`, which
|
||||
causes the lock to be written persistently to the wallet database. This
|
||||
allows UTXOs to remain locked even after node restarts or crashes.
|
||||
|
||||
GUI changes
|
||||
-----------
|
||||
|
||||
- UTXOs which are locked via the GUI are now stored persistently in the
|
||||
wallet database, so are not lost on node shutdown or crash.
|
@ -178,7 +178,7 @@ Commit your signature for the signed macOS/Windows binaries:
|
||||
```sh
|
||||
pushd ./guix.sigs
|
||||
git add "${VERSION}/${SIGNER}"/all.SHA256SUMS{,.asc}
|
||||
git commit -m "Add ${SIGNER} ${VERSION} signed binaries signatures"
|
||||
git commit -m "Add attestations by ${SIGNER} for ${VERSION} codesigned"
|
||||
git push # Assuming you can push to the guix.sigs tree
|
||||
popd
|
||||
```
|
||||
|
@ -136,10 +136,10 @@ public:
|
||||
virtual bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) = 0;
|
||||
|
||||
//! Lock coin.
|
||||
virtual void lockCoin(const COutPoint& output) = 0;
|
||||
virtual bool lockCoin(const COutPoint& output, const bool write_to_db) = 0;
|
||||
|
||||
//! Unlock coin.
|
||||
virtual void unlockCoin(const COutPoint& output) = 0;
|
||||
virtual bool unlockCoin(const COutPoint& output) = 0;
|
||||
|
||||
//! Return whether coin is locked.
|
||||
virtual bool isLockedCoin(const COutPoint& output) = 0;
|
||||
|
@ -207,7 +207,7 @@ void CoinControlDialog::buttonToggleLockClicked()
|
||||
item->setIcon(COLUMN_CHECKBOX, QIcon());
|
||||
}
|
||||
else{
|
||||
model->wallet().lockCoin(outpt);
|
||||
model->wallet().lockCoin(outpt, /* write_to_db = */ true);
|
||||
item->setDisabled(true);
|
||||
item->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
|
||||
}
|
||||
@ -300,7 +300,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);
|
||||
model->wallet().lockCoin(outpt, /* write_to_db = */ true);
|
||||
contextMenuItem->setDisabled(true);
|
||||
contextMenuItem->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
|
||||
updateLabelLocked();
|
||||
|
@ -152,6 +152,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "gettxoutsetinfo", 2, "use_index"},
|
||||
{ "lockunspent", 0, "unlock" },
|
||||
{ "lockunspent", 1, "transactions" },
|
||||
{ "lockunspent", 2, "persistent" },
|
||||
{ "send", 0, "outputs" },
|
||||
{ "send", 1, "conf_target" },
|
||||
{ "send", 3, "fee_rate"},
|
||||
|
@ -124,10 +124,9 @@ size_t CTxMemPoolEntry::GetTxSize() const
|
||||
return GetVirtualTransactionSize(nTxSize, sigOpCount);
|
||||
}
|
||||
|
||||
// Update the given tx for any in-mempool descendants.
|
||||
// Assumes that CTxMemPool::m_children is correct for the given tx and all
|
||||
// descendants.
|
||||
void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendants, const std::set<uint256> &setExclude)
|
||||
void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants,
|
||||
const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove,
|
||||
uint64_t ancestor_size_limit, uint64_t ancestor_count_limit)
|
||||
{
|
||||
CTxMemPoolEntry::Children stageEntries, descendants;
|
||||
stageEntries = updateIt->GetMemPoolChildrenConst();
|
||||
@ -164,17 +163,18 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendan
|
||||
cachedDescendants[updateIt].insert(mapTx.iterator_to(descendant));
|
||||
// Update ancestor state for each descendant
|
||||
mapTx.modify(mapTx.iterator_to(descendant), update_ancestor_state(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCount()));
|
||||
// Don't directly remove the transaction here -- doing so would
|
||||
// invalidate iterators in cachedDescendants. Mark it for removal
|
||||
// by inserting into descendants_to_remove.
|
||||
if (descendant.GetCountWithAncestors() > ancestor_count_limit || descendant.GetSizeWithAncestors() > ancestor_size_limit) {
|
||||
descendants_to_remove.insert(descendant.GetTx().GetHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
mapTx.modify(updateIt, update_descendant_state(modifySize, modifyFee, modifyCount));
|
||||
}
|
||||
|
||||
// vHashesToUpdate is the set of transaction hashes from a disconnected block
|
||||
// which has been re-added to the mempool.
|
||||
// for each entry, look for descendants that are outside vHashesToUpdate, and
|
||||
// add fee/size information for such descendants to the parent.
|
||||
// for each such descendant, also update the ancestor state to include the parent.
|
||||
void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate)
|
||||
void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate, uint64_t ancestor_size_limit, uint64_t ancestor_count_limit)
|
||||
{
|
||||
AssertLockHeld(cs);
|
||||
// For each entry in vHashesToUpdate, store the set of in-mempool, but not
|
||||
@ -186,6 +186,8 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
|
||||
// accounted for in the state of their ancestors)
|
||||
std::set<uint256> setAlreadyIncluded(vHashesToUpdate.begin(), vHashesToUpdate.end());
|
||||
|
||||
std::set<uint256> descendants_to_remove;
|
||||
|
||||
// Iterate in reverse, so that whenever we are looking at a transaction
|
||||
// we are sure that all in-mempool descendants have already been processed.
|
||||
// This maximizes the benefit of the descendant cache and guarantees that
|
||||
@ -215,7 +217,15 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
|
||||
}
|
||||
}
|
||||
} // release epoch guard for UpdateForDescendants
|
||||
UpdateForDescendants(it, mapMemPoolDescendantsToUpdate, setAlreadyIncluded);
|
||||
UpdateForDescendants(it, mapMemPoolDescendantsToUpdate, setAlreadyIncluded, descendants_to_remove, ancestor_size_limit, ancestor_count_limit);
|
||||
}
|
||||
|
||||
for (const auto& txid : descendants_to_remove) {
|
||||
// This txid may have been removed already in a prior call to removeRecursive.
|
||||
// Therefore we ensure it is not yet removed already.
|
||||
if (const std::optional<txiter> txiter = GetIter(txid)) {
|
||||
removeRecursive((*txiter)->GetTx(), MemPoolRemovalReason::SIZELIMIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -675,16 +675,25 @@ public:
|
||||
*/
|
||||
void RemoveStaged(setEntries& stage, bool updateDescendants, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
|
||||
/** When adding transactions from a disconnected block back to the mempool,
|
||||
* new mempool entries may have children in the mempool (which is generally
|
||||
* not the case when otherwise adding transactions).
|
||||
* UpdateTransactionsFromBlock() will find child transactions and update the
|
||||
* descendant state for each transaction in vHashesToUpdate (excluding any
|
||||
* child transactions present in vHashesToUpdate, which are already accounted
|
||||
* for). Note: vHashesToUpdate should be the set of transactions from the
|
||||
* disconnected block that have been accepted back into the mempool.
|
||||
/** UpdateTransactionsFromBlock is called when adding transactions from a
|
||||
* disconnected block back to the mempool, new mempool entries may have
|
||||
* children in the mempool (which is generally not the case when otherwise
|
||||
* adding transactions).
|
||||
* @post updated descendant state for descendants of each transaction in
|
||||
* vHashesToUpdate (excluding any child transactions present in
|
||||
* vHashesToUpdate, which are already accounted for). Updated state
|
||||
* includes add fee/size information for such descendants to the
|
||||
* parent and updated ancestor state to include the parent.
|
||||
*
|
||||
* @param[in] vHashesToUpdate The set of txids from the
|
||||
* disconnected block that have been accepted back into the mempool.
|
||||
* @param[in] ancestor_size_limit The maximum allowed size in virtual
|
||||
* bytes of an entry and its ancestors
|
||||
* @param[in] ancestor_count_limit The maximum allowed number of
|
||||
* transactions including the entry and its ancestors.
|
||||
*/
|
||||
void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch);
|
||||
void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate,
|
||||
uint64_t ancestor_size_limit, uint64_t ancestor_count_limit) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch);
|
||||
|
||||
/** Try to calculate all in-mempool ancestors of entry.
|
||||
* (these are all calculated including the tx itself)
|
||||
@ -828,19 +837,38 @@ private:
|
||||
/** UpdateForDescendants is used by UpdateTransactionsFromBlock to update
|
||||
* the descendants for a single transaction that has been added to the
|
||||
* mempool but may have child transactions in the mempool, eg during a
|
||||
* chain reorg. setExclude is the set of descendant transactions in the
|
||||
* mempool that must not be accounted for (because any descendants in
|
||||
* setExclude were added to the mempool after the transaction being
|
||||
* updated and hence their state is already reflected in the parent
|
||||
* state).
|
||||
* chain reorg.
|
||||
*
|
||||
* cachedDescendants will be updated with the descendants of the transaction
|
||||
* being updated, so that future invocations don't need to walk the
|
||||
* same transaction again, if encountered in another transaction chain.
|
||||
* @pre CTxMemPool::m_children is correct for the given tx and all
|
||||
* descendants.
|
||||
* @pre cachedDescendants is an accurate cache where each entry has all
|
||||
* descendants of the corresponding key, including those that should
|
||||
* be removed for violation of ancestor limits.
|
||||
* @post if updateIt has any non-excluded descendants, cachedDescendants has
|
||||
* a new cache line for updateIt.
|
||||
* @post descendants_to_remove has a new entry for any descendant which exceeded
|
||||
* ancestor limits relative to updateIt.
|
||||
*
|
||||
* @param[in] updateIt the entry to update for its descendants
|
||||
* @param[in,out] cachedDescendants a cache where each line corresponds to all
|
||||
* descendants. It will be updated with the descendants of the transaction
|
||||
* being updated, so that future invocations don't need to walk the same
|
||||
* transaction again, if encountered in another transaction chain.
|
||||
* @param[in] setExclude the set of descendant transactions in the mempool
|
||||
* that must not be accounted for (because any descendants in setExclude
|
||||
* were added to the mempool after the transaction being updated and hence
|
||||
* their state is already reflected in the parent state).
|
||||
* @param[out] descendants_to_remove Populated with the txids of entries that
|
||||
* exceed ancestor limits. It's the responsibility of the caller to
|
||||
* removeRecursive them.
|
||||
* @param[in] ancestor_size_limit the max number of ancestral bytes allowed
|
||||
* for any descendant
|
||||
* @param[in] ancestor_count_limit the max number of ancestor transactions
|
||||
* allowed for any descendant
|
||||
*/
|
||||
void UpdateForDescendants(txiter updateIt,
|
||||
cacheMap &cachedDescendants,
|
||||
const std::set<uint256> &setExclude) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
void UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants,
|
||||
const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove,
|
||||
uint64_t ancestor_size_limit, uint64_t ancestor_count_limit) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
/** Update ancestors of hash to add/remove it as a descendant transaction. */
|
||||
void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
/** Set ancestor state for an entry */
|
||||
|
@ -365,7 +365,9 @@ void CChainState::MaybeUpdateMempoolForReorg(
|
||||
// previously-confirmed transactions back to the mempool.
|
||||
// UpdateTransactionsFromBlock finds descendants of any transactions in
|
||||
// the disconnectpool that were added back and cleans up the mempool state.
|
||||
m_mempool->UpdateTransactionsFromBlock(vHashUpdate);
|
||||
const uint64_t ancestor_count_limit = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
|
||||
const uint64_t ancestor_size_limit = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000;
|
||||
m_mempool->UpdateTransactionsFromBlock(vHashUpdate, ancestor_size_limit, ancestor_count_limit);
|
||||
|
||||
// Predicate to use for filtering transactions in removeForReorg.
|
||||
// Checks whether the transaction is still final and, if it spends a coinbase output, mature.
|
||||
|
@ -238,15 +238,17 @@ public:
|
||||
WalletBatch batch{m_wallet->GetDatabase()};
|
||||
return m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
|
||||
}
|
||||
void lockCoin(const COutPoint& output) override
|
||||
bool lockCoin(const COutPoint& output, const bool write_to_db) override
|
||||
{
|
||||
LOCK(m_wallet->cs_wallet);
|
||||
return m_wallet->LockCoin(output);
|
||||
std::unique_ptr<WalletBatch> batch = write_to_db ? std::make_unique<WalletBatch>(m_wallet->GetDatabase()) : nullptr;
|
||||
return m_wallet->LockCoin(output, batch.get());
|
||||
}
|
||||
void unlockCoin(const COutPoint& output) override
|
||||
bool unlockCoin(const COutPoint& output) override
|
||||
{
|
||||
LOCK(m_wallet->cs_wallet);
|
||||
return m_wallet->UnlockCoin(output);
|
||||
std::unique_ptr<WalletBatch> batch = std::make_unique<WalletBatch>(m_wallet->GetDatabase());
|
||||
return m_wallet->UnlockCoin(output, batch.get());
|
||||
}
|
||||
bool isLockedCoin(const COutPoint& output) override
|
||||
{
|
||||
|
@ -2204,8 +2204,9 @@ static RPCHelpMan lockunspent()
|
||||
"If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n"
|
||||
"A locked transaction output will not be chosen by automatic coin selection, when spending Dash.\n"
|
||||
"Manually selected coins are automatically unlocked.\n"
|
||||
"Locks are stored in memory only. Nodes start with zero locked outputs, and the locked output list\n"
|
||||
"is always cleared (by virtue of process exit) when a node stops or fails.\n"
|
||||
"Locks are stored in memory only, unless persistent=true, in which case they will be written to the\n"
|
||||
"wallet database and loaded on node start. Unwritten (persistent=false) locks are always cleared\n"
|
||||
"(by virtue of process exit) when a node stops or fails. Unlocking will clear both persistent and not.\n"
|
||||
"Also see the listunspent call\n",
|
||||
{
|
||||
{"unlock", RPCArg::Type::BOOL, RPCArg::Optional::NO, "Whether to unlock (true) or lock (false) the specified transactions"},
|
||||
@ -2217,6 +2218,7 @@ static RPCHelpMan lockunspent()
|
||||
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
|
||||
},
|
||||
},
|
||||
{"persistent", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to write/erase this lock in the wallet database, or keep the change in memory only. Ignored for unlocking."},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -2232,6 +2234,8 @@ static RPCHelpMan lockunspent()
|
||||
+ HelpExampleCli("listlockunspent", "") +
|
||||
"\nUnlock the transaction again\n"
|
||||
+ HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
|
||||
"\nLock the transaction persistently in the wallet database\n"
|
||||
+ HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\" true") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"")
|
||||
},
|
||||
@ -2250,9 +2254,13 @@ static RPCHelpMan lockunspent()
|
||||
|
||||
bool fUnlock = request.params[0].get_bool();
|
||||
|
||||
const bool persistent{request.params[2].isNull() ? false : request.params[2].get_bool()};
|
||||
|
||||
if (request.params[1].isNull()) {
|
||||
if (fUnlock)
|
||||
pwallet->UnlockAllCoins();
|
||||
if (fUnlock) {
|
||||
if (!pwallet->UnlockAllCoins())
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coins failed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2303,17 +2311,24 @@ static RPCHelpMan lockunspent()
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output");
|
||||
}
|
||||
|
||||
if (!fUnlock && is_locked) {
|
||||
if (!fUnlock && is_locked && !persistent) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output already locked");
|
||||
}
|
||||
|
||||
outputs.push_back(outpt);
|
||||
}
|
||||
|
||||
std::unique_ptr<WalletBatch> batch = nullptr;
|
||||
// Unlock is always persistent
|
||||
if (fUnlock || persistent) batch = std::make_unique<WalletBatch>(pwallet->GetDatabase());
|
||||
|
||||
// Atomically set (un)locked status for the outputs.
|
||||
for (const COutPoint& outpt : outputs) {
|
||||
if (fUnlock) pwallet->UnlockCoin(outpt);
|
||||
else pwallet->LockCoin(outpt);
|
||||
if (fUnlock) {
|
||||
if (!pwallet->UnlockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coin failed");
|
||||
} else {
|
||||
if (!pwallet->LockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Locking coin failed");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -625,12 +625,17 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
|
||||
return false;
|
||||
}
|
||||
|
||||
void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid)
|
||||
void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid, WalletBatch* batch)
|
||||
{
|
||||
mapTxSpends.insert(std::make_pair(outpoint, wtxid));
|
||||
setWalletUTXO.erase(outpoint);
|
||||
|
||||
setLockedCoins.erase(outpoint);
|
||||
if (batch) {
|
||||
UnlockCoin(outpoint, batch);
|
||||
} else {
|
||||
WalletBatch temp_batch(GetDatabase());
|
||||
UnlockCoin(outpoint, &temp_batch);
|
||||
}
|
||||
|
||||
std::pair<TxSpends::iterator, TxSpends::iterator> range;
|
||||
range = mapTxSpends.equal_range(outpoint);
|
||||
@ -638,7 +643,7 @@ void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid)
|
||||
}
|
||||
|
||||
|
||||
void CWallet::AddToSpends(const uint256& wtxid)
|
||||
void CWallet::AddToSpends(const uint256& wtxid, WalletBatch* batch)
|
||||
{
|
||||
auto it = mapWallet.find(wtxid);
|
||||
assert(it != mapWallet.end());
|
||||
@ -647,7 +652,7 @@ void CWallet::AddToSpends(const uint256& wtxid)
|
||||
return;
|
||||
|
||||
for (const CTxIn& txin : thisTx.tx->vin)
|
||||
AddToSpends(txin.prevout, wtxid);
|
||||
AddToSpends(txin.prevout, wtxid, batch);
|
||||
}
|
||||
|
||||
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
||||
@ -915,7 +920,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
|
||||
wtx.nOrderPos = IncOrderPosNext(&batch);
|
||||
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
|
||||
wtx.nTimeSmart = ComputeTimeSmart(wtx);
|
||||
AddToSpends(hash);
|
||||
AddToSpends(hash, &batch);
|
||||
|
||||
std::vector<std::pair<const CTransactionRef&, unsigned int>> outputs;
|
||||
for(unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
|
||||
@ -4444,7 +4449,7 @@ void ReserveDestination::ReturnDestination()
|
||||
address = CNoDestination();
|
||||
}
|
||||
|
||||
void CWallet::LockCoin(const COutPoint& output)
|
||||
bool CWallet::LockCoin(const COutPoint& output, WalletBatch* batch)
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
setLockedCoins.insert(output);
|
||||
@ -4453,23 +4458,38 @@ void CWallet::LockCoin(const COutPoint& output)
|
||||
|
||||
fAnonymizableTallyCached = false;
|
||||
fAnonymizableTallyCachedNonDenom = false;
|
||||
if (batch) {
|
||||
return batch->WriteLockedUTXO(output);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CWallet::UnlockCoin(const COutPoint& output)
|
||||
bool CWallet::UnlockCoin(const COutPoint& output, WalletBatch* batch)
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
setLockedCoins.erase(output);
|
||||
std::map<uint256, CWalletTx>::iterator it = mapWallet.find(output.hash);
|
||||
if (it != mapWallet.end()) it->second.MarkDirty(); // recalculate all credits for this tx
|
||||
|
||||
fAnonymizableTallyCached = false;
|
||||
fAnonymizableTallyCachedNonDenom = false;
|
||||
|
||||
bool was_locked = setLockedCoins.erase(output);
|
||||
if (batch && was_locked) {
|
||||
return batch->EraseLockedUTXO(output);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CWallet::UnlockAllCoins()
|
||||
bool CWallet::UnlockAllCoins()
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
bool success = true;
|
||||
WalletBatch batch(GetDatabase());
|
||||
for (auto it = setLockedCoins.begin(); it != setLockedCoins.end(); ++it) {
|
||||
success &= batch.EraseLockedUTXO(*it);
|
||||
}
|
||||
setLockedCoins.clear();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
|
||||
|
@ -759,8 +759,8 @@ private:
|
||||
*/
|
||||
typedef std::multimap<COutPoint, uint256> TxSpends;
|
||||
TxSpends mapTxSpends GUARDED_BY(cs_wallet);
|
||||
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void AddToSpends(const uint256& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void AddToSpends(const uint256& wtxid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
std::set<COutPoint> setWalletUTXO;
|
||||
mutable std::map<COutPoint, int> mapOutpointRoundsCache GUARDED_BY(cs_wallet);
|
||||
@ -1033,9 +1033,9 @@ public:
|
||||
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool separate_coins, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const;
|
||||
|
||||
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void UnlockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool LockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool UnlockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void ListLockedCoins(std::vector<COutPoint>& vOutpts) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void ListProTxCoins(std::vector<COutPoint>& vOutpts) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
|
@ -47,6 +47,7 @@ const std::string HDCHAIN{"hdchain"};
|
||||
const std::string HDPUBKEY{"hdpubkey"};
|
||||
const std::string KEYMETA{"keymeta"};
|
||||
const std::string KEY{"key"};
|
||||
const std::string LOCKED_UTXO{"lockedutxo"};
|
||||
const std::string MASTER_KEY{"mkey"};
|
||||
const std::string MINVERSION{"minversion"};
|
||||
const std::string NAME{"name"};
|
||||
@ -308,6 +309,16 @@ bool WalletBatch::WriteDescriptorCacheItems(const uint256& desc_id, const Descri
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteLockedUTXO(const COutPoint& output)
|
||||
{
|
||||
return WriteIC(std::make_pair(DBKeys::LOCKED_UTXO, std::make_pair(output.hash, output.n)), uint8_t{'1'});
|
||||
}
|
||||
|
||||
bool WalletBatch::EraseLockedUTXO(const COutPoint& output)
|
||||
{
|
||||
return EraseIC(std::make_pair(DBKeys::LOCKED_UTXO, std::make_pair(output.hash, output.n)));
|
||||
}
|
||||
|
||||
class CWalletScanState {
|
||||
public:
|
||||
unsigned int nKeys{0};
|
||||
@ -709,6 +720,12 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
|
||||
wss.m_descriptor_crypt_keys.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), std::make_pair(pubkey, privkey)));
|
||||
wss.fIsEncrypted = true;
|
||||
} else if (strType == DBKeys::LOCKED_UTXO) {
|
||||
uint256 hash;
|
||||
uint32_t n;
|
||||
ssKey >> hash;
|
||||
ssKey >> n;
|
||||
pwallet->LockCoin(COutPoint(hash, n));
|
||||
} else if (strType != DBKeys::BESTBLOCK && strType != DBKeys::BESTBLOCK_NOMERKLE &&
|
||||
strType != DBKeys::MINVERSION && strType != DBKeys::ACENTRY &&
|
||||
strType != DBKeys::VERSION && strType != DBKeys::SETTINGS &&
|
||||
|
@ -74,6 +74,7 @@ extern const std::string HDCHAIN;
|
||||
extern const std::string HDPUBKEY;
|
||||
extern const std::string KEY;
|
||||
extern const std::string KEYMETA;
|
||||
extern const std::string LOCKED_UTXO;
|
||||
extern const std::string MASTER_KEY;
|
||||
extern const std::string MINVERSION;
|
||||
extern const std::string NAME;
|
||||
@ -219,6 +220,9 @@ public:
|
||||
bool WriteDescriptorLastHardenedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index);
|
||||
bool WriteDescriptorCacheItems(const uint256& desc_id, const DescriptorCache& cache);
|
||||
|
||||
bool WriteLockedUTXO(const COutPoint& output);
|
||||
bool EraseLockedUTXO(const COutPoint& output);
|
||||
|
||||
/// Write destination data key,value tuple to database
|
||||
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
|
||||
/// Erase destination data tuple from wallet database
|
||||
|
@ -17,7 +17,7 @@ from test_framework.util import assert_equal
|
||||
class MempoolUpdateFromBlockTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000']]
|
||||
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100']]
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
@ -129,13 +129,49 @@ class WalletTest(BitcoinTestFramework):
|
||||
# Exercise locking of unspent outputs
|
||||
unspent_0 = self.nodes[2].listunspent()[0]
|
||||
unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]}
|
||||
# Trying to unlock an output which isn't locked should error
|
||||
assert_raises_rpc_error(-8, "Invalid parameter, expected locked output", self.nodes[2].lockunspent, True, [unspent_0])
|
||||
|
||||
# Locking an already-locked output should error
|
||||
self.nodes[2].lockunspent(False, [unspent_0])
|
||||
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
|
||||
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 200)
|
||||
assert_equal([unspent_0], self.nodes[2].listlockunspent())
|
||||
self.nodes[2].lockunspent(True, [unspent_0])
|
||||
|
||||
# Restarting the node should clear the lock
|
||||
self.restart_node(2)
|
||||
self.nodes[2].lockunspent(False, [unspent_0])
|
||||
|
||||
# Unloading and reloating the wallet should clear the lock
|
||||
assert_equal(self.nodes[0].listwallets(), [self.default_wallet_name])
|
||||
self.nodes[2].unloadwallet(self.default_wallet_name)
|
||||
self.nodes[2].loadwallet(self.default_wallet_name)
|
||||
assert_equal(len(self.nodes[2].listlockunspent()), 0)
|
||||
|
||||
# Locking non-persistently, then re-locking persistently, is allowed
|
||||
self.nodes[2].lockunspent(False, [unspent_0])
|
||||
self.nodes[2].lockunspent(False, [unspent_0], True)
|
||||
|
||||
# Restarting the node with the lock written to the wallet should keep the lock
|
||||
self.restart_node(2)
|
||||
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
|
||||
|
||||
# Unloading and reloading the wallet with a persistent lock should keep the lock
|
||||
self.nodes[2].unloadwallet(self.default_wallet_name)
|
||||
self.nodes[2].loadwallet(self.default_wallet_name)
|
||||
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
|
||||
|
||||
# Locked outputs should not be used, even if they are the only available funds
|
||||
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
|
||||
assert_equal([unspent_0], self.nodes[2].listlockunspent())
|
||||
|
||||
# Unlocking should remove the persistent lock
|
||||
self.nodes[2].lockunspent(True, [unspent_0])
|
||||
self.restart_node(2)
|
||||
assert_equal(len(self.nodes[2].listlockunspent()), 0)
|
||||
|
||||
# Reconnect node 2 after restarts
|
||||
self.connect_nodes(1, 2)
|
||||
self.connect_nodes(0, 2)
|
||||
|
||||
assert_raises_rpc_error(-8, "txid must be of length 64 (not 34, for '0000000000000000000000000000000000')",
|
||||
self.nodes[2].lockunspent, False,
|
||||
[{"txid": "0000000000000000000000000000000000", "vout": 0}])
|
||||
|
Loading…
Reference in New Issue
Block a user