Relay double-spends, subject to anti-DOS
cef3711500 (diff-7ec3c68a81efff79b6ca22ac1f1eabbaL929)
This commit is contained in:
98
src/main.cpp
98
src/main.cpp
@@ -561,6 +561,14 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<CBl
|
||||
|
||||
} // anon namespace
|
||||
|
||||
// Bloom filter to limit respend relays to one
|
||||
static const unsigned int MAX_DOUBLESPEND_BLOOM = 100000;
|
||||
static CBloomFilter doubleSpendFilter;
|
||||
void InitRespendFilter() {
|
||||
seed_insecure_rand();
|
||||
doubleSpendFilter = CBloomFilter(MAX_DOUBLESPEND_BLOOM, 0.01, insecure_rand(), BLOOM_UPDATE_NONE);
|
||||
}
|
||||
|
||||
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) {
|
||||
LOCK(cs_main);
|
||||
CNodeState *state = State(nodeid);
|
||||
@@ -1017,6 +1025,41 @@ std::string FormatStateMessage(const CValidationState &state)
|
||||
state.GetRejectCode());
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
static bool RespendRelayExceeded(const CTransaction& doubleSpend)
|
||||
{
|
||||
// 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 true;
|
||||
}
|
||||
|
||||
LogPrint("mempool", "Rate limit dRespendCount: %g => %g\n", dRespendCount, dRespendCount+nSize);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
|
||||
bool* pfMissingInputs, bool fOverrideMempoolLimit, bool fRejectAbsurdFee,
|
||||
std::vector<uint256>& vHashTxnToUncache)
|
||||
@@ -1066,13 +1109,26 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
|
||||
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-in-mempool");
|
||||
|
||||
// Check for conflicts with in-memory transactions
|
||||
bool fRespend = false;
|
||||
COutPoint relayForOutpoint;
|
||||
|
||||
set<uint256> setConflicts;
|
||||
{
|
||||
LOCK(pool.cs); // protect pool.mapNextTx
|
||||
BOOST_FOREACH(const CTxIn &txin, tx.vin)
|
||||
{
|
||||
COutPoint outpoint = txin.prevout;
|
||||
// A respend is a tx that conflicts with a member of the pool
|
||||
if (pool.mapNextTx.count(txin.prevout))
|
||||
{
|
||||
fRespend = true;
|
||||
// Relay only one tx per respent outpoint, but not if tx is equivalent to pool member
|
||||
if (!doubleSpendFilter.contains(outpoint) && !tx.IsEquivalentTo(*pool.mapNextTx[outpoint].ptx))
|
||||
{
|
||||
relayForOutpoint = outpoint;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
const CTransaction *ptxConflicting = pool.mapNextTx[txin.prevout].ptx;
|
||||
if (!setConflicts.count(ptxConflicting->GetHash()))
|
||||
{
|
||||
@@ -1105,8 +1161,12 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
|
||||
|
||||
setConflicts.insert(ptxConflicting->GetHash());
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
if (fRespend && (relayForOutpoint.IsNull() || RespendRelayExceeded(tx)))
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1215,22 +1275,15 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
|
||||
// be annoying or make others' transactions take longer to confirm.
|
||||
if (fLimitFree && nModifiedFees < ::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", DEFAULT_LIMITFREERELAY);
|
||||
|
||||
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", DEFAULT_LIMITFREERELAY) * 10 * 1000)
|
||||
if (RateLimitExceeded(dFreeCount, nLastFreeTime, nFreeLimit, nSize))
|
||||
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "rate limited free transaction");
|
||||
LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
|
||||
dFreeCount += nSize;
|
||||
}
|
||||
|
||||
if (fRejectAbsurdFee && nFees > ::minRelayTxFee.GetFee(nSize) * 10000)
|
||||
@@ -1434,8 +1487,19 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
|
||||
}
|
||||
pool.RemoveStaged(allConflicting);
|
||||
|
||||
// Store transaction in memory
|
||||
pool.addUnchecked(hash, entry, setAncestors, !IsInitialBlockDownload());
|
||||
if (fRespend)
|
||||
{
|
||||
// Clear the filter on average every MAX_DOUBLE_SPEND_BLOOM insertions
|
||||
if (insecure_rand()%MAX_DOUBLESPEND_BLOOM == 0)
|
||||
doubleSpendFilter.clear();
|
||||
doubleSpendFilter.insert(relayForOutpoint);
|
||||
RelayTransaction(tx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Store transaction in memory
|
||||
pool.addUnchecked(hash, entry, !IsInitialBlockDownload());
|
||||
}
|
||||
|
||||
// Add memory address index
|
||||
if (fAddressIndex) {
|
||||
@@ -1455,9 +1519,9 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
|
||||
}
|
||||
}
|
||||
|
||||
SyncWithWallets(tx, NULL);
|
||||
SyncWithWallets(tx, NULL, fRespend);
|
||||
|
||||
return true;
|
||||
return !fRespend;
|
||||
}
|
||||
|
||||
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
|
||||
@@ -3149,7 +3213,7 @@ bool static DisconnectTip(CValidationState& state, const Consensus::Params& cons
|
||||
// Let wallets know transactions went from 1-confirmed to
|
||||
// 0-confirmed or conflicted:
|
||||
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
|
||||
SyncWithWallets(tx, NULL);
|
||||
SyncWithWallets(tx, NULL, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -3208,11 +3272,11 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams,
|
||||
// Tell wallet about transactions that went from mempool
|
||||
// to conflicted:
|
||||
BOOST_FOREACH(const CTransaction &tx, txConflicted) {
|
||||
SyncWithWallets(tx, NULL);
|
||||
SyncWithWallets(tx, NULL, false);
|
||||
}
|
||||
// ... and about transactions that got confirmed:
|
||||
BOOST_FOREACH(const CTransaction &tx, pblock->vtx) {
|
||||
SyncWithWallets(tx, pblock);
|
||||
SyncWithWallets(tx, pblock, false);
|
||||
}
|
||||
|
||||
int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1;
|
||||
|
||||
Reference in New Issue
Block a user