Merge pull request #3883 from dgenr8/first_double_spend

Relay and alert user to double spends
This commit is contained in:
Gavin Andresen 2014-06-30 08:35:12 -04:00
commit 8ceb28afc3
21 changed files with 296 additions and 38 deletions

View file

@ -139,6 +139,9 @@ Execute command when the best block changes (%s in cmd is replaced by block hash
\fB\-walletnotify=\fR<cmd>
Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)
.TP
\fB\-respendnotify=\fR<cmd>
Execute command when a network tx respends wallet tx input (%s=respend TxID, %t=wallet TxID)
.TP
\fB\-alertnotify=\fR<cmd>
Execute command when a relevant alert is received (%s in cmd is replaced by message)
.TP

View file

@ -19,3 +19,49 @@ estimate.
Statistics used to estimate fees and priorities are saved in the
data directory in the 'fee_estimates.dat' file just before
program shutdown, and are read in at startup.
Double-Spend Relay and Alerts
=============================
VERY IMPORTANT: *It has never been safe, and remains unsafe, to rely*
*on unconfirmed transactions.*
Relay
-----
When an attempt is seen on the network to spend the same unspent funds
more than once, it is no longer ignored. Instead, it is broadcast, to
serve as an alert. This broadcast is subject to protections against
denial-of-service attacks.
Wallets and other bitcoin services should alert their users to
double-spends that affect them. Merchants and other users may have
enough time to withhold goods or services when payment becomes
uncertain, until confirmation.
Bitcoin Core Wallet Alerts
--------------------------
The Bitcoin Core wallet now makes respend attempts visible in several
ways.
If you are online, and a respend affecting one of your wallet
transactions is seen, a notification is immediately issued to the
command registered with `-respendnotify=<cmd>`. Additionally, if
using the GUI:
- An alert box is immediately displayed.
- The affected wallet transaction is highlighted in red until it is
confirmed (and it may never be confirmed).
A `respendsobserved` array is added to `gettransaction`, `listtransactions`,
and `listsinceblock` RPC results.
Warning
-------
*If you rely on an unconfirmed transaction, these change do VERY*
*LITTLE to protect you from a malicious double-spend, because:*
- You may learn about the respend too late to avoid doing whatever
you were being paid for
- Using other relay rules, a double-spender can craft his crime to
resist broadcast
- Miners can choose which conflicting spend to confirm, and some
miners may not confirm the first acceptable spend they see

View file

@ -94,6 +94,13 @@ bool CBloomFilter::contains(const uint256& hash) const
return contains(data);
}
void CBloomFilter::clear()
{
vData.assign(vData.size(),0);
isFull = false;
isEmpty = true;
}
bool CBloomFilter::IsWithinSizeConstraints() const
{
return vData.size() <= MAX_BLOOM_FILTER_SIZE && nHashFuncs <= MAX_HASH_FUNCS;

View file

@ -78,6 +78,8 @@ public:
bool contains(const COutPoint& outpoint) const;
bool contains(const uint256& hash) const;
void clear();
// True if the size is <= MAX_BLOOM_FILTER_SIZE and the number of hash functions is <= MAX_HASH_FUNCS
// (catch a filter which was just deserialized which was too big)
bool IsWithinSizeConstraints() const;

View file

@ -119,6 +119,22 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) {
return *this;
}
bool CTransaction::IsEquivalentTo(const CTransaction& tx) const
{
if (nVersion != tx.nVersion ||
nLockTime != tx.nLockTime ||
vin.size() != tx.vin.size() ||
vout != tx.vout)
return false;
for (unsigned int i = 0; i < vin.size(); i++)
{
if (vin[i].nSequence != tx.vin[i].nSequence ||
vin[i].prevout != tx.vin[i].prevout)
return false;
}
return true;
}
int64_t CTransaction::GetValueOut() const
{
int64_t nValueOut = 0;

View file

@ -256,6 +256,9 @@ public:
return hash;
}
// True if only scriptSigs are different
bool IsEquivalentTo(const CTransaction& tx) const;
// Return sum of txouts.
int64_t GetValueOut() const;
// GetValueIn() is a method on CCoinsViewCache, because

View file

