Skip to content

Commit

Permalink
Implement fLimitFree to CTxMemPool::accept()
Browse files Browse the repository at this point in the history
Works around a potential DoS vulnerability (see Bitcoin commit
ce99358f4aa4182d6983fde3e33a8fdbe1dfe4c3).
  • Loading branch information
ghostlander committed Jul 19, 2017
1 parent 1d42157 commit 987dd68
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 45 deletions.
69 changes: 32 additions & 37 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,9 @@ bool CTransaction::CheckTransaction() const
}


bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs,
bool* pfMissingInputs)
{
bool CTxMemPool::accept(CTxDB &txdb, CTransaction &tx, bool fCheckInputs,
bool fLimitFree, bool *pfMissingInputs) {

if (pfMissingInputs)
*pfMissingInputs = false;

Expand Down Expand Up @@ -566,32 +566,29 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs,
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);

// Don't accept it if it can't get into a block
if(nFees < tx.GetMinFee(nTxSize, true, GMF_RELAY))
return error("CTxMemPool::accept() : not enough fees");
if(fLimitFree && (nFees < tx.GetMinFee(nTxSize, true, GMF_RELAY)))
return(error("CTxMemPool::accept() : not enough fees"));

// Continuously rate-limit free transactions
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to
// be annoying or make other's transactions take longer to confirm.
if (nFees < MIN_RELAY_TX_FEE)
{
static CCriticalSection cs;
if(fLimitFree && (nFees < MIN_RELAY_TX_FEE)) {
static double dFreeCount;
static int64 nLastTime;
int64 nNow = GetTime();

{
LOCK(cs);
// 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 && !IsFromMe(tx))
return error("CTxMemPool::accept() : free transaction rejected by rate limiter");
if (fDebug)
printf("Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nTxSize);
dFreeCount += nTxSize;
}
LOCK(cs);

// 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)
return(error("CTxMemPool::accept() : free transaction rejected by rate limiter"));
if(fDebug)
printf("Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount + nTxSize);
dFreeCount += nTxSize;
}

// Check against previous transactions
Expand Down Expand Up @@ -624,9 +621,10 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs,
return true;
}

bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMissingInputs)
{
return mempool.accept(txdb, *this, fCheckInputs, pfMissingInputs);
bool CTransaction::AcceptToMemoryPool(CTxDB &txdb, bool fCheckInputs, bool fLimitFree,
bool *pfMissingInputs) {

return mempool.accept(txdb, *this, fCheckInputs, fLimitFree, pfMissingInputs);
}

bool CTxMemPool::addUnchecked(const uint256& hash, CTransaction &tx)
Expand Down Expand Up @@ -730,9 +728,9 @@ int CMerkleTx::GetBlocksToMaturity() const
}


bool CMerkleTx::AcceptToMemoryPool(CTxDB &txdb, bool fCheckInputs) {
bool CMerkleTx::AcceptToMemoryPool(CTxDB &txdb, bool fCheckInputs, bool fLimitFree) {

return(CTransaction::AcceptToMemoryPool(txdb, fCheckInputs));
return(CTransaction::AcceptToMemoryPool(txdb, fCheckInputs, fLimitFree));
}

bool CMerkleTx::AcceptToMemoryPool()
Expand All @@ -755,10 +753,10 @@ bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs)
{
uint256 hash = tx.GetHash();
if (!mempool.exists(hash) && !txdb.ContainsTx(hash))
tx.AcceptToMemoryPool(txdb, fCheckInputs);
tx.AcceptToMemoryPool(txdb, fCheckInputs, false);
}
}
return AcceptToMemoryPool(txdb, fCheckInputs);
return(AcceptToMemoryPool(txdb, fCheckInputs, false));
}
return false;
}
Expand Down Expand Up @@ -1581,7 +1579,7 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)

// Resurrect memory transactions that were in the disconnected branch
BOOST_FOREACH(CTransaction& tx, vResurrect)
tx.AcceptToMemoryPool(txdb, false);
tx.AcceptToMemoryPool(txdb, true, false);

// Delete redundant memory transactions that are in the connected branch
BOOST_FOREACH(CTransaction &tx, vDelete) {
Expand Down Expand Up @@ -3022,8 +3020,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
}

bool fMissingInputs = false;
if (tx.AcceptToMemoryPool(txdb, true, &fMissingInputs))
{
if(tx.AcceptToMemoryPool(txdb, true, true, &fMissingInputs)) {
SyncWithWallets(tx, NULL, true);
RelayMessage(inv, vMsg);
mapAlreadyAskedFor.erase(inv);
Expand All @@ -3044,20 +3041,18 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
CInv inv(MSG_TX, tx.GetHash());
bool fMissingInputs2 = false;

if (tx.AcceptToMemoryPool(txdb, true, &fMissingInputs2))
{
if(tx.AcceptToMemoryPool(txdb, true, true, &fMissingInputs2)) {
printf(" accepted orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
SyncWithWallets(tx, NULL, true);
RelayMessage(inv, vMsg);
mapAlreadyAskedFor.erase(inv);
vWorkQueue.push_back(inv.hash);
vEraseQueue.push_back(inv.hash);
}
else if (!fMissingInputs2)
{
// invalid orphan
else if(!fMissingInputs2) {
/* Invalid or insufficient fee orphan */
vEraseQueue.push_back(inv.hash);
printf(" removed invalid orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
printf(" removed orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
}
}
}
Expand Down
9 changes: 5 additions & 4 deletions src/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,8 @@ class CTransaction
const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash=true);
bool ClientConnectInputs();
bool CheckTransaction() const;
bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL);
bool AcceptToMemoryPool(CTxDB &txdb, bool fCheckInputs=true, bool fLimitFree=true,
bool *pfMissingInputs=NULL);

protected:
const CTxOut& GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const;
Expand Down Expand Up @@ -771,7 +772,7 @@ class CMerkleTx : public CTransaction
int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
bool IsInMainChain() const { return GetDepthInMainChain() > 0; }
int GetBlocksToMaturity() const;
bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true);
bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool fLimitFree=true);
bool AcceptToMemoryPool();
};

Expand Down Expand Up @@ -1721,8 +1722,8 @@ class CTxMemPool
std::map<uint256, CTransaction> mapTx;
std::map<COutPoint, CInPoint> mapNextTx;

bool accept(CTxDB& txdb, CTransaction &tx,
bool fCheckInputs, bool* pfMissingInputs);
bool accept(CTxDB &txdb, CTransaction &tx, bool fCheckInputs, bool fLimitFree,
bool *pfMissingInputs);
bool addUnchecked(const uint256& hash, CTransaction &tx);
bool remove(const CTransaction &tx, bool fRecursive = false);
bool removeConflicts(const CTransaction &tx);
Expand Down
4 changes: 2 additions & 2 deletions src/rpcrawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,8 +494,8 @@ Value sendrawtransaction(const Array& params, bool fHelp)
{
// push to local node
CTxDB txdb("r");
if (!tx.AcceptToMemoryPool(txdb))
throw JSONRPCError(-22, "TX rejected");
if(!tx.AcceptToMemoryPool(txdb, true, false))
throw(JSONRPCError(-22, "TX rejected"));

SyncWithWallets(tx, NULL, true);
}
Expand Down
4 changes: 2 additions & 2 deletions src/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1251,8 +1251,8 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
mapRequestCount[wtxNew.GetHash()] = 0;

// Broadcast
if (!wtxNew.AcceptToMemoryPool())
{
CTxDB txdb("r");
if(!wtxNew.AcceptToMemoryPool(txdb, true, false)) {
// This must not fail. The transaction has already been signed and recorded.
printf("CommitTransaction() : Error: Transaction not valid");
return false;
Expand Down

0 comments on commit 987dd68

Please sign in to comment.