Skip to content

Commit a31be09

Browse files
author
Antoine Riard
committed
Encapsulate tx status in a Confirmation struct
Instead of relying on combination of hashBlock and nIndex values to manage tx in its lifecycle, we introduce 4 status : CONFIRMED, UNCONFIRMED, CONFLICTED, ABANDONED. hashBlock and nIndex magic values should only be used at serialization/deserialization for backward-compatibility. At block disconnection, we know flag txn as UNCONFIRMED where previously they kept their states until being override by a block connection or abandontransaction call. This is a change in behavior for which user may have to call abandon twice if transaction is disconnected and not accepted back in the mempool. We assert status transitioning right in AddToWallet. Doing so flagged a misbehavior in ComputeTimeSmart unit test where same tx is confirmed twice in different block. To avoid inconsistencies we unconfirmed tx before new connection in different block. We also remove a cs_main lock in test, as AddToWallet and its callees don't rely on locked chain.
1 parent 442a9c6 commit a31be09

File tree

6 files changed

+127
-75
lines changed

6 files changed

+127
-75
lines changed

src/interfaces/wallet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co
6565
WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx)
6666
{
6767
WalletTxStatus result;
68-
result.block_height = locked_chain.getBlockHeight(wtx.hashBlock).get_value_or(std::numeric_limits<int>::max());
68+
result.block_height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock).get_value_or(std::numeric_limits<int>::max());
6969
result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain);
7070
result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain);
7171
result.time_received = wtx.nTimeReceived;

src/wallet/rpcdump.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
384384
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
385385
}
386386

387-
wtx.nIndex = txnIndex;
388-
wtx.hashBlock = merkleBlock.header.GetHash();
387+
wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), txnIndex);
389388

390389
auto locked_chain = pwallet->chain().lock();
391390
LOCK(pwallet->cs_wallet);

src/wallet/rpcwallet.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo
134134
entry.pushKV("generated", true);
135135
if (confirms > 0)
136136
{
137-
entry.pushKV("blockhash", wtx.hashBlock.GetHex());
138-
entry.pushKV("blockindex", wtx.nIndex);
137+
entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex());
138+
entry.pushKV("blockindex", wtx.m_confirm.nIndex);
139139
int64_t block_time;
140-
bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time);
140+
bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time);
141141
assert(found_block);
142142
entry.pushKV("blocktime", block_time);
143143
} else {

src/wallet/test/wallet_tests.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
249249
LockAssertion lock(::cs_main);
250250
LOCK(wallet.cs_wallet);
251251

252-
wtx.hashBlock = ::ChainActive().Tip()->GetBlockHash();
253-
wtx.nIndex = 0;
252+
wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0);
254253

255254
// Call GetImmatureCredit() once before adding the key to the wallet to
256255
// cache the current immature credit amount, which is 0.
@@ -281,14 +280,19 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
281280
}
282281

283282
CWalletTx wtx(&wallet, MakeTransactionRef(tx));
284-
if (block) {
285-
wtx.SetMerkleBranch(block->GetBlockHash(), 0);
286-
}
287-
{
288-
LOCK(cs_main);
283+
LOCK(cs_main);
284+
LOCK(wallet.cs_wallet);
285+
// If transaction is already in map, to avoid inconsistencies, unconfirmation
286+
// is needed before confirm again with different block.
287+
std::map<uint256, CWalletTx>::iterator it = wallet.mapWallet.find(wtx.GetHash());
288+
if (it != wallet.mapWallet.end()) {
289+
wtx.setUnconfirmed();
289290
wallet.AddToWallet(wtx);
290291
}
291-
LOCK(wallet.cs_wallet);
292+
if (block) {
293+
wtx.SetConf(CWalletTx::Status::CONFIRMED, block->GetBlockHash(), 0);
294+
}
295+
wallet.AddToWallet(wtx);
292296
return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
293297
}
294298

@@ -382,7 +386,7 @@ class ListCoinsTestingSetup : public TestChain100Setup
382386
LOCK(wallet->cs_wallet);
383387
auto it = wallet->mapWallet.find(tx->GetHash());
384388
BOOST_CHECK(it != wallet->mapWallet.end());
385-
it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1);
389+
it->second.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 1);
386390
return it->second;
387391
}
388392

src/wallet/wallet.cpp

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,22 +1118,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
11181118
bool fUpdated = false;
11191119
if (!fInsertedNew)
11201120
{
1121-
// Merge
1122-
if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock)
1123-
{
1124-
wtx.hashBlock = wtxIn.hashBlock;
1125-
fUpdated = true;
1126-
}
1127-
// If no longer abandoned, update
1128-
if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned())
1129-
{
1130-
wtx.hashBlock = wtxIn.hashBlock;
1131-
fUpdated = true;
1132-
}
1133-
if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex))
1134-
{
1135-
wtx.nIndex = wtxIn.nIndex;
1121+
if (wtxIn.m_confirm.status != wtx.m_confirm.status) {
1122+
wtx.m_confirm.status = wtxIn.m_confirm.status;
1123+
wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex;
1124+
wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock;
11361125
fUpdated = true;
1126+
} else {
1127+
assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex);
1128+
assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock);
11371129
}
11381130
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
11391131
{
@@ -1194,14 +1186,14 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn)
11941186
auto it = mapWallet.find(txin.prevout.hash);
11951187
if (it != mapWallet.end()) {
11961188
CWalletTx& prevtx = it->second;
1197-
if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
1198-
MarkConflicted(prevtx.hashBlock, wtx.GetHash());
1189+
if (prevtx.isConflicted()) {
1190+
MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetHash());
11991191
}
12001192
}
12011193
}
12021194
}
12031195

1204-
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate)
1196+
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate)
12051197
{
12061198
const CTransaction& tx = *ptx;
12071199
{
@@ -1248,9 +1240,9 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256
12481240

12491241
CWalletTx wtx(this, ptx);
12501242

1251-
// Get merkle branch if transaction was found in a block
1252-
if (!block_hash.IsNull())
1253-
wtx.SetMerkleBranch(block_hash, posInBlock);
1243+
// Block disconnection override an abandoned tx as unconfirmed
1244+
// which means user may have to call abandontransaction again
1245+
wtx.SetConf(status, block_hash, posInBlock);
12541246

12551247
return AddToWallet(wtx, false);
12561248
}
@@ -1310,7 +1302,7 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
13101302
if (currentconfirm == 0 && !wtx.isAbandoned()) {
13111303
// If the orig tx was not in block/mempool, none of its spends can be in mempool
13121304
assert(!wtx.InMempool());
1313-
wtx.nIndex = -1;
1305+
wtx.m_confirm.nIndex = 0;
13141306
wtx.setAbandoned();
13151307
wtx.MarkDirty();
13161308
batch.WriteTx(wtx);
@@ -1364,8 +1356,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
13641356
if (conflictconfirms < currentconfirm) {
13651357
// Block is 'more conflicted' than current confirm; update.
13661358
// Mark transaction as conflicted with this block.
1367-
wtx.nIndex = -1;
1368-
wtx.hashBlock = hashBlock;
1359+
wtx.m_confirm.nIndex = 0;
1360+
wtx.m_confirm.hashBlock = hashBlock;
1361+
wtx.setConflicted();
13691362
wtx.MarkDirty();
13701363
batch.WriteTx(wtx);
13711364
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
@@ -1383,8 +1376,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
13831376
}
13841377
}
13851378

1386-
void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) {
1387-
if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx))
1379+
void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool update_tx)
1380+
{
1381+
if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, update_tx))
13881382
return; // Not one of ours
13891383

13901384
// If a transaction changes 'conflicted' state, that changes the balance
@@ -1396,7 +1390,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_h
13961390
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
13971391
auto locked_chain = chain().lock();
13981392
LOCK(cs_wallet);
1399-
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
1393+
SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
14001394

14011395
auto it = mapWallet.find(ptx->GetHash());
14021396
if (it != mapWallet.end()) {
@@ -1425,11 +1419,11 @@ void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransaction
14251419
// the notification that the conflicted transaction was evicted.
14261420

14271421
for (const CTransactionRef& ptx : vtxConflicted) {
1428-
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
1422+
SyncTransaction(ptx, CWalletTx::Status::CONFLICTED, {} /* block hash */, 0 /* position in block */);
14291423
TransactionRemovedFromMempool(ptx);
14301424
}
14311425
for (size_t i = 0; i < block.vtx.size(); i++) {
1432-
SyncTransaction(block.vtx[i], block_hash, i);
1426+
SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash, i);
14331427
TransactionRemovedFromMempool(block.vtx[i]);
14341428
}
14351429

@@ -1440,8 +1434,12 @@ void CWallet::BlockDisconnected(const CBlock& block) {
14401434
auto locked_chain = chain().lock();
14411435
LOCK(cs_wallet);
14421436

1437+
// At block disconnection, this will change an abandoned transaction to
1438+
// be unconfirmed, whether or not the transaction is added back to the mempool.
1439+
// User may have to call abandontransaction again. It may be addressed in the
1440+
// future with a stickier abandoned state or even removing abandontransaction call.
14431441
for (const CTransactionRef& ptx : block.vtx) {
1444-
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
1442+
SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
14451443
}
14461444
}
14471445

@@ -2078,7 +2076,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
20782076
break;
20792077
}
20802078
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
2081-
SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate);
2079+
SyncTransaction(block.vtx[posInBlock], CWalletTx::Status::CONFIRMED, block_hash, posInBlock, fUpdate);
20822080
}
20832081
// scan succeeded, record block as most recent successfully scanned
20842082
result.last_scanned_block = block_hash;
@@ -4050,7 +4048,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
40504048
for (const auto& entry : mapWallet) {
40514049
// iterate over all wallet transactions...
40524050
const CWalletTx &wtx = entry.second;
4053-
if (Optional<int> height = locked_chain.getBlockHeight(wtx.hashBlock)) {
4051+
if (Optional<int> height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock)) {
40544052
// ... which are already in a block
40554053
for (const CTxOut &txout : wtx.tx->vout) {
40564054
// iterate over all their outputs
@@ -4093,9 +4091,9 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
40934091
unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
40944092
{
40954093
unsigned int nTimeSmart = wtx.nTimeReceived;
4096-
if (!wtx.hashUnset()) {
4094+
if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) {
40974095
int64_t blocktime;
4098-
if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) {
4096+
if (chain().findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &blocktime)) {
40994097
int64_t latestNow = wtx.nTimeReceived;
41004098
int64_t latestEntry = 0;
41014099

@@ -4123,7 +4121,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
41234121

41244122
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
41254123
} else {
4126-
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString());
4124+
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.m_confirm.hashBlock.ToString());
41274125
}
41284126
}
41294127
return nTimeSmart;
@@ -4634,21 +4632,23 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
46344632
m_pre_split = false;
46354633
}
46364634

4637-
void CWalletTx::SetMerkleBranch(const uint256& block_hash, int posInBlock)
4635+
void CWalletTx::SetConf(Status status, const uint256& block_hash, int posInBlock)
46384636
{
4637+
// Update tx status
4638+
m_confirm.status = status;
4639+
46394640
// Update the tx's hashBlock
4640-
hashBlock = block_hash;
4641+
m_confirm.hashBlock = block_hash;
46414642

46424643
// set the position of the transaction in the block
4643-
nIndex = posInBlock;
4644+
m_confirm.nIndex = posInBlock;
46444645
}
46454646

46464647
int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const
46474648
{
4648-
if (hashUnset())
4649-
return 0;
4649+
if (isUnconfirmed() || isAbandoned()) return 0;
46504650

4651-
return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1);
4651+
return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1);
46524652
}
46534653

46544654
int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const

0 commit comments

Comments
 (0)