mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-19 14:53:43 +01:00
wallet, rpc: Move (Un)LockCoin WalletBatch creation out of RPC
If the locked coin needs to be persisted to the wallet database, insteead of having the RPC figure out when to create a WalletBatch and having LockCoin's behavior depend on it, have LockCoin take whether to persist as a parameter so it makes the batch. Since unlocking a persisted locked coin requires a database write as well, we need to track whether the locked coin was persisted to the wallet database so that it can erase the locked coin when necessary. Keeping track of whether a locked coin was persisted is also useful information for future PRs.
This commit is contained in:
@@ -249,14 +249,12 @@ public:
|
|||||||
bool lockCoin(const COutPoint& output, const bool write_to_db) override
|
bool lockCoin(const COutPoint& output, const bool write_to_db) override
|
||||||
{
|
{
|
||||||
LOCK(m_wallet->cs_wallet);
|
LOCK(m_wallet->cs_wallet);
|
||||||
std::unique_ptr<WalletBatch> batch = write_to_db ? std::make_unique<WalletBatch>(m_wallet->GetDatabase()) : nullptr;
|
return m_wallet->LockCoin(output, write_to_db);
|
||||||
return m_wallet->LockCoin(output, batch.get());
|
|
||||||
}
|
}
|
||||||
bool unlockCoin(const COutPoint& output) override
|
bool unlockCoin(const COutPoint& output) override
|
||||||
{
|
{
|
||||||
LOCK(m_wallet->cs_wallet);
|
LOCK(m_wallet->cs_wallet);
|
||||||
std::unique_ptr<WalletBatch> batch = std::make_unique<WalletBatch>(m_wallet->GetDatabase());
|
return m_wallet->UnlockCoin(output);
|
||||||
return m_wallet->UnlockCoin(output, batch.get());
|
|
||||||
}
|
}
|
||||||
bool isLockedCoin(const COutPoint& output) override
|
bool isLockedCoin(const COutPoint& output) override
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -356,16 +356,12 @@ RPCHelpMan lockunspent()
|
|||||||
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) {
|
if (fUnlock) {
|
||||||
if (!pwallet->UnlockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coin failed");
|
if (!pwallet->UnlockCoin(outpt)) throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coin failed");
|
||||||
} else {
|
} else {
|
||||||
if (!pwallet->LockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Locking coin failed");
|
if (!pwallet->LockCoin(outpt, persistent)) throw JSONRPCError(RPC_WALLET_ERROR, "Locking coin failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1579,7 +1579,7 @@ RPCHelpMan sendall()
|
|||||||
const bool lock_unspents{options.exists("lock_unspents") ? options["lock_unspents"].get_bool() : false};
|
const bool lock_unspents{options.exists("lock_unspents") ? options["lock_unspents"].get_bool() : false};
|
||||||
if (lock_unspents) {
|
if (lock_unspents) {
|
||||||
for (const CTxIn& txin : rawTx.vin) {
|
for (const CTxIn& txin : rawTx.vin) {
|
||||||
pwallet->LockCoin(txin.prevout);
|
pwallet->LockCoin(txin.prevout, /*persist=*/false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1467,7 +1467,7 @@ util::Result<CreatedTransactionResult> FundTransaction(CWallet& wallet, const CM
|
|||||||
|
|
||||||
if (lockUnspents) {
|
if (lockUnspents) {
|
||||||
for (const CTxIn& txin : res->tx->vin) {
|
for (const CTxIn& txin : res->tx->vin) {
|
||||||
wallet.LockCoin(txin.prevout);
|
wallet.LockCoin(txin.prevout, /*persist=*/false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
|
|||||||
for (const auto& group : list) {
|
for (const auto& group : list) {
|
||||||
for (const auto& coin : group.second) {
|
for (const auto& coin : group.second) {
|
||||||
LOCK(wallet->cs_wallet);
|
LOCK(wallet->cs_wallet);
|
||||||
wallet->LockCoin(coin.outpoint);
|
wallet->LockCoin(coin.outpoint, /*persist=*/false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -486,7 +486,7 @@ void TestCoinsResult(ListCoinsTest& context, OutputType out_type, CAmount amount
|
|||||||
filter.skip_locked = false;
|
filter.skip_locked = false;
|
||||||
CoinsResult available_coins = AvailableCoins(*context.wallet, nullptr, std::nullopt, filter);
|
CoinsResult available_coins = AvailableCoins(*context.wallet, nullptr, std::nullopt, filter);
|
||||||
// Lock outputs so they are not spent in follow-up transactions
|
// Lock outputs so they are not spent in follow-up transactions
|
||||||
for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) context.wallet->LockCoin({wtx.GetHash(), i});
|
for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) context.wallet->LockCoin({wtx.GetHash(), i}, /*persist=*/false);
|
||||||
for (const auto& [type, size] : expected_coins_sizes) BOOST_CHECK_EQUAL(size, available_coins.coins[type].size());
|
for (const auto& [type, size] : expected_coins_sizes) BOOST_CHECK_EQUAL(size, available_coins.coins[type].size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -785,16 +785,11 @@ bool CWallet::IsSpent(const COutPoint& outpoint) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::AddToSpends(const COutPoint& outpoint, const Txid& txid, WalletBatch* batch)
|
void CWallet::AddToSpends(const COutPoint& outpoint, const Txid& txid)
|
||||||
{
|
{
|
||||||
mapTxSpends.insert(std::make_pair(outpoint, txid));
|
mapTxSpends.insert(std::make_pair(outpoint, txid));
|
||||||
|
|
||||||
if (batch) {
|
UnlockCoin(outpoint);
|
||||||
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);
|
||||||
@@ -802,13 +797,13 @@ void CWallet::AddToSpends(const COutPoint& outpoint, const Txid& txid, WalletBat
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CWallet::AddToSpends(const CWalletTx& wtx, WalletBatch* batch)
|
void CWallet::AddToSpends(const CWalletTx& wtx)
|
||||||
{
|
{
|
||||||
if (wtx.IsCoinBase()) // Coinbases don't spend anything!
|
if (wtx.IsCoinBase()) // Coinbases don't spend anything!
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (const CTxIn& txin : wtx.tx->vin)
|
for (const CTxIn& txin : wtx.tx->vin)
|
||||||
AddToSpends(txin.prevout, wtx.GetHash(), batch);
|
AddToSpends(txin.prevout, wtx.GetHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
||||||
@@ -1058,7 +1053,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const
|
|||||||
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, rescanning_old_block);
|
wtx.nTimeSmart = ComputeTimeSmart(wtx, rescanning_old_block);
|
||||||
AddToSpends(wtx, &batch);
|
AddToSpends(wtx);
|
||||||
|
|
||||||
// Update birth time when tx time is older than it.
|
// Update birth time when tx time is older than it.
|
||||||
MaybeUpdateBirthTime(wtx.GetTxTime());
|
MaybeUpdateBirthTime(wtx.GetTxTime());
|
||||||
@@ -2622,22 +2617,34 @@ util::Result<void> CWallet::DisplayAddress(const CTxDestination& dest)
|
|||||||
return util::Error{_("There is no ScriptPubKeyManager for this address")};
|
return util::Error{_("There is no ScriptPubKeyManager for this address")};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::LockCoin(const COutPoint& output, WalletBatch* batch)
|
void CWallet::LoadLockedCoin(const COutPoint& coin, bool persistent)
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
setLockedCoins.insert(output);
|
m_locked_coins.emplace(coin, persistent);
|
||||||
if (batch) {
|
}
|
||||||
return batch->WriteLockedUTXO(output);
|
|
||||||
|
bool CWallet::LockCoin(const COutPoint& output, bool persist)
|
||||||
|
{
|
||||||
|
AssertLockHeld(cs_wallet);
|
||||||
|
LoadLockedCoin(output, persist);
|
||||||
|
if (persist) {
|
||||||
|
WalletBatch batch(GetDatabase());
|
||||||
|
return batch.WriteLockedUTXO(output);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::UnlockCoin(const COutPoint& output, WalletBatch* batch)
|
bool CWallet::UnlockCoin(const COutPoint& output)
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
bool was_locked = setLockedCoins.erase(output);
|
auto locked_coin_it = m_locked_coins.find(output);
|
||||||
if (batch && was_locked) {
|
if (locked_coin_it != m_locked_coins.end()) {
|
||||||
return batch->EraseLockedUTXO(output);
|
bool persisted = locked_coin_it->second;
|
||||||
|
m_locked_coins.erase(locked_coin_it);
|
||||||
|
if (persisted) {
|
||||||
|
WalletBatch batch(GetDatabase());
|
||||||
|
return batch.EraseLockedUTXO(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2647,26 +2654,24 @@ bool CWallet::UnlockAllCoins()
|
|||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
bool success = true;
|
bool success = true;
|
||||||
WalletBatch batch(GetDatabase());
|
WalletBatch batch(GetDatabase());
|
||||||
for (auto it = setLockedCoins.begin(); it != setLockedCoins.end(); ++it) {
|
for (const auto& [coin, persistent] : m_locked_coins) {
|
||||||
success &= batch.EraseLockedUTXO(*it);
|
if (persistent) success = success && batch.EraseLockedUTXO(coin);
|
||||||
}
|
}
|
||||||
setLockedCoins.clear();
|
m_locked_coins.clear();
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::IsLockedCoin(const COutPoint& output) const
|
bool CWallet::IsLockedCoin(const COutPoint& output) const
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
return setLockedCoins.count(output) > 0;
|
return m_locked_coins.count(output) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
|
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
for (std::set<COutPoint>::iterator it = setLockedCoins.begin();
|
for (const auto& [coin, _] : m_locked_coins) {
|
||||||
it != setLockedCoins.end(); it++) {
|
vOutpts.push_back(coin);
|
||||||
COutPoint outpt = (*it);
|
|
||||||
vOutpts.push_back(outpt);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -333,8 +333,8 @@ private:
|
|||||||
*/
|
*/
|
||||||
typedef std::unordered_multimap<COutPoint, Txid, SaltedOutpointHasher> TxSpends;
|
typedef std::unordered_multimap<COutPoint, Txid, SaltedOutpointHasher> TxSpends;
|
||||||
TxSpends mapTxSpends GUARDED_BY(cs_wallet);
|
TxSpends mapTxSpends GUARDED_BY(cs_wallet);
|
||||||
void AddToSpends(const COutPoint& outpoint, const Txid& txid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void AddToSpends(const COutPoint& outpoint, const Txid& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void AddToSpends(const CWalletTx& wtx, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void AddToSpends(const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a transaction to the wallet, or update it. confirm.block_* should
|
* Add a transaction to the wallet, or update it. confirm.block_* should
|
||||||
@@ -497,8 +497,10 @@ public:
|
|||||||
/** Set of Coins owned by this wallet that we won't try to spend from. A
|
/** Set of Coins owned by this wallet that we won't try to spend from. A
|
||||||
* Coin may be locked if it has already been used to fund a transaction
|
* Coin may be locked if it has already been used to fund a transaction
|
||||||
* that hasn't confirmed yet. We wouldn't consider the Coin spent already,
|
* that hasn't confirmed yet. We wouldn't consider the Coin spent already,
|
||||||
* but also shouldn't try to use it again. */
|
* but also shouldn't try to use it again.
|
||||||
std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet);
|
* bool to track whether this locked coin is persisted to disk.
|
||||||
|
*/
|
||||||
|
std::map<COutPoint, bool> m_locked_coins GUARDED_BY(cs_wallet);
|
||||||
|
|
||||||
/** Registered interfaces::Chain::Notifications handler. */
|
/** Registered interfaces::Chain::Notifications handler. */
|
||||||
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
|
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
|
||||||
@@ -546,8 +548,9 @@ public:
|
|||||||
util::Result<void> DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
util::Result<void> DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
bool IsLockedCoin(const COutPoint& output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool IsLockedCoin(const COutPoint& output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
bool LockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void LoadLockedCoin(const COutPoint& coin, bool persistent) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
bool UnlockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool LockCoin(const COutPoint& output, bool persist) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
bool UnlockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
bool 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);
|
||||||
|
|
||||||
|
|||||||
@@ -1072,7 +1072,7 @@ static DBErrors LoadTxRecords(CWallet* pwallet, DatabaseBatch& batch, std::vecto
|
|||||||
uint32_t n;
|
uint32_t n;
|
||||||
key >> hash;
|
key >> hash;
|
||||||
key >> n;
|
key >> n;
|
||||||
pwallet->LockCoin(COutPoint(hash, n));
|
pwallet->LoadLockedCoin(COutPoint(hash, n), /*persistent=*/true);
|
||||||
return DBErrors::LOAD_OK;
|
return DBErrors::LOAD_OK;
|
||||||
});
|
});
|
||||||
result = std::max(result, locked_utxo_res.m_result);
|
result = std::max(result, locked_utxo_res.m_result);
|
||||||
|
|||||||
Reference in New Issue
Block a user