Merge #7542: Implement "feefilter" P2P message

0371797 modify release-notes.md and bips.md (Alex Morcos)
b536a6f Add p2p test for feefilter (Alex Morcos)
5fa66e4 Create SingleNodeConnCB class for RPC tests (Alex Morcos)
9e072a6 Implement "feefilter" P2P message. (Alex Morcos)
This commit is contained in:
Wladimir J. van der Laan 2016-03-21 18:02:47 +01:00 committed by Alexander Block
parent 2839222434
commit 11ac70af9e
20 changed files with 275 additions and 26 deletions

View File

@ -20,3 +20,4 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.12.0**):
* [`BIP 111`](https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki): `NODE_BLOOM` service bit added, and enforced for all peer versions as of **v0.13.0** ([PR #6579](https://github.com/bitcoin/bitcoin/pull/6579) and [PR #6641](https://github.com/bitcoin/bitcoin/pull/6641)). * [`BIP 111`](https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki): `NODE_BLOOM` service bit added, and enforced for all peer versions as of **v0.13.0** ([PR #6579](https://github.com/bitcoin/bitcoin/pull/6579) and [PR #6641](https://github.com/bitcoin/bitcoin/pull/6641)).
* [`BIP 125`](https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki): Opt-in full replace-by-fee signaling honoured in mempool and mining as of **v0.12.0** ([PR 6871](https://github.com/bitcoin/bitcoin/pull/6871)). * [`BIP 125`](https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki): Opt-in full replace-by-fee signaling honoured in mempool and mining as of **v0.12.0** ([PR 6871](https://github.com/bitcoin/bitcoin/pull/6871)).
* [`BIP 130`](https://github.com/bitcoin/bips/blob/master/bip-0130.mediawiki): direct headers announcement is negotiated with peer versions `>=70012` as of **v0.12.0** ([PR 6494](https://github.com/bitcoin/bitcoin/pull/6494)). * [`BIP 130`](https://github.com/bitcoin/bips/blob/master/bip-0130.mediawiki): direct headers announcement is negotiated with peer versions `>=70012` as of **v0.12.0** ([PR 6494](https://github.com/bitcoin/bitcoin/pull/6494)).
* [`BIP 133`](https://github.com/bitcoin/bips/blob/master/bip-0133.mediawiki): feefilter messages are respected and sent for peer versions `>=70013` as of **v0.13.0** ([PR 7542](https://github.com/bitcoin/bitcoin/pull/7542)).

View File

@ -152,7 +152,6 @@ testScriptsExt = [
'getblocktemplate_proposals.py', 'getblocktemplate_proposals.py',
'txn_doublespend.py', 'txn_doublespend.py',
'txn_clone.py --mineblock', 'txn_clone.py --mineblock',
# 'pruning.py', # Prune mode is incompatible with -txindex.
'forknotify.py', 'forknotify.py',
'invalidateblock.py', 'invalidateblock.py',
# 'rpcbind_test.py', #temporary, bug in libevent, see #6655 # 'rpcbind_test.py', #temporary, bug in libevent, see #6655
@ -162,6 +161,8 @@ testScriptsExt = [
'mempool_packages.py', 'mempool_packages.py',
'maxuploadtarget.py', 'maxuploadtarget.py',
# 'replace-by-fee.py', # RBF is disabled in Dash Core # 'replace-by-fee.py', # RBF is disabled in Dash Core
'p2p-feefilter.py',
# 'pruning.py', # leave pruning last as it takes a REALLY long time #### Prune mode is incompatible with -txindex.
] ]
def runtests(): def runtests():

99
qa/rpc-tests/p2p-feefilter.py Executable file
View File

@ -0,0 +1,99 @@
#!/usr/bin/env python2
# Copyright (c) 2016 The Bitcoin Core developers
# Distributed under the MIT/X11 software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
from test_framework.mininode import *
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
import time
'''
FeeFilterTest -- test processing of feefilter messages
'''
def hashToHex(hash):
return format(hash, '064x').decode('utf-8')
# Wait up to 60 secs to see if the testnode has received all the expected invs
def allInvsMatch(invsExpected, testnode):
for x in xrange(60):
with mininode_lock:
if (sorted(invsExpected) == sorted(testnode.txinvs)):
return True;
time.sleep(1)
return False;
# TestNode: bare-bones "peer". Used to track which invs are received from a node
# and to send the node feefilter messages.
class TestNode(SingleNodeConnCB):
def __init__(self):
SingleNodeConnCB.__init__(self)
self.txinvs = []
def on_inv(self, conn, message):
for i in message.inv:
if (i.type == 1):
self.txinvs.append(hashToHex(i.hash))
def clear_invs(self):
with mininode_lock:
self.txinvs = []
def send_filter(self, feerate):
self.send_message(msg_feefilter(feerate))
self.sync_with_ping()
class FeeFilterTest(BitcoinTestFramework):
def setup_network(self):
# Node1 will be used to generate txs which should be relayed from Node0
# to our test node
self.nodes = []
self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-logtimemicros"]))
self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-logtimemicros"]))
connect_nodes(self.nodes[0], 1)
def run_test(self):
node1 = self.nodes[1]
# Get out of IBD
node1.generate(1)
sync_blocks(self.nodes)
# Setup the p2p connections and start up the network thread.
test_node = TestNode()
connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)
test_node.add_connection(connection)
NetworkThread().start()
test_node.wait_for_verack()
# Test that invs are received for all txs at feerate of 20 sat/byte
node1.settxfee(Decimal("0.00020000"))
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in xrange(3)]
assert(allInvsMatch(txids, test_node))
test_node.clear_invs()
# Set a filter of 15 sat/byte
test_node.send_filter(15000)
# Test that txs are still being received (paying 20 sat/byte)
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in xrange(3)]
assert(allInvsMatch(txids, test_node))
test_node.clear_invs()
# Change tx fee rate to 10 sat/byte and test they are no longer received
node1.settxfee(Decimal("0.00010000"))
[node1.sendtoaddress(node1.getnewaddress(), 1) for x in xrange(3)]
sync_mempools(self.nodes) # must be sure node 0 has received all txs
time.sleep(10) # wait 10 secs to be sure its doesn't relay any
assert(allInvsMatch([], test_node))
test_node.clear_invs()
# Remove fee filter and check that txs are received again
test_node.send_filter(0)
txids = [node1.sendtoaddress(node1.getnewaddress(), 1) for x in xrange(3)]
assert(allInvsMatch(txids, test_node))
test_node.clear_invs()
if __name__ == '__main__':
FeeFilterTest().main()

View File

@ -1031,6 +1031,23 @@ def wait_until(predicate, attempts=float('inf'), timeout=float('inf')):
return False return False
class msg_feefilter(object):
command = "feefilter"
def __init__(self, feerate=0L):
self.feerate = feerate
def deserialize(self, f):
self.feerate = struct.unpack("<Q", f.read(8))[0]
def serialize(self):
r = ""
r += struct.pack("<Q", self.feerate)
return r
def __repr__(self):
return "msg_feefilter(feerate=%08x)" % self.feerate
# This is what a callback should look like for NodeConn # This is what a callback should look like for NodeConn
# Reimplement the on_* functions to provide handling for events # Reimplement the on_* functions to provide handling for events
class NodeConnCB(object): class NodeConnCB(object):
@ -1106,6 +1123,7 @@ class NodeConnCB(object):
def on_close(self, conn): pass def on_close(self, conn): pass
def on_mempool(self, conn): pass def on_mempool(self, conn): pass
def on_pong(self, conn, message): pass def on_pong(self, conn, message): pass
def on_feefilter(self, conn, message): pass
# More useful callbacks and functions for NodeConnCB's which have a single NodeConn # More useful callbacks and functions for NodeConnCB's which have a single NodeConn
class SingleNodeConnCB(NodeConnCB): class SingleNodeConnCB(NodeConnCB):
@ -1154,6 +1172,7 @@ class NodeConn(asyncore.dispatcher):
b"getheaders": msg_getheaders, b"getheaders": msg_getheaders,
b"reject": msg_reject, b"reject": msg_reject,
b"mempool": msg_mempool, b"mempool": msg_mempool,
b"feefilter": msg_feefilter
} }
MAGIC_BYTES = { MAGIC_BYTES = {
"mainnet": b"\xbf\x0c\x6b\xbd", # mainnet "mainnet": b"\xbf\x0c\x6b\xbd", # mainnet

View File

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

View File

@ -2474,7 +2474,7 @@ bool CConnman::DisconnectNode(NodeId id)
return false; return false;
} }
void CConnman::RelayTransaction(const CTransaction& tx) void CConnman::RelayTransaction(const CTransaction& tx, CFeeRate feerate)
{ {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss.reserve(10000); ss.reserve(10000);
@ -2488,10 +2488,10 @@ void CConnman::RelayTransaction(const CTransaction& tx)
} else { // MSG_TX } else { // MSG_TX
ss << tx; ss << tx;
} }
RelayTransaction(tx, ss); RelayTransaction(tx, feerate, ss);
} }
void CConnman::RelayTransaction(const CTransaction& tx, const CDataStream& ss) void CConnman::RelayTransaction(const CTransaction& tx, CFeeRate feerate, const CDataStream& ss)
{ {
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
int nInv = static_cast<bool>(CPrivateSend::GetDSTX(hash)) ? MSG_DSTX : int nInv = static_cast<bool>(CPrivateSend::GetDSTX(hash)) ? MSG_DSTX :
@ -2515,6 +2515,11 @@ void CConnman::RelayTransaction(const CTransaction& tx, const CDataStream& ss)
{ {
if(!pnode->fRelayTxes) if(!pnode->fRelayTxes)
continue; continue;
{
LOCK(pnode->cs_feeFilter);
if (feerate.GetFeePerK() < pnode->minFeeFilter)
continue;
}
LOCK(pnode->cs_filter); LOCK(pnode->cs_filter);
if (pnode->pfilter) if (pnode->pfilter)
{ {
@ -2711,6 +2716,9 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn
fPingQueued = false; fPingQueued = false;
fMasternode = false; fMasternode = false;
nMinPingUsecTime = std::numeric_limits<int64_t>::max(); nMinPingUsecTime = std::numeric_limits<int64_t>::max();
minFeeFilter = 0;
lastSentFeeFilter = 0;
nextSendTimeFeeFilter = 0;
vchKeyedNetGroup = CalculateKeyedNetGroup(addr); vchKeyedNetGroup = CalculateKeyedNetGroup(addr);
id = idIn; id = idIn;
nLocalServices = nLocalServicesIn; nLocalServices = nLocalServicesIn;

View File

@ -8,6 +8,7 @@
#include "addrdb.h" #include "addrdb.h"
#include "addrman.h" #include "addrman.h"
#include "amount.h"
#include "bloom.h" #include "bloom.h"
#include "compat.h" #include "compat.h"
#include "limitedmap.h" #include "limitedmap.h"
@ -304,8 +305,8 @@ public:
std::vector<CNode*> CopyNodeVector(); std::vector<CNode*> CopyNodeVector();
void ReleaseNodeVector(const std::vector<CNode*>& vecNodes); void ReleaseNodeVector(const std::vector<CNode*>& vecNodes);
void RelayTransaction(const CTransaction& tx); void RelayTransaction(const CTransaction& tx, CFeeRate feerate);
void RelayTransaction(const CTransaction& tx, const CDataStream& ss); void RelayTransaction(const CTransaction& tx, CFeeRate feerate, const CDataStream& ss);
void RelayInv(CInv &inv, const int minProtoVersion = MIN_PEER_PROTO_VERSION); void RelayInv(CInv &inv, const int minProtoVersion = MIN_PEER_PROTO_VERSION);
// Addrman functions // Addrman functions
@ -765,6 +766,11 @@ public:
int64_t nMinPingUsecTime; int64_t nMinPingUsecTime;
// Whether a ping is requested. // Whether a ping is requested.
bool fPingQueued; bool fPingQueued;
// Minimum fee rate with which to filter inv's to this node
CAmount minFeeFilter;
CCriticalSection cs_feeFilter;
CAmount lastSentFeeFilter;
int64_t nextSendTimeFeeFilter;
std::vector<unsigned char> vchKeyedNetGroup; std::vector<unsigned char> vchKeyedNetGroup;

View File

@ -50,6 +50,8 @@ using namespace std;
int64_t nTimeBestReceived = 0; // Used only to inform the wallet of when we last received a block int64_t nTimeBestReceived = 0; // Used only to inform the wallet of when we last received a block
extern FeeFilterRounder filterRounder;
struct COrphanTx { struct COrphanTx {
CTransaction tx; CTransaction tx;
NodeId fromPeer; NodeId fromPeer;
@ -1624,8 +1626,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
mapAlreadyAskedFor.erase(inv.hash); mapAlreadyAskedFor.erase(inv.hash);
if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs)) CFeeRate txFeeRate = CFeeRate(0);
{ if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs, &txFeeRate)) {
// Process custom txes, this changes AlreadyHave to "true" // Process custom txes, this changes AlreadyHave to "true"
if (strCommand == NetMsgType::DSTX) { if (strCommand == NetMsgType::DSTX) {
LogPrintf("DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n", LogPrintf("DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n",
@ -1638,7 +1640,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
} }
mempool.check(pcoinsTip); mempool.check(pcoinsTip);
connman.RelayTransaction(tx); connman.RelayTransaction(tx, txFeeRate);
vWorkQueue.push_back(inv.hash); vWorkQueue.push_back(inv.hash);
pfrom->nLastTXTime = GetTime(); pfrom->nLastTXTime = GetTime();
@ -1671,10 +1673,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if (setMisbehaving.count(fromPeer)) if (setMisbehaving.count(fromPeer))
continue; continue;
if (AcceptToMemoryPool(mempool, stateDummy, orphanTx, true, &fMissingInputs2)) CFeeRate orphanFeeRate = CFeeRate(0);
if (AcceptToMemoryPool(mempool, stateDummy, orphanTx, true, &fMissingInputs2, &orphanFeeRate))
{ {
LogPrint("mempool", " accepted orphan tx %s\n", orphanHash.ToString()); LogPrint("mempool", " accepted orphan tx %s\n", orphanHash.ToString());
connman.RelayTransaction(orphanTx); connman.RelayTransaction(orphanTx, orphanFeeRate);
vWorkQueue.push_back(orphanHash); vWorkQueue.push_back(orphanHash);
vEraseQueue.push_back(orphanHash); vEraseQueue.push_back(orphanHash);
} }
@ -1725,7 +1728,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
instantsend.RejectLockRequest(txLockRequest); instantsend.RejectLockRequest(txLockRequest);
// this lets other nodes to create lock request candidate i.e. // this lets other nodes to create lock request candidate i.e.
// this allows multiple conflicting lock requests to compete for votes // this allows multiple conflicting lock requests to compete for votes
connman.RelayTransaction(tx); connman.RelayTransaction(tx, txFeeRate);
} }
if (pfrom->fWhitelisted && GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) { if (pfrom->fWhitelisted && GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) {
@ -1740,7 +1743,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
int nDoS = 0; int nDoS = 0;
if (!state.IsInvalid(nDoS) || nDoS == 0) { if (!state.IsInvalid(nDoS) || nDoS == 0) {
LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->id); LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->id);
connman.RelayTransaction(tx); connman.RelayTransaction(tx, txFeeRate);
} else { } else {
LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->id, FormatStateMessage(state)); LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->id, FormatStateMessage(state));
} }
@ -1952,6 +1955,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if (!fInMemPool) continue; // another thread removed since queryHashes, maybe... if (!fInMemPool) continue; // another thread removed since queryHashes, maybe...
if (!pfrom->pfilter->IsRelevantAndUpdate(tx)) continue; if (!pfrom->pfilter->IsRelevantAndUpdate(tx)) continue;
} }
if (pfrom->minFeeFilter) {
CFeeRate feeRate;
mempool.lookupFeeRate(hash, feeRate);
LOCK(pfrom->cs_feeFilter);
if (feeRate.GetFeePerK() < pfrom->minFeeFilter)
continue;
}
vInv.push_back(inv); vInv.push_back(inv);
if (vInv.size() == MAX_INV_SZ) { if (vInv.size() == MAX_INV_SZ) {
connman.PushMessage(pfrom, NetMsgType::INV, vInv); connman.PushMessage(pfrom, NetMsgType::INV, vInv);
@ -2151,8 +2161,20 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
} }
} }
} }
else
{ else if (strCommand == NetMsgType::FEEFILTER) {
CAmount newFeeFilter = 0;
vRecv >> newFeeFilter;
if (MoneyRange(newFeeFilter)) {
{
LOCK(pfrom->cs_feeFilter);
pfrom->minFeeFilter = newFeeFilter;
}
LogPrint("net", "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom->id);
}
}
else {
bool found = false; bool found = false;
const std::vector<std::string> &allMessages = getAllNetMessageTypes(); const std::vector<std::string> &allMessages = getAllNetMessageTypes();
BOOST_FOREACH(const std::string msg, allMessages) { BOOST_FOREACH(const std::string msg, allMessages) {
@ -2707,6 +2729,29 @@ bool SendMessages(CNode* pto, CConnman& connman, std::atomic<bool>& interruptMsg
LogPrint("net", "SendMessages -- GETDATA -- pushed size = %lu peer=%d\n", vGetData.size(), pto->id); LogPrint("net", "SendMessages -- GETDATA -- pushed size = %lu peer=%d\n", vGetData.size(), pto->id);
} }
//
// Message: feefilter
//
// We don't want white listed peers to filter txs to us if we have -whitelistforcerelay
if (pto->nVersion >= FEEFILTER_VERSION && GetBoolArg("-feefilter", DEFAULT_FEEFILTER) &&
!(pto->fWhitelisted && GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY))) {
CAmount currentFilter = mempool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
int64_t timeNow = GetTimeMicros();
if (timeNow > pto->nextSendTimeFeeFilter) {
CAmount filterToSend = filterRounder.round(currentFilter);
if (filterToSend != pto->lastSentFeeFilter) {
connman.PushMessage(pto, NetMsgType::FEEFILTER, filterToSend);
pto->lastSentFeeFilter = filterToSend;
}
pto->nextSendTimeFeeFilter = PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL);
}
// If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY
// until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY.
else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->nextSendTimeFeeFilter &&
(currentFilter < 3 * pto->lastSentFeeFilter / 4 || currentFilter > 4 * pto->lastSentFeeFilter / 3)) {
pto->nextSendTimeFeeFilter = timeNow + (insecure_rand() % MAX_FEEFILTER_CHANGE_DELAY) * 1000000;
}
}
} }
return true; return true;
} }

