mirror of
https://github.com/dashpay/dash.git
synced 2024-12-24 19:42:46 +01:00
Compare commits
6 Commits
491d17833a
...
42c5fcc455
Author | SHA1 | Date | |
---|---|---|---|
|
42c5fcc455 | ||
|
ad7a373529 | ||
|
d75ee3a9e1 | ||
|
84deba5456 | ||
|
2d0e5d7370 | ||
|
136bf01154 |
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.
|
@ -86,10 +86,10 @@ likely require a reindex.
|
|||||||
- **glibc Requirement**
|
- **glibc Requirement**
|
||||||
- The minimum required glibc to run Dash Core is now **2.31**. This means that **RHEL 8** and **Ubuntu 18.04 (Bionic)** are no longer supported.
|
- The minimum required glibc to run Dash Core is now **2.31**. This means that **RHEL 8** and **Ubuntu 18.04 (Bionic)** are no longer supported.
|
||||||
|
|
||||||
## New RPCs
|
- **FreeBSD Improvements**
|
||||||
|
- Fixed issues with building Dash Core on FreeBSD.
|
||||||
|
|
||||||
- **`quorum platformsign`**
|
## New RPCs
|
||||||
- A new subcommand has been introduced, offering a structured way to perform platform-related quorum signing operations.
|
|
||||||
|
|
||||||
- **`coinjoinsalt`**
|
- **`coinjoinsalt`**
|
||||||
- Allows manipulation of a CoinJoin salt stored in a wallet.
|
- Allows manipulation of a CoinJoin salt stored in a wallet.
|
||||||
@ -153,7 +153,7 @@ likely require a reindex.
|
|||||||
## Devnet Breaking Changes
|
## Devnet Breaking Changes
|
||||||
|
|
||||||
- **Hardfork Activation Changes**
|
- **Hardfork Activation Changes**
|
||||||
- `BRR` (`realloc`), `DIP0020`, `DIP0024`, `V19`, `V20`, and `MN_R` hardforks are now activated at **block 2** instead of block **300** on devnets.
|
- `BRR` (`realloc`), `DIP0020`, `DIP0024`, `V19`, `V20`, and `MN_RR` hardforks are now activated at **block 2** instead of block **300** on devnets.
|
||||||
- **Implications:**
|
- **Implications:**
|
||||||
- Breaking change.
|
- Breaking change.
|
||||||
- Inability to sync on devnets created with earlier Dash Core versions and vice versa.
|
- Inability to sync on devnets created with earlier Dash Core versions and vice versa.
|
||||||
|
@ -235,7 +235,7 @@ Remote Procedure Calls (RPCs)
|
|||||||
support for coin selection and a custom fee rate. The `send` RPC is experimental
|
support for coin selection and a custom fee rate. The `send` RPC is experimental
|
||||||
and may change in subsequent releases. Using it is encouraged once it's no
|
and may change in subsequent releases. Using it is encouraged once it's no
|
||||||
longer experimental: `sendmany` and `sendtoaddress` may be deprecated in a future release.
|
longer experimental: `sendmany` and `sendtoaddress` may be deprecated in a future release.
|
||||||
- A new `quorum signplatform` RPC is added for Platform needs. This composite command limits Platform to only request signatures from the Platform quorum type. It is equivalent to `quorum sign <platform type>`.
|
- A new `quorum platformsign` RPC is added for Platform needs. This composite command limits Platform to only request signatures from the Platform quorum type. It is equivalent to `quorum sign <platform type>`.
|
||||||
|
|
||||||
### RPC changes
|
### RPC changes
|
||||||
- `createwallet` has an updated argument list: `createwallet "wallet_name" ( disable_private_keys blank "passphrase" avoid_reuse descriptors load_on_startup )`
|
- `createwallet` has an updated argument list: `createwallet "wallet_name" ( disable_private_keys blank "passphrase" avoid_reuse descriptors load_on_startup )`
|
||||||
|
@ -178,7 +178,7 @@ Commit your signature for the signed macOS/Windows binaries:
|
|||||||
```sh
|
```sh
|
||||||
pushd ./guix.sigs
|
pushd ./guix.sigs
|
||||||
git add "${VERSION}/${SIGNER}"/all.SHA256SUMS{,.asc}
|
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
|
git push # Assuming you can push to the guix.sigs tree
|
||||||
popd
|
popd
|
||||||
```
|
```
|
||||||
|
@ -136,10 +136,10 @@ public:
|
|||||||
virtual bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) = 0;
|
virtual bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) = 0;
|
||||||
|
|
||||||
//! Lock coin.
|
//! Lock coin.
|
||||||
virtual void lockCoin(const COutPoint& output) = 0;
|
virtual bool lockCoin(const COutPoint& output, const bool write_to_db) = 0;
|
||||||
|
|
||||||
//! Unlock coin.
|
//! Unlock coin.
|
||||||
virtual void unlockCoin(const COutPoint& output) = 0;
|
virtual bool unlockCoin(const COutPoint& output) = 0;
|
||||||
|
|
||||||
//! Return whether coin is locked.
|
//! Return whether coin is locked.
|
||||||
virtual bool isLockedCoin(const COutPoint& output) = 0;
|
virtual bool isLockedCoin(const COutPoint& output) = 0;
|
||||||
|
@ -207,7 +207,7 @@ void CoinControlDialog::buttonToggleLockClicked()
|
|||||||
item->setIcon(COLUMN_CHECKBOX, QIcon());
|
item->setIcon(COLUMN_CHECKBOX, QIcon());
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
model->wallet().lockCoin(outpt);
|
model->wallet().lockCoin(outpt, /* write_to_db = */ true);
|
||||||
item->setDisabled(true);
|
item->setDisabled(true);
|
||||||
item->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
|
item->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
|
||||||
}
|
}
|
||||||
@ -300,7 +300,7 @@ void CoinControlDialog::lockCoin()
|
|||||||
contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
|
contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
|
||||||
|
|
||||||
COutPoint outpt(uint256S(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toUInt());
|
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->setDisabled(true);
|
||||||
contextMenuItem->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
|
contextMenuItem->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
|
||||||
updateLabelLocked();
|
updateLabelLocked();
|
||||||
|
@ -152,6 +152,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||||||
{ "gettxoutsetinfo", 2, "use_index"},
|
{ "gettxoutsetinfo", 2, "use_index"},
|
||||||
{ "lockunspent", 0, "unlock" },
|
{ "lockunspent", 0, "unlock" },
|
||||||
{ "lockunspent", 1, "transactions" },
|
{ "lockunspent", 1, "transactions" },
|
||||||
|
{ "lockunspent", 2, "persistent" },
|
||||||
{ "send", 0, "outputs" },
|
{ "send", 0, "outputs" },
|
||||||
{ "send", 1, "conf_target" },
|
{ "send", 1, "conf_target" },
|
||||||
{ "send", 3, "fee_rate"},
|
{ "send", 3, "fee_rate"},
|
||||||
|
@ -124,10 +124,9 @@ size_t CTxMemPoolEntry::GetTxSize() const
|
|||||||
return GetVirtualTransactionSize(nTxSize, sigOpCount);
|
return GetVirtualTransactionSize(nTxSize, sigOpCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the given tx for any in-mempool descendants.
|
void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants,
|
||||||
// Assumes that CTxMemPool::m_children is correct for the given tx and all
|
const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove,
|
||||||
// descendants.
|
uint64_t ancestor_size_limit, uint64_t ancestor_count_limit)
|
||||||
void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendants, const std::set<uint256> &setExclude)
|
|
||||||
{
|
{
|
||||||
CTxMemPoolEntry::Children stageEntries, descendants;
|
CTxMemPoolEntry::Children stageEntries, descendants;
|
||||||
stageEntries = updateIt->GetMemPoolChildrenConst();
|
stageEntries = updateIt->GetMemPoolChildrenConst();
|
||||||
@ -164,17 +163,18 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendan
|
|||||||
cachedDescendants[updateIt].insert(mapTx.iterator_to(descendant));
|
cachedDescendants[updateIt].insert(mapTx.iterator_to(descendant));
|
||||||
// Update ancestor state for each descendant
|
// Update ancestor state for each descendant
|
||||||
mapTx.modify(mapTx.iterator_to(descendant), update_ancestor_state(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCount()));
|
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));
|
mapTx.modify(updateIt, update_descendant_state(modifySize, modifyFee, modifyCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
// vHashesToUpdate is the set of transaction hashes from a disconnected block
|
void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate, uint64_t ancestor_size_limit, uint64_t ancestor_count_limit)
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs);
|
AssertLockHeld(cs);
|
||||||
// For each entry in vHashesToUpdate, store the set of in-mempool, but not
|
// 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)
|
// accounted for in the state of their ancestors)
|
||||||
std::set<uint256> setAlreadyIncluded(vHashesToUpdate.begin(), vHashesToUpdate.end());
|
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
|
// Iterate in reverse, so that whenever we are looking at a transaction
|
||||||
// we are sure that all in-mempool descendants have already been processed.
|
// we are sure that all in-mempool descendants have already been processed.
|
||||||
// This maximizes the benefit of the descendant cache and guarantees that
|
// 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
|
} // 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);
|
void RemoveStaged(setEntries& stage, bool updateDescendants, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||||
|
|
||||||
/** When adding transactions from a disconnected block back to the mempool,
|
/** UpdateTransactionsFromBlock is called when adding transactions from a
|
||||||
* new mempool entries may have children in the mempool (which is generally
|
* disconnected block back to the mempool, new mempool entries may have
|
||||||
* not the case when otherwise adding transactions).
|
* children in the mempool (which is generally not the case when otherwise
|
||||||
* UpdateTransactionsFromBlock() will find child transactions and update the
|
* adding transactions).
|
||||||
* descendant state for each transaction in vHashesToUpdate (excluding any
|
* @post updated descendant state for descendants of each transaction in
|
||||||
* child transactions present in vHashesToUpdate, which are already accounted
|
* vHashesToUpdate (excluding any child transactions present in
|
||||||
* for). Note: vHashesToUpdate should be the set of transactions from the
|
* vHashesToUpdate, which are already accounted for). Updated state
|
||||||
* disconnected block that have been accepted back into the mempool.
|
* 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.
|
/** Try to calculate all in-mempool ancestors of entry.
|
||||||
* (these are all calculated including the tx itself)
|
* (these are all calculated including the tx itself)
|
||||||
@ -828,19 +837,38 @@ private:
|
|||||||
/** UpdateForDescendants is used by UpdateTransactionsFromBlock to update
|
/** UpdateForDescendants is used by UpdateTransactionsFromBlock to update
|
||||||
* the descendants for a single transaction that has been added to the
|
* the descendants for a single transaction that has been added to the
|
||||||
* mempool but may have child transactions in the mempool, eg during a
|
* mempool but may have child transactions in the mempool, eg during a
|
||||||
* chain reorg. setExclude is the set of descendant transactions in the
|
* chain reorg.
|
||||||
* 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).
|
|
||||||
*
|
*
|
||||||
* cachedDescendants will be updated with the descendants of the transaction
|
* @pre CTxMemPool::m_children is correct for the given tx and all
|
||||||
* being updated, so that future invocations don't need to walk the
|
* descendants.
|
||||||
* same transaction again, if encountered in another transaction chain.
|
* @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,
|
void UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants,
|
||||||
cacheMap &cachedDescendants,
|
const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove,
|
||||||
const std::set<uint256> &setExclude) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
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. */
|
/** Update ancestors of hash to add/remove it as a descendant transaction. */
|
||||||
void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||||
/** Set ancestor state for an entry */
|
/** Set ancestor state for an entry */
|
||||||
|
@ -365,7 +365,9 @@ void CChainState::MaybeUpdateMempoolForReorg(
|
|||||||
// previously-confirmed transactions back to the mempool.
|
// previously-confirmed transactions back to the mempool.
|
||||||
// UpdateTransactionsFromBlock finds descendants of any transactions in
|
// UpdateTransactionsFromBlock finds descendants of any transactions in
|
||||||
// the disconnectpool that were added back and cleans up the mempool state.
|
// 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.
|
// Predicate to use for filtering transactions in removeForReorg.
|
||||||
// Checks whether the transaction is still final and, if it spends a coinbase output, mature.
|
// 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()};
|
WalletBatch batch{m_wallet->GetDatabase()};
|
||||||
return m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
|
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);
|
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);
|
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
|
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"
|
"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"
|
"A locked transaction output will not be chosen by automatic coin selection, when spending Dash.\n"
|
||||||
"Manually selected coins are automatically unlocked.\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"
|
"Locks are stored in memory only, unless persistent=true, in which case they will be written to the\n"
|
||||||
"is always cleared (by virtue of process exit) when a node stops or fails.\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",
|
"Also see the listunspent call\n",
|
||||||
{
|
{
|
||||||
{"unlock", RPCArg::Type::BOOL, RPCArg::Optional::NO, "Whether to unlock (true) or lock (false) the specified transactions"},
|
{"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"},
|
{"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", "") +
|
+ HelpExampleCli("listlockunspent", "") +
|
||||||
"\nUnlock the transaction again\n"
|
"\nUnlock the transaction again\n"
|
||||||
+ HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
|
+ 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"
|
"\nAs a JSON-RPC call\n"
|
||||||
+ HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"")
|
+ HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"")
|
||||||
},
|
},
|
||||||
@ -2250,9 +2254,13 @@ static RPCHelpMan lockunspent()
|
|||||||
|
|
||||||
bool fUnlock = request.params[0].get_bool();
|
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 (request.params[1].isNull()) {
|
||||||
if (fUnlock)
|
if (fUnlock) {
|
||||||
pwallet->UnlockAllCoins();
|
if (!pwallet->UnlockAllCoins())
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coins failed");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2303,17 +2311,24 @@ static RPCHelpMan lockunspent()
|
|||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output");
|
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");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output already locked");
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs.push_back(outpt);
|
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.
|
// Atomically set (un)locked status for the outputs.
|
||||||
for (const COutPoint& outpt : outputs) {
|
for (const COutPoint& outpt : outputs) {
|
||||||
if (fUnlock) pwallet->UnlockCoin(outpt);
|
if (fUnlock) {
|
||||||
else pwallet->LockCoin(outpt);
|
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;
|
return true;
|
||||||
|
@ -625,12 +625,17 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
|
|||||||
return false;
|
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));
|
mapTxSpends.insert(std::make_pair(outpoint, wtxid));
|
||||||
setWalletUTXO.erase(outpoint);
|
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;
|
std::pair<TxSpends::iterator, TxSpends::iterator> range;
|
||||||
range = mapTxSpends.equal_range(outpoint);
|
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);
|
auto it = mapWallet.find(wtxid);
|
||||||
assert(it != mapWallet.end());
|
assert(it != mapWallet.end());
|
||||||
@ -647,7 +652,7 @@ void CWallet::AddToSpends(const uint256& wtxid)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
for (const CTxIn& txin : thisTx.tx->vin)
|
for (const CTxIn& txin : thisTx.tx->vin)
|
||||||
AddToSpends(txin.prevout, wtxid);
|
AddToSpends(txin.prevout, wtxid, batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
||||||
@ -915,7 +920,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
|
|||||||
wtx.nOrderPos = IncOrderPosNext(&batch);
|
wtx.nOrderPos = IncOrderPosNext(&batch);
|
||||||
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
|
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
|
||||||
wtx.nTimeSmart = ComputeTimeSmart(wtx);
|
wtx.nTimeSmart = ComputeTimeSmart(wtx);
|
||||||
AddToSpends(hash);
|
AddToSpends(hash, &batch);
|
||||||
|
|
||||||
std::vector<std::pair<const CTransactionRef&, unsigned int>> outputs;
|
std::vector<std::pair<const CTransactionRef&, unsigned int>> outputs;
|
||||||
for(unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
|
for(unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
|
||||||
@ -4444,7 +4449,7 @@ void ReserveDestination::ReturnDestination()
|
|||||||
address = CNoDestination();
|
address = CNoDestination();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::LockCoin(const COutPoint& output)
|
bool CWallet::LockCoin(const COutPoint& output, WalletBatch* batch)
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
setLockedCoins.insert(output);
|
setLockedCoins.insert(output);
|
||||||
@ -4453,23 +4458,38 @@ void CWallet::LockCoin(const COutPoint& output)
|
|||||||
|
|
||||||
fAnonymizableTallyCached = false;
|
fAnonymizableTallyCached = false;
|
||||||
fAnonymizableTallyCachedNonDenom = 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);
|
AssertLockHeld(cs_wallet);
|
||||||
setLockedCoins.erase(output);
|
|
||||||
std::map<uint256, CWalletTx>::iterator it = mapWallet.find(output.hash);
|
std::map<uint256, CWalletTx>::iterator it = mapWallet.find(output.hash);
|
||||||
if (it != mapWallet.end()) it->second.MarkDirty(); // recalculate all credits for this tx
|
if (it != mapWallet.end()) it->second.MarkDirty(); // recalculate all credits for this tx
|
||||||
|
|
||||||
fAnonymizableTallyCached = false;
|
fAnonymizableTallyCached = false;
|
||||||
fAnonymizableTallyCachedNonDenom = 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);
|
AssertLockHeld(cs_wallet);
|
||||||
|
bool success = true;
|
||||||
|
WalletBatch batch(GetDatabase());
|
||||||
|
for (auto it = setLockedCoins.begin(); it != setLockedCoins.end(); ++it) {
|
||||||
|
success &= batch.EraseLockedUTXO(*it);
|
||||||
|
}
|
||||||
setLockedCoins.clear();
|
setLockedCoins.clear();
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
|
bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
|
||||||
|
@ -759,8 +759,8 @@ private:
|
|||||||
*/
|
*/
|
||||||
typedef std::multimap<COutPoint, uint256> TxSpends;
|
typedef std::multimap<COutPoint, uint256> TxSpends;
|
||||||
TxSpends mapTxSpends GUARDED_BY(cs_wallet);
|
TxSpends mapTxSpends GUARDED_BY(cs_wallet);
|
||||||
void AddToSpends(const COutPoint& outpoint, 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) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void AddToSpends(const uint256& wtxid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
std::set<COutPoint> setWalletUTXO;
|
std::set<COutPoint> setWalletUTXO;
|
||||||
mutable std::map<COutPoint, int> mapOutpointRoundsCache GUARDED_BY(cs_wallet);
|
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;
|
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);
|
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool LockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void UnlockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool UnlockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void UnlockAllCoins() 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 ListLockedCoins(std::vector<COutPoint>& vOutpts) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void ListProTxCoins(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 HDPUBKEY{"hdpubkey"};
|
||||||
const std::string KEYMETA{"keymeta"};
|
const std::string KEYMETA{"keymeta"};
|
||||||
const std::string KEY{"key"};
|
const std::string KEY{"key"};
|
||||||
|
const std::string LOCKED_UTXO{"lockedutxo"};
|
||||||
const std::string MASTER_KEY{"mkey"};
|
const std::string MASTER_KEY{"mkey"};
|
||||||
const std::string MINVERSION{"minversion"};
|
const std::string MINVERSION{"minversion"};
|
||||||
const std::string NAME{"name"};
|
const std::string NAME{"name"};
|
||||||
@ -308,6 +309,16 @@ bool WalletBatch::WriteDescriptorCacheItems(const uint256& desc_id, const Descri
|
|||||||
return true;
|
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 {
|
class CWalletScanState {
|
||||||
public:
|
public:
|
||||||
unsigned int nKeys{0};
|
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.m_descriptor_crypt_keys.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), std::make_pair(pubkey, privkey)));
|
||||||
wss.fIsEncrypted = true;
|
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 &&
|
} else if (strType != DBKeys::BESTBLOCK && strType != DBKeys::BESTBLOCK_NOMERKLE &&
|
||||||
strType != DBKeys::MINVERSION && strType != DBKeys::ACENTRY &&
|
strType != DBKeys::MINVERSION && strType != DBKeys::ACENTRY &&
|
||||||
strType != DBKeys::VERSION && strType != DBKeys::SETTINGS &&
|
strType != DBKeys::VERSION && strType != DBKeys::SETTINGS &&
|
||||||
|
@ -74,6 +74,7 @@ extern const std::string HDCHAIN;
|
|||||||
extern const std::string HDPUBKEY;
|
extern const std::string HDPUBKEY;
|
||||||
extern const std::string KEY;
|
extern const std::string KEY;
|
||||||
extern const std::string KEYMETA;
|
extern const std::string KEYMETA;
|
||||||
|
extern const std::string LOCKED_UTXO;
|
||||||
extern const std::string MASTER_KEY;
|
extern const std::string MASTER_KEY;
|
||||||
extern const std::string MINVERSION;
|
extern const std::string MINVERSION;
|
||||||
extern const std::string NAME;
|
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 WriteDescriptorLastHardenedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index);
|
||||||
bool WriteDescriptorCacheItems(const uint256& desc_id, const DescriptorCache& cache);
|
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
|
/// Write destination data key,value tuple to database
|
||||||
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
|
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
|
||||||
/// Erase destination data tuple from wallet database
|
/// Erase destination data tuple from wallet database
|
||||||
|
@ -17,7 +17,7 @@ from test_framework.util import assert_equal
|
|||||||
class MempoolUpdateFromBlockTest(BitcoinTestFramework):
|
class MempoolUpdateFromBlockTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.num_nodes = 1
|
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):
|
def skip_test_if_missing_module(self):
|
||||||
self.skip_if_no_wallet()
|
self.skip_if_no_wallet()
|
||||||
|
@ -129,13 +129,49 @@ class WalletTest(BitcoinTestFramework):
|
|||||||
# Exercise locking of unspent outputs
|
# Exercise locking of unspent outputs
|
||||||
unspent_0 = self.nodes[2].listunspent()[0]
|
unspent_0 = self.nodes[2].listunspent()[0]
|
||||||
unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]}
|
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])
|
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])
|
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(-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())
|
# Restarting the node should clear the lock
|
||||||
self.nodes[2].lockunspent(True, [unspent_0])
|
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)
|
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')",
|
assert_raises_rpc_error(-8, "txid must be of length 64 (not 34, for '0000000000000000000000000000000000')",
|
||||||
self.nodes[2].lockunspent, False,
|
self.nodes[2].lockunspent, False,
|
||||||
[{"txid": "0000000000000000000000000000000000", "vout": 0}])
|
[{"txid": "0000000000000000000000000000000000", "vout": 0}])
|
||||||
|
Loading…
Reference in New Issue
Block a user