DoS protection: Weighted random drop of txs if mempool full

This commit is contained in:
Eirik Ogilvie-Wigley 2019-09-26 09:22:25 -06:00
parent fafc479fcf
commit ae9ecf09e8
11 changed files with 503 additions and 10 deletions

View File

@ -41,6 +41,7 @@ testScripts=(
'rawtransactions.py'
'getrawtransaction_insight.py'
'rest.py'
'mempool_limit.py'
'mempool_spendcoinbase.py'
'mempool_reorg.py'
'mempool_nu_activation.py'

90
qa/rpc-tests/mempool_limit.py Executable file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env python
# Copyright (c) 2019 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
import sys; assert sys.version_info < (3,), ur"This script does not run under Python 3. Please use Python 2.7.x."
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
initialize_chain_clean,
start_nodes,
)
from decimal import Decimal
from time import sleep
# Test wallet behaviour with Sapling addresses
class MempoolLimit(BitcoinTestFramework):
def setup_chain(self):
print("Initializing test directory " + self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)
def setup_nodes(self):
args = [
'-nuparams=5ba81b19:1', # Overwinter
'-nuparams=76b809bb:1', # Sapling
"-debug=mempool",
]
extra_args = [
args + ['-mempooltotalcostlimit=8000'], # 2 transactions at min cost
args + ['-mempooltotalcostlimit=8000'], # 2 transactions at min cost
args + ['-mempooltotalcostlimit=8000'], # 2 transactions at min cost
# Let node 3 hold one more transaction
args + ['-mempooltotalcostlimit=12000'], # 3 transactions at min cost
]
return start_nodes(4, self.options.tmpdir, extra_args)
def run_test(self):
print("Mining blocks...")
self.sync_all()
self.nodes[1].generate(1)
self.sync_all()
self.nodes[2].generate(1)
self.sync_all()
self.nodes[3].generate(1)
self.sync_all()
self.nodes[0].generate(100)
self.sync_all()
assert_equal(Decimal("10.00"), Decimal(self.nodes[1].z_gettotalbalance()['transparent']))
assert_equal(Decimal("10.00"), Decimal(self.nodes[2].z_gettotalbalance()['transparent']))
assert_equal(Decimal("10.00"), Decimal(self.nodes[3].z_gettotalbalance()['transparent']))
taddr1 = self.nodes[0].getnewaddress()
taddr2 = self.nodes[0].getnewaddress()
taddr3 = self.nodes[0].getnewaddress()
print("Filling mempool...")
self.nodes[1].sendtoaddress(taddr1, 9.999)
self.nodes[2].sendtoaddress(taddr2, 9.999)
self.sync_all()
for i in range(0, 4):
mempool = self.nodes[i].getrawmempool()
print("Mempool for node {}: {}".format(i, mempool))
assert_equal(2, len(mempool), "node {}".format(i))
print("Adding one more transaction...")
self.nodes[3].sendtoaddress(taddr3, 9.999)
# The mempools are no longer guarenteed to be in a consistent state, so we cannot sync
sleep(5)
mempool_node3 = self.nodes[i].getrawmempool()
print("Mempool for node 3: {}".format(mempool_node3))
assert_equal(3, len(mempool_node3), "node {}".format(i))
print("Checking mempool size...")
# Due to the size limit, there should only be 2 transactions in the mempool
for i in range(0, 3):
mempool = self.nodes[i].getrawmempool()
print("Mempool for node {}: {}".format(i, mempool))
assert_equal(2, len(mempool), "node {}".format(i))
# self.nodes[0].generate(1)
# self.sync_all()
print("Success")
if __name__ == '__main__':
MempoolLimit().main()

View File

@ -192,6 +192,7 @@ BITCOIN_CORE_H = \
torcontrol.h \
transaction_builder.h \
txdb.h \
mempoollimit.h \
txmempool.h \
ui_interface.h \
uint256.h \
@ -266,6 +267,7 @@ libbitcoin_server_a_SOURCES = \
timedata.cpp \
torcontrol.cpp \
txdb.cpp \
mempoollimit.cpp \
txmempool.cpp \
validationinterface.cpp \
$(BITCOIN_CORE_H) \