@ -260,6 +260,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += " -upgradewallet " + _("Upgrade wallet to latest format") + " " + _("on startup") + "\n";
strUsage += " -wallet=<file> " + _("Specify wallet file (within data directory)") + " " + _("(default: wallet.dat)") + "\n";
strUsage += " -walletnotify=<cmd> " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n";
strUsage += " -respendnotify=<cmd> " + _("Execute command when a network tx respends wallet tx input (%s=respend TxID, %t=wallet TxID)") + "\n";
strUsage += " -zapwallettxes=<mode> " + _("Delete all wallet transactions and only recover those part of the blockchain through -rescan on startup") + "\n";
strUsage += " " + _("(default: 1, 1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)") + "\n";
#endif
@ -1175,6 +1176,7 @@ bool AppInit2(boost::thread_group& threadGroup)
LogPrintf("mapAddressBook.size() = %u\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0);
#endif
RegisterInternalSignals();
StartNode(threadGroup);
if (fServer)
StartRPCThreads();

View file

@ -7,6 +7,7 @@
#include "addrman.h"
#include "alert.h"
#include "bloom.h"
#include "chainparams.h"
#include "checkpoints.h"
#include "checkqueue.h"
@ -124,6 +125,10 @@ namespace {
} // anon namespace
// Forward reference functions defined here:
static const unsigned int MAX_DOUBLESPEND_BLOOM = 1000;
static void RelayDoubleSpend(const COutPoint& outPoint, const CTransaction& doubleSpend, bool fInBlock, CBloomFilter& filter);
//////////////////////////////////////////////////////////////////////////////
//
// dispatching functions
@ -146,10 +151,25 @@ struct CMainSignals {
boost::signals2::signal<void (const uint256 &)> Inventory;
// Tells listeners to broadcast their data.
boost::signals2::signal<void ()> Broadcast;
// Notifies listeners of detection of a double-spent transaction. Arguments are outpoint that is
// double-spent, first transaction seen, double-spend transaction, and whether the second double-spend
// transaction was first seen in a block.
// Note: only notifies if the previous transaction is in the memory pool; if previous transction was in a block,
// then the double-spend simply fails when we try to lookup the inputs in the current UTXO set.
boost::signals2::signal<void (const COutPoint&, const CTransaction&, bool)> DetectedDoubleSpend;
} g_signals;
} // anon namespace
void RegisterInternalSignals() {
static CBloomFilter doubleSpendFilter;
seed_insecure_rand();
doubleSpendFilter = CBloomFilter(MAX_DOUBLESPEND_BLOOM, 0.01, insecure_rand(), BLOOM_UPDATE_NONE);
g_signals.DetectedDoubleSpend.connect(boost::bind(RelayDoubleSpend, _1, _2, _3, doubleSpendFilter));
}
void RegisterWallet(CWalletInterface* pwalletIn) {
g_signals.SyncTransaction.connect(boost::bind(&CWalletInterface::SyncTransaction, pwalletIn, _1, _2));
g_signals.EraseTransaction.connect(boost::bind(&CWalletInterface::EraseFromWallet, pwalletIn, _1));
@ -872,6 +892,21 @@ int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree,
return nMinFee;
}
// Exponentially limit the rate of nSize flow to nLimit. nLimit unit is thousands-per-minute.
bool RateLimitExceeded(double& dCount, int64_t& nLastTime, int64_t nLimit, unsigned int nSize)
{
static CCriticalSection csLimiter;
int64_t nNow = GetTime();
LOCK(csLimiter);
dCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime));
nLastTime = nNow;
if (dCount >= nLimit*10*1000)
return true;
dCount += nSize;
return false;
}
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
bool* pfMissingInputs, bool fRejectInsaneFee)
@ -906,9 +941,10 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
COutPoint outpoint = tx.vin[i].prevout;
if (pool.mapNextTx.count(outpoint))
// Does tx conflict with a member of the pool, and is it not equivalent to that member?
if (pool.mapNextTx.count(outpoint) && !tx.IsEquivalentTo(*pool.mapNextTx[outpoint].ptx))
{
// Disable replacement feature for now
g_signals.DetectedDoubleSpend(outpoint, tx, false);
return false;
}
}
@ -980,23 +1016,15 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
// be annoying or make others' transactions take longer to confirm.
if (fLimitFree && nFees < CTransaction::minRelayTxFee.GetFee(nSize))
{
static CCriticalSection csFreeLimiter;
static double dFreeCount;
static int64_t nLastTime;
int64_t nNow = GetTime();
static int64_t nLastFreeTime;
static int64_t nFreeLimit = GetArg("-limitfreerelay", 15);
LOCK(csFreeLimiter);
// Use an exponentially decaying ~10-minute window:
dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime));
nLastTime = nNow;
// -limitfreerelay unit is thousand-bytes-per-minute
// At default rate it would take over a month to fill 1GB
if (dFreeCount >= GetArg("-limitfreerelay", 15)*10*1000)
if (RateLimitExceeded(dFreeCount, nLastFreeTime, nFreeLimit, nSize))
return state.DoS(0, error("AcceptToMemoryPool : free transaction rejected by rate limiter"),
REJECT_INSUFFICIENTFEE, "insufficient priority");
LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
dFreeCount += nSize;
}
if (fRejectInsaneFee && nFees > CTransaction::minRelayTxFee.GetFee(nSize) * 10000)
@ -1019,6 +1047,48 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
return true;
}
static void RelayDoubleSpend(const COutPoint& outPoint, const CTransaction& doubleSpend, bool fInBlock, CBloomFilter& filter)
{
// Relaying double-spend attempts to our peers lets them detect when
// somebody might be trying to cheat them. However, blindly relaying
// every double-spend across the entire network gives attackers
// a denial-of-service attack: just generate a stream of double-spends
// re-spending the same (limited) set of outpoints owned by the attacker.
// So, we use a bloom filter and only relay (at most) the first double
// spend for each outpoint. False-positives ("we have already relayed")
// are OK, because if the peer doesn't hear about the double-spend
// from us they are very likely to hear about it from another peer, since
// each peer uses a different, randomized bloom filter.
if (fInBlock || filter.contains(outPoint)) return;
// Apply an independent rate limit to double-spend relays
static double dRespendCount;
static int64_t nLastRespendTime;
static int64_t nRespendLimit = GetArg("-limitrespendrelay", 100);
unsigned int nSize = ::GetSerializeSize(doubleSpend, SER_NETWORK, PROTOCOL_VERSION);
if (RateLimitExceeded(dRespendCount, nLastRespendTime, nRespendLimit, nSize))
{
LogPrint("mempool", "Double-spend relay rejected by rate limiter\n");
return;
}
LogPrint("mempool", "Rate limit dRespendCount: %g => %g\n", dRespendCount, dRespendCount+nSize);
// Clear the filter on average every MAX_DOUBLE_SPEND_BLOOM
// insertions
if (insecure_rand()%MAX_DOUBLESPEND_BLOOM == 0)
filter.clear();
filter.insert(outPoint);
RelayTransaction(doubleSpend);
// Share conflict with wallet
g_signals.SyncTransaction(doubleSpend, NULL);
}
int CMerkleTx::GetDepthInMainChainINTERNAL(CBlockIndex* &pindexRet) const
{

View file

@ -108,6 +108,9 @@ struct CNodeStateStats;
struct CBlockTemplate;
/** Set up internal signal handlers **/
void RegisterInternalSignals();
/** Register a wallet to receive updates from core */
void RegisterWallet(CWalletInterface* pwalletIn);
/** Unregister a wallet from core */

View file

@ -23,6 +23,10 @@ static const int STATUSBAR_ICONSIZE = 16;
#define COLOR_NEGATIVE QColor(255, 0, 0)
/* Transaction list -- bare address (without label) */
#define COLOR_BAREADDRESS QColor(140, 140, 140)
/* Transaction list -- has conflicting transactions */
#define COLOR_HASCONFLICTING Qt::white;
/* Transaction list -- has conflicting transactions - background */
#define COLOR_HASCONFLICTING_BG QColor(192, 0, 0)
/* Tooltips longer than this (in characters) are converted into rich text,
so that they can be word-wrapped.

View file

@ -24,7 +24,7 @@ TransactionFilterProxy::TransactionFilterProxy(QObject *parent) :
typeFilter(ALL_TYPES),
minAmount(0),
limitRows(-1),
showInactive(true)
showInactive(false)
{
}
@ -39,7 +39,7 @@ bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &
qint64 amount = llabs(index.data(TransactionTableModel::AmountRole).toLongLong());
int status = index.data(TransactionTableModel::StatusRole).toInt();
if(!showInactive && status == TransactionStatus::Conflicted)
if(!showInactive && status == TransactionStatus::Conflicted && type == TransactionRecord::Other)
return false;
if(!(TYPE(type) & typeFilter))
return false;

View file

@ -170,6 +170,8 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
status.depth = wtx.GetDepthInMainChain();
status.cur_num_blocks = chainActive.Height();
status.hasConflicting = false;
if (!IsFinalTx(wtx, chainActive.Height() + 1))
{
if (wtx.nLockTime < LOCKTIME_THRESHOLD)
@ -213,6 +215,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
if (status.depth < 0)
{
status.status = TransactionStatus::Conflicted;
status.hasConflicting = !(wtx.GetConflicts(false).empty());
}
else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
{
@ -221,6 +224,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
else if (status.depth == 0)
{
status.status = TransactionStatus::Unconfirmed;
status.hasConflicting = !(wtx.GetConflicts(false).empty());
}
else if (status.depth < RecommendedNumConfirmations)
{
@ -231,13 +235,13 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
status.status = TransactionStatus::Confirmed;
}
}
}
bool TransactionRecord::statusUpdateNeeded()
bool TransactionRecord::statusUpdateNeeded(int64_t nConflictsReceived)
{
AssertLockHeld(cs_main);
return status.cur_num_blocks != chainActive.Height();
return (status.cur_num_blocks != chainActive.Height() ||
status.cur_num_conflicts != nConflictsReceived);
}
QString TransactionRecord::getTxID() const

View file

@ -19,9 +19,17 @@ class TransactionStatus
{
public:
TransactionStatus():
countsForBalance(false), sortKey(""),
matures_in(0), status(Offline), depth(0), open_for(0), cur_num_blocks(-1)
{ }
countsForBalance(false),
sortKey(""),
matures_in(0),
status(Offline),
hasConflicting(false),
depth(0),
open_for(0),
cur_num_blocks(-1),
cur_num_conflicts(-1)
{
}
enum Status {
Confirmed, /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/
@ -51,6 +59,10 @@ public:
/** @name Reported status
@{*/
Status status;
// Has conflicting transactions spending same prevout
bool hasConflicting;
qint64 depth;
qint64 open_for; /**< Timestamp if status==OpenUntilDate, otherwise number
of additional blocks that need to be mined before
@ -59,6 +71,10 @@ public:
/** Current number of blocks (to know whether cached status is still valid) */
int cur_num_blocks;
/** Number of conflicts received into wallet as of last status update */
int64_t cur_num_conflicts;
};
/** UI model for a transaction. A core transaction can be represented by multiple UI transactions if it has
@ -133,7 +149,7 @@ public:
/** Return whether a status update is needed.
*/
bool statusUpdateNeeded();
bool statusUpdateNeeded(int64_t nConflictsReceived);
};
#endif // TRANSACTIONRECORD_H

