Skip to content

Commit 45af4f9

Browse files
achow101vijaydasmp
authored andcommitted
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
1 parent 18a3f11 commit 45af4f9

4 files changed

Lines changed: 43 additions & 17 deletions

File tree

src/wallet/transaction.cpp

Lines changed: 25 additions & 0 deletions
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
{
@@ -24,4 +28,25 @@ int64_t CWalletTx::GetTxTime() const
2428
int64_t n = nTimeSmart;
2529
return n ? n : nTimeReceived;
2630
}
31+
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+
}
2752
} // namespace wallet

src/wallet/transaction.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
#include <variant>
2121
#include <vector>
2222

23+
namespace interfaces {
24+
class Chain;
25+
} // namespace interfaces
26+
2327
namespace wallet {
2428
//! State of transaction confirmed in a block.
2529
struct TxStateConfirmed {
@@ -309,6 +313,10 @@ class CWalletTx
309313
template<typename T> const T* state() const { return std::get_if<T>(&m_state); }
310314
template<typename T> T* state() { return std::get_if<T>(&m_state); }
311315

316+
//! Update transaction state when attaching to a chain, filling in heights
317+
//! of conflicted and confirmed blocks
318+
void updateState(interfaces::Chain& chain);
319+
312320
bool isAbandoned() const { return state<TxStateInactive>() && state<TxStateInactive>()->abandoned; }
313321
bool isConflicted() const { return state<TxStateConflicted>(); }
314322
bool isUnconfirmed() const { return !isAbandoned() && !isConflicted() && !isConfirmed(); }

src/wallet/wallet.cpp

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,23 +1127,7 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
11271127
// If wallet doesn't have a chain (e.g when using dash-wallet tool),
11281128
// don't bother to update txn.
11291129
if (HaveChain()) {
1130-
bool active;
1131-
auto lookup_block = [&](const uint256& hash, int& height, TxState& state) {
1132-
// If tx block (or conflicting block) was reorged out of chain
1133-
// while the wallet was shutdown, change tx status to UNCONFIRMED
1134-
// and reset block height, hash, and index. ABANDONED tx don't have
1135-
// associated blocks and don't need to be updated. The case where a
1136-
// transaction was reorged out while online and then reconfirmed
1137-
// while offline is covered by the rescan logic.
1138-
if (!chain().findBlock(hash, FoundBlock().inActiveChain(active).height(height)) || !active) {
1139-
state = TxStateInactive{};
1140-
}
1141-
};
1142-
if (auto* conf = wtx.state<TxStateConfirmed>()) {
1143-
lookup_block(conf->confirmed_block_hash, conf->confirmed_block_height, wtx.m_state);
1144-
} else if (auto* conf = wtx.state<TxStateConflicted>()) {
1145-
lookup_block(conf->conflicting_block_hash, conf->conflicting_block_height, wtx.m_state);
1146-
}
1130+
wtx.updateState(chain());
11471131
}
11481132
if (/* insertion took place */ ins.second) {
11491133
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
@@ -3721,8 +3705,10 @@ int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const
37213705
{
37223706
AssertLockHeld(cs_wallet);
37233707
if (auto* conf = wtx.state<TxStateConfirmed>()) {
3708+
assert(conf->confirmed_block_height >= 0);
37243709
return GetLastBlockHeight() - conf->confirmed_block_height + 1;
37253710
} else if (auto* conf = wtx.state<TxStateConflicted>()) {
3711+
assert(conf->conflicting_block_height >= 0);
37263712
return -1 * (GetLastBlockHeight() - conf->conflicting_block_height + 1);
37273713
} else {
37283714
return 0;

src/wallet/wallet.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,13 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
543543
* <0 : conflicts with a transaction this deep in the blockchain
544544
* 0 : in memory pool, waiting to be included in a block
545545
* >=1 : this many blocks deep in the main chain
546+
*
547+
* Preconditions: it is only valid to call this function when the wallet is
548+
* online and the block index is loaded. So this cannot be called by
549+
* bitcoin-wallet tool code or by wallet migration code. If this is called
550+
* without the wallet being online, it won't be able able to determine the
551+
* the height of the last block processed, or the heights of blocks
552+
* referenced in transaction, and might cause assert failures.
546553
*/
547554
int GetTxDepthInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
548555
bool IsTxInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)

0 commit comments

Comments
 (0)