Skip to content

Commit 9e072a6

Browse files
committed
Implement "feefilter" P2P message.
The "feefilter" p2p message is used to inform other nodes of your mempool min fee which is the feerate that any new transaction must meet to be accepted to your mempool. This will allow them to filter invs to you according to this feerate.
1 parent 29b2be6 commit 9e072a6

File tree

15 files changed

+152
-32
lines changed

15 files changed

+152
-32
lines changed

src/init.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ std::string HelpMessage(HelpMessageMode mode)
330330
}
331331
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
332332
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
333+
strUsage += HelpMessageOpt("-feefilter", strprintf(_("Tell other nodes to filter invs to us by our mempool min fee (default: %u)"), DEFAULT_FEEFILTER));
333334
strUsage += HelpMessageOpt("-loadblock=<file>", _("Imports blocks from external blk000??.dat file on startup"));
334335
strUsage += HelpMessageOpt("-maxorphantx=<n>", strprintf(_("Keep at most <n> unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS));
335336
strUsage += HelpMessageOpt("-maxmempool=<n>", strprintf(_("Keep the transaction memory pool below <n> megabytes (default: %u)"), DEFAULT_MAX_MEMPOOL_SIZE));

src/main.cpp

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
#include "init.h"
1818
#include "merkleblock.h"
1919
#include "net.h"
20+
#include "policy/fees.h"
2021
#include "policy/policy.h"
2122
#include "pow.h"
2223
#include "primitives/block.h"
2324
#include "primitives/transaction.h"
25+
#include "random.h"
2426
#include "script/script.h"
2527
#include "script/sigcache.h"
2628
#include "script/standard.h"
@@ -81,6 +83,7 @@ CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
8183
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
8284

8385
CTxMemPool mempool(::minRelayTxFee);
86+
FeeFilterRounder filterRounder(::minRelayTxFee);
8487

8588
struct COrphanTx {
8689
CTransaction tx;
@@ -987,7 +990,7 @@ std::string FormatStateMessage(const CValidationState &state)
987990
}
988991

989992
bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransaction& tx, bool fLimitFree,
990-
bool* pfMissingInputs, bool fOverrideMempoolLimit, const CAmount nAbsurdFee,
993+
bool* pfMissingInputs, CFeeRate* txFeeRate, bool fOverrideMempoolLimit, const CAmount& nAbsurdFee,
991994
std::vector<uint256>& vHashTxnToUncache)
992995
{
993996
const uint256 hash = tx.GetHash();
@@ -1144,6 +1147,9 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
11441147

11451148
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase, nSigOps, lp);
11461149
unsigned int nSize = entry.GetTxSize();
1150+
if (txFeeRate) {
1151+
*txFeeRate = CFeeRate(nFees, nSize);
1152+
}
11471153

11481154
// Check that the transaction doesn't have an excessive number of
11491155
// sigops, making it impossible to mine. Since the coinbase transaction
@@ -1392,10 +1398,10 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
13921398
}
13931399

13941400
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
1395-
bool* pfMissingInputs, bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
1401+
bool* pfMissingInputs, CFeeRate* txFeeRate, bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
13961402
{
13971403
std::vector<uint256> vHashTxToUncache;
1398-
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache);
1404+
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, txFeeRate, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache);
13991405
if (!res) {
14001406
BOOST_FOREACH(const uint256& hashTx, vHashTxToUncache)
14011407
pcoinsTip->Uncache(hashTx);
@@ -2620,7 +2626,7 @@ bool static DisconnectTip(CValidationState& state, const Consensus::Params& cons
26202626
// ignore validation errors in resurrected transactions
26212627
list<CTransaction> removed;
26222628
CValidationState stateDummy;
2623-
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, true)) {
2629+
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, NULL, true)) {
26242630
mempool.removeRecursive(tx, removed);
26252631
} else if (mempool.exists(tx.GetHash())) {
26262632
vHashUpdate.push_back(tx.GetHash());
@@ -4916,10 +4922,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
49164922
pfrom->setAskFor.erase(inv.hash);
49174923
mapAlreadyAskedFor.erase(inv);
49184924

4919-
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs))
4920-
{
4925+
CFeeRate txFeeRate = CFeeRate(0);
4926+
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs, &txFeeRate)) {
49214927
mempool.check(pcoinsTip);
4922-
RelayTransaction(tx);
4928+
RelayTransaction(tx, txFeeRate);
49234929
vWorkQueue.push_back(inv.hash);
49244930

49254931
LogPrint("mempool", "AcceptToMemoryPool: peer=%d: accepted %s (poolsz %u txn, %u kB)\n",
@@ -4950,10 +4956,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
49504956

49514957
if (setMisbehaving.count(fromPeer))
49524958
continue;
4953-
if (AcceptToMemoryPool(mempool, stateDummy, orphanTx, true, &fMissingInputs2))
4954-
{
4959+
CFeeRate orphanFeeRate = CFeeRate(0);
4960+
if (AcceptToMemoryPool(mempool, stateDummy, orphanTx, true, &fMissingInputs2, &orphanFeeRate)) {
49554961
LogPrint("mempool", " accepted orphan tx %s\n", orphanHash.ToString());
4956-
RelayTransaction(orphanTx);
4962+
RelayTransaction(orphanTx, orphanFeeRate);
49574963
vWorkQueue.push_back(orphanHash);
49584964
vEraseQueue.push_back(orphanHash);
49594965
}
@@ -5006,7 +5012,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
50065012
int nDoS = 0;
50075013
if (!state.IsInvalid(nDoS) || nDoS == 0) {
50085014
LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->id);
5009-
RelayTransaction(tx);
5015+
RelayTransaction(tx, txFeeRate);
50105016
} else {
50115017
LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->id, FormatStateMessage(state));
50125018
}
@@ -5200,6 +5206,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
52005206
if (!fInMemPool) continue; // another thread removed since queryHashes, maybe...
52015207
if (!pfrom->pfilter->IsRelevantAndUpdate(tx)) continue;
52025208
}
5209+
if (pfrom->minFeeFilter) {
5210+
CFeeRate feeRate;
5211+
mempool.lookupFeeRate(hash, feeRate);
5212+
LOCK(pfrom->cs_feeFilter);
5213+
if (feeRate.GetFeePerK() < pfrom->minFeeFilter)
5214+
continue;
5215+
}
52035216
vInv.push_back(inv);
52045217
if (vInv.size() == MAX_INV_SZ) {
52055218
pfrom->PushMessage(NetMsgType::INV, vInv);
@@ -5362,8 +5375,19 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
53625375
}
53635376
}
53645377

5365-
else
5366-
{
5378+
else if (strCommand == NetMsgType::FEEFILTER) {
5379+
CAmount newFeeFilter = 0;
5380+
vRecv >> newFeeFilter;
5381+
if (MoneyRange(newFeeFilter)) {
5382+
{
5383+
LOCK(pfrom->cs_feeFilter);
5384+
pfrom->minFeeFilter = newFeeFilter;
5385+
}
5386+
LogPrint("net", "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom->id);
5387+
}
5388+
}
5389+
5390+
else {
53675391
// Ignore unknown commands for extensibility
53685392
LogPrint("net", "Unknown command \"%s\" from peer=%d\n", SanitizeString(strCommand), pfrom->id);
53695393
}
@@ -5845,6 +5869,29 @@ bool SendMessages(CNode* pto)
58455869
if (!vGetData.empty())
58465870
pto->PushMessage(NetMsgType::GETDATA, vGetData);
58475871

5872+
//
5873+
// Message: feefilter
5874+
//
5875+
// We don't want white listed peers to filter txs to us if we have -whitelistforcerelay
5876+
if (pto->nVersion >= FEEFILTER_VERSION && GetBoolArg("-feefilter", DEFAULT_FEEFILTER) &&
5877+
!(pto->fWhitelisted && GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY))) {
5878+
CAmount currentFilter = mempool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
5879+
int64_t timeNow = GetTimeMicros();
5880+
if (timeNow > pto->nextSendTimeFeeFilter) {
5881+
CAmount filterToSend = filterRounder.round(currentFilter);
5882+
if (filterToSend != pto->lastSentFeeFilter) {
5883+
pto->PushMessage(NetMsgType::FEEFILTER, filterToSend);
5884+
pto->lastSentFeeFilter = filterToSend;
5885+
}
5886+
pto->nextSendTimeFeeFilter = PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL);
5887+
}
5888+
// If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY
5889+
// until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY.
5890+
else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->nextSendTimeFeeFilter &&
5891+
(currentFilter < 3 * pto->lastSentFeeFilter / 4 || currentFilter > 4 * pto->lastSentFeeFilter / 3)) {
5892+
pto->nextSendTimeFeeFilter = timeNow + (insecure_rand() % MAX_FEEFILTER_CHANGE_DELAY) * 1000000;
5893+
}
5894+
}
58485895
}
58495896
return true;
58505897
}

src/main.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ static const unsigned int AVG_ADDRESS_BROADCAST_INTERVAL = 30;
102102
/** Average delay between trickled inventory broadcasts in seconds.
103103
* Blocks, whitelisted receivers, and a random 25% of transactions bypass this. */
104104
static const unsigned int AVG_INVENTORY_BROADCAST_INTERVAL = 5;
105+
/** Average delay between feefilter broadcasts in seconds. */
106+
static const unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60;
107+
/** Maximum feefilter broadcast delay after significant change. */
108+
static const unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60;
105109

106110
static const unsigned int DEFAULT_LIMITFREERELAY = 15;
107111
static const bool DEFAULT_RELAYPRIORITY = true;
@@ -117,6 +121,8 @@ static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100;
117121
static const bool DEFAULT_TESTSAFEMODE = false;
118122
/** Default for -mempoolreplacement */
119123
static const bool DEFAULT_ENABLE_REPLACEMENT = true;
124+
/** Default for using fee filter */
125+
static const bool DEFAULT_FEEFILTER = true;
120126

121127
/** Maximum number of headers to announce when relaying blocks with headers message.*/
122128
static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;
@@ -282,7 +288,7 @@ void PruneAndFlush();
282288

283289
/** (try to) add transaction to memory pool **/
284290
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
285-
bool* pfMissingInputs, bool fOverrideMempoolLimit=false, const CAmount nAbsurdFee=0);
291+
bool* pfMissingInputs, CFeeRate* txFeeRate, bool fOverrideMempoolLimit=false, const CAmount nAbsurdFee=0);
286292

287293
/** Convert CValidationState to a human-readable message for logging */
288294
std::string FormatStateMessage(const CValidationState &state);

src/net.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,20 +2053,15 @@ class CNetCleanup
20532053
instance_of_cnetcleanup;
20542054

20552055

2056-
2057-
2058-
2059-
2060-
2061-
void RelayTransaction(const CTransaction& tx)
2056+
void RelayTransaction(const CTransaction& tx, CFeeRate feerate)
20622057
{
20632058
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
20642059
ss.reserve(10000);
20652060
ss << tx;
2066-
RelayTransaction(tx, ss);
2061+
RelayTransaction(tx, feerate, ss);
20672062
}
20682063

2069-
void RelayTransaction(const CTransaction& tx, const CDataStream& ss)
2064+
void RelayTransaction(const CTransaction& tx, CFeeRate feerate, const CDataStream& ss)
20702065
{
20712066
CInv inv(MSG_TX, tx.GetHash());
20722067
{
@@ -2087,6 +2082,11 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss)
20872082
{
20882083
if(!pnode->fRelayTxes)
20892084
continue;
2085+
{
2086+
LOCK(pnode->cs_feeFilter);
2087+
if (feerate.GetFeePerK() < pnode->minFeeFilter)
2088+
continue;
2089+
}
20902090
LOCK(pnode->cs_filter);
20912091
if (pnode->pfilter)
20922092
{
@@ -2390,6 +2390,10 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa
23902390
nPingUsecTime = 0;
23912391
fPingQueued = false;
23922392
nMinPingUsecTime = std::numeric_limits<int64_t>::max();
2393+
minFeeFilter = 0;
2394+
lastSentFeeFilter = 0;
2395+
nextSendTimeFeeFilter = 0;
2396+
23932397
BOOST_FOREACH(const std::string &msg, getAllNetMessageTypes())
23942398
mapRecvBytesPerMsgCmd[msg] = 0;
23952399
mapRecvBytesPerMsgCmd[NET_MESSAGE_COMMAND_OTHER] = 0;

src/net.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#ifndef BITCOIN_NET_H
77
#define BITCOIN_NET_H
88

9+
#include "amount.h"
910
#include "bloom.h"
1011
#include "compat.h"
1112
#include "limitedmap.h"
@@ -415,6 +416,11 @@ class CNode
415416
int64_t nMinPingUsecTime;
416417
// Whether a ping is requested.
417418
bool fPingQueued;
419+
// Minimum fee rate with which to filter inv's to this node
420+
CAmount minFeeFilter;
421+
CCriticalSection cs_feeFilter;
422+
CAmount lastSentFeeFilter;
423+
int64_t nextSendTimeFeeFilter;
418424

419425
CNode(SOCKET hSocketIn, const CAddress &addrIn, const std::string &addrNameIn = "", bool fInboundIn = false);
420426
~CNode();
@@ -766,8 +772,8 @@ class CNode
766772

767773

768774
class CTransaction;
769-
void RelayTransaction(const CTransaction& tx);
770-
void RelayTransaction(const CTransaction& tx, const CDataStream& ss);
775+
void RelayTransaction(const CTransaction& tx, CFeeRate feerate);
776+
void RelayTransaction(const CTransaction& tx, CFeeRate feerate, const CDataStream& ss);
771777

772778
/** Access to the (IP) address database (peers.dat) */
773779
class CAddrDB

src/policy/fees.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "amount.h"
1010
#include "primitives/transaction.h"
11+
#include "random.h"
1112
#include "streams.h"
1213
#include "txmempool.h"
1314
#include "util.h"
@@ -580,3 +581,21 @@ void CBlockPolicyEstimator::Read(CAutoFile& filein)
580581
priStats.Read(filein);
581582
nBestSeenHeight = nFileBestSeenHeight;
582583
}
584+
585+
FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)
586+
{
587+
CAmount minFeeLimit = minIncrementalFee.GetFeePerK() / 2;
588+
feeset.insert(0);
589+
for (double bucketBoundary = minFeeLimit; bucketBoundary <= MAX_FEERATE; bucketBoundary *= FEE_SPACING) {
590+
feeset.insert(bucketBoundary);
591+
}
592+
}
593+
594+
CAmount FeeFilterRounder::round(CAmount currentMinFee)
595+
{
596+
std::set<double>::iterator it = feeset.lower_bound(currentMinFee);
597+
if ((it != feeset.begin() && insecure_rand() % 3 != 0) || it == feeset.end()) {
598+
it--;
599+
}
600+
return *it;
601+
}

src/policy/fees.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,17 @@ class CBlockPolicyEstimator
286286
CFeeRate feeLikely, feeUnlikely;
287287
double priLikely, priUnlikely;
288288
};
289+
290+
class FeeFilterRounder
291+
{
292+
public:
293+
/** Create new FeeFilterRounder */
294+
FeeFilterRounder(const CFeeRate& minIncrementalFee);
295+
296+
/** Quantize a minimum fee for privacy purpose before broadcast **/
297+
CAmount round(CAmount currentMinFee);
298+
299+
private:
300+
std::set<double> feeset;
301+
};
289302
#endif /*BITCOIN_POLICYESTIMATOR_H */

src/protocol.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const char *FILTERADD="filteradd";
3434
const char *FILTERCLEAR="filterclear";
3535
const char *REJECT="reject";
3636
const char *SENDHEADERS="sendheaders";
37+
const char *FEEFILTER="feefilter";
3738
};
3839

3940
static const char* ppszTypeName[] =
@@ -68,7 +69,8 @@ const static std::string allNetMessageTypes[] = {
6869
NetMsgType::FILTERADD,
6970
NetMsgType::FILTERCLEAR,
7071
NetMsgType::REJECT,
71-
NetMsgType::SENDHEADERS
72+
NetMsgType::SENDHEADERS,
73+
NetMsgType::FEEFILTER
7274
};
7375
const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
7476

src/protocol.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,12 @@ extern const char *REJECT;
211211
* @see https://bitcoin.org/en/developer-reference#sendheaders
212212
*/
213213
extern const char *SENDHEADERS;
214-
214+
/**
215+
* The feefilter message tells the receiving peer not to inv us any txs
216+
* which do not meet the specified min fee rate.
217+
* @since protocol version 70013 as described by BIP133
218+
*/
219+
extern const char *FEEFILTER;
215220
};
216221

217222
/* Get a vector of all valid message types (see above) */

src/rpc/rawtransaction.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -818,11 +818,12 @@ UniValue sendrawtransaction(const UniValue& params, bool fHelp)
818818
const CCoins* existingCoins = view.AccessCoins(hashTx);
819819
bool fHaveMempool = mempool.exists(hashTx);
820820
bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000;
821+
CFeeRate txFeeRate = CFeeRate(0);
821822
if (!fHaveMempool && !fHaveChain) {
822823
// push to local node and sync with wallets
823824
CValidationState state;
824825
bool fMissingInputs;
825-
if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, false, nMaxRawTxFee)) {
826+
if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, &txFeeRate, false, nMaxRawTxFee)) {
826827
if (state.IsInvalid()) {
827828
throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
828829
} else {
@@ -835,7 +836,7 @@ UniValue sendrawtransaction(const UniValue& params, bool fHelp)
835836
} else if (fHaveChain) {
836837
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
837838
}
838-
RelayTransaction(tx);
839+
RelayTransaction(tx, txFeeRate);
839840

840841
return hashTx.GetHex();
841842
}

0 commit comments

Comments
 (0)