Skip to content

Commit 9ac63d6

Browse files
committed
Keep track of explicit wallet conflicts instead of using mempool
1 parent 5d5ef3a commit 9ac63d6

File tree

6 files changed

+124
-26
lines changed

6 files changed

+124
-26
lines changed

doc/release-notes.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,24 @@ of just announcing the hash. In a reorganization, all new headers are sent,
215215
instead of just the new tip. This can often prevent an extra roundtrip before
216216
the actual block is downloaded.
217217

218+
Negative confirmations and conflict detection
219+
---------------------------------------------
220+
221+
The wallet will now report a negative number for confirmations that indicates
222+
how deep in the block chain the conflict is found. For example, if a transaction
223+
A has 5 confirmations and spends the same input as a wallet transaction B, B
224+
will be reported as having -5 confirmations. If another wallet transaction C
225+
spends an output from B, it will also be reported as having -5 confirmations.
226+
To detect conflicts with historical transactions in the chain a one-time
227+
`-rescan` may be needed.
228+
229+
Unlike earlier versions, unconfirmed but non-conflicting transactions will never
230+
get a negative confirmation count. They are not treated as spendable unless
231+
they're coming from ourself (change) and accepted into our local mempool,
232+
however. The new "trusted" field in the `listtransactions` RPC output
233+
indicates whether outputs of an unconfirmed transaction are considered
234+
spendable.
235+
218236
0.12.0 Change log
219237
=================
220238

qa/rpc-tests/txn_clone.py

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

138138
# Verify expected confirmations
139-
assert_equal(tx1["confirmations"], -1)
139+
assert_equal(tx1["confirmations"], -2)
140140
assert_equal(tx1_clone["confirmations"], 2)
141141
assert_equal(tx2["confirmations"], 1)
142142

qa/rpc-tests/txn_doublespend.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,22 +99,23 @@ def run_test(self):
9999
# Now give doublespend and its parents to miner:
100100
self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
101101
self.nodes[2].sendrawtransaction(fund_bar_tx["hex"])
102-
self.nodes[2].sendrawtransaction(doublespend["hex"])
102+
doublespend_txid = self.nodes[2].sendrawtransaction(doublespend["hex"])
103103
# ... mine a block...
104104
self.nodes[2].generate(1)
105105

106106
# Reconnect the split network, and sync chain:
107107
connect_nodes(self.nodes[1], 2)
108108
self.nodes[2].generate(1) # Mine another block to make sure we sync
109109
sync_blocks(self.nodes)
110+
assert_equal(self.nodes[0].gettransaction(doublespend_txid)["confirmations"], 2)
110111

111112
# Re-fetch transaction info:
112113
tx1 = self.nodes[0].gettransaction(txid1)
113114
tx2 = self.nodes[0].gettransaction(txid2)
114-
115+
115116
# Both transactions should be conflicted
116-
assert_equal(tx1["confirmations"], -1)
117-
assert_equal(tx2["confirmations"], -1)
117+
assert_equal(tx1["confirmations"], -2)
118+
assert_equal(tx2["confirmations"], -2)
118119

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

src/wallet/rpcwallet.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
6565
entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
6666
entry.push_back(Pair("blockindex", wtx.nIndex));
6767
entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
68+
} else {
69+
entry.push_back(Pair("trusted", wtx.IsTrusted()));
6870
}
6971
uint256 hash = wtx.GetHash();
7072
entry.push_back(Pair("txid", hash.GetHex()));
@@ -1421,7 +1423,9 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
14211423
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
14221424
" 'send' category of transactions.\n"
14231425
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n"
1424-
" 'receive' category of transactions.\n"
1426+
" 'receive' category of transactions. Negative confirmations indicate the\n"
1427+
" transation conflicts with the block chain\n"
1428+
" \"trusted\": xxx (bool) Whether we consider the outputs of this unconfirmed transaction safe to spend.\n"
14251429
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive'\n"
14261430
" category of transactions.\n"
14271431
" \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive'\n"

src/wallet/wallet.cpp

Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
608608
wtx.BindWallet(this);
609609
wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
610610
AddToSpends(hash);
611+
BOOST_FOREACH(const CTxIn& txin, wtx.vin) {
612+
if (mapWallet.count(txin.prevout.hash)) {
613+
CWalletTx& prevtx = mapWallet[txin.prevout.hash];
614+
if (prevtx.nIndex == -1 && !prevtx.hashBlock.IsNull()) {
615+
MarkConflicted(prevtx.hashBlock, wtx.GetHash());
616+
}
617+
}
618+
}
611619
}
612620
else
613621
{
@@ -727,6 +735,20 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
727735
{
728736
{
729737
AssertLockHeld(cs_wallet);
738+
739+
if (pblock) {
740+
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
741+
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
742+
while (range.first != range.second) {
743+
if (range.first->second != tx.GetHash()) {
744+
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);
745+
MarkConflicted(pblock->GetHash(), range.first->second);
746+
}
747+
range.first++;
748+
}
749+
}
750+
}
751+
730752
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
731753
if (fExisted && !fUpdate) return false;
732754
if (fExisted || IsMine(tx) || IsFromMe(tx))
@@ -747,9 +769,57 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
747769
return false;
748770
}
749771

772+
void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
773+
{
774+
LOCK2(cs_main, cs_wallet);
775+
776+
CBlockIndex* pindex;
777+
assert(mapBlockIndex.count(hashBlock));
778+
pindex = mapBlockIndex[hashBlock];
779+
int conflictconfirms = 0;
780+
if (chainActive.Contains(pindex)) {
781+
conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
782+
}
783+
assert(conflictconfirms < 0);
784+
785+
// Do not flush the wallet here for performance reasons
786+
CWalletDB walletdb(strWalletFile, "r+", false);
787+
788+
std::deque<uint256> todo;
789+
std::set<uint256> done;
790+
791+
todo.push_back(hashTx);
792+
793+
while (!todo.empty()) {
794+
uint256 now = todo.front();
795+
todo.pop_front();
796+
done.insert(now);
797+
assert(mapWallet.count(now));
798+
CWalletTx& wtx = mapWallet[now];
799+
int currentconfirm = wtx.GetDepthInMainChain();
800+
if (conflictconfirms < currentconfirm) {
801+
// Block is 'more conflicted' than current confirm; update.
802+
// Mark transaction as conflicted with this block.
803+
wtx.nIndex = -1;
804+
wtx.hashBlock = hashBlock;
805+
wtx.MarkDirty();
806+
wtx.WriteToDisk(&walletdb);
807+
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
808+
TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
809+
while (iter != mapTxSpends.end() && iter->first.hash == now) {
810+
if (!done.count(iter->second)) {
811+
todo.push_back(iter->second);
812+
}
813+
iter++;
814+
}
815+
}
816+
}
817+
}
818+
750819
void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock)
751820
{
752821
LOCK2(cs_main, cs_wallet);
822+
753823
if (!AddToWalletIfInvolvingMe(tx, pblock, true))
754824
return; // Not one of ours
755825

@@ -1089,7 +1159,7 @@ void CWallet::ReacceptWalletTransactions()
10891159

10901160
int nDepth = wtx.GetDepthInMainChain();
10911161

1092-
if (!wtx.IsCoinBase() && nDepth < 0) {
1162+
if (!wtx.IsCoinBase() && nDepth == 0) {
10931163
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
10941164
}
10951165
}
@@ -1303,6 +1373,14 @@ bool CWalletTx::IsTrusted() const
13031373
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
13041374
return false;
13051375

1376+
// Don't trust unconfirmed transactions from us unless they are in the mempool.
1377+
{
1378+
LOCK(mempool.cs);
1379+
if (!mempool.exists(GetHash())) {
1380+
return false;
1381+
}
1382+
}
1383+
13061384
// Trusted if all inputs are from us and are in the mempool:
13071385
BOOST_FOREACH(const CTxIn& txin, vin)
13081386
{
@@ -1879,6 +1957,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
18791957
//a chance at a free transaction.
18801958
//But mempool inputs might still be in the mempool, so their age stays 0
18811959
int age = pcoin.first->GetDepthInMainChain();
1960+
assert(age >= 0);
18821961
if (age != 0)
18831962
age += 1;
18841963
dPriority += (double)nCredit * age;
@@ -2814,9 +2893,9 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block)
28142893
return chainActive.Height() - pindex->nHeight + 1;
28152894
}
28162895

2817-
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
2896+
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
28182897
{
2819-
if (hashBlock.IsNull() || nIndex == -1)
2898+
if (hashBlock.IsNull())
28202899
return 0;
28212900
AssertLockHeld(cs_main);
28222901

@@ -2829,17 +2908,7 @@ int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
28292908
return 0;
28302909

28312910
pindexRet = pindex;
2832-
return chainActive.Height() - pindex->nHeight + 1;
2833-
}
2834-
2835-
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
2836-
{
2837-
AssertLockHeld(cs_main);
2838-
int nResult = GetDepthInMainChainINTERNAL(pindexRet);
2839-
if (nResult == 0 && !mempool.exists(GetHash()))
2840-
return -1; // Not in chain, not in mempool
2841-
2842-
return nResult;
2911+
return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1);
28432912
}
28442913

28452914
int CMerkleTx::GetBlocksToMaturity() const

src/wallet/wallet.h

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,14 @@ struct COutputEntry
156156
/** A transaction with a merkle branch linking it to the block chain. */
157157
class CMerkleTx : public CTransaction
158158
{
159-
private:
160-
int GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const;
161-
162159
public:
163160
uint256 hashBlock;
161+
162+
/* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest
163+
* block in the chain we know this or any in-wallet dependency conflicts
164+
* with. Older clients interpret nIndex == -1 as unconfirmed for backward
165+
* compatibility.
166+
*/
164167
int nIndex;
165168

166169
CMerkleTx()
@@ -193,16 +196,15 @@ class CMerkleTx : public CTransaction
193196

194197
int SetMerkleBranch(const CBlock& block);
195198

196-
197199
/**
198200
* Return depth of transaction in blockchain:
199-
* -1 : not in blockchain, and not in memory pool (conflicted transaction)
201+
* <0 : conflicts with a transaction this deep in the blockchain
200202
* 0 : in memory pool, waiting to be included in a block
201203
* >=1 : this many blocks deep in the main chain
202204
*/
203205
int GetDepthInMainChain(const CBlockIndex* &pindexRet) const;
204206
int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
205-
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; }
207+
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; }
206208
int GetBlocksToMaturity() const;
207209
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
208210
};
@@ -481,6 +483,10 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
481483
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid);
482484
void AddToSpends(const uint256& wtxid);
483485

486+
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
487+
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
488+
489+
484490
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
485491

486492
public:

0 commit comments

Comments
 (0)