Keep track of explicit wallet conflicts instead of using mempool
This commit is contained in:
parent
5d5ef3a4cf
commit
9ac63d6d30
6 changed files with 124 additions and 26 deletions
|
@ -215,6 +215,24 @@ of just announcing the hash. In a reorganization, all new headers are sent,
|
||||||
instead of just the new tip. This can often prevent an extra roundtrip before
|
instead of just the new tip. This can often prevent an extra roundtrip before
|
||||||
the actual block is downloaded.
|
the actual block is downloaded.
|
||||||
|
|
||||||
|
Negative confirmations and conflict detection
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
The wallet will now report a negative number for confirmations that indicates
|
||||||
|
how deep in the block chain the conflict is found. For example, if a transaction
|
||||||
|
A has 5 confirmations and spends the same input as a wallet transaction B, B
|
||||||
|
will be reported as having -5 confirmations. If another wallet transaction C
|
||||||
|
spends an output from B, it will also be reported as having -5 confirmations.
|
||||||
|
To detect conflicts with historical transactions in the chain a one-time
|
||||||
|
`-rescan` may be needed.
|
||||||
|
|
||||||
|
Unlike earlier versions, unconfirmed but non-conflicting transactions will never
|
||||||
|
get a negative confirmation count. They are not treated as spendable unless
|
||||||
|
they're coming from ourself (change) and accepted into our local mempool,
|
||||||
|
however. The new "trusted" field in the `listtransactions` RPC output
|
||||||
|
indicates whether outputs of an unconfirmed transaction are considered
|
||||||
|
spendable.
|
||||||
|
|
||||||
0.12.0 Change log
|
0.12.0 Change log
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ class TxnMallTest(BitcoinTestFramework):
|
||||||
tx2 = self.nodes[0].gettransaction(txid2)
|
tx2 = self.nodes[0].gettransaction(txid2)
|
||||||
|
|
||||||
# Verify expected confirmations
|
# Verify expected confirmations
|
||||||
assert_equal(tx1["confirmations"], -1)
|
assert_equal(tx1["confirmations"], -2)
|
||||||
assert_equal(tx1_clone["confirmations"], 2)
|
assert_equal(tx1_clone["confirmations"], 2)
|
||||||
assert_equal(tx2["confirmations"], 1)
|
assert_equal(tx2["confirmations"], 1)
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ class TxnMallTest(BitcoinTestFramework):
|
||||||
# Now give doublespend and its parents to miner:
|
# Now give doublespend and its parents to miner:
|
||||||
self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
|
self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
|
||||||
self.nodes[2].sendrawtransaction(fund_bar_tx["hex"])
|
self.nodes[2].sendrawtransaction(fund_bar_tx["hex"])
|
||||||
self.nodes[2].sendrawtransaction(doublespend["hex"])
|
doublespend_txid = self.nodes[2].sendrawtransaction(doublespend["hex"])
|
||||||
# ... mine a block...
|
# ... mine a block...
|
||||||
self.nodes[2].generate(1)
|
self.nodes[2].generate(1)
|
||||||
|
|
||||||
|
@ -107,14 +107,15 @@ class TxnMallTest(BitcoinTestFramework):
|
||||||
connect_nodes(self.nodes[1], 2)
|
connect_nodes(self.nodes[1], 2)
|
||||||
self.nodes[2].generate(1) # Mine another block to make sure we sync
|
self.nodes[2].generate(1) # Mine another block to make sure we sync
|
||||||
sync_blocks(self.nodes)
|
sync_blocks(self.nodes)
|
||||||
|
assert_equal(self.nodes[0].gettransaction(doublespend_txid)["confirmations"], 2)
|
||||||
|
|
||||||
# Re-fetch transaction info:
|
# Re-fetch transaction info:
|
||||||
tx1 = self.nodes[0].gettransaction(txid1)
|
tx1 = self.nodes[0].gettransaction(txid1)
|
||||||
tx2 = self.nodes[0].gettransaction(txid2)
|
tx2 = self.nodes[0].gettransaction(txid2)
|
||||||
|
|
||||||
# Both transactions should be conflicted
|
# Both transactions should be conflicted
|
||||||
assert_equal(tx1["confirmations"], -1)
|
assert_equal(tx1["confirmations"], -2)
|
||||||
assert_equal(tx2["confirmations"], -1)
|
assert_equal(tx2["confirmations"], -2)
|
||||||
|
|
||||||
# Node0's total balance should be starting balance, plus 100BTC for
|
# 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
|
# two more matured blocks, minus 1240 for the double-spend, plus fees (which are
|
||||||
|
|
|
@ -65,6 +65,8 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
|
||||||
entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
|
entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
|
||||||
entry.push_back(Pair("blockindex", wtx.nIndex));
|
entry.push_back(Pair("blockindex", wtx.nIndex));
|
||||||
entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
|
entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
|
||||||
|
} else {
|
||||||
|
entry.push_back(Pair("trusted", wtx.IsTrusted()));
|
||||||
}
|
}
|
||||||
uint256 hash = wtx.GetHash();
|
uint256 hash = wtx.GetHash();
|
||||||
entry.push_back(Pair("txid", hash.GetHex()));
|
entry.push_back(Pair("txid", hash.GetHex()));
|
||||||
|
@ -1421,7 +1423,9 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
|
||||||
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
|
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
|
||||||
" 'send' category of transactions.\n"
|
" 'send' category of transactions.\n"
|
||||||
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n"
|
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n"
|
||||||
" 'receive' category of transactions.\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"
|
||||||
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive'\n"
|
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive'\n"
|
||||||
" category of transactions.\n"
|
" category of transactions.\n"
|
||||||
" \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive'\n"
|
" \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive'\n"
|
||||||
|
|
|
@ -608,6 +608,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
|
||||||
wtx.BindWallet(this);
|
wtx.BindWallet(this);
|
||||||
wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
|
wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
|
||||||
AddToSpends(hash);
|
AddToSpends(hash);
|
||||||
|
BOOST_FOREACH(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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -727,6 +735,20 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet);
|
AssertLockHeld(cs_wallet);
|
||||||
|
|
||||||
|
if (pblock) {
|
||||||
|
BOOST_FOREACH(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;
|
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
|
||||||
if (fExisted && !fUpdate) return false;
|
if (fExisted && !fUpdate) return false;
|
||||||
if (fExisted || IsMine(tx) || IsFromMe(tx))
|
if (fExisted || IsMine(tx) || IsFromMe(tx))
|
||||||
|
@ -747,9 +769,57 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
|
||||||
return false;
|
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)
|
void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock)
|
||||||
{
|
{
|
||||||
LOCK2(cs_main, cs_wallet);
|
LOCK2(cs_main, cs_wallet);
|
||||||
|
|
||||||
if (!AddToWalletIfInvolvingMe(tx, pblock, true))
|
if (!AddToWalletIfInvolvingMe(tx, pblock, true))
|
||||||
return; // Not one of ours
|
return; // Not one of ours
|
||||||
|
|
||||||
|
@ -1089,7 +1159,7 @@ void CWallet::ReacceptWalletTransactions()
|
||||||
|
|
||||||
int nDepth = wtx.GetDepthInMainChain();
|
int nDepth = wtx.GetDepthInMainChain();
|
||||||
|
|
||||||
if (!wtx.IsCoinBase() && nDepth < 0) {
|
if (!wtx.IsCoinBase() && nDepth == 0) {
|
||||||
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
|
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1303,6 +1373,14 @@ bool CWalletTx::IsTrusted() const
|
||||||
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
|
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
|
||||||
return false;
|
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:
|
// Trusted if all inputs are from us and are in the mempool:
|
||||||
BOOST_FOREACH(const CTxIn& txin, vin)
|
BOOST_FOREACH(const CTxIn& txin, vin)
|
||||||
{
|
{
|
||||||
|
@ -1879,6 +1957,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
|
||||||
//a chance at a free transaction.
|
//a chance at a free transaction.
|
||||||
//But mempool inputs might still be in the mempool, so their age stays 0
|
//But mempool inputs might still be in the mempool, so their age stays 0
|
||||||
int age = pcoin.first->GetDepthInMainChain();
|
int age = pcoin.first->GetDepthInMainChain();
|
||||||
|
assert(age >= 0);
|
||||||
if (age != 0)
|
if (age != 0)
|
||||||
age += 1;
|
age += 1;
|
||||||
dPriority += (double)nCredit * age;
|
dPriority += (double)nCredit * age;
|
||||||
|
@ -2814,9 +2893,9 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block)
|
||||||
return chainActive.Height() - pindex->nHeight + 1;
|
return chainActive.Height() - pindex->nHeight + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
|
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
|
||||||
{
|
{
|
||||||
if (hashBlock.IsNull() || nIndex == -1)
|
if (hashBlock.IsNull())
|
||||||
return 0;
|
return 0;
|
||||||
AssertLockHeld(cs_main);
|
AssertLockHeld(cs_main);
|
||||||
|
|
||||||
|
@ -2829,17 +2908,7 @@ int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
pindexRet = pindex;
|
pindexRet = pindex;
|
||||||
return chainActive.Height() - pindex->nHeight + 1;
|
return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1);
|
||||||
}
|
|
||||||
|
|
||||||
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
|
|
||||||
{
|
|
||||||
AssertLockHeld(cs_main);
|
|
||||||
int nResult = GetDepthInMainChainINTERNAL(pindexRet);
|
|
||||||
if (nResult == 0 && !mempool.exists(GetHash()))
|
|
||||||
return -1; // Not in chain, not in mempool
|
|
||||||
|
|
||||||
return nResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int CMerkleTx::GetBlocksToMaturity() const
|
int CMerkleTx::GetBlocksToMaturity() const
|
||||||
|
|
|
@ -156,11 +156,14 @@ struct COutputEntry
|
||||||
/** A transaction with a merkle branch linking it to the block chain. */
|
/** A transaction with a merkle branch linking it to the block chain. */
|
||||||
class CMerkleTx : public CTransaction
|
class CMerkleTx : public CTransaction
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
int GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
uint256 hashBlock;
|
uint256 hashBlock;
|
||||||
|
|
||||||
|
/* 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;
|
int nIndex;
|
||||||
|
|
||||||
CMerkleTx()
|
CMerkleTx()
|
||||||
|
@ -193,16 +196,15 @@ public:
|
||||||
|
|
||||||
int SetMerkleBranch(const CBlock& block);
|
int SetMerkleBranch(const CBlock& block);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return depth of transaction in blockchain:
|
* 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
|
* 0 : in memory pool, waiting to be included in a block
|
||||||
* >=1 : this many blocks deep in the main chain
|
* >=1 : this many blocks deep in the main chain
|
||||||
*/
|
*/
|
||||||
int GetDepthInMainChain(const CBlockIndex* &pindexRet) const;
|
int GetDepthInMainChain(const CBlockIndex* &pindexRet) const;
|
||||||
int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
|
int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
|
||||||
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; }
|
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; }
|
||||||
int GetBlocksToMaturity() const;
|
int GetBlocksToMaturity() const;
|
||||||
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
|
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
|
||||||
};
|
};
|
||||||
|
@ -481,6 +483,10 @@ private:
|
||||||
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid);
|
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid);
|
||||||
void AddToSpends(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>);
|
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
Loading…
Reference in a new issue