View file

@ -168,8 +168,7 @@ public:
parent->endRemoveRows();
break;
case CT_UPDATED:
// Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
// visible transactions.
emit parent->dataChanged(parent->index(lowerIndex, parent->Status), parent->index(upperIndex-1, parent->Amount));
break;
}
}
@ -190,20 +189,21 @@ public:
// stuck if the core is holding the locks for a longer time - for
// example, during a wallet rescan.
//
// If a status update is needed (blocks came in since last check),
// update the status of this transaction from the wallet. Otherwise,
// If a status update is needed (blocks or conflicts came in since last check),
// update the status of this transaction from the wallet. Otherwise,
// simply re-use the cached status.
TRY_LOCK(cs_main, lockMain);
if(lockMain)
{
TRY_LOCK(wallet->cs_wallet, lockWallet);
if(lockWallet && rec->statusUpdateNeeded())
if(lockWallet && rec->statusUpdateNeeded(wallet->nConflictsReceived))
{
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
if(mi != wallet->mapWallet.end())
{
rec->updateStatus(mi->second);
rec->status.cur_num_conflicts = wallet->nConflictsReceived;
}
}
}
@ -363,6 +363,8 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
return tr("Payment to yourself");
case TransactionRecord::Generated:
return tr("Mined");
case TransactionRecord::Other:
return tr("Other");
default:
return QString();
}
@ -535,7 +537,13 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
return formatTooltip(rec);
case Qt::TextAlignmentRole:
return column_alignments[index.column()];
case Qt::BackgroundColorRole:
if (rec->status.hasConflicting)
return COLOR_HASCONFLICTING_BG;
break;
case Qt::ForegroundRole:
if (rec->status.hasConflicting)
return COLOR_HASCONFLICTING;
// Non-confirmed (but not immature) as transactions are grey
if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature)
{

View file

@ -138,6 +138,14 @@ void WalletModel::checkBalanceChanged()
void WalletModel::updateTransaction(const QString &hash, int status)
{
if (status == CT_GOT_CONFLICT)
{
emit message(tr("Conflict Received"),
tr("WARNING: Transaction may never be confirmed. Its input was seen being spent by another transaction on the network. Wait for confirmation!"),
CClientUIInterface::MSG_WARNING);
return;
}
if(transactionTableModel)
transactionTableModel->updateTransaction(hash, status);

View file

@ -58,6 +58,10 @@ void WalletTxToJSON(const CWalletTx& wtx, Object& entry)
BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts())
conflicts.push_back(conflict.GetHex());
entry.push_back(Pair("walletconflicts", conflicts));
Array respends;
BOOST_FOREACH(const uint256& respend, wtx.GetConflicts(false))
respends.push_back(respend.GetHex());
entry.push_back(Pair("respendsobserved", respends));
entry.push_back(Pair("time", wtx.GetTxTime()));
entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived));
BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue)
@ -1211,6 +1215,12 @@ Value listtransactions(const Array& params, bool fHelp)
" \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive'\n"
" category of transactions.\n"
" \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n"
" \"walletconflicts\" : [\n"
" \"conflictid\", (string) Ids of transactions, including equivalent clones, that re-spend a txid input.\n"
" ],\n"
" \"respendsobserved\" : [\n"
" \"respendid\", (string) Ids of transactions, NOT equivalent clones, that re-spend a txid input. \"Double-spends.\"\n"
" ],\n"
" \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n"
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT). Available \n"
" for 'send' and 'receive' category of transactions.\n"
@ -1376,6 +1386,12 @@ Value listsinceblock(const Array& params, bool fHelp)
" \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive' category of transactions.\n"
" \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n"
" \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n"
" \"walletconflicts\" : [\n"
" \"conflictid\", (string) Ids of transactions, including equivalent clones, that re-spend a txid input.\n"
" ],\n"
" \"respendsobserved\" : [\n"
" \"respendid\", (string) Ids of transactions, NOT equivalent clones, that re-spend a txid input. \"Double-spends.\"\n"
" ],\n"
" \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n"
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n"
" \"comment\": \"...\", (string) If a comment is associated with the transaction.\n"
@ -1448,6 +1464,12 @@ Value gettransaction(const Array& params, bool fHelp)
" \"blockindex\" : xx, (numeric) The block index\n"
" \"blocktime\" : ttt, (numeric) The time in seconds since epoch (1 Jan 1970 GMT)\n"
" \"txid\" : \"transactionid\", (string) The transaction id.\n"
" \"walletconflicts\" : [\n"
" \"conflictid\", (string) Ids of transactions, including equivalent clones, that re-spend a txid input.\n"
" ],\n"
" \"respendsobserved\" : [\n"
" \"respendid\", (string) Ids of transactions, NOT equivalent clones, that re-spend a txid input. \"Double-spends.\"\n"
" ],\n"
" \"time\" : ttt, (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)\n"
" \"timereceived\" : ttt, (numeric) The time received in seconds since epoch (1 Jan 1970 GMT)\n"
" \"details\" : [\n"

View file

@ -45,6 +45,10 @@ BOOST_AUTO_TEST_CASE(bloom_create_insert_serialize)
expected[i] = (char)vch[i];
BOOST_CHECK_EQUAL_COLLECTIONS(stream.begin(), stream.end(), expected.begin(), expected.end());
BOOST_CHECK_MESSAGE( filter.contains(ParseHex("99108ad8ed9bb6274d3980bab5a85c048f0950c8")), "BloomFilter doesn't contain just-inserted object!");
filter.clear();
BOOST_CHECK_MESSAGE( !filter.contains(ParseHex("99108ad8ed9bb6274d3980bab5a85c048f0950c8")), "BloomFilter should be empty!");
}
BOOST_AUTO_TEST_CASE(bloom_create_insert_serialize_with_tweak)

View file

@ -415,7 +415,6 @@ void CTxMemPool::remove(const CTransaction &tx, std::list<CTransaction>& removed
void CTxMemPool::removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed)
{
// Remove transactions which depend on inputs of tx, recursively
list<CTransaction> result;
LOCK(cs);
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(txin.prevout);

View file

@ -21,7 +21,8 @@ enum ChangeType
{
CT_NEW,
CT_UPDATED,
CT_DELETED
CT_DELETED,
CT_GOT_CONFLICT
};
/** Signals for UI communication. */

View file

@ -256,7 +256,7 @@ bool CWallet::SetMaxVersion(int nVersion)
return true;
}
set<uint256> CWallet::GetConflicts(const uint256& txid) const
set<uint256> CWallet::GetConflicts(const uint256& txid, bool includeEquivalent) const
{
set<uint256> result;
AssertLockHeld(cs_wallet);
@ -274,7 +274,8 @@ set<uint256> CWallet::GetConflicts(const uint256& txid) const
continue; // No conflict if zero or one spends
range = mapTxSpends.equal_range(txin.prevout);
for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
result.insert(it->second);
if (includeEquivalent || !wtx.IsEquivalentTo(mapWallet.at(it->second)))
result.insert(it->second);
}
return result;
}
@ -303,6 +304,7 @@ void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::iterator> range)
const uint256& hash = it->second;
CWalletTx* copyTo = &mapWallet[hash];
if (copyFrom == copyTo) continue;
if (!copyFrom->IsEquivalentTo(*copyTo)) continue;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
// fTimeReceivedIsTxTime not copied on purpose
@ -588,6 +590,28 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet)
// Notify UI of new or updated transaction
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
// Notifications for existing transactions that now have conflicts with this one
if (fInsertedNew)
{
BOOST_FOREACH(const uint256& conflictHash, wtxIn.GetConflicts(false))
{
CWalletTx& txConflict = mapWallet[conflictHash];
NotifyTransactionChanged(this, conflictHash, CT_UPDATED); //Updates UI table
if (IsFromMe(txConflict) || IsMine(txConflict))
{
NotifyTransactionChanged(this, conflictHash, CT_GOT_CONFLICT); //Throws dialog
// external respend notify
std::string strCmd = GetArg("-respendnotify", "");
if (!strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
boost::replace_all(strCmd, "%t", conflictHash.GetHex());
boost::thread t(runCommand, strCmd); // thread runs free
}
}
}
}
// notify an external script when a wallet transaction comes in or is updated
std::string strCmd = GetArg("-walletnotify", "");
@ -610,7 +634,12 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
AssertLockHeld(cs_wallet);
bool fExisted = mapWallet.count(tx.GetHash());
if (fExisted && !fUpdate) return false;
if (fExisted || IsMine(tx) || IsFromMe(tx))
bool fIsConflicting = IsConflicting(tx);
if (fIsConflicting)
nConflictsReceived++;
if (fExisted || IsMine(tx) || IsFromMe(tx) || fIsConflicting)
{
CWalletTx wtx(this,tx);
// Get merkle branch if transaction was found in a block
@ -896,7 +925,7 @@ void CWallet::ReacceptWalletTransactions()
int nDepth = wtx.GetDepthInMainChain();
if (!wtx.IsCoinBase() && nDepth < 0)
if (!wtx.IsCoinBase() && nDepth < 0 && (IsMine(wtx) || IsFromMe(wtx)))
{
// Try to add to memory pool
LOCK(mempool.cs);
@ -916,13 +945,13 @@ void CWalletTx::RelayWalletTransaction()
}
}
set<uint256> CWalletTx::GetConflicts() const
set<uint256> CWalletTx::GetConflicts(bool includeEquivalent) const
{
set<uint256> result;
if (pwallet != NULL)
{
uint256 myHash = GetHash();
result = pwallet->GetConflicts(myHash);
result = pwallet->GetConflicts(myHash, includeEquivalent);
result.erase(myHash);
}
return result;

View file

@ -141,6 +141,9 @@ public:
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID;
// Increment to cause UI refresh, similar to new block
int64_t nConflictsReceived;
CWallet()
{
SetNull();
@ -163,6 +166,7 @@ public:
nNextResend = 0;
nLastResend = 0;
nTimeFirstKey = 0;
nConflictsReceived = 0;
}
std::map<uint256, CWalletTx> mapWallet;
@ -305,6 +309,13 @@ public:
{
return (GetDebit(tx) > 0);
}
bool IsConflicting(const CTransaction& tx) const
{
BOOST_FOREACH(const CTxIn& txin, tx.vin)
if (mapTxSpends.count(txin.prevout))
return true;
return false;
}
int64_t GetDebit(const CTransaction& tx) const
{
int64_t nDebit = 0;
@ -377,7 +388,7 @@ public:
int GetVersion() { LOCK(cs_wallet); return nWalletVersion; }
// Get wallet transactions that conflict with given transaction (spend same outputs)
std::set<uint256> GetConflicts(const uint256& txid) const;
std::set<uint256> GetConflicts(const uint256& txid, bool includeEquivalent) const;
/** Address book entry changed.
* @note called with lock cs_wallet held.
@ -699,7 +710,7 @@ public:
void RelayWalletTransaction();
std::set<uint256> GetConflicts() const;
std::set<uint256> GetConflicts(bool includeEquivalent=true) const;
};