DoS protection: Weighted random drop of txs if mempool full
This commit is contained in:
parent
fafc479fcf
commit
ae9ecf09e8
|
@ -41,6 +41,7 @@ testScripts=(
|
||||||
'rawtransactions.py'
|
'rawtransactions.py'
|
||||||
'getrawtransaction_insight.py'
|
'getrawtransaction_insight.py'
|
||||||
'rest.py'
|
'rest.py'
|
||||||
|
'mempool_limit.py'
|
||||||
'mempool_spendcoinbase.py'
|
'mempool_spendcoinbase.py'
|
||||||
'mempool_reorg.py'
|
'mempool_reorg.py'
|
||||||
'mempool_nu_activation.py'
|
'mempool_nu_activation.py'
|
||||||
|
|
|
@ -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()
|
|
@ -192,6 +192,7 @@ BITCOIN_CORE_H = \
|
||||||
torcontrol.h \
|
torcontrol.h \
|
||||||
transaction_builder.h \
|
transaction_builder.h \
|
||||||
txdb.h \
|
txdb.h \
|
||||||
|
mempoollimit.h \
|
||||||
txmempool.h \
|
txmempool.h \
|
||||||
ui_interface.h \
|
ui_interface.h \
|
||||||
uint256.h \
|
uint256.h \
|
||||||
|
@ -266,6 +267,7 @@ libbitcoin_server_a_SOURCES = \
|
||||||
timedata.cpp \
|
timedata.cpp \
|
||||||
torcontrol.cpp \
|
torcontrol.cpp \
|
||||||
txdb.cpp \
|
txdb.cpp \
|
||||||
|
mempoollimit.cpp \
|
||||||
txmempool.cpp \
|
txmempool.cpp \
|
||||||
validationinterface.cpp \
|
validationinterface.cpp \
|
||||||
$(BITCOIN_CORE_H) \
|
$(BITCOIN_CORE_H) \
|
||||||
|
|
|
@ -27,6 +27,7 @@ zcash_gtest_SOURCES += \
|
||||||
gtest/test_keystore.cpp \
|
gtest/test_keystore.cpp \
|
||||||
gtest/test_noteencryption.cpp \
|
gtest/test_noteencryption.cpp \
|
||||||
gtest/test_mempool.cpp \
|
gtest/test_mempool.cpp \
|
||||||
|
gtest/test_mempoollimit.cpp \
|
||||||
gtest/test_merkletree.cpp \
|
gtest/test_merkletree.cpp \
|
||||||
gtest/test_metrics.cpp \
|
gtest/test_metrics.cpp \
|
||||||
gtest/test_miner.cpp \
|
gtest/test_miner.cpp \
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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("-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.") +
|
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"));
|
" " + _("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
|
#ifdef ENABLE_WALLET
|
||||||
strUsage += HelpMessageGroup(_("Wallet options:"));
|
strUsage += HelpMessageGroup(_("Wallet options:"));
|
||||||
|
@ -974,6 +976,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||||
if (ratio != 0) {
|
if (ratio != 0) {
|
||||||
mempool.setSanityCheck(1.0 / ratio);
|
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());
|
fCheckBlockIndex = GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks());
|
||||||
fCheckpointsEnabled = GetBoolArg("-checkpoints", true);
|
fCheckpointsEnabled = GetBoolArg("-checkpoints", true);
|
||||||
|
|
||||||
|
@ -1595,7 +1602,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||||
mempool.ReadFeeEstimates(est_filein);
|
mempool.ReadFeeEstimates(est_filein);
|
||||||
fFeeEstimatesInitialized = true;
|
fFeeEstimatesInitialized = true;
|
||||||
|
|
||||||
|
|
||||||
// ********************************************************* Step 8: load wallet
|
// ********************************************************* Step 8: load wallet
|
||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
if (fDisableWallet) {
|
if (fDisableWallet) {
|
||||||
|
|
37
src/main.cpp
37
src/main.cpp
|
@ -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();
|
auto verifier = libzcash::ProofVerifier::Strict();
|
||||||
if (!CheckTransaction(tx, state, verifier))
|
if (!CheckTransaction(tx, state, verifier))
|
||||||
return error("AcceptToMemoryPool: CheckTransaction failed");
|
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());
|
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
|
// Add memory address index
|
||||||
if (fAddressIndex) {
|
if (fAddressIndex) {
|
||||||
pool.addAddressIndex(entry, view);
|
pool.addAddressIndex(entry, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
// insightexplorer: Add memory spent index
|
// insightexplorer: Add memory spent index
|
||||||
if (fSpentIndex) {
|
if (fSpentIndex) {
|
||||||
pool.addSpentIndex(entry, view);
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -102,6 +102,9 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
|
||||||
// Used by main.cpp AcceptToMemoryPool(), which DOES do
|
// Used by main.cpp AcceptToMemoryPool(), which DOES do
|
||||||
// all the appropriate checks.
|
// all the appropriate checks.
|
||||||
LOCK(cs);
|
LOCK(cs);
|
||||||
|
if (weightedTxList) {
|
||||||
|
weightedTxList->add(WeightedTxInfo::from(entry.GetTx()));
|
||||||
|
}
|
||||||
mapTx.insert(entry);
|
mapTx.insert(entry);
|
||||||
const CTransaction& tx = mapTx.find(hash)->GetTx();
|
const CTransaction& tx = mapTx.find(hash)->GetTx();
|
||||||
mapRecentlyAddedTx[tx.GetHash()] = &tx;
|
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.
|
// 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;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "amount.h"
|
#include "amount.h"
|
||||||
#include "coins.h"
|
#include "coins.h"
|
||||||
|
#include "mempoollimit.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "sync.h"
|
#include "sync.h"
|
||||||
#include "addressindex.h"
|
#include "addressindex.h"
|
||||||
|
@ -139,6 +140,8 @@ private:
|
||||||
|
|
||||||
std::map<uint256, const CTransaction*> mapSproutNullifiers;
|
std::map<uint256, const CTransaction*> mapSproutNullifiers;
|
||||||
std::map<uint256, const CTransaction*> mapSaplingNullifiers;
|
std::map<uint256, const CTransaction*> mapSaplingNullifiers;
|
||||||
|
RecentlyEvictedList* recentlyEvicted = nullptr;
|
||||||
|
WeightedTransactionList* weightedTxList = nullptr;
|
||||||
|
|
||||||
void checkNullifiers(ShieldedType type) const;
|
void checkNullifiers(ShieldedType type) const;
|
||||||
|
|
||||||
|
@ -260,6 +263,12 @@ public:
|
||||||
uint32_t GetCheckFrequency() const {
|
uint32_t GetCheckFrequency() const {
|
||||||
return nCheckFrequency;
|
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();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue