Skip to content

Commit 6a50e03

Browse files
committed
[Wallet] Keep track of explicit wallet conflicts instead of using
mempool Backport of bitcoin#7105 (9ac63d6)
1 parent 7ccb2b5 commit 6a50e03

File tree

5 files changed

+98
-7
lines changed

5 files changed

+98
-7
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
5757
entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
5858
entry.push_back(Pair("blockindex", wtx.nIndex));
5959
entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
60+
} else {
61+
entry.push_back(Pair("trusted", wtx.IsTrusted()));
6062
}
6163
uint256 hash = wtx.GetHash();
6264
entry.push_back(Pair("txid", hash.GetHex()));
@@ -1355,6 +1357,9 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
13551357
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n"
13561358
" 'receive' category of transactions.\n"
13571359
" \"bcconfirmations\": n, (numeric) The number of blockchain confirmations for the transaction. Available for 'send'\n"
1360+
" 'receive' category of transactions. Negative confirmations indicate the\n"
1361+
" transation conflicts with the block chain\n"
1362+
" \"trusted\": xxx (bool) Whether we consider the outputs of this unconfirmed transaction safe to spend.\n"
13581363
" and 'receive' category of transactions.\n"
13591364
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive'\n"
13601365
" category of transactions.\n"

src/wallet/wallet.cpp

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
691691
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
692692
wtx.nTimeSmart = ComputeTimeSmart(wtx);
693693
AddToSpends(hash);
694+
for (const CTxIn& txin : wtx.vin) {
695+
if (mapWallet.count(txin.prevout.hash)) {
696+
CWalletTx& prevtx = mapWallet[txin.prevout.hash];
697+
if (prevtx.nIndex == -1 && !prevtx.hashBlock.IsNull()) {
698+
MarkConflicted(prevtx.hashBlock, wtx.GetHash());
699+
}
700+
}
701+
}
694702
}
695703