View File

@ -8,6 +8,7 @@
#include "amount.h" #include "amount.h"
#include "primitives/transaction.h" #include "primitives/transaction.h"
#include "random.h"
#include "streams.h" #include "streams.h"
#include "txmempool.h" #include "txmempool.h"
#include "util.h" #include "util.h"
@ -578,3 +579,21 @@ void CBlockPolicyEstimator::Read(CAutoFile& filein)
priStats.Read(filein); priStats.Read(filein);
nBestSeenHeight = nFileBestSeenHeight; nBestSeenHeight = nFileBestSeenHeight;
} }
FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)
{
CAmount minFeeLimit = minIncrementalFee.GetFeePerK() / 2;
feeset.insert(0);
for (double bucketBoundary = minFeeLimit; bucketBoundary <= MAX_FEERATE; bucketBoundary *= FEE_SPACING) {
feeset.insert(bucketBoundary);
}
}
CAmount FeeFilterRounder::round(CAmount currentMinFee)
{
std::set<double>::iterator it = feeset.lower_bound(currentMinFee);
if ((it != feeset.begin() && insecure_rand() % 3 != 0) || it == feeset.end()) {
it--;
}
return *it;
}

View File

@ -286,4 +286,17 @@ private:
CFeeRate feeLikely, feeUnlikely; CFeeRate feeLikely, feeUnlikely;
double priLikely, priUnlikely; double priLikely, priUnlikely;
}; };
class FeeFilterRounder
{
public:
/** Create new FeeFilterRounder */
FeeFilterRounder(const CFeeRate& minIncrementalFee);
/** Quantize a minimum fee for privacy purpose before broadcast **/
CAmount round(CAmount currentMinFee);
private:
std::set<double> feeset;
};
#endif /*BITCOIN_POLICYESTIMATOR_H */ #endif /*BITCOIN_POLICYESTIMATOR_H */

View File

@ -35,6 +35,7 @@ const char *FILTERADD="filteradd";
const char *FILTERCLEAR="filterclear"; const char *FILTERCLEAR="filterclear";
const char *REJECT="reject"; const char *REJECT="reject";
const char *SENDHEADERS="sendheaders"; const char *SENDHEADERS="sendheaders";
const char *FEEFILTER="feefilter";
// Dash message types // Dash message types
const char *TXLOCKREQUEST="ix"; const char *TXLOCKREQUEST="ix";
const char *TXLOCKVOTE="txlvote"; const char *TXLOCKVOTE="txlvote";
@ -119,6 +120,7 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::FILTERCLEAR, NetMsgType::FILTERCLEAR,
NetMsgType::REJECT, NetMsgType::REJECT,
NetMsgType::SENDHEADERS, NetMsgType::SENDHEADERS,
NetMsgType::FEEFILTER,
// Dash message types // Dash message types
// NOTE: do NOT include non-implmented here, we want them to be "Unknown command" in ProcessMessage() // NOTE: do NOT include non-implmented here, we want them to be "Unknown command" in ProcessMessage()
NetMsgType::TXLOCKREQUEST, NetMsgType::TXLOCKREQUEST,

View File

@ -218,6 +218,12 @@ extern const char *REJECT;
* @see https://bitcoin.org/en/developer-reference#sendheaders * @see https://bitcoin.org/en/developer-reference#sendheaders
*/ */
extern const char *SENDHEADERS; extern const char *SENDHEADERS;
/**
* The feefilter message tells the receiving peer not to inv us any txs
* which do not meet the specified min fee rate.
* @since protocol version 70013 as described by BIP133
*/
extern const char *FEEFILTER;
// Dash message types // Dash message types
// NOTE: do NOT declare non-implmented here, we don't want them to be exposed to the outside // NOTE: do NOT declare non-implmented here, we don't want them to be exposed to the outside

View File

@ -875,6 +875,7 @@ UniValue sendrawtransaction(const UniValue& params, bool fHelp)
fHaveChain = !existingCoin.IsSpent(); fHaveChain = !existingCoin.IsSpent();
} }
bool fHaveMempool = mempool.exists(hashTx); bool fHaveMempool = mempool.exists(hashTx);
CFeeRate txFeeRate = CFeeRate(0);
if (!fHaveMempool && !fHaveChain) { if (!fHaveMempool && !fHaveChain) {
// push to local node and sync with wallets // push to local node and sync with wallets
if (fInstantSend && !instantsend.ProcessTxLockRequest(tx, *g_connman)) { if (fInstantSend && !instantsend.ProcessTxLockRequest(tx, *g_connman)) {
@ -882,7 +883,7 @@ UniValue sendrawtransaction(const UniValue& params, bool fHelp)
} }
CValidationState state; CValidationState state;
bool fMissingInputs; bool fMissingInputs;
if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, false, nMaxRawTxFee)) { if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, &txFeeRate, false, nMaxRawTxFee)) {
if (state.IsInvalid()) { if (state.IsInvalid()) {
throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason())); throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
} else { } else {
@ -898,7 +899,7 @@ UniValue sendrawtransaction(const UniValue& params, bool fHelp)
if(!g_connman) if(!g_connman)
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
g_connman->RelayTransaction(tx); g_connman->RelayTransaction(tx, txFeeRate);
return hashTx.GetHex(); return hashTx.GetHex();
} }

View File

@ -23,7 +23,7 @@ ToMemPool(CMutableTransaction& tx)
LOCK(cs_main); LOCK(cs_main);
CValidationState state; CValidationState state;
return AcceptToMemoryPool(mempool, state, tx, false, NULL, true, 0); return AcceptToMemoryPool(mempool, state, tx, false, NULL, NULL, true, 0);
} }
BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)

View File

@ -907,6 +907,16 @@ bool CTxMemPool::lookup(uint256 hash, CTransaction& result) const
return true; return true;
} }
bool CTxMemPool::lookupFeeRate(const uint256& hash, CFeeRate& feeRate) const
{
LOCK(cs);
indexed_transaction_set::const_iterator i = mapTx.find(hash);
if (i == mapTx.end())
return false;
feeRate = CFeeRate(i->GetFee(), i->GetTxSize());
return true;
}
CFeeRate CTxMemPool::estimateFee(int nBlocks) const CFeeRate CTxMemPool::estimateFee(int nBlocks) const
{ {
LOCK(cs); LOCK(cs);

View File

@ -646,6 +646,7 @@ public:
} }
bool lookup(uint256 hash, CTransaction& result) const; bool lookup(uint256 hash, CTransaction& result) const;
bool lookupFeeRate(const uint256& hash, CFeeRate& feeRate) const;
/** Estimate fee rate needed to get into the next nBlocks /** Estimate fee rate needed to get into the next nBlocks
* If no answer can be given at nBlocks, return an estimate * If no answer can be given at nBlocks, return an estimate

View File

@ -16,10 +16,12 @@
#include "consensus/validation.h" #include "consensus/validation.h"
#include "hash.h" #include "hash.h"
#include "init.h" #include "init.h"
#include "policy/fees.h"
#include "policy/policy.h" #include "policy/policy.h"
#include "pow.h" #include "pow.h"
#include "primitives/block.h" #include "primitives/block.h"
#include "primitives/transaction.h" #include "primitives/transaction.h"
#include "random.h"
#include "script/script.h" #include "script/script.h"
#include "script/sigcache.h" #include "script/sigcache.h"
#include "script/standard.h" #include "script/standard.h"
@ -95,6 +97,7 @@ CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE; CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
CTxMemPool mempool(::minRelayTxFee); CTxMemPool mempool(::minRelayTxFee);
FeeFilterRounder filterRounder(::minRelayTxFee);
map<uint256, int64_t> mapRejectedBlocks GUARDED_BY(cs_main); map<uint256, int64_t> mapRejectedBlocks GUARDED_BY(cs_main);
/** /**
@ -553,7 +556,7 @@ std::string FormatStateMessage(const CValidationState &state)
} }
bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransaction& tx, bool fLimitFree, bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransaction& tx, bool fLimitFree,
bool* pfMissingInputs, bool fOverrideMempoolLimit, const CAmount nAbsurdFee, bool* pfMissingInputs, CFeeRate* txFeeRate, bool fOverrideMempoolLimit, const CAmount& nAbsurdFee,
std::vector<COutPoint>& coins_to_uncache, bool fDryRun) std::vector<COutPoint>& coins_to_uncache, bool fDryRun)
{ {
const uint256 hash = tx.GetHash(); const uint256 hash = tx.GetHash();
@ -748,6 +751,9 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase, nSigOps, lp); CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase, nSigOps, lp);
unsigned int nSize = entry.GetTxSize(); unsigned int nSize = entry.GetTxSize();
if (txFeeRate) {
*txFeeRate = CFeeRate(nFees, nSize);
}
// Check that the transaction doesn't have an excessive number of // Check that the transaction doesn't have an excessive number of
// sigops, making it impossible to mine. Since the coinbase transaction // sigops, making it impossible to mine. Since the coinbase transaction
@ -1010,10 +1016,10 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
} }
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree, bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
bool* pfMissingInputs, bool fOverrideMempoolLimit, const CAmount nAbsurdFee, bool fDryRun) bool* pfMissingInputs, CFeeRate* txFeeRate, bool fOverrideMempoolLimit, const CAmount nAbsurdFee, bool fDryRun)
{ {
std::vector<COutPoint> coins_to_uncache; std::vector<COutPoint> coins_to_uncache;
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, fOverrideMempoolLimit, nAbsurdFee, coins_to_uncache, fDryRun); bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, txFeeRate, fOverrideMempoolLimit, nAbsurdFee, coins_to_uncache, fDryRun);
if (!res || fDryRun) { if (!res || fDryRun) {
if(!res) LogPrint("mempool", "%s: %s %s\n", __func__, tx.GetHash().ToString(), state.GetRejectReason()); if(!res) LogPrint("mempool", "%s: %s %s\n", __func__, tx.GetHash().ToString(), state.GetRejectReason());
BOOST_FOREACH(const COutPoint& hashTx, coins_to_uncache) BOOST_FOREACH(const COutPoint& hashTx, coins_to_uncache)
@ -2497,7 +2503,7 @@ bool static DisconnectTip(CValidationState& state, const Consensus::Params& cons
// ignore validation errors in resurrected transactions // ignore validation errors in resurrected transactions
list<CTransaction> removed; list<CTransaction> removed;
CValidationState stateDummy; CValidationState stateDummy;
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, true)) { if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, NULL, true)) {
mempool.removeRecursive(tx, removed); mempool.removeRecursive(tx, removed);
} else if (mempool.exists(tx.GetHash())) { } else if (mempool.exists(tx.GetHash())) {
vHashUpdate.push_back(tx.GetHash()); vHashUpdate.push_back(tx.GetHash());

View File

@ -110,6 +110,10 @@ static const unsigned int AVG_ADDRESS_BROADCAST_INTERVAL = 30;
/** Average delay between trickled inventory broadcasts in seconds. /** Average delay between trickled inventory broadcasts in seconds.
* Blocks, whitelisted receivers, and a random 25% of transactions bypass this. */ * Blocks, whitelisted receivers, and a random 25% of transactions bypass this. */
static const unsigned int AVG_INVENTORY_BROADCAST_INTERVAL = 5; static const unsigned int AVG_INVENTORY_BROADCAST_INTERVAL = 5;
/** Average delay between feefilter broadcasts in seconds. */
static const unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60;
/** Maximum feefilter broadcast delay after significant change. */
static const unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60;
/** Block download timeout base, expressed in millionths of the block interval (i.e. 2.5 min) */ /** Block download timeout base, expressed in millionths of the block interval (i.e. 2.5 min) */
static const int64_t BLOCK_DOWNLOAD_TIMEOUT_BASE = 250000; static const int64_t BLOCK_DOWNLOAD_TIMEOUT_BASE = 250000;
/** Additional block download timeout per parallel downloading peer (i.e. 1.25 min) */ /** Additional block download timeout per parallel downloading peer (i.e. 1.25 min) */
@ -132,6 +136,8 @@ static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100;
static const bool DEFAULT_TESTSAFEMODE = false; static const bool DEFAULT_TESTSAFEMODE = false;
/** Default for -mempoolreplacement */ /** Default for -mempoolreplacement */
static const bool DEFAULT_ENABLE_REPLACEMENT = false; static const bool DEFAULT_ENABLE_REPLACEMENT = false;
/** Default for using fee filter */
static const bool DEFAULT_FEEFILTER = true;
/** Maximum number of headers to announce when relaying blocks with headers message.*/ /** Maximum number of headers to announce when relaying blocks with headers message.*/
static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;
@ -310,7 +316,7 @@ void PruneAndFlush();
/** (try to) add transaction to memory pool **/ /** (try to) add transaction to memory pool **/
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree, bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
bool* pfMissingInputs, bool fOverrideMempoolLimit=false, const CAmount nAbsurdFee=0, bool fDryRun=false); bool* pfMissingInputs, CFeeRate* txFeeRate, bool fOverrideMempoolLimit=false, const CAmount nAbsurdFee=0, bool fDryRun=false);
bool GetUTXOCoin(const COutPoint& outpoint, Coin& coin); bool GetUTXOCoin(const COutPoint& outpoint, Coin& coin);
int GetUTXOHeight(const COutPoint& outpoint); int GetUTXOHeight(const COutPoint& outpoint);

View File

@ -37,7 +37,10 @@ static const int NO_BLOOM_VERSION = 70201;
//! "sendheaders" command and announcing blocks with headers starts with this version //! "sendheaders" command and announcing blocks with headers starts with this version
static const int SENDHEADERS_VERSION = 70201; static const int SENDHEADERS_VERSION = 70201;
//! "feefilter" tells peers to filter invs to you by fee starts with this version
static const int FEEFILTER_VERSION = 70209;
//! DIP0001 was activated in this version //! DIP0001 was activated in this version
static const int DIP0001_PROTOCOL_VERSION = 70208; static const int DIP0001_PROTOCOL_VERSION = 99999; // disable for now (clarify deployment later)
#endif // BITCOIN_VERSION_H #endif // BITCOIN_VERSION_H

View File

@ -1759,12 +1759,14 @@ bool CWalletTx::RelayWalletTransaction(CConnman* connman, std::string strCommand
if (GetDepthInMainChain() == 0 && !isAbandoned() && InMempool()) { if (GetDepthInMainChain() == 0 && !isAbandoned() && InMempool()) {
uint256 hash = GetHash(); uint256 hash = GetHash();
LogPrintf("Relaying wtx %s\n", hash.ToString()); LogPrintf("Relaying wtx %s\n", hash.ToString());
CFeeRate feeRate;
mempool.lookupFeeRate(GetHash(), feeRate);
if(strCommand == NetMsgType::TXLOCKREQUEST) { if(strCommand == NetMsgType::TXLOCKREQUEST) {
instantsend.ProcessTxLockRequest(((CTxLockRequest)*this), *connman); instantsend.ProcessTxLockRequest(((CTxLockRequest)*this), *connman);
} }
if (connman) { if (connman) {
connman->RelayTransaction((CTransaction)*this); connman->RelayTransaction((CTransaction)*this, feeRate);
return true; return true;
} }
} }
@ -4695,5 +4697,5 @@ int CMerkleTx::GetBlocksToMaturity() const
bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, CAmount nAbsurdFee) bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, CAmount nAbsurdFee)
{ {
CValidationState state; CValidationState state;
return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, nAbsurdFee); return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, NULL, false, nAbsurdFee);
} }