aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/bips.md1
-rw-r--r--doc/release-notes.md9
-rwxr-xr-xqa/pull-tester/rpc-tests.py3
-rwxr-xr-xqa/rpc-tests/maxuploadtarget.py1
-rwxr-xr-xqa/rpc-tests/p2p-feefilter.py99
-rwxr-xr-xqa/rpc-tests/test_framework/comptool.py14
-rwxr-xr-xqa/rpc-tests/test_framework/mininode.py61
-rw-r--r--src/init.cpp1
-rw-r--r--src/main.cpp73
-rw-r--r--src/main.h8
-rw-r--r--src/net.cpp20
-rw-r--r--src/net.h10
-rw-r--r--src/policy/fees.cpp19
-rw-r--r--src/policy/fees.h13
-rw-r--r--src/protocol.cpp4
-rw-r--r--src/protocol.h7
-rw-r--r--src/rpc/rawtransaction.cpp5
-rw-r--r--src/test/txvalidationcache_tests.cpp2
-rw-r--r--src/txmempool.cpp10
-rw-r--r--src/txmempool.h1
-rw-r--r--src/version.h5
-rw-r--r--src/wallet/wallet.cpp6
22 files changed, 323 insertions, 49 deletions
diff --git a/doc/bips.md b/doc/bips.md
index 2552a7f03a..b8efabbcf2 100644
--- a/doc/bips.md
+++ b/doc/bips.md
@@ -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 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 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)).
diff --git a/doc/release-notes.md b/doc/release-notes.md
index 43e1e3fb9a..806d174ebf 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -53,6 +53,15 @@ The following outputs are affected by this change:
The p2p alert system has been removed in #7692 and the 'alert' message is no longer supported.
+
+Fee filtering of invs (BIP 133)
+------------------------------------
+
+The optional new p2p message "feefilter" is implemented and the protocol
+version is bumped to 70013. Upon receiving a feefilter message from a peer,
+a node will not send invs for any transactions which do not meet the filter
+feerate. [BIP 133](https://github.com/bitcoin/bips/blob/master/bip-0133.mediawiki)
+
### Validation
### Build system
diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py
index f15eaacbda..74be96da74 100755
--- a/qa/pull-tester/rpc-tests.py
+++ b/qa/pull-tester/rpc-tests.py
@@ -127,7 +127,6 @@ testScriptsExt = [
'getblocktemplate_proposals.py',
'txn_doublespend.py',
'txn_clone.py --mineblock',
- 'pruning.py',
'forknotify.py',
'invalidateblock.py',
# 'rpcbind_test.py', #temporary, bug in libevent, see #6655
@@ -137,6 +136,8 @@ testScriptsExt = [
'mempool_packages.py',
'maxuploadtarget.py',
'replace-by-fee.py',
+ 'p2p-feefilter.py',
+ 'pruning.py', # leave pruning last as it takes a REALLY long time
]
#Enable ZMQ tests
diff --git a/qa/rpc-tests/maxuploadtarget.py b/qa/rpc-tests/maxuploadtarget.py
index 2517bed470..e4127500cd 100755
--- a/qa/rpc-tests/maxuploadtarget.py
+++ b/qa/rpc-tests/maxuploadtarget.py
@@ -7,7 +7,6 @@
from test_framework.mininode import *
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
-from test_framework.comptool import wait_until
import time
'''
diff --git a/qa/rpc-tests/p2p-feefilter.py b/qa/rpc-tests/p2p-feefilter.py
new file mode 100755
index 0000000000..f85c18dcd5
--- /dev/null
+++ b/qa/rpc-tests/p2p-feefilter.py
@@ -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()
diff --git a/qa/rpc-tests/test_framework/comptool.py b/qa/rpc-tests/test_framework/comptool.py
index a4cd4d0a89..e3f9b43235 100755
--- a/qa/rpc-tests/test_framework/comptool.py
+++ b/qa/rpc-tests/test_framework/comptool.py
@@ -27,20 +27,6 @@ generator that returns TestInstance objects. See below for definition.
global mininode_lock
-def wait_until(predicate, attempts=float('inf'), timeout=float('inf')):
- attempt = 0
- elapsed = 0
-
- while attempt < attempts and elapsed < timeout:
- with mininode_lock:
- if predicate():
- return True
- attempt += 1
- elapsed += 0.05
- time.sleep(0.05)
-
- return False
-
class RejectResult(object):
'''
Outcome that expects rejection of a transaction or block.
diff --git a/qa/rpc-tests/test_framework/mininode.py b/qa/rpc-tests/test_framework/mininode.py
index 934d0c7a75..20386c642c 100755
--- a/qa/rpc-tests/test_framework/mininode.py
+++ b/qa/rpc-tests/test_framework/mininode.py
@@ -1008,6 +1008,37 @@ class msg_reject(object):
return "msg_reject: %s %d %s [%064x]" \
% (self.message, self.code, self.reason, self.data)
+# Helper function
+def wait_until(predicate, attempts=float('inf'), timeout=float('inf')):
+ attempt = 0
+ elapsed = 0
+
+ while attempt < attempts and elapsed < timeout:
+ with mininode_lock:
+ if predicate():
+ return True
+ attempt += 1
+ elapsed += 0.05
+ time.sleep(0.05)
+
+ 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
# Reimplement the on_* functions to provide handling for events
@@ -1084,7 +1115,34 @@ class NodeConnCB(object):
def on_close(self, conn): pass
def on_mempool(self, conn): 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
+class SingleNodeConnCB(NodeConnCB):
+ def __init__(self):
+ NodeConnCB.__init__(self)
+ self.connection = None
+ self.ping_counter = 1
+ self.last_pong = msg_pong()
+
+ def add_connection(self, conn):
+ self.connection = conn
+
+ # Wrapper for the NodeConn's send_message function
+ def send_message(self, message):
+ self.connection.send_message(message)
+
+ def on_pong(self, conn, message):
+ self.last_pong = message
+
+ # Sync up with the node
+ def sync_with_ping(self, timeout=30):
+ def received_pong():
+ return (self.last_pong.nonce == self.ping_counter)
+ self.send_message(msg_ping(nonce=self.ping_counter))
+ success = wait_until(received_pong, timeout)
+ self.ping_counter += 1
+ return success
# The actual NodeConn class
# This class provides an interface for a p2p connection to a specified node
@@ -1105,7 +1163,8 @@ class NodeConn(asyncore.dispatcher):
"headers": msg_headers,
"getheaders": msg_getheaders,
"reject": msg_reject,
- "mempool": msg_mempool
+ "mempool": msg_mempool,
+ "feefilter": msg_feefilter
}
MAGIC_BYTES = {
"mainnet": "\xf9\xbe\xb4\xd9", # mainnet
diff --git a/src/init.cpp b/src/init.cpp
index f53d7d3c3c..38ac91b2af 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -330,6 +330,7 @@ std::string HelpMessage(HelpMessageMode mode)
}
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("-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("-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));
diff --git a/src/main.cpp b/src/main.cpp
index fc443cfb72..36189f4ffc 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,10 +17,12 @@
#include "init.h"
#include "merkleblock.h"
#include "net.h"
+#include "policy/fees.h"
#include "policy/policy.h"
#include "pow.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
+#include "random.h"
#include "script/script.h"
#include "script/sigcache.h"
#include "script/standard.h"
@@ -81,6 +83,7 @@ CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
CTxMemPool mempool(::minRelayTxFee);
+FeeFilterRounder filterRounder(::minRelayTxFee);
struct COrphanTx {
CTransaction tx;
@@ -987,7 +990,7 @@ std::string FormatStateMessage(const CValidationState &state)
}
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<uint256>& vHashTxnToUncache)
{
const uint256 hash = tx.GetHash();
@@ -1144,6 +1147,9 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase, nSigOps, lp);
unsigned int nSize = entry.GetTxSize();
+ if (txFeeRate) {
+ *txFeeRate = CFeeRate(nFees, nSize);
+ }
// Check that the transaction doesn't have an excessive number of
// sigops, making it impossible to mine. Since the coinbase transaction
@@ -1392,10 +1398,10 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
}
bool AcceptToMemoryPool(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<uint256> vHashTxToUncache;
- bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache);
+ bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, txFeeRate, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache);
if (!res) {
BOOST_FOREACH(const uint256& hashTx, vHashTxToUncache)
pcoinsTip->Uncache(hashTx);
@@ -2620,7 +2626,7 @@ bool static DisconnectTip(CValidationState& state, const Consensus::Params& cons
// ignore validation errors in resurrected transactions
list<CTransaction> removed;
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);
} else if (mempool.exists(tx.GetHash())) {
vHashUpdate.push_back(tx.GetHash());
@@ -4916,10 +4922,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
pfrom->setAskFor.erase(inv.hash);
mapAlreadyAskedFor.erase(inv);
- if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs))
- {
+ CFeeRate txFeeRate = CFeeRate(0);
+ if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs, &txFeeRate)) {
mempool.check(pcoinsTip);
- RelayTransaction(tx);
+ RelayTransaction(tx, txFeeRate);
vWorkQueue.push_back(inv.hash);
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,
if (setMisbehaving.count(fromPeer))
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());
- RelayTransaction(orphanTx);
+ RelayTransaction(orphanTx, orphanFeeRate);
vWorkQueue.push_back(orphanHash);
vEraseQueue.push_back(orphanHash);
}
@@ -5006,7 +5012,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
int nDoS = 0;
if (!state.IsInvalid(nDoS) || nDoS == 0) {
LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->id);
- RelayTransaction(tx);
+ RelayTransaction(tx, txFeeRate);
} else {
LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->id, FormatStateMessage(state));
}
@@ -5200,6 +5206,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if (!fInMemPool) continue; // another thread removed since queryHashes, maybe...
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);
if (vInv.size() == MAX_INV_SZ) {
pfrom->PushMessage(NetMsgType::INV, vInv);
@@ -5362,8 +5375,19 @@ 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 {
// Ignore unknown commands for extensibility
LogPrint("net", "Unknown command \"%s\" from peer=%d\n", SanitizeString(strCommand), pfrom->id);
}
@@ -5845,6 +5869,29 @@ bool SendMessages(CNode* pto)
if (!vGetData.empty())
pto->PushMessage(NetMsgType::GETDATA, vGetData);
+ //
+ // 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) {
+ pto->PushMessage(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;
}
diff --git a/src/main.h b/src/main.h
index a011ba4e55..0bfcfab213 100644
--- a/src/main.h
+++ b/src/main.h
@@ -102,6 +102,10 @@ static const unsigned int AVG_ADDRESS_BROADCAST_INTERVAL = 30;
/** Average delay between trickled inventory broadcasts in seconds.
* Blocks, whitelisted receivers, and a random 25% of transactions bypass this. */
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;
static const unsigned int DEFAULT_LIMITFREERELAY = 15;
static const bool DEFAULT_RELAYPRIORITY = true;
@@ -117,6 +121,8 @@ static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100;
static const bool DEFAULT_TESTSAFEMODE = false;
/** Default for -mempoolreplacement */
static const bool DEFAULT_ENABLE_REPLACEMENT = true;
+/** Default for using fee filter */
+static const bool DEFAULT_FEEFILTER = true;
/** Maximum number of headers to announce when relaying blocks with headers message.*/
static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;
@@ -282,7 +288,7 @@ void PruneAndFlush();
/** (try to) add transaction to memory pool **/
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
- bool* pfMissingInputs, bool fOverrideMempoolLimit=false, const CAmount nAbsurdFee=0);
+ bool* pfMissingInputs, CFeeRate* txFeeRate, bool fOverrideMempoolLimit=false, const CAmount nAbsurdFee=0);
/** Convert CValidationState to a human-readable message for logging */
std::string FormatStateMessage(const CValidationState &state);
diff --git a/src/net.cpp b/src/net.cpp
index b589692d1b..e8cc753a48 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -2053,20 +2053,15 @@ public:
instance_of_cnetcleanup;
-
-
-
-
-
-void RelayTransaction(const CTransaction& tx)
+void RelayTransaction(const CTransaction& tx, CFeeRate feerate)
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss.reserve(10000);
ss << tx;
- RelayTransaction(tx, ss);
+ RelayTransaction(tx, feerate, ss);
}
-void RelayTransaction(const CTransaction& tx, const CDataStream& ss)
+void RelayTransaction(const CTransaction& tx, CFeeRate feerate, const CDataStream& ss)
{
CInv inv(MSG_TX, tx.GetHash());
{
@@ -2087,6 +2082,11 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss)
{
if(!pnode->fRelayTxes)
continue;
+ {
+ LOCK(pnode->cs_feeFilter);
+ if (feerate.GetFeePerK() < pnode->minFeeFilter)
+ continue;
+ }
LOCK(pnode->cs_filter);
if (pnode->pfilter)
{
@@ -2390,6 +2390,10 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa
nPingUsecTime = 0;
fPingQueued = false;
nMinPingUsecTime = std::numeric_limits<int64_t>::max();
+ minFeeFilter = 0;
+ lastSentFeeFilter = 0;
+ nextSendTimeFeeFilter = 0;
+
BOOST_FOREACH(const std::string &msg, getAllNetMessageTypes())
mapRecvBytesPerMsgCmd[msg] = 0;
mapRecvBytesPerMsgCmd[NET_MESSAGE_COMMAND_OTHER] = 0;
diff --git a/src/net.h b/src/net.h
index ec296e2aba..ab9eb68d85 100644
--- a/src/net.h
+++ b/src/net.h
@@ -6,6 +6,7 @@
#ifndef BITCOIN_NET_H
#define BITCOIN_NET_H
+#include "amount.h"
#include "bloom.h"
#include "compat.h"
#include "limitedmap.h"
@@ -415,6 +416,11 @@ public:
int64_t nMinPingUsecTime;
// Whether a ping is requested.
bool fPingQueued;
+ // Minimum fee rate with which to filter inv's to this node
+ CAmount minFeeFilter;
+ CCriticalSection cs_feeFilter;
+ CAmount lastSentFeeFilter;
+ int64_t nextSendTimeFeeFilter;
CNode(SOCKET hSocketIn, const CAddress &addrIn, const std::string &addrNameIn = "", bool fInboundIn = false);
~CNode();
@@ -766,8 +772,8 @@ public:
class CTransaction;
-void RelayTransaction(const CTransaction& tx);
-void RelayTransaction(const CTransaction& tx, const CDataStream& ss);
+void RelayTransaction(const CTransaction& tx, CFeeRate feerate);
+void RelayTransaction(const CTransaction& tx, CFeeRate feerate, const CDataStream& ss);
/** Access to the (IP) address database (peers.dat) */
class CAddrDB
diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp
index de3c060d6a..7b0e8b7d08 100644
--- a/src/policy/fees.cpp
+++ b/src/policy/fees.cpp
@@ -8,6 +8,7 @@
#include "amount.h"
#include "primitives/transaction.h"
+#include "random.h"
#include "streams.h"
#include "txmempool.h"
#include "util.h"
@@ -580,3 +581,21 @@ void CBlockPolicyEstimator::Read(CAutoFile& filein)
priStats.Read(filein);
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;
+}
diff --git a/src/policy/fees.h b/src/policy/fees.h
index 3fa31c39e7..cdd984de7d 100644
--- a/src/policy/fees.h
+++ b/src/policy/fees.h
@@ -286,4 +286,17 @@ private:
CFeeRate feeLikely, feeUnlikely;
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 */
diff --git a/src/protocol.cpp b/src/protocol.cpp
index 1ddb65b796..8c4bd05725 100644
--- a/src/protocol.cpp
+++ b/src/protocol.cpp
@@ -34,6 +34,7 @@ const char *FILTERADD="filteradd";
const char *FILTERCLEAR="filterclear";
const char *REJECT="reject";
const char *SENDHEADERS="sendheaders";
+const char *FEEFILTER="feefilter";
};
static const char* ppszTypeName[] =
@@ -68,7 +69,8 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::FILTERADD,
NetMsgType::FILTERCLEAR,
NetMsgType::REJECT,
- NetMsgType::SENDHEADERS
+ NetMsgType::SENDHEADERS,
+ NetMsgType::FEEFILTER
};
const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
diff --git a/src/protocol.h b/src/protocol.h
index 5504f213f4..1b049e52af 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -211,7 +211,12 @@ extern const char *REJECT;
* @see https://bitcoin.org/en/developer-reference#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;
};
/* Get a vector of all valid message types (see above) */
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index de89fdeb0f..c72339313c 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -818,11 +818,12 @@ UniValue sendrawtransaction(const UniValue& params, bool fHelp)
const CCoins* existingCoins = view.AccessCoins(hashTx);
bool fHaveMempool = mempool.exists(hashTx);
bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000;
+ CFeeRate txFeeRate = CFeeRate(0);
if (!fHaveMempool && !fHaveChain) {
// push to local node and sync with wallets
CValidationState state;
bool fMissingInputs;
- if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, false, nMaxRawTxFee)) {
+ if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, &txFeeRate, false, nMaxRawTxFee)) {
if (state.IsInvalid()) {
throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
} else {
@@ -835,7 +836,7 @@ UniValue sendrawtransaction(const UniValue& params, bool fHelp)
} else if (fHaveChain) {
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
}
- RelayTransaction(tx);
+ RelayTransaction(tx, txFeeRate);
return hashTx.GetHex();
}
diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp
index c29e30792a..237b26329b 100644
--- a/src/test/txvalidationcache_tests.cpp
+++ b/src/test/txvalidationcache_tests.cpp
@@ -23,7 +23,7 @@ ToMemPool(CMutableTransaction& tx)
LOCK(cs_main);
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)
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 088e5edde5..52c7793118 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -771,6 +771,16 @@ bool CTxMemPool::lookup(uint256 hash, CTransaction& result) const
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
{
LOCK(cs);
diff --git a/src/txmempool.h b/src/txmempool.h
index 665bb44cf3..9dbb37dad0 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -600,6 +600,7 @@ public:
}
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
* If no answer can be given at nBlocks, return an estimate
diff --git a/src/version.h b/src/version.h
index af2eb8eab6..0e1d8a63ce 100644
--- a/src/version.h
+++ b/src/version.h
@@ -9,7 +9,7 @@
* network protocol versioning
*/
-static const int PROTOCOL_VERSION = 70012;
+static const int PROTOCOL_VERSION = 70013;
//! initial proto version, to be increased after version/verack negotiation
static const int INIT_PROTO_VERSION = 209;
@@ -36,4 +36,7 @@ static const int NO_BLOOM_VERSION = 70011;
//! "sendheaders" command and announcing blocks with headers starts with this version
static const int SENDHEADERS_VERSION = 70012;
+//! "feefilter" tells peers to filter invs to you by fee starts with this version
+static const int FEEFILTER_VERSION = 70013;
+
#endif // BITCOIN_VERSION_H
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 1ef055e552..654e617071 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1268,7 +1268,9 @@ bool CWalletTx::RelayWalletTransaction()
{
if (GetDepthInMainChain() == 0 && !isAbandoned() && InMempool()) {
LogPrintf("Relaying wtx %s\n", GetHash().ToString());
- RelayTransaction((CTransaction)*this);
+ CFeeRate feeRate;
+ mempool.lookupFeeRate(GetHash(), feeRate);
+ RelayTransaction((CTransaction)*this, feeRate);
return true;
}
}
@@ -3231,5 +3233,5 @@ int CMerkleTx::GetBlocksToMaturity() const
bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, CAmount nAbsurdFee)
{
CValidationState state;
- return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, nAbsurdFee);
+ return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, NULL, false, nAbsurdFee);
}