696704
bool fUpdated = false;
@@ -744,6 +752,20 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
744752
{
745753
{
746754
AssertLockHeld(cs_wallet);
755+
756+
if (pblock) {
757+
for (const CTxIn& txin : tx.vin) {
758+
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
759+
while (range.first != range.second) {
760+
if (range.first->second != tx.GetHash()) {
761+
LogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pblock->GetHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
762+
MarkConflicted(pblock->GetHash(), range.first->second);
763+
}
764+
range.first++;
765+
}
766+
}
767+
}
768+
747769
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
748770
if (fExisted && !fUpdate) return false;
749771
if (fExisted || IsMine(tx) || IsFromMe(tx)) {
@@ -761,6 +783,53 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
761783
return false;
762784
}
763785

786+
void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
787+
{
788+
LOCK2(cs_main, cs_wallet);
789+
790+
CBlockIndex* pindex;
791+
assert(mapBlockIndex.count(hashBlock));
792+
pindex = mapBlockIndex[hashBlock];
793+
int conflictconfirms = 0;
794+
if (chainActive.Contains(pindex)) {
795+
conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
796+
}
797+
assert(conflictconfirms < 0);
798+
799+
// Do not flush the wallet here for performance reasons
800+
CWalletDB walletdb(strWalletFile, "r+", false);
801+
802+
std::deque<uint256> todo;
803+
std::set<uint256> done;
804+
805+
todo.push_back(hashTx);
806+
807+
while (!todo.empty()) {
808+
uint256 now = todo.front();
809+
todo.pop_front();
810+
done.insert(now);
811+
assert(mapWallet.count(now));
812+
CWalletTx& wtx = mapWallet[now];
813+
int currentconfirm = wtx.GetDepthInMainChain();
814+
if (conflictconfirms < currentconfirm) {
815+
// Block is 'more conflicted' than current confirm; update.
816+
// Mark transaction as conflicted with this block.
817+
wtx.nIndex = -1;
818+
wtx.hashBlock = hashBlock;
819+
wtx.MarkDirty();
820+
wtx.WriteToDisk(&walletdb);
821+
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
822+
TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
823+
while (iter != mapTxSpends.end() && iter->first.hash == now) {
824+
if (!done.count(iter->second)) {
825+
todo.push_back(iter->second);
826+
}
827+
iter++;
828+
}
829+
}
830+
}
831+
}
832+
764833
void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock)
765834
{
766835
LOCK2(cs_main, cs_wallet);
@@ -1375,7 +1444,7 @@ void CWallet::ReacceptWalletTransactions()
13751444

13761445
int nDepth = wtx.GetDepthInMainChain();
13771446

1378-
if (!wtx.IsCoinBase() && !wtx.IsCoinStake() && nDepth < 0) {
1447+
if (!wtx.IsCoinBase() && !wtx.IsCoinStake() && nDepth == 0) {
13791448
// Try to add to memory pool
13801449
LOCK(mempool.cs);
13811450
wtx.AcceptToMemoryPool(false);
@@ -2216,6 +2285,7 @@ bool CWallet::CreateTransaction(const std::vector<std::pair<CScript, CAmount> >&
22162285
//a chance at a free transaction.
22172286
//But mempool inputs might still be in the mempool, so their age stays 0
22182287
int age = pcoin.first->GetDepthInMainChain();
2288+
assert(age >= 0);
22192289
if (age != 0)
22202290
age += 1;
22212291
dPriority += (double)nCredit * age;
@@ -3671,7 +3741,7 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block)
36713741

36723742
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex*& pindexRet) const
36733743
{
3674-
if (hashBlock == 0 || nIndex == -1)
3744+
if (hashBlock == 0)
36753745
return 0;
36763746
AssertLockHeld(cs_main);
36773747

@@ -3684,7 +3754,7 @@ int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex*& pindexRet) const
36843754
return 0;
36853755

36863756
pindexRet = pindex;
3687-
return chainActive.Height() - pindex->nHeight + 1;
3757+
return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1);
36883758
}
36893759

36903760
int CMerkleTx::GetDepthInMainChain(const CBlockIndex*& pindexRet, bool enableIX) const
@@ -5386,6 +5456,14 @@ bool CWalletTx::IsTrusted() const
53865456
return false;
53875457
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
53885458
return false;
5459+
5460+
// Don't trust unconfirmed transactions from us unless they are in the mempool.
5461+
{
5462+
LOCK(mempool.cs);
5463+
if (!mempool.exists(GetHash())) {
5464+
return false;
5465+
}
5466+
}
53895467

53905468
// Trusted if all inputs are from us and are in the mempool:
53915469
for (const CTxIn& txin : vin) {

src/wallet/wallet.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
193193
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid);
194194
void AddToSpends(const uint256& wtxid);
195195

196+
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
197+
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
198+
196199
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
197200

198201
public:
@@ -617,6 +620,11 @@ class CMerkleTx : public CTransaction
617620

618621
public:
619622
uint256 hashBlock;
623+
/* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest
624+
* block in the chain we know this or any in-wallet dependency conflicts
625+
* with. Older clients interpret nIndex == -1 as unconfirmed for backward
626+
* compatibility.
627+
*/
620628
int nIndex;
621629

622630
CMerkleTx()
@@ -653,7 +661,7 @@ class CMerkleTx : public CTransaction
653661

654662
/**
655663
* Return depth of transaction in blockchain:
656-
* -1 : not in blockchain, and not in memory pool (conflicted transaction)
664+
* <0 : conflicts with a transaction this deep in the blockchain
657665
* 0 : in memory pool, waiting to be included in a block
658666
* >=1 : this many blocks deep in the main chain
659667
*/

test/functional/wallet_txn_clone.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def run_test(self):
126126
tx2 = self.nodes[0].gettransaction(txid2)
127127

128128
# Verify expected confirmations
129-
assert_equal(tx1["confirmations"], -1)
129+
assert_equal(tx1["confirmations"], -2)
130130
assert_equal(tx1_clone["confirmations"], 2)
131131
assert_equal(tx2["confirmations"], 1)
132132

test/functional/wallet_txn_doublespend.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ def run_test(self):
117117
tx2 = self.nodes[0].gettransaction(txid2)
118118

119119
# Both transactions should be conflicted
120-
assert_equal(tx1["bcconfirmations"], -1)
121-
assert_equal(tx2["bcconfirmations"], -1)
120+
assert_equal(tx1["bcconfirmations"], -2)
121+
assert_equal(tx2["bcconfirmations"], -2)
122122

123123
# Node0's total balance should be starting balance, plus 100BTC for
124124
# two more matured blocks, minus 1240 for the double-spend, plus fees (which are

0 commit comments

Comments
 (0)