Compare commits

...

6 Commits

Author SHA1 Message Date
Vijay
42c5fcc455
Merge 84deba5456 into ad7a373529 2024-12-19 22:22:51 +00:00
pasta
ad7a373529
Merge #6498: docs: fixes for Release Notes v22.0.0 and v21.0.0
Some checks failed
Label Merge Conflicts / main (push) Failing after 11s
Guix Build / build-image (push) Failing after 1m30s
Guix Build / build (aarch64-linux-gnu) (push) Has been skipped
Guix Build / build (arm-linux-gnueabihf) (push) Has been skipped
Guix Build / build (arm64-apple-darwin) (push) Has been skipped
Guix Build / build (powerpc64-linux-gnu) (push) Has been skipped
Guix Build / build (riscv64-linux-gnu) (push) Has been skipped
Guix Build / build (x86_64-apple-darwin) (push) Has been skipped
Guix Build / build (x86_64-linux-gnu) (push) Has been skipped
Guix Build / build (x86_64-w64-mingw32) (push) Has been skipped
Check Merge Fast-Forward Only / check_merge (push) Successful in 1m21s
CI / Build Image (push) Failing after 2m3s
CI / Build Dependencies (arm-linux, arm-linux-gnueabihf) (push) Has been skipped
CI / Build Dependencies (linux64, x86_64-pc-linux-gnu) (push) Has been skipped
CI / Build (arm-linux, arm-linux, arm-linux-gnueabihf) (push) Has been skipped
CI / Build (linux64, linux64, x86_64-pc-linux-gnu) (push) Has been skipped
CI / Build (linux64_cxx20, linux64, x86_64-pc-linux-gnu) (push) Has been skipped
CI / Build (linux64_fuzz, linux64, x86_64-pc-linux-gnu) (push) Has been skipped
CI / Build (linux64_nowallet, linux64, x86_64-pc-linux-gnu) (push) Has been skipped
CI / Build (linux64_sqlite, linux64, x86_64-pc-linux-gnu) (push) Has been skipped
CI / Build (linux64_tsan, linux64, x86_64-pc-linux-gnu) (push) Has been skipped
CI / Build (linux64_ubsan, linux64, x86_64-pc-linux-gnu) (push) Has been skipped
d75ee3a9e1 docs: fixes for Release Notes v22.0.0 and v21.0.0 (Konstantin Akimov)

Pull request description:

  ## Issue being fixed or feature implemented
  I noticed that we announced in current release new RPC `quorum platformsign` which has been actually released with v21.
  Though, in release notes for v21 it has a wrong name.

  ## What was done?
  Removed `quorum platformsign` from v22 release notes; fixed name in v21.
  Also mentioned build for Freebsd and minor typo `s/MN_R/MN_RR/` with name of forks on regtest.

  ## How Has This Been Tested?
  N/A

  ## Breaking Changes
  N/A

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [ ] I have commented my code, particularly in hard-to-understand areas
  - [ ] I have added or updated relevant unit/integration/functional/e2e tests
  - [ ] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone

ACKs for top commit:
  UdjinM6:
    utACK d75ee3a9e1

Tree-SHA512: 2d2309e81607c25a512577c96f15ee36b04b585c5751dc1500f2b432d90fc19e70f1b72cf1f7bf843abf854b7869481765902c7c61e9ea7e21b087a126c49a99
2024-12-19 16:22:31 -06:00
Konstantin Akimov
d75ee3a9e1
docs: fixes for Release Notes v22.0.0 and v21.0.0
platformsign has been released in v21 and name there is wrong: platformsign vs signplatform
2024-12-19 21:22:09 +07:00
W. J. van der Laan
84deba5456
Merge bitcoin/bitcoin#23065: Allow UTXO locks to be written to wallet DB
d96b000e94d72d041689c5c47e374df2ebc0e011 Make GUI UTXO lock/unlock persistent (Samuel Dobson)
077154fe698f5556ad6e26ef49c9024c2f07ff68 Add release note for lockunspent change (Samuel Dobson)
719ae927dcdb60c0f9902fa79796256035228c4e Update lockunspent tests for lock persistence (Samuel Dobson)
f13fc16295c19a156f2974d2d73fba56d52fc161 Allow lockunspent to store the lock in the wallet DB (Samuel Dobson)
c52789365e5dbcb25aa5f1775de4d318da79e5a7 Allow locked UTXOs to be store in the wallet database (Samuel Dobson)

Pull request description:

  Addresses and closes #22368

  As per that issue (and its predecessor #14907), there seems to be some interest in allowing unspent outputs to be locked persistently. This PR does so by adding a flag to lockunspent to store the change in the wallet database. Defaults to false, so there is no change in default behaviour.

  Edit: GUI commit changes default behaviour. UTXOs locked/unlocked via the GUI are now persistent.

ACKs for top commit:
  achow101:
    ACK d96b000e94d72d041689c5c47e374df2ebc0e011
  kristapsk:
    ACK d96b000e94d72d041689c5c47e374df2ebc0e011
  lsilva01:
    Tested ACK d96b000e94 on Ubuntu 20.04
  prayank23:
    ACK d96b000e94

Tree-SHA512: 957a5bbfe7f763036796906ccb1598feb6c14c5975838be1ba24a198840bf59e83233165cb112cebae909b6b25bf27275a4d7fa425923ef6c788ff671d7a89a8
2024-11-25 09:00:48 +05:30
fanquake
2d0e5d7370
Merge bitcoin/bitcoin#21464: Mempool Update Cut-Through Optimization
c5b36b1c1b11f04e5da7fb44183f61d09a14e40d Mempool Update Cut-Through Optimization (Jeremy Rubin)
c49daf9885e86ba08acdc8332d2a34bc5951a487 [TESTS] Increase limitancestorcount in tournament RPC test to showcase improved algorithm (Jeremy Rubin)

Pull request description:

  Often when we're updating mempool entries we update entries that we ultimately end up removing the updated entries shortly thereafter. This patch makes it so that we filter for such entries a bit earlier in processing, which yields a mild improvement for these cases, and is negligible overhead otherwise.

  There's potential for a better -- but more sophisticated -- algorithm that can be used taking advantage of epochs, but I figured it is better to do something that is simple and works first and upgrade it later as the other epoch mempool work proceeds as it makes the patches for the epoch algorithm simpler to understand, so you can consider this as preparatory work. It could either go in now if it is not controversial, or we could wait until the other patch is ready to go.

ACKs for top commit:
  instagibbs:
    reACK c5b36b1
  sipa:
    utACK c5b36b1c1b11f04e5da7fb44183f61d09a14e40d
  mzumsande:
    Code Review ACK c5b36b1c1b11f04e5da7fb44183f61d09a14e40d

Tree-SHA512: 78b16864f77a637d8a68a65e23c019a9757d8b2243486728ef601d212ae482f6084cf8e69d810958c356f1803178046e4697207ba40d6d10529ca57de647fae6
2024-11-24 21:31:33 +05:30
W. J. van der Laan
136bf01154
Merge bitcoin/bitcoin#22507: doc: Adjust commit message template for the guix.sigs repo
fafade9c79f55c186c1938ce3e27077d12dee6c5 doc: Adjust commit message template for the guix.sigs repo (MarcoFalke)

Pull request description:

  Seems to be the most common template used, so adjust this here, too.

ACKs for top commit:
  laanwj:
    ACK fafade9c79f55c186c1938ce3e27077d12dee6c5
  hebasto:
    re-ACK fafade9c79f55c186c1938ce3e27077d12dee6c5

Tree-SHA512: 20477d14ecfad94f3b28b94786a4c0d98df539360d0c1deefa94766064a7d0700c849e54d6b251f922e135fcfa964ada0c724090f7f92b459ea39f7c3ca8c65d
2024-11-24 21:07:28 +05:30
18 changed files with 221 additions and 71 deletions

View 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.

View File

@ -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.

View File

@ -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 )`

View File

@ -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
``` ```

View File

@ -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;

View File

@ -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();

View File

@ -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"},

View File

@ -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);
}
} }
} }

View File

@ -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 */

View File

@ -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.

View File

@ -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
{ {

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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 &&

View File

@ -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

View File

@ -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()

View File

@ -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}])