Skip to content

Commit

Permalink
[Wallet] Keep track of explicit wallet conflicts instead of using
Browse files Browse the repository at this point in the history
mempool

Backport of bitcoin#7105
(9ac63d6)
  • Loading branch information
Warrows committed Jul 19, 2019
1 parent 13c6947 commit 9c03c2f
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 7 deletions.
5 changes: 5 additions & 0 deletions src/wallet/rpcwallet.cpp
Expand Up @@ -59,6 +59,8 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
entry.push_back(Pair("blockindex", wtx.nIndex));
entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
} else {
entry.push_back(Pair("trusted", wtx.IsTrusted()));
}
uint256 hash = wtx.GetHash();
entry.push_back(Pair("txid", hash.GetHex()));
Expand Down Expand Up @@ -1357,6 +1359,9 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n"
" 'receive' category of transactions.\n"
" \"bcconfirmations\": n, (numeric) The number of blockchain confirmations for the transaction. Available for 'send'\n"
" 'receive' category of transactions. Negative confirmations indicate the\n"
" transation conflicts with the block chain\n"
" \"trusted\": xxx (bool) Whether we consider the outputs of this unconfirmed transaction safe to spend.\n"
" and 'receive' category of transactions.\n"
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive'\n"
" category of transactions.\n"
Expand Down
84 changes: 81 additions & 3 deletions src/wallet/wallet.cpp
Expand Up @@ -667,6 +667,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
wtx.nTimeSmart = ComputeTimeSmart(wtx);
AddToSpends(hash);
for (const CTxIn& txin : wtx.vin) {
if (mapWallet.count(txin.prevout.hash)) {
CWalletTx& prevtx = mapWallet[txin.prevout.hash];
if (prevtx.nIndex == -1 && !prevtx.hashBlock.IsNull()) {
MarkConflicted(prevtx.hashBlock, wtx.GetHash());
}
}
}
}

bool fUpdated = false;
Expand Down Expand Up @@ -721,6 +729,20 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
{
{
AssertLockHeld(cs_wallet);

if (pblock) {
for (const CTxIn& txin : tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) {
if (range.first->second != tx.GetHash()) {
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);
MarkConflicted(pblock->GetHash(), range.first->second);
}
range.first++;
}
}
}

bool fExisted = mapWallet.count(tx.GetHash()) != 0;
if (fExisted && !fUpdate) return false;
if (fExisted || IsMine(tx) || IsFromMe(tx)) {
Expand All @@ -738,6 +760,53 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
return false;
}

void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
{
LOCK2(cs_main, cs_wallet);

CBlockIndex* pindex;
assert(mapBlockIndex.count(hashBlock));
pindex = mapBlockIndex[hashBlock];
int conflictconfirms = 0;
if (chainActive.Contains(pindex)) {
conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
}
assert(conflictconfirms < 0);

// Do not flush the wallet here for performance reasons
CWalletDB walletdb(strWalletFile, "r+", false);

std::deque<uint256> todo;
std::set<uint256> done;

todo.push_back(hashTx);

while (!todo.empty()) {
uint256 now = todo.front();
todo.pop_front();
done.insert(now);
assert(mapWallet.count(now));
CWalletTx& wtx = mapWallet[now];
int currentconfirm = wtx.GetDepthInMainChain();
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
wtx.nIndex = -1;
wtx.hashBlock = hashBlock;
wtx.MarkDirty();
wtx.WriteToDisk(&walletdb);
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.hash == now) {
if (!done.count(iter->second)) {
todo.push_back(iter->second);
}
iter++;
}
}
}
}

void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock)
{
LOCK2(cs_main, cs_wallet);
Expand Down Expand Up @@ -1347,7 +1416,7 @@ void CWallet::ReacceptWalletTransactions()

int nDepth = wtx.GetDepthInMainChain();

if (!wtx.IsCoinBase() && !wtx.IsCoinStake() && nDepth < 0) {
if (!wtx.IsCoinBase() && !wtx.IsCoinStake() && nDepth == 0) {
// Try to add to memory pool
LOCK(mempool.cs);
wtx.AcceptToMemoryPool(false);
Expand Down Expand Up @@ -2197,6 +2266,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
//a chance at a free transaction.
//But mempool inputs might still be in the mempool, so their age stays 0
int age = pcoin.first->GetDepthInMainChain();
assert(age >= 0);
if (age != 0)
age += 1;
dPriority += (double)nCredit * age;
Expand Down Expand Up @@ -3653,7 +3723,7 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block)

int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex*& pindexRet) const
{
if (hashBlock == 0 || nIndex == -1)
if (hashBlock == 0)
return 0;
AssertLockHeld(cs_main);

Expand All @@ -3673,7 +3743,7 @@ int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex*& pindexRet) const
}

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

int CMerkleTx::GetDepthInMainChain(const CBlockIndex*& pindexRet, bool enableIX) const
Expand Down Expand Up @@ -5344,6 +5414,14 @@ bool CWalletTx::IsTrusted() const
return false;
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
return false;

// Don't trust unconfirmed transactions from us unless they are in the mempool.
{
LOCK(mempool.cs);
if (!mempool.exists(GetHash())) {
return false;
}
}

// Trusted if all inputs are from us and are in the mempool:
for (const CTxIn& txin : vin) {
Expand Down
10 changes: 9 additions & 1 deletion src/wallet/wallet.h
Expand Up @@ -193,6 +193,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid);
void AddToSpends(const uint256& wtxid);

/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);

void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);

public:
Expand Down Expand Up @@ -587,6 +590,11 @@ class CMerkleTx : public CTransaction
public:
uint256 hashBlock;
std::vector<uint256> vMerkleBranch;
/* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest
* block in the chain we know this or any in-wallet dependency conflicts
* with. Older clients interpret nIndex == -1 as unconfirmed for backward
* compatibility.
*/
int nIndex;

// memory only
Expand Down Expand Up @@ -627,7 +635,7 @@ class CMerkleTx : public CTransaction

/**
* Return depth of transaction in blockchain:
* -1 : not in blockchain, and not in memory pool (conflicted transaction)
* <0 : conflicts with a transaction this deep in the blockchain
* 0 : in memory pool, waiting to be included in a block
* >=1 : this many blocks deep in the main chain
*/
Expand Down
2 changes: 1 addition & 1 deletion test/functional/wallet_txn_clone.py
Expand Up @@ -126,7 +126,7 @@ def run_test(self):
tx2 = self.nodes[0].gettransaction(txid2)

# Verify expected confirmations
assert_equal(tx1["confirmations"], -1)
assert_equal(tx1["confirmations"], -2)
assert_equal(tx1_clone["confirmations"], 2)
assert_equal(tx2["confirmations"], 1)

Expand Down
4 changes: 2 additions & 2 deletions test/functional/wallet_txn_doublespend.py
Expand Up @@ -117,8 +117,8 @@ def run_test(self):
tx2 = self.nodes[0].gettransaction(txid2)

# Both transactions should be conflicted
assert_equal(tx1["bcconfirmations"], -1)
assert_equal(tx2["bcconfirmations"], -1)
assert_equal(tx1["bcconfirmations"], -2)
assert_equal(tx2["bcconfirmations"], -2)

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

0 comments on commit 9c03c2f

Please sign in to comment.