View File

@ -27,6 +27,7 @@ zcash_gtest_SOURCES += \
gtest/test_keystore.cpp \
gtest/test_noteencryption.cpp \
gtest/test_mempool.cpp \
gtest/test_mempoollimit.cpp \
gtest/test_merkletree.cpp \
gtest/test_metrics.cpp \
gtest/test_miner.cpp \

View File

@ -0,0 +1,140 @@
// Copyright (c) 2019 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php
#include <gtest/gtest.h>
#include <iostream>
#include "arith_uint256.h"
#include "mempoollimit.h"
#include "utiltime.h"
#include "utiltest.h"
#include "transaction_builder.h"
const uint256 TX_ID1 = ArithToUint256(1);
const uint256 TX_ID2 = ArithToUint256(2);
const uint256 TX_ID3 = ArithToUint256(3);
TEST(MempoolLimitTests, RecentlyEvictedList_AddWrapsAfterMaxSize)
{
RecentlyEvictedList recentlyEvicted(2, 100);
SetMockTime(1);
recentlyEvicted.add(TX_ID1);
recentlyEvicted.add(TX_ID2);
recentlyEvicted.add(TX_ID3);
// tx 1 should be overwritten by tx 3 due maxSize 2
EXPECT_FALSE(recentlyEvicted.contains(TX_ID1));
EXPECT_TRUE(recentlyEvicted.contains(TX_ID2));
EXPECT_TRUE(recentlyEvicted.contains(TX_ID3));
}
TEST(MempoolLimitTests, RecentlyEvictedList_DoesNotContainAfterExpiry)
{
SetMockTime(1);
// maxSize=3, timeToKeep=1
RecentlyEvictedList recentlyEvicted(3, 1);
recentlyEvicted.add(TX_ID1);
SetMockTime(2);
recentlyEvicted.add(TX_ID2);
recentlyEvicted.add(TX_ID3);
// After 1 second the txId will still be there
EXPECT_TRUE(recentlyEvicted.contains(TX_ID1));
EXPECT_TRUE(recentlyEvicted.contains(TX_ID2));
EXPECT_TRUE(recentlyEvicted.contains(TX_ID3));
SetMockTime(3);
// After 2 second it is gone
EXPECT_FALSE(recentlyEvicted.contains(TX_ID1));
EXPECT_TRUE(recentlyEvicted.contains(TX_ID2));
EXPECT_TRUE(recentlyEvicted.contains(TX_ID3));
SetMockTime(4);
EXPECT_FALSE(recentlyEvicted.contains(TX_ID2));
EXPECT_FALSE(recentlyEvicted.contains(TX_ID3));
}
TEST(MempoolLimitTests, WeightedTransactionList_CheckSizeAfterDropping)
{
std::set<uint256> testedDropping;
// Run the test until we have tested dropping each of the elements
int trialNum = 0;
while (testedDropping.size() < 3) {
WeightedTransactionList list(MIN_TX_COST * 2);
EXPECT_EQ(0, list.getTotalCost());
EXPECT_EQ(0, list.getTotalLowFeePenaltyCost());
list.add(WeightedTxInfo(TX_ID1, MIN_TX_COST, MIN_TX_COST));
EXPECT_EQ(4000, list.getTotalCost());
EXPECT_EQ(4000, list.getTotalLowFeePenaltyCost());
list.add(WeightedTxInfo(TX_ID2, MIN_TX_COST, MIN_TX_COST));
EXPECT_EQ(8000, list.getTotalCost());
EXPECT_EQ(8000, list.getTotalLowFeePenaltyCost());
EXPECT_FALSE(list.maybeDropRandom().is_initialized());
list.add(WeightedTxInfo(TX_ID3, MIN_TX_COST, MIN_TX_COST + LOW_FEE_PENALTY));
EXPECT_EQ(12000, list.getTotalCost());
EXPECT_EQ(12000 + LOW_FEE_PENALTY, list.getTotalLowFeePenaltyCost());
boost::optional<WeightedTxInfo> drop = list.maybeDropRandom();
ASSERT_TRUE(drop.is_initialized());
uint256 txid = drop.get().txId;
std::cerr << "Trial " << trialNum++ << ": dropped " << txid.ToString() << std::endl;
testedDropping.insert(txid);
// Do not continue to test if a particular trial fails
ASSERT_EQ(8000, list.getTotalCost());
ASSERT_EQ(txid == TX_ID3 ? 8000 : 8000 + LOW_FEE_PENALTY, list.getTotalLowFeePenaltyCost());
}
std::cerr << "All 3 scenarios tested in " << trialNum << " trials" << std::endl;
}
TEST(MempoolLimitTests, WeightedTXInfo_FromTx)
{
// The transaction creation is based on the test:
// test_transaction_builder.cpp/TEST(TransactionBuilder, SetFee)
auto consensusParams = RegtestActivateSapling();
auto sk = libzcash::SaplingSpendingKey::random();
auto expsk = sk.expanded_spending_key();
auto fvk = sk.full_viewing_key();
auto pa = sk.default_address();
auto testNote = GetTestSaplingNote(pa, 50000);
// Default fee
{
auto builder = TransactionBuilder(consensusParams, 1);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
WeightedTxInfo info = WeightedTxInfo::from(builder.Build().GetTxOrThrow());
EXPECT_EQ(MIN_TX_COST, info.cost);
EXPECT_EQ(MIN_TX_COST, info.lowFeePenaltyCost);
}
// Lower than standard fee
{
auto builder = TransactionBuilder(consensusParams, 1);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
builder.SetFee(1);
WeightedTxInfo info = WeightedTxInfo::from(builder.Build().GetTxOrThrow());
EXPECT_EQ(MIN_TX_COST, info.cost);
EXPECT_EQ(MIN_TX_COST + LOW_FEE_PENALTY, info.lowFeePenaltyCost);
}
// Larger Tx
{
auto testNote2 = GetTestSaplingNote(pa, 50000);
auto testNote3 = GetTestSaplingNote(pa, 50000);
auto testNote4 = GetTestSaplingNote(pa, 50000);
auto builder = TransactionBuilder(consensusParams, 1);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingSpend(expsk, testNote2.note, testNote2.tree.root(), testNote2.tree.witness());
builder.AddSaplingSpend(expsk, testNote3.note, testNote3.tree.root(), testNote3.tree.witness());
builder.AddSaplingSpend(expsk, testNote4.note, testNote4.tree.root(), testNote4.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
WeightedTxInfo info = WeightedTxInfo::from(builder.Build().GetTxOrThrow());
EXPECT_EQ(MIN_TX_COST, info.cost);
EXPECT_EQ(MIN_TX_COST, info.lowFeePenaltyCost);
}
RegtestDeactivateSapling();
}

View File

@ -399,6 +399,8 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-whitebind=<addr>", _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6"));
strUsage += HelpMessageOpt("-whitelist=<netmask>", _("Whitelist peers connecting from the given netmask or IP address. Can be specified multiple times.") +
" " + _("Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway"));
strUsage += HelpMessageOpt("-mempooltotalcostlimit=<n>", _("An upper bound on the maximum size in bytes of all txs in the mempool. (default: 80000000)"));
strUsage += HelpMessageOpt("-mempoolevictionmemoryminutes=<n>", _("The number of minutes before reallowing rejected transactions from reentering the mempool. (default: 60)"));
#ifdef ENABLE_WALLET
strUsage += HelpMessageGroup(_("Wallet options:"));
@ -974,6 +976,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (ratio != 0) {
mempool.setSanityCheck(1.0 / ratio);
}
int64_t mempoolTotalCostLimit = GetArg("-mempooltotalcostlimit", 80000000);
int64_t mempoolEvictionMemorySeconds = GetArg("-mempoolevictionmemoryminutes", 60) * 1000;
mempool.setMempoolCostLimit(mempoolTotalCostLimit, mempoolEvictionMemorySeconds);
fCheckBlockIndex = GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks());
fCheckpointsEnabled = GetBoolArg("-checkpoints", true);
@ -1595,7 +1602,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
mempool.ReadFeeEstimates(est_filein);
fFeeEstimatesInitialized = true;
// ********************************************************* Step 8: load wallet
#ifdef ENABLE_WALLET
if (fDisableWallet) {

View File

@ -1395,6 +1395,10 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
}
}
if (pool.isRecentlyEvicted(tx.GetHash())) {
return error("AcceptToMemoryPool: Transaction recently evicted");
}
auto verifier = libzcash::ProofVerifier::Strict();
if (!CheckTransaction(tx, state, verifier))
return error("AcceptToMemoryPool: CheckTransaction failed");
@ -1616,17 +1620,32 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
return error("AcceptToMemoryPool: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString());
}
// Store transaction in memory
pool.addUnchecked(hash, entry, !IsInitialBlockDownload(Params()));
{
// We lock to prevent other threads from accessing the mempool between adding and evicting
LOCK(pool.cs);
// Store transaction in memory
pool.addUnchecked(hash, entry, !IsInitialBlockDownload(Params()));
// Add memory address index
if (fAddressIndex) {
pool.addAddressIndex(entry, view);
}
// Add memory address index
if (fAddressIndex) {
pool.addAddressIndex(entry, view);
}
// insightexplorer: Add memory spent index
if (fSpentIndex) {
pool.addSpentIndex(entry, view);
// insightexplorer: Add memory spent index
if (fSpentIndex) {
pool.addSpentIndex(entry, view);
}
// In normal circumstances the following should be empty
list<CTransaction> removed;
std::vector<uint256> evictedTxIds = pool.ensureSizeLimit();
for (const uint256& txId : evictedTxIds) {
CTransaction toRemove = pool.mapTx.find(txId)->GetTx();
pool.remove(toRemove, removed, true);
}
// TODO rebuild list here and when a block is made
}
}

116
src/mempoollimit.cpp Normal file
View File

@ -0,0 +1,116 @@
// Copyright (c) 2019 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php
#include "core_memusage.h"
#include "mempoollimit.h"
#include "random.h"
#include "timedata.h"
const int64_t DEFAULT_FEE = 10000;
void RecentlyEvictedList::pruneList()
{
if (txIdSet.empty()) {
return;
}
int64_t now = GetAdjustedTime();
size_t startIndex = (txIdsAndTimesIndex + maxSize - txIdSet.size()) % maxSize;
boost::optional<std::pair<uint256, int64_t>> txIdAndTime;
while ((txIdAndTime = txIdsAndTimes[startIndex]).is_initialized() && (now - txIdAndTime.get().second) > timeToKeep) {
txIdsAndTimes[startIndex] = boost::none;
txIdSet.erase(txIdAndTime.get().first);
startIndex = (startIndex + 1) % maxSize;
}
}
void RecentlyEvictedList::add(uint256 txId)
{
pruneList();
if (txIdsAndTimes[txIdsAndTimesIndex].is_initialized()) {
auto txIdAndTime = txIdsAndTimes[txIdsAndTimesIndex];
txIdSet.erase(txIdAndTime.get().first);
}
txIdsAndTimes[txIdsAndTimesIndex] = std::make_pair(txId, GetAdjustedTime());
txIdSet.insert(txId);
txIdsAndTimesIndex = (txIdsAndTimesIndex + 1) % maxSize;
}
bool RecentlyEvictedList::contains(const uint256& txId)
{
pruneList();
return txIdSet.count(txId) > 0;
}
int64_t WeightedTransactionList::getTotalCost()
{
return weightedTxInfos.empty() ? 0 : weightedTxInfos.back().cost;
}
int64_t WeightedTransactionList::getTotalLowFeePenaltyCost()
{
return weightedTxInfos.empty() ? 0 : weightedTxInfos.back().lowFeePenaltyCost;
}
void WeightedTransactionList::add(WeightedTxInfo weightedTxInfo)
{
if (weightedTxInfos.empty()) {
weightedTxInfos.push_back(weightedTxInfo);
return;
}
weightedTxInfo.plusEquals(weightedTxInfos.back());
weightedTxInfos.push_back(weightedTxInfo);
for (int i =0; i < weightedTxInfos.size(); ++i) {
WeightedTxInfo info = weightedTxInfos[i];
}
}
boost::optional<WeightedTxInfo> WeightedTransactionList::maybeDropRandom()
{
int64_t totalCost = getTotalCost();
if (totalCost <= maxTotalCost) {
return boost::none;
}
LogPrint("mempool", "Mempool cost limit exceeded (cost=%d, limit=%d)\n", totalCost, maxTotalCost);
int randomWeight = GetRand(getTotalLowFeePenaltyCost());
int i = 0;
while (randomWeight > weightedTxInfos[i].lowFeePenaltyCost) {
++i;
}
WeightedTxInfo drop = weightedTxInfos[i];
if (i > 0) {
drop.minusEquals(weightedTxInfos[i - 1]);
}
while (++i < weightedTxInfos.size()) {
WeightedTxInfo nextTx = weightedTxInfos[i];
nextTx.minusEquals(drop);
weightedTxInfos[i - 1] = nextTx;
}
weightedTxInfos.pop_back();
LogPrint("mempool", "Evicting transaction (txid=%s, cost=%d, penaltyCost=%d)\n", drop.txId.ToString(), drop.cost, drop.lowFeePenaltyCost);
return drop;
}
WeightedTxInfo WeightedTxInfo::from(const CTransaction& tx)
{
size_t memUsage = RecursiveDynamicUsage(tx);
int64_t cost = std::max(memUsage, MIN_TX_COST);
int64_t lowFeePenaltyCost = cost;
int64_t fee = DEFAULT_FEE;
if (fee < DEFAULT_FEE) {
lowFeePenaltyCost += LOW_FEE_PENALTY;
}
return WeightedTxInfo(tx.GetHash(), cost, lowFeePenaltyCost);
}
void WeightedTxInfo::plusEquals(const WeightedTxInfo& other)
{
cost += other.cost;
lowFeePenaltyCost += other.lowFeePenaltyCost;
}
void WeightedTxInfo::minusEquals(const WeightedTxInfo& other)
{
cost -= other.cost;
lowFeePenaltyCost -= other.lowFeePenaltyCost;
}

76
src/mempoollimit.h Normal file
View File

@ -0,0 +1,76 @@
// Copyright (c) 2019 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#ifndef MEMPOOLLIMIT_H
#define MEMPOOLLIMIT_H
#include <vector>
#include <set>
#include "uint256.h"
#include "primitives/transaction.h"
#include "boost/optional.hpp"
const size_t RECENTLY_EVICTED_SIZE = 10000;
const uint64_t MIN_TX_COST = 4000;
const uint64_t LOW_FEE_PENALTY = 16000;
struct WeightedTxInfo;
class RecentlyEvictedList
{
const size_t maxSize;
const int64_t timeToKeep;
// Pairs of txid and time (seconds since epoch)
boost::optional<std::pair<uint256, int64_t>> txIdsAndTimes[RECENTLY_EVICTED_SIZE];
size_t txIdsAndTimesIndex;
std::set<uint256> txIdSet;
void pruneList();
public:
RecentlyEvictedList(size_t maxSize_, int64_t timeToKeep_) :
maxSize(maxSize_),
timeToKeep(timeToKeep_),
txIdsAndTimesIndex(0)
{
assert(maxSize <= RECENTLY_EVICTED_SIZE);
std::fill_n(txIdsAndTimes, maxSize, boost::none);
}
RecentlyEvictedList(int64_t timeToKeep_) : RecentlyEvictedList(RECENTLY_EVICTED_SIZE, timeToKeep_) {}
void add(uint256 txId);
bool contains(const uint256& txId);
};
class WeightedTransactionList
{
const uint64_t maxTotalCost;
std::vector<WeightedTxInfo> weightedTxInfos;
public:
WeightedTransactionList(int64_t maxTotalCost_) : maxTotalCost(maxTotalCost_) {}
int64_t getTotalCost();
int64_t getTotalLowFeePenaltyCost();
void add(WeightedTxInfo weightedTxInfo);
boost::optional<WeightedTxInfo> maybeDropRandom();
};
struct WeightedTxInfo {
uint256 txId;
uint64_t cost;
uint64_t lowFeePenaltyCost;
WeightedTxInfo(uint256 txId_, uint64_t cost_, uint64_t lowFeePenaltyCost_)
: txId(txId_), cost(cost_), lowFeePenaltyCost(lowFeePenaltyCost_) {}
static WeightedTxInfo from(const CTransaction& tx);
void plusEquals(const WeightedTxInfo& other);
void minusEquals(const WeightedTxInfo& other);
};
#endif // MEMPOOLLIMIT_H

View File

@ -102,6 +102,9 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
// Used by main.cpp AcceptToMemoryPool(), which DOES do
// all the appropriate checks.
LOCK(cs);
if (weightedTxList) {
weightedTxList->add(WeightedTxInfo::from(entry.GetTx()));
}
mapTx.insert(entry);
const CTransaction& tx = mapTx.find(hash)->GetTx();
mapRecentlyAddedTx[tx.GetHash()] = &tx;
@ -799,3 +802,33 @@ size_t CTxMemPool::DynamicMemoryUsage() const {
// Estimate the overhead of mapTx to be 6 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 6 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + cachedInnerUsage;
}
void CTxMemPool::setMempoolCostLimit(int64_t totalCostLimit, int64_t evictionMemorySeconds) {
LogPrint("mempool", "Setting mempool cost limit: (limit=%d, time=%d)\n", totalCostLimit, evictionMemorySeconds);
// This method should not be called more than once
assert(!recentlyEvicted);
assert(!weightedTxList);
recentlyEvicted = new RecentlyEvictedList(evictionMemorySeconds);
weightedTxList = new WeightedTransactionList(totalCostLimit);
}
bool CTxMemPool::isRecentlyEvicted(const uint256& txId) {
if (!recentlyEvicted) {
return false;
}
return recentlyEvicted->contains(txId);
}
std::vector<uint256> CTxMemPool::ensureSizeLimit() {
std::vector<uint256> evicted;
if (!weightedTxList || !recentlyEvicted) {
return evicted;
}
boost::optional<WeightedTxInfo> txToDrop;
while ((txToDrop = weightedTxList->maybeDropRandom()).is_initialized()) {
uint256 txId = txToDrop->txId;
recentlyEvicted->add(txId);
evicted.push_back(txId);
}
return evicted;
}

View File

@ -10,6 +10,7 @@
#include "amount.h"
#include "coins.h"
#include "mempoollimit.h"
#include "primitives/transaction.h"
#include "sync.h"
#include "addressindex.h"
@ -139,6 +140,8 @@ private:
std::map<uint256, const CTransaction*> mapSproutNullifiers;
std::map<uint256, const CTransaction*> mapSaplingNullifiers;
RecentlyEvictedList* recentlyEvicted = nullptr;
WeightedTransactionList* weightedTxList = nullptr;
void checkNullifiers(ShieldedType type) const;
@ -260,6 +263,12 @@ public:
uint32_t GetCheckFrequency() const {
return nCheckFrequency;
}
void setMempoolCostLimit(int64_t totalCostLimit, int64_t evictionMemorySeconds);
// Returns true if a transaction has been recently evicted
bool isRecentlyEvicted(const uint256& txId);
// Returns a list of txids which have been evicted
std::vector<uint256> ensureSizeLimit();
};
/**