Skip to content

Commit 3da69c4

Browse files
committed
Merge bitcoin#28546: wallet: prevent bugs from invalid transaction heights with asserts, comments, and refactoring
f06016d wallet: Add asserts to detect unset transaction height values (Ryan Ofsky) 262a78b wallet, refactor: Add CWalletTx::updateState function (Ryan Ofsky) Pull request description: Originally, this PR fixed a wallet migration bug that could cause the watchonly wallet created by legacy wallet migration to have incorrect transaction height values. A different fix for the bug was implemented in bitcoin#28609, but that PR did not add any test coverage that would have caught the bug, and didn't include other changes from this PR intended to prevent problems from invalid transaction heights. This PR adds new asserts to catch invalid transaction heights, which would trigger test failures without bugfix in bitcoin#28609. This PR also refactors code and adds comments to clarify assumptions and make it less likely a bug from invalid transaction height values would be introduced. ACKs for top commit: achow101: ACK f06016d Sjors: utACK f06016d furszy: Code review ACK f06016d Tree-SHA512: 82657c403724d60354f7676b53bcfcc95bdc5864e051a2eb8bfad09d8ad35615393b2d6b432b46f908def9be37bebded3a55ec9ae19e19371d35897fe842c92e
2 parents 2b3f43b + f06016d commit 3da69c4

File tree

4 files changed

+43
-17
lines changed

4 files changed

+43
-17
lines changed

src/wallet/transaction.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
#include <wallet/transaction.h>
66

7+
#include <interfaces/chain.h>
8+
9+
using interfaces::FoundBlock;
10+
711
namespace wallet {
812
bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
913
{
@@ -25,6 +29,27 @@ int64_t CWalletTx::GetTxTime() const
2529
return n ? n : nTimeReceived;
2630
}
2731

32+
void CWalletTx::updateState(interfaces::Chain& chain)
33+
{
34+
bool active;
35+
auto lookup_block = [&](const uint256& hash, int& height, TxState& state) {
36+
// If tx block (or conflicting block) was reorged out of chain
37+
// while the wallet was shutdown, change tx status to UNCONFIRMED
38+
// and reset block height, hash, and index. ABANDONED tx don't have
39+
// associated blocks and don't need to be updated. The case where a
40+
// transaction was reorged out while online and then reconfirmed
41+
// while offline is covered by the rescan logic.
42+
if (!chain.findBlock(hash, FoundBlock().inActiveChain(active).height(height)) || !active) {
43+
state = TxStateInactive{};
44+
}
45+
};
46+
if (auto* conf = state<TxStateConfirmed>()) {
47+
lookup_block(conf->confirmed_block_hash, conf->confirmed_block_height, m_state);
48+
} else if (auto* conf = state<TxStateConflicted>()) {
49+
lookup_block(conf->conflicting_block_hash, conf->conflicting_block_height, m_state);
50+
}
51+
}
52+
2853
void CWalletTx::CopyFrom(const CWalletTx& _tx)
2954
{
3055
*this = _tx;

src/wallet/transaction.h

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
#include <variant>
2323
#include <vector>
2424

25+
namespace interfaces {
26+
class Chain;
27+
} // namespace interfaces
28+
2529
namespace wallet {
2630
//! State of transaction confirmed in a block.
2731
struct TxStateConfirmed {
@@ -326,6 +330,10 @@ class CWalletTx
326330
template<typename T> const T* state() const { return std::get_if<T>(&m_state); }
327331
template<typename T> T* state() { return std::get_if<T>(&m_state); }
328332

333+
//! Update transaction state when attaching to a chain, filling in heights
334+
//! of conflicted and confirmed blocks
335+
void updateState(interfaces::Chain& chain);
336+
329337
bool isAbandoned() const { return state<TxStateInactive>() && state<TxStateInactive>()->abandoned; }
330338
bool isConflicted() const { return state<TxStateConflicted>(); }
331339
bool isInactive() const { return state<TxStateInactive>(); }

src/wallet/wallet.cpp

+3-17
Original file line numberDiff line numberDiff line change
@@ -1184,23 +1184,7 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
11841184
// If wallet doesn't have a chain (e.g when using bitcoin-wallet tool),
11851185
// don't bother to update txn.
11861186
if (HaveChain()) {
1187-
bool active;
1188-
auto lookup_block = [&](const uint256& hash, int& height, TxState& state) {
1189-
// If tx block (or conflicting block) was reorged out of chain
1190-
// while the wallet was shutdown, change tx status to UNCONFIRMED
1191-
// and reset block height, hash, and index. ABANDONED tx don't have
1192-
// associated blocks and don't need to be updated. The case where a
1193-
// transaction was reorged out while online and then reconfirmed
1194-
// while offline is covered by the rescan logic.
1195-
if (!chain().findBlock(hash, FoundBlock().inActiveChain(active).height(height)) || !active) {
1196-
state = TxStateInactive{};
1197-
}
1198-
};
1199-
if (auto* conf = wtx.state<TxStateConfirmed>()) {
1200-
lookup_block(conf->confirmed_block_hash, conf->confirmed_block_height, wtx.m_state);
1201-
} else if (auto* conf = wtx.state<TxStateConflicted>()) {
1202-
lookup_block(conf->conflicting_block_hash, conf->conflicting_block_height, wtx.m_state);
1203-
}
1187+
wtx.updateState(chain());
12041188
}
12051189
if (/* insertion took place */ ins.second) {
12061190
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
@@ -3319,8 +3303,10 @@ int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const
33193303
{
33203304
AssertLockHeld(cs_wallet);
33213305
if (auto* conf = wtx.state<TxStateConfirmed>()) {
3306+
assert(conf->confirmed_block_height >= 0);
33223307
return GetLastBlockHeight() - conf->confirmed_block_height + 1;
33233308
} else if (auto* conf = wtx.state<TxStateConflicted>()) {
3309+
assert(conf->conflicting_block_height >= 0);
33243310
return -1 * (GetLastBlockHeight() - conf->conflicting_block_height + 1);
33253311
} else {
33263312
return 0;

src/wallet/wallet.h

+7
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,13 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
503503
* <0 : conflicts with a transaction this deep in the blockchain
504504
* 0 : in memory pool, waiting to be included in a block
505505
* >=1 : this many blocks deep in the main chain
506+
*
507+
* Preconditions: it is only valid to call this function when the wallet is
508+
* online and the block index is loaded. So this cannot be called by
509+
* bitcoin-wallet tool code or by wallet migration code. If this is called
510+
* without the wallet being online, it won't be able able to determine the
511+
* the height of the last block processed, or the heights of blocks
512+
* referenced in transaction, and might cause assert failures.
506513
*/
507514
int GetTxDepthInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
508515
bool IsTxInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)

0 commit comments

Comments
 (0)