aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md23
-rw-r--r--contrib/qos/README.md4
-rw-r--r--contrib/qos/tc.sh30
-rwxr-xr-xqa/pull-tester/rpc-tests.py1
-rwxr-xr-xqa/rpc-tests/bumpfee.py317
-rw-r--r--src/Makefile.bench.include1
-rw-r--r--src/bench/checkqueue.cpp103
-rw-r--r--src/net.cpp25
-rw-r--r--src/net.h2
-rw-r--r--src/qt/bitcoingui.cpp14
-rw-r--r--src/qt/bitcoingui.h2
-rw-r--r--src/qt/clientmodel.h2
-rw-r--r--src/qt/modaloverlay.h3
-rw-r--r--src/rpc/client.cpp1
-rw-r--r--src/rpc/server.cpp14
-rw-r--r--src/rpc/server.h5
-rw-r--r--src/wallet/rpcwallet.cpp289
-rw-r--r--src/wallet/wallet.cpp90
-rw-r--r--src/wallet/wallet.h12
19 files changed, 895 insertions, 43 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 06fcd8dd81..47648cde22 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -53,7 +53,28 @@ about Git.
- Create pull request
The title of the pull request should be prefixed by the component or area that
-the pull request affects. Examples:
+the pull request affects. Valid areas as:
+
+ - *Consensus* for changes to consensus critical code
+ - *Docs* for changes to the documentation
+ - *Qt* for changes to bitcoin-qt
+ - *Mining* for changes to the mining code
+ - *Net* or *P2P* for changes to the peer-to-peer network code
+ - *RPC/REST/ZMQ* for changes to the RPC, REST or ZMQ APIs
+ - *Scripts and tools* for changes to the scripts and tools
+ - *Tests* for changes to the bitcoin unit tests or QA tests
+ - *Trivial* should **only** be used for PRs that do not change generated
+ executable code. Notably, refactors (change of function arguments and code
+ reorganization) and changes in behavior should **not** be marked as trivial.
+ Examples of trivial PRs are changes to:
+ - comments
+ - whitespace
+ - variable names
+ - logging and messages
+ - *Utils and libraries* for changes to the utils and libraries
+ - *Wallet* for changes to the wallet code
+
+Examples:
Consensus: Add new opcode for BIP-XXXX OP_CHECKAWESOMESIG
Net: Automatically create hidden service, listen on Tor
diff --git a/contrib/qos/README.md b/contrib/qos/README.md
index 5e0a975fc6..0ded87c58f 100644
--- a/contrib/qos/README.md
+++ b/contrib/qos/README.md
@@ -1,5 +1,5 @@
-### Qos ###
+### QoS (Quality of service) ###
-This is a Linux bash script that will set up tc to limit the outgoing bandwidth for connections to the Bitcoin network. It limits outbound TCP traffic with a source or destination port of 8333, but not if the destination IP is within a LAN (defined as 192.168.x.x).
+This is a Linux bash script that will set up tc to limit the outgoing bandwidth for connections to the Bitcoin network. It limits outbound TCP traffic with a source or destination port of 8333, but not if the destination IP is within a LAN.
This means one can have an always-on bitcoind instance running, and another local bitcoind/bitcoin-qt instance which connects to this node and receives blocks from it.
diff --git a/contrib/qos/tc.sh b/contrib/qos/tc.sh
index aaf5e1fa11..0d1dd65b4f 100644
--- a/contrib/qos/tc.sh
+++ b/contrib/qos/tc.sh
@@ -1,4 +1,4 @@
-# Copyright (c) 2013 The Bitcoin Core developers
+# Copyright (c) 2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -8,8 +8,10 @@ IF="eth0"
LINKCEIL="1gbit"
#limit outbound Bitcoin protocol traffic to this rate
LIMIT="160kbit"
-#defines the address space for which you wish to disable rate limiting
-LOCALNET="192.168.0.0/16"
+#defines the IPv4 address space for which you wish to disable rate limiting
+LOCALNET_V4="192.168.0.0/16"
+#defines the IPv6 address space for which you wish to disable rate limiting
+LOCALNET_V6="fe80::/10"
#delete existing rules
tc qdisc del dev ${IF} root
@@ -28,6 +30,12 @@ tc class add dev ${IF} parent 1:1 classid 1:11 htb rate ${LIMIT} ceil ${LIMIT} p
tc filter add dev ${IF} parent 1: protocol ip prio 1 handle 1 fw classid 1:10
tc filter add dev ${IF} parent 1: protocol ip prio 2 handle 2 fw classid 1:11
+if [ ! -z "${LOCALNET_V6}" ] ; then
+ # v6 cannot have the same priority value as v4
+ tc filter add dev ${IF} parent 1: protocol ipv6 prio 3 handle 1 fw classid 1:10
+ tc filter add dev ${IF} parent 1: protocol ipv6 prio 4 handle 2 fw classid 1:11
+fi
+
#delete any existing rules
#disable for now
#ret=0
@@ -37,9 +45,15 @@ tc filter add dev ${IF} parent 1: protocol ip prio 2 handle 2 fw classid 1:11
#done
#limit outgoing traffic to and from port 8333. but not when dealing with a host on the local network
-# (defined by $LOCALNET)
-# --set-mark marks packages matching these criteria with the number "2"
-# these packages are filtered by the tc filter with "handle 2"
+# (defined by $LOCALNET_V4 and $LOCALNET_V6)
+# --set-mark marks packages matching these criteria with the number "2" (v4)
+# --set-mark marks packages matching these criteria with the number "4" (v6)
+# these packets are filtered by the tc filter with "handle 2"
# this filter sends the packages into the 1:11 class, and this class is limited to ${LIMIT}
-iptables -t mangle -A OUTPUT -p tcp -m tcp --dport 8333 ! -d ${LOCALNET} -j MARK --set-mark 0x2
-iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 8333 ! -d ${LOCALNET} -j MARK --set-mark 0x2
+iptables -t mangle -A OUTPUT -p tcp -m tcp --dport 8333 ! -d ${LOCALNET_V4} -j MARK --set-mark 0x2
+iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 8333 ! -d ${LOCALNET_V4} -j MARK --set-mark 0x2
+
+if [ ! -z "${LOCALNET_V6}" ] ; then
+ ip6tables -t mangle -A OUTPUT -p tcp -m tcp --dport 8333 ! -d ${LOCALNET_V6} -j MARK --set-mark 0x4
+ ip6tables -t mangle -A OUTPUT -p tcp -m tcp --sport 8333 ! -d ${LOCALNET_V6} -j MARK --set-mark 0x4
+fi
diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py
index 83b6bdfe48..c87d3c7127 100755
--- a/qa/pull-tester/rpc-tests.py
+++ b/qa/pull-tester/rpc-tests.py
@@ -151,6 +151,7 @@ testScripts = [
'signmessages.py',
'nulldummy.py',
'import-rescan.py',
+ 'bumpfee.py',
'rpcnamedargs.py',
]
if ENABLE_ZMQ:
diff --git a/qa/rpc-tests/bumpfee.py b/qa/rpc-tests/bumpfee.py
new file mode 100755
index 0000000000..0ebd79f7f3
--- /dev/null
+++ b/qa/rpc-tests/bumpfee.py
@@ -0,0 +1,317 @@
+#!/usr/bin/env python3
+# Copyright (c) 2016 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from segwit import send_to_witness
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework import blocktools
+from test_framework.mininode import CTransaction
+from test_framework.util import *
+from test_framework.util import *
+
+import io
+import time
+
+# Sequence number that is BIP 125 opt-in and BIP 68-compliant
+BIP125_SEQUENCE_NUMBER = 0xfffffffd
+
+WALLET_PASSPHRASE = "test"
+WALLET_PASSPHRASE_TIMEOUT = 3600
+
+
+class BumpFeeTest(BitcoinTestFramework):
+ def __init__(self):
+ super().__init__()
+ self.num_nodes = 2
+ self.setup_clean_chain = True
+
+ def setup_network(self, split=False):
+ extra_args = [["-debug", "-prematurewitness", "-walletprematurewitness", "-walletrbf={}".format(i)]
+ for i in range(self.num_nodes)]
+ self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args)
+
+ # Encrypt wallet for test_locked_wallet_fails test
+ self.nodes[1].encryptwallet(WALLET_PASSPHRASE)
+ bitcoind_processes[1].wait()
+ self.nodes[1] = start_node(1, self.options.tmpdir, extra_args[1])
+ self.nodes[1].walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
+
+ connect_nodes_bi(self.nodes, 0, 1)
+ self.is_network_split = False
+ self.sync_all()
+
+ def run_test(self):
+ peer_node, rbf_node = self.nodes
+ rbf_node_address = rbf_node.getnewaddress()
+
+ # fund rbf node with 10 coins of 0.001 btc (100,000 satoshis)
+ print("Mining blocks...")
+ peer_node.generate(110)
+ self.sync_all()
+ for i in range(25):
+ peer_node.sendtoaddress(rbf_node_address, 0.001)
+ self.sync_all()
+ peer_node.generate(1)
+ self.sync_all()
+ assert_equal(rbf_node.getbalance(), Decimal("0.025"))
+
+ print("Running tests")
+ dest_address = peer_node.getnewaddress()
+ test_small_output_fails(rbf_node, dest_address)
+ test_dust_to_fee(rbf_node, dest_address)
+ test_simple_bumpfee_succeeds(rbf_node, peer_node, dest_address)
+ test_segwit_bumpfee_succeeds(rbf_node, dest_address)
+ test_nonrbf_bumpfee_fails(peer_node, dest_address)
+ test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address)
+ test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address)
+ test_settxfee(rbf_node, dest_address)
+ test_rebumping(rbf_node, dest_address)
+ test_rebumping_not_replaceable(rbf_node, dest_address)
+ test_unconfirmed_not_spendable(rbf_node, rbf_node_address)
+ test_locked_wallet_fails(rbf_node, dest_address)
+ print("Success")
+
+
+def test_simple_bumpfee_succeeds(rbf_node, peer_node, dest_address):
+ rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000})
+ rbftx = rbf_node.gettransaction(rbfid)
+ sync_mempools((rbf_node, peer_node))
+ assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
+ bumped_tx = rbf_node.bumpfee(rbfid)
+ assert bumped_tx["fee"] - abs(rbftx["fee"]) > 0
+ # check that bumped_tx propogates, original tx was evicted and has a wallet conflict
+ sync_mempools((rbf_node, peer_node))
+ assert bumped_tx["txid"] in rbf_node.getrawmempool()
+ assert bumped_tx["txid"] in peer_node.getrawmempool()
+ assert rbfid not in rbf_node.getrawmempool()
+ assert rbfid not in peer_node.getrawmempool()
+ oldwtx = rbf_node.gettransaction(rbfid)
+ assert len(oldwtx["walletconflicts"]) > 0
+ # check wallet transaction replaces and replaced_by values
+ bumpedwtx = rbf_node.gettransaction(bumped_tx["txid"])
+ assert_equal(oldwtx["replaced_by_txid"], bumped_tx["txid"])
+ assert_equal(bumpedwtx["replaces_txid"], rbfid)
+
+
+def test_segwit_bumpfee_succeeds(rbf_node, dest_address):
+ # Create a transaction with segwit output, then create an RBF transaction
+ # which spends it, and make sure bumpfee can be called on it.
+
+ segwit_in = next(u for u in rbf_node.listunspent() if u["amount"] == Decimal("0.001"))
+ segwit_out = rbf_node.validateaddress(rbf_node.getnewaddress())
+ rbf_node.addwitnessaddress(segwit_out["address"])
+ segwitid = send_to_witness(
+ version=0,
+ node=rbf_node,
+ utxo=segwit_in,
+ pubkey=segwit_out["pubkey"],
+ encode_p2sh=False,
+ amount=Decimal("0.0009"),
+ sign=True)
+
+ rbfraw = rbf_node.createrawtransaction([{
+ 'txid': segwitid,
+ 'vout': 0,
+ "sequence": BIP125_SEQUENCE_NUMBER
+ }], {dest_address: Decimal("0.0005"),
+ get_change_address(rbf_node): Decimal("0.0003")})
+ rbfsigned = rbf_node.signrawtransaction(rbfraw)
+ rbfid = rbf_node.sendrawtransaction(rbfsigned["hex"])
+ assert rbfid in rbf_node.getrawmempool()
+
+ bumped_tx = rbf_node.bumpfee(rbfid)
+ assert bumped_tx["txid"] in rbf_node.getrawmempool()
+ assert rbfid not in rbf_node.getrawmempool()
+
+
+def test_nonrbf_bumpfee_fails(peer_node, dest_address):
+ # cannot replace a non RBF transaction (from node which did not enable RBF)
+ not_rbfid = create_fund_sign_send(peer_node, {dest_address: 0.00090000})
+ assert_raises_message(JSONRPCException, "not BIP 125 replaceable", peer_node.bumpfee, not_rbfid)
+
+
+def test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address):
+ # cannot bump fee unless the tx has only inputs that we own.
+ # here, the rbftx has a peer_node coin and then adds a rbf_node input
+ # Note that this test depends upon the RPC code checking input ownership prior to change outputs
+ # (since it can't use fundrawtransaction, it lacks a proper change output)
+ utxos = [node.listunspent()[-1] for node in (rbf_node, peer_node)]
+ inputs = [{
+ "txid": utxo["txid"],
+ "vout": utxo["vout"],
+ "address": utxo["address"],
+ "sequence": BIP125_SEQUENCE_NUMBER
+ } for utxo in utxos]
+ output_val = sum(utxo["amount"] for utxo in utxos) - Decimal("0.001")
+ rawtx = rbf_node.createrawtransaction(inputs, {dest_address: output_val})
+ signedtx = rbf_node.signrawtransaction(rawtx)
+ signedtx = peer_node.signrawtransaction(signedtx["hex"])
+ rbfid = rbf_node.sendrawtransaction(signedtx["hex"])
+ assert_raises_message(JSONRPCException, "Transaction contains inputs that don't belong to this wallet",
+ rbf_node.bumpfee, rbfid)
+
+
+def test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address):
+ # cannot bump fee if the transaction has a descendant
+ # parent is send-to-self, so we don't have to check which output is change when creating the child tx
+ parent_id = create_fund_sign_send(rbf_node, {rbf_node_address: 0.00050000})
+ tx = rbf_node.createrawtransaction([{"txid": parent_id, "vout": 0}], {dest_address: 0.00020000})
+ tx = rbf_node.signrawtransaction(tx)
+ txid = rbf_node.sendrawtransaction(tx["hex"])
+ assert_raises_message(JSONRPCException, "Transaction has descendants in the wallet", rbf_node.bumpfee, parent_id)
+
+
+def test_small_output_fails(rbf_node, dest_address):
+ # cannot bump fee with a too-small output
+ rbfid = spend_one_input(rbf_node,
+ Decimal("0.00100000"),
+ {dest_address: 0.00080000,
+ get_change_address(rbf_node): Decimal("0.00010000")})
+ rbf_node.bumpfee(rbfid, {"totalFee": 20000})
+
+ rbfid = spend_one_input(rbf_node,
+ Decimal("0.00100000"),
+ {dest_address: 0.00080000,
+ get_change_address(rbf_node): Decimal("0.00010000")})
+ assert_raises_message(JSONRPCException, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 20001})
+
+
+def test_dust_to_fee(rbf_node, dest_address):
+ # check that if output is reduced to dust, it will be converted to fee
+ # the bumped tx sets fee=9900, but it converts to 10,000
+ rbfid = spend_one_input(rbf_node,
+ Decimal("0.00100000"),
+ {dest_address: 0.00080000,
+ get_change_address(rbf_node): Decimal("0.00010000")})
+ fulltx = rbf_node.getrawtransaction(rbfid, 1)
+ bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 19900})
+ full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1)
+ assert_equal(bumped_tx["fee"], Decimal("0.00020000"))
+ assert_equal(len(fulltx["vout"]), 2)
+ assert_equal(len(full_bumped_tx["vout"]), 1) #change output is eliminated
+
+
+def test_settxfee(rbf_node, dest_address):
+ # check that bumpfee reacts correctly to the use of settxfee (paytxfee)
+ # increase feerate by 2.5x, test that fee increased at least 2x
+ rbf_node.settxfee(Decimal("0.00001000"))
+ rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000})
+ rbftx = rbf_node.gettransaction(rbfid)
+ rbf_node.settxfee(Decimal("0.00002500"))
+ bumped_tx = rbf_node.bumpfee(rbfid)
+ assert bumped_tx["fee"] > 2 * abs(rbftx["fee"])
+ rbf_node.settxfee(Decimal("0.00000000")) # unset paytxfee
+
+
+def test_rebumping(rbf_node, dest_address):
+ # check that re-bumping the original tx fails, but bumping the bumper succeeds
+ rbf_node.settxfee(Decimal("0.00001000"))
+ rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000})
+ bumped = rbf_node.bumpfee(rbfid, {"totalFee": 1000})
+ assert_raises_message(JSONRPCException, "already bumped", rbf_node.bumpfee, rbfid, {"totalFee": 2000})
+ rbf_node.bumpfee(bumped["txid"], {"totalFee": 2000})
+
+
+def test_rebumping_not_replaceable(rbf_node, dest_address):
+ # check that re-bumping a non-replaceable bump tx fails
+ rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000})
+ bumped = rbf_node.bumpfee(rbfid, {"totalFee": 10000, "replaceable": False})
+ assert_raises_message(JSONRPCException, "Transaction is not BIP 125 replaceable", rbf_node.bumpfee, bumped["txid"],
+ {"totalFee": 20000})
+
+
+def test_unconfirmed_not_spendable(rbf_node, rbf_node_address):
+ # check that unconfirmed outputs from bumped transactions are not spendable
+ rbfid = create_fund_sign_send(rbf_node, {rbf_node_address: 0.00090000})
+ rbftx = rbf_node.gettransaction(rbfid)["hex"]
+ assert rbfid in rbf_node.getrawmempool()
+ bumpid = rbf_node.bumpfee(rbfid)["txid"]
+ assert bumpid in rbf_node.getrawmempool()
+ assert rbfid not in rbf_node.getrawmempool()
+
+ # check that outputs from the bump transaction are not spendable
+ # due to the replaces_txid check in CWallet::AvailableCoins
+ assert_equal([t for t in rbf_node.listunspent(minconf=0, include_unsafe=False) if t["txid"] == bumpid], [])
+
+ # submit a block with the rbf tx to clear the bump tx out of the mempool,
+ # then call abandon to make sure the wallet doesn't attempt to resubmit the
+ # bump tx, then invalidate the block so the rbf tx will be put back in the
+ # mempool. this makes it possible to check whether the rbf tx outputs are
+ # spendable before the rbf tx is confirmed.
+ block = submit_block_with_tx(rbf_node, rbftx)
+ rbf_node.abandontransaction(bumpid)
+ rbf_node.invalidateblock(block.hash)
+ assert bumpid not in rbf_node.getrawmempool()
+ assert rbfid in rbf_node.getrawmempool()
+
+ # check that outputs from the rbf tx are not spendable before the
+ # transaction is confirmed, due to the replaced_by_txid check in
+ # CWallet::AvailableCoins
+ assert_equal([t for t in rbf_node.listunspent(minconf=0, include_unsafe=False) if t["txid"] == rbfid], [])
+
+ # check that the main output from the rbf tx is spendable after confirmed
+ rbf_node.generate(1)
+ assert_equal(
+ sum(1 for t in rbf_node.listunspent(minconf=0, include_unsafe=False)
+ if t["txid"] == rbfid and t["address"] == rbf_node_address and t["spendable"]), 1)
+
+
+def test_locked_wallet_fails(rbf_node, dest_address):
+ rbfid = create_fund_sign_send(rbf_node, {dest_address: 0.00090000})
+ rbf_node.walletlock()
+ assert_raises_message(JSONRPCException, "Please enter the wallet passphrase with walletpassphrase first.",
+ rbf_node.bumpfee, rbfid)
+
+
+def create_fund_sign_send(node, outputs):
+ rawtx = node.createrawtransaction([], outputs)
+ fundtx = node.fundrawtransaction(rawtx)
+ signedtx = node.signrawtransaction(fundtx["hex"])
+ txid = node.sendrawtransaction(signedtx["hex"])
+ return txid
+
+
+def spend_one_input(node, input_amount, outputs):
+ input = dict(sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in node.listunspent() if u["amount"] == input_amount))
+ rawtx = node.createrawtransaction([input], outputs)
+ signedtx = node.signrawtransaction(rawtx)
+ txid = node.sendrawtransaction(signedtx["hex"])
+ return txid
+
+
+def get_change_address(node):
+ """Get a wallet change address.
+
+ There is no wallet RPC to access unused change addresses, so this creates a
+ dummy transaction, calls fundrawtransaction to give add an input and change
+ output, then returns the change address."""
+ dest_address = node.getnewaddress()
+ dest_amount = Decimal("0.00012345")
+ rawtx = node.createrawtransaction([], {dest_address: dest_amount})
+ fundtx = node.fundrawtransaction(rawtx)
+ info = node.decoderawtransaction(fundtx["hex"])
+ return next(address for out in info["vout"]
+ if out["value"] != dest_amount for address in out["scriptPubKey"]["addresses"])
+
+
+def submit_block_with_tx(node, tx):
+ ctx = CTransaction()
+ ctx.deserialize(io.BytesIO(hex_str_to_bytes(tx)))
+
+ tip = node.getbestblockhash()
+ height = node.getblockcount() + 1
+ block_time = node.getblockheader(tip)["mediantime"] + 1
+ block = blocktools.create_block(int(tip, 16), blocktools.create_coinbase(height), block_time)
+ block.vtx.append(ctx)
+ block.rehash()
+ block.hashMerkleRoot = block.calc_merkle_root()
+ block.solve()
+ error = node.submitblock(bytes_to_hex_str(block.serialize(True)))
+ if error is not None:
+ raise Exception(error)
+ return block
+
+
+if __name__ == "__main__":
+ BumpFeeTest().main()
diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include
index e58bd9dfbf..8c699c2f8c 100644
--- a/src/Makefile.bench.include
+++ b/src/Makefile.bench.include
@@ -15,6 +15,7 @@ bench_bench_bitcoin_SOURCES = \
bench/bench.cpp \
bench/bench.h \
bench/checkblock.cpp \
+ bench/checkqueue.cpp \
bench/Examples.cpp \
bench/rollingbloom.cpp \
bench/crypto_hash.cpp \
diff --git a/src/bench/checkqueue.cpp b/src/bench/checkqueue.cpp
new file mode 100644
index 0000000000..6fa9fe4fe8
--- /dev/null
+++ b/src/bench/checkqueue.cpp
@@ -0,0 +1,103 @@
+// Copyright (c) 2015 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "bench.h"
+#include "util.h"
+#include "validation.h"
+#include "checkqueue.h"
+#include "prevector.h"
+#include <vector>
+#include <boost/thread/thread.hpp>
+#include "random.h"
+
+
+// This Benchmark tests the CheckQueue with the lightest
+// weight Checks, so it should make any lock contention
+// particularly visible
+static const int MIN_CORES = 2;
+static const size_t BATCHES = 101;
+static const size_t BATCH_SIZE = 30;
+static const int PREVECTOR_SIZE = 28;
+static const int QUEUE_BATCH_SIZE = 128;
+static void CCheckQueueSpeed(benchmark::State& state)
+{
+ struct FakeJobNoWork {
+ bool operator()()
+ {
+ return true;
+ }
+ void swap(FakeJobNoWork& x){};
+ };
+ CCheckQueue<FakeJobNoWork> queue {QUEUE_BATCH_SIZE};
+ boost::thread_group tg;
+ for (auto x = 0; x < std::max(MIN_CORES, GetNumCores()); ++x) {
+ tg.create_thread([&]{queue.Thread();});
+ }
+ while (state.KeepRunning()) {
+ CCheckQueueControl<FakeJobNoWork> control(&queue);
+
+ // We call Add a number of times to simulate the behavior of adding
+ // a block of transactions at once.
+
+ std::vector<std::vector<FakeJobNoWork>> vBatches(BATCHES);
+ for (auto& vChecks : vBatches) {
+ vChecks.resize(BATCH_SIZE);
+ }
+ for (auto& vChecks : vBatches) {
+ // We can't make vChecks in the inner loop because we want to measure
+ // the cost of getting the memory to each thread and we might get the same
+ // memory
+ control.Add(vChecks);
+ }
+ // control waits for completion by RAII, but
+ // it is done explicitly here for clarity
+ control.Wait();
+ }
+ tg.interrupt_all();
+ tg.join_all();
+}
+
+// This Benchmark tests the CheckQueue with a slightly realistic workload,
+// where checks all contain a prevector that is indirect 50% of the time
+// and there is a little bit of work done between calls to Add.
+static void CCheckQueueSpeedPrevectorJob(benchmark::State& state)
+{
+ struct PrevectorJob {
+ prevector<PREVECTOR_SIZE, uint8_t> p;
+ PrevectorJob(){
+ }
+ PrevectorJob(FastRandomContext& insecure_rand){
+ p.resize(insecure_rand.rand32() % (PREVECTOR_SIZE*2));
+ }
+ bool operator()()
+ {
+ return true;
+ }
+ void swap(PrevectorJob& x){p.swap(x.p);};
+ };
+ CCheckQueue<PrevectorJob> queue {QUEUE_BATCH_SIZE};
+ boost::thread_group tg;
+ for (auto x = 0; x < std::max(MIN_CORES, GetNumCores()); ++x) {
+ tg.create_thread([&]{queue.Thread();});
+ }
+ while (state.KeepRunning()) {
+ // Make insecure_rand here so that each iteration is identical.
+ FastRandomContext insecure_rand(true);
+ CCheckQueueControl<PrevectorJob> control(&queue);
+ std::vector<std::vector<PrevectorJob>> vBatches(BATCHES);
+ for (auto& vChecks : vBatches) {
+ vChecks.reserve(BATCH_SIZE);
+ for (size_t x = 0; x < BATCH_SIZE; ++x)
+ vChecks.emplace_back(insecure_rand);
+ control.Add(vChecks);
+ }
+ // control waits for completion by RAII, but
+ // it is done explicitly here for clarity
+ control.Wait();
+ }
+ tg.interrupt_all();
+ tg.join_all();
+}
+BENCHMARK(CCheckQueueSpeed);
+BENCHMARK(CCheckQueueSpeedPrevectorJob);
diff --git a/src/net.cpp b/src/net.cpp
index b275bdd809..1019d59544 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1147,12 +1147,10 @@ void CConnman::ThreadSocketHandler()
// * Hand off all complete messages to the processor, to be handled without
// blocking here.
{
- TRY_LOCK(pnode->cs_vSend, lockSend);
- if (lockSend) {
- if (!pnode->vSendMsg.empty()) {
- FD_SET(pnode->hSocket, &fdsetSend);
- continue;
- }
+ LOCK(pnode->cs_vSend);
+ if (!pnode->vSendMsg.empty()) {
+ FD_SET(pnode->hSocket, &fdsetSend);
+ continue;
}
}
{
@@ -1272,12 +1270,10 @@ void CConnman::ThreadSocketHandler()
continue;
if (FD_ISSET(pnode->hSocket, &fdsetSend))
{
- TRY_LOCK(pnode->cs_vSend, lockSend);
- if (lockSend) {
- size_t nBytes = SocketSendData(pnode);
- if (nBytes) {
- RecordBytesSent(nBytes);
- }
+ LOCK(pnode->cs_vSend);
+ size_t nBytes = SocketSendData(pnode);
+ if (nBytes) {
+ RecordBytesSent(nBytes);
}
}
@@ -1875,9 +1871,8 @@ void CConnman::ThreadMessageHandler()
// Send messages
{
- TRY_LOCK(pnode->cs_vSend, lockSend);
- if (lockSend)
- GetNodeSignals().SendMessages(pnode, *this, flagInterruptMsgProc);
+ LOCK(pnode->cs_sendProcessing);
+ GetNodeSignals().SendMessages(pnode, *this, flagInterruptMsgProc);
}
if (flagInterruptMsgProc)
return;
diff --git a/src/net.h b/src/net.h
index a9da38146b..505962f51a 100644
--- a/src/net.h
+++ b/src/net.h
@@ -618,6 +618,8 @@ public:
std::list<CNetMessage> vProcessMsg;
size_t nProcessQueueSize;
+ CCriticalSection cs_sendProcessing;
+
std::deque<CInv> vRecvGetData;
uint64_t nRecvBytes;
std::atomic<int> nRecvVersion;
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index a3cb376bcf..b86437cede 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -748,6 +748,15 @@ void BitcoinGUI::setNetworkActive(bool networkActive)
updateNetworkState();
}
+void BitcoinGUI::updateHeadersSyncProgressLabel()
+{
+ int64_t headersTipTime = clientModel->getHeaderTipTime();
+ int headersTipHeight = clientModel->getHeaderTipHeight();
+ int estHeadersLeft = (GetTime() - headersTipTime)/600;
+ if (estHeadersLeft > REQ_HEADER_HEIGHT_DELTA_SYNC)
+ progressBarLabel->setText(tr("Syncing Headers (%1%)...").arg(QString::number(100.0 / (headersTipHeight+estHeadersLeft)*headersTipHeight, 'f', 1)));
+}
+
void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool header)
{
if (modalOverlay)
@@ -768,9 +777,11 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer
switch (blockSource) {
case BLOCK_SOURCE_NETWORK:
if (header) {
+ updateHeadersSyncProgressLabel();
return;
}
progressBarLabel->setText(tr("Synchronizing with network..."));
+ updateHeadersSyncProgressLabel();
break;
case BLOCK_SOURCE_DISK:
if (header) {
@@ -786,8 +797,7 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer
if (header) {
return;
}
- // Case: not Importing, not Reindexing and no network connection
- progressBarLabel->setText(tr("No block source available..."));
+ progressBarLabel->setText(tr("Connecting to peers..."));
break;
}
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index c8b6ce5474..62d419d3ef 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -149,6 +149,8 @@ private:
/** Update UI with latest network info from model. */
void updateNetworkState();
+ void updateHeadersSyncProgressLabel();
+
Q_SIGNALS:
/** Signal raised when a URI was entered or dragged to the GUI */
void receivedURI(const QString &uri);
diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h
index c01c1a84cf..2c10e633b8 100644
--- a/src/qt/clientmodel.h
+++ b/src/qt/clientmodel.h
@@ -66,7 +66,7 @@ public:
//! Return true if core is doing initial block download
bool inInitialBlockDownload() const;
- //! Return true if core is importing blocks
+ //! Returns enum BlockSource of the current importing/syncing state
enum BlockSource getBlockSource() const;
//! Return true if network activity in core is enabled
bool getNetworkActive() const;
diff --git a/src/qt/modaloverlay.h b/src/qt/modaloverlay.h
index 70d37b87ad..6d1f12164e 100644
--- a/src/qt/modaloverlay.h
+++ b/src/qt/modaloverlay.h
@@ -8,6 +8,9 @@
#include <QDateTime>
#include <QWidget>
+//! The required delta of headers to the estimated number of available headers until we show the IBD progress
+static const int REQ_HEADER_HEIGHT_DELTA_SYNC = 24;
+
namespace Ui {
class ModalOverlay;
}
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 5d3c458455..5bdd84e555 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -117,6 +117,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "setnetworkactive", 0, "state" },
{ "getmempoolancestors", 1, "verbose" },
{ "getmempooldescendants", 1, "verbose" },
+ { "bumpfee", 1, "options" },
// Echo with conversion (For testing only)
{ "echojson", 0, "arg0" },
{ "echojson", 1, "arg1" },
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index 1b94e10071..283d458c8d 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -79,16 +79,20 @@ void RPCTypeCheck(const UniValue& params,
break;
const UniValue& v = params[i];
- if (!((v.type() == t) || (fAllowNull && (v.isNull()))))
- {
- string err = strprintf("Expected type %s, got %s",
- uvTypeName(t), uvTypeName(v.type()));
- throw JSONRPCError(RPC_TYPE_ERROR, err);
+ if (!(fAllowNull && v.isNull())) {
+ RPCTypeCheckArgument(v, t);
}
i++;
}
}
+void RPCTypeCheckArgument(const UniValue& value, UniValue::VType typeExpected)
+{
+ if (value.type() != typeExpected) {
+ throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected), uvTypeName(value.type())));
+ }
+}
+
void RPCTypeCheckObj(const UniValue& o,
const map<string, UniValueType>& typesExpected,
bool fAllowNull,
diff --git a/src/rpc/server.h b/src/rpc/server.h
index fed3d8c90f..52f82866dc 100644
--- a/src/rpc/server.h
+++ b/src/rpc/server.h
@@ -78,6 +78,11 @@ bool RPCIsInWarmup(std::string *statusOut);
void RPCTypeCheck(const UniValue& params,
const std::list<UniValue::VType>& typesExpected, bool fAllowNull=false);
+/**
+ * Type-check one argument; throws JSONRPCError if wrong type given.
+ */
+void RPCTypeCheckArgument(const UniValue& value, UniValue::VType typeExpected);
+
/*
Check for expected keys/value types in an Object.
*/
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index fa54197e75..55e7558498 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -11,8 +11,10 @@
#include "init.h"
#include "validation.h"
#include "net.h"
+#include "policy/policy.h"
#include "policy/rbf.h"
#include "rpc/server.h"
+#include "script/sign.h"
#include "timedata.h"
#include "util.h"
#include "utilmoneystr.h"
@@ -2364,9 +2366,9 @@ UniValue listunspent(const JSONRPCRequest& request)
if (!EnsureWalletIsAvailable(request.fHelp))
return NullUniValue;
- if (request.fHelp || request.params.size() > 3)
+ if (request.fHelp || request.params.size() > 4)
throw runtime_error(
- "listunspent ( minconf maxconf [\"addresses\",...] )\n"
+ "listunspent ( minconf maxconf [\"addresses\",...] [include_unsafe] )\n"
"\nReturns array of unspent transaction outputs\n"
"with between minconf and maxconf (inclusive) confirmations.\n"
"Optionally filter to only include txouts paid to specified addresses.\n"
@@ -2378,6 +2380,10 @@ UniValue listunspent(const JSONRPCRequest& request)
" \"address\" (string) bitcoin address\n"
" ,...\n"
" ]\n"
+ "4. include_unsafe (bool, optional, default=true) Include outputs that are not safe to spend\n"
+ " because they come from unconfirmed untrusted transactions or unconfirmed\n"
+ " replacement transactions (cases where we are less sure that a conflicting\n"
+ " transaction won't be mined).\n"
"\nResult\n"
"[ (array of json object)\n"
" {\n"
@@ -2401,18 +2407,21 @@ UniValue listunspent(const JSONRPCRequest& request)
+ HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"")
);
- RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VARR));
-
int nMinDepth = 1;
- if (request.params.size() > 0)
+ if (request.params.size() > 0 && !request.params[0].isNull()) {
+ RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
nMinDepth = request.params[0].get_int();
+ }
int nMaxDepth = 9999999;
- if (request.params.size() > 1)
+ if (request.params.size() > 1 && !request.params[1].isNull()) {
+ RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
nMaxDepth = request.params[1].get_int();
+ }
set<CBitcoinAddress> setAddress;
- if (request.params.size() > 2) {
+ if (request.params.size() > 2 && !request.params[2].isNull()) {
+ RPCTypeCheckArgument(request.params[2], UniValue::VARR);
UniValue inputs = request.params[2].get_array();
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
const UniValue& input = inputs[idx];
@@ -2425,11 +2434,17 @@ UniValue listunspent(const JSONRPCRequest& request)
}
}
+ bool include_unsafe = true;
+ if (request.params.size() > 3 && !request.params[3].isNull()) {
+ RPCTypeCheckArgument(request.params[3], UniValue::VBOOL);
+ include_unsafe = request.params[3].get_bool();
+ }
+
UniValue results(UniValue::VARR);
vector<COutput> vecOutputs;
assert(pwalletMain != NULL);
LOCK2(cs_main, pwalletMain->cs_wallet);
- pwalletMain->AvailableCoins(vecOutputs, false, NULL, true);
+ pwalletMain->AvailableCoins(vecOutputs, !include_unsafe, NULL, true);
BOOST_FOREACH(const COutput& out, vecOutputs) {
if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth)
continue;
@@ -2625,6 +2640,261 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
return result;
}
+UniValue bumpfee(const JSONRPCRequest& request)
+{
+ if (!EnsureWalletIsAvailable(request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
+ throw runtime_error(
+ "bumpfee \"txid\" ( options ) \n"
+ "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
+ "An opt-in RBF transaction with the given txid must be in the wallet.\n"
+ "The command will pay the additional fee by decreasing (or perhaps removing) its change output.\n"
+ "If the change output is not big enough to cover the increased fee, the command will currently fail\n"
+ "instead of adding new inputs to compensate. (A future implementation could improve this.)\n"
+ "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n"
+ "By default, the new fee will be calculated automatically using estimatefee.\n"
+ "The user can specify a confirmation target for estimatefee.\n"
+ "Alternatively, the user can specify totalFee, or use RPC setpaytxfee to set a higher fee rate.\n"
+ "At a minimum, the new fee rate must be high enough to pay a new relay fee (relay fee amount returned\n"
+ "by getnetworkinfo RPC) and to enter the node's mempool.\n"
+ "\nArguments:\n"
+ "1. txid (string, required) The txid to be bumped\n"
+ "2. options (object, optional)\n"
+ " {\n"
+ " \"confTarget\" (numeric, optional) Confirmation target (in blocks)\n"
+ " \"totalFee\" (numeric, optional) Total fee (NOT feerate) to pay, in satoshis.\n"
+ " In rare cases, the actual fee paid might be slightly higher than the specified\n"
+ " totalFee if the tx change output has to be removed because it is too close to\n"
+ " the dust threshold.\n"
+ " \"replaceable\" (boolean, optional, default true) Whether the new transaction should still be\n"
+ " marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n"
+ " be left unchanged from the original. If false, any input sequence numbers in the\n"
+ " original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n"
+ " so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
+ " still be replacable in practice, for example if it has unconfirmed ancestors which\n"
+ " are replaceable).\n"
+ " }\n"
+ "\nResult:\n"
+ "{\n"
+ " \"txid\": \"value\", (string) The id of the new transaction\n"
+ " \"oldfee\": n, (numeric) Fee of the replaced transaction\n"
+ " \"fee\": n, (numeric) Fee of the new transaction\n"
+ "}\n"
+ "\nExamples:\n"
+ "\nBump the fee, get the new transaction\'s txid\n" +
+ HelpExampleCli("bumpfee", "<txid>"));
+ }
+
+ RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ));
+ uint256 hash;
+ hash.SetHex(request.params[0].get_str());
+
+ // retrieve the original tx from the wallet
+ LOCK2(cs_main, pwalletMain->cs_wallet);
+ EnsureWalletIsUnlocked();
+ if (!pwalletMain->mapWallet.count(hash)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
+ }
+ CWalletTx& wtx = pwalletMain->mapWallet[hash];
+
+ if (pwalletMain->HasWalletSpend(hash)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction has descendants in the wallet");
+ }
+
+ {
+ LOCK(mempool.cs);
+ auto it = mempool.mapTx.find(hash);
+ if (it != mempool.mapTx.end() && it->GetCountWithDescendants() > 1) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction has descendants in the mempool");
+ }
+ }
+
+ if (wtx.GetDepthInMainChain() != 0) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction has been mined, or is conflicted with a mined transaction");
+ }
+
+ if (!SignalsOptInRBF(wtx)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction is not BIP 125 replaceable");
+ }
+
+ if (wtx.mapValue.count("replaced_by_txid")) {
+ throw JSONRPCError(RPC_INVALID_REQUEST, strprintf("Cannot bump transaction %s which was already bumped by transaction %s", hash.ToString(), wtx.mapValue.at("replaced_by_txid")));
+ }
+
+ // check that original tx consists entirely of our inputs
+ // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee)
+ if (!pwalletMain->IsAllFromMe(wtx, ISMINE_SPENDABLE)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that don't belong to this wallet");
+ }
+
+ // figure out which output was change
+ // if there was no change output or multiple change outputs, fail
+ int nOutput = -1;
+ for (size_t i = 0; i < wtx.tx->vout.size(); ++i) {
+ if (pwalletMain->IsChange(wtx.tx->vout[i])) {
+ if (nOutput != -1) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction has multiple change outputs");
+ }
+ nOutput = i;
+ }
+ }
+ if (nOutput == -1) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction does not have a change output");
+ }
+
+ // optional parameters
+ bool specifiedConfirmTarget = false;
+ int newConfirmTarget = nTxConfirmTarget;
+ CAmount totalFee = 0;
+ bool replaceable = true;
+ if (request.params.size() > 1) {
+ UniValue options = request.params[1];
+ RPCTypeCheckObj(options,
+ {
+ {"confTarget", UniValueType(UniValue::VNUM)},
+ {"totalFee", UniValueType(UniValue::VNUM)},
+ {"replaceable", UniValueType(UniValue::VBOOL)},
+ },
+ true, true);
+
+ if (options.exists("confTarget") && options.exists("totalFee")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and totalFee options should not both be set. Please provide either a confirmation target for fee estimation or an explicit total fee for the transaction.");
+ } else if (options.exists("confTarget")) {
+ specifiedConfirmTarget = true;
+ newConfirmTarget = options["confTarget"].get_int();
+ if (newConfirmTarget <= 0) { // upper-bound will be checked by estimatefee/smartfee
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid confTarget (cannot be <= 0)");
+ }
+ } else if (options.exists("totalFee")) {
+ totalFee = options["totalFee"].get_int64();
+ if (totalFee <= 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid totalFee (cannot be <= 0)");
+ } else if (totalFee > maxTxFee) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid totalFee (cannot be higher than maxTxFee)");
+ }
+ }
+
+ if (options.exists("replaceable")) {
+ replaceable = options["replaceable"].get_bool();
+ }
+ }
+
+ // signature sizes can vary by a byte, so add 1 for each input when calculating the new fee
+ int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
+ const int64_t maxNewTxSize = txSize + wtx.tx->vin.size();
+
+ // calculate the old fee and fee-rate
+ CAmount nOldFee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
+ CFeeRate nOldFeeRate(nOldFee, txSize);
+ CAmount nNewFee;
+ CFeeRate nNewFeeRate;
+
+ if (totalFee > 0) {
+ CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + minRelayTxFee.GetFee(maxNewTxSize);
+ if (totalFee < minTotalFee) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid totalFee, must be at least %s (oldFee %s + relayFee %s)", FormatMoney(minTotalFee), nOldFeeRate.GetFee(maxNewTxSize), minRelayTxFee.GetFee(maxNewTxSize)));
+ }
+ nNewFee = totalFee;
+ nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
+ } else {
+ // use the user-defined payTxFee if possible, otherwise use smartfee / fallbackfee
+ if (!specifiedConfirmTarget && payTxFee.GetFeePerK() != 0) {
+ nNewFeeRate = payTxFee;
+ } else {
+ nNewFeeRate = mempool.estimateSmartFee(newConfirmTarget);
+ }
+ if (nNewFeeRate.GetFeePerK() == 0) {
+ nNewFeeRate = CWallet::fallbackFee;
+ }
+
+ // new fee rate must be at least old rate + minimum relay rate
+ if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + ::minRelayTxFee.GetFeePerK()) {
+ nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + ::minRelayTxFee.GetFeePerK());
+ }
+
+ nNewFee = nNewFeeRate.GetFee(maxNewTxSize);
+ }
+
+ // check that fee rate is higher than mempool's minimum fee
+ // (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
+ // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps,
+ // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a
+ // moment earlier. In this case, we report an error to the user, who may use totalFee to make an adjustment.
+ CFeeRate minMempoolFeeRate = mempool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
+ if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) {
+ throw JSONRPCError(RPC_MISC_ERROR, strprintf("New fee rate (%s) is less than the minimum fee rate (%s) to get into the mempool. totalFee value should to be at least %s or settxfee value should be at least %s to add transaction.", FormatMoney(nNewFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), FormatMoney(minMempoolFeeRate.GetFeePerK())));
+ }
+
+ // Now modify the output to increase the fee.
+ // If the output is not large enough to pay the fee, fail.
+ CAmount nDelta = nNewFee - nOldFee;
+ assert(nDelta > 0);
+ CMutableTransaction tx(*(wtx.tx));
+ CTxOut* poutput = &(tx.vout[nOutput]);
+ if (poutput->nValue < nDelta) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Change output is too small to bump the fee");
+ }
+
+ // If the output would become dust, discard it (converting the dust to fee)
+ poutput->nValue -= nDelta;
+ if (poutput->nValue <= poutput->GetDustThreshold(::minRelayTxFee)) {
+ LogPrint("rpc", "Bumping fee and discarding dust output\n");
+ nNewFee += poutput->nValue;
+ tx.vout.erase(tx.vout.begin() + nOutput);
+ }
+
+ // Mark new tx not replaceable, if requested.
+ if (!replaceable) {
+ for (auto& input : tx.vin) {
+ if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
+ }
+ }
+
+ // sign the new tx
+ CTransaction txNewConst(tx);
+ int nIn = 0;
+ for (auto& input : tx.vin) {
+ std::map<uint256, CWalletTx>::const_iterator mi = pwalletMain->mapWallet.find(input.prevout.hash);
+ assert(mi != pwalletMain->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
+ const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey;
+ const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue;
+ SignatureData sigdata;
+ if (!ProduceSignature(TransactionSignatureCreator(pwalletMain, &txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
+ }
+ UpdateTransaction(tx, nIn, sigdata);
+ nIn++;
+ }
+
+ // commit/broadcast the tx
+ CReserveKey reservekey(pwalletMain);
+ CWalletTx wtxBumped(pwalletMain, MakeTransactionRef(std::move(tx)));
+ wtxBumped.mapValue["replaces_txid"] = hash.ToString();
+ CValidationState state;
+ if (!pwalletMain->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state) || !state.IsValid()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()));
+ }
+
+ // mark the original tx as bumped
+ if (!pwalletMain->MarkReplaced(wtx.GetHash(), wtxBumped.GetHash())) {
+ // TODO: see if JSON-RPC has a standard way of returning a response
+ // along with an exception. It would be good to return information about
+ // wtxBumped to the caller even if marking the original transaction
+ // replaced does not succeed for some reason.
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
+ }
+
+ UniValue result(UniValue::VOBJ);
+ result.push_back(Pair("txid", wtxBumped.GetHash().GetHex()));
+ result.push_back(Pair("oldfee", ValueFromAmount(nOldFee)));
+ result.push_back(Pair("fee", ValueFromAmount(nNewFee)));
+
+ return result;
+}
+
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
extern UniValue importprivkey(const JSONRPCRequest& request);
extern UniValue importaddress(const JSONRPCRequest& request);
@@ -2644,6 +2914,7 @@ static const CRPCCommand commands[] =
{ "wallet", "addmultisigaddress", &addmultisigaddress, true, {"nrequired","keys","account"} },
{ "wallet", "addwitnessaddress", &addwitnessaddress, true, {"address"} },
{ "wallet", "backupwallet", &backupwallet, true, {"destination"} },
+ { "wallet", "bumpfee", &bumpfee, true, {"txid", "options"} },
{ "wallet", "dumpprivkey", &dumpprivkey, true, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, true, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, true, {"passphrase"} },
@@ -2672,7 +2943,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} },
{ "wallet", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly"} },
{ "wallet", "listtransactions", &listtransactions, false, {"account","count","skip","include_watchonly"} },
- { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses"} },
+ { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe"} },
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 06382af184..358a5ecfc1 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -411,6 +411,13 @@ set<uint256> CWallet::GetConflicts(const uint256& txid) const
return result;
}
+bool CWallet::HasWalletSpend(const uint256& txid) const
+{
+ AssertLockHeld(cs_wallet);
+ auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0));
+ return (iter != mapTxSpends.end() && iter->first.hash == txid);
+}
+
void CWallet::Flush(bool shutdown)
{
bitdb.Flush(shutdown);
@@ -826,6 +833,35 @@ void CWallet::MarkDirty()
}
}
+bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
+{
+ LOCK(cs_wallet);
+
+ auto mi = mapWallet.find(originalHash);
+
+ // There is a bug if MarkReplaced is not called on an existing wallet transaction.
+ assert(mi != mapWallet.end());
+
+ CWalletTx& wtx = (*mi).second;
+
+ // Ensure for now that we're not overwriting data
+ assert(wtx.mapValue.count("replaced_by_txid") == 0);
+
+ wtx.mapValue["replaced_by_txid"] = newHash.ToString();
+
+ CWalletDB walletdb(strWalletFile, "r+");
+
+ bool success = true;
+ if (!walletdb.WriteTx(wtx)) {
+ LogPrintf("%s: Updating walletdb tx %s failed", __func__, wtx.GetHash().ToString());
+ success = false;
+ }
+
+ NotifyTransactionChanged(this, originalHash, CT_UPDATED);
+
+ return success;
+}
+
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
{
LOCK(cs_wallet);
@@ -1154,6 +1190,8 @@ isminetype CWallet::IsMine(const CTxIn &txin) const
return ISMINE_NO;
}
+// Note that this function doesn't distinguish between a 0-valued input,
+// and a not-"is mine" (according to the filter) input.
CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
{
{
@@ -1236,6 +1274,27 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co
return nDebit;
}
+bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const
+{
+ LOCK(cs_wallet);
+
+ BOOST_FOREACH(const CTxIn& txin, tx.vin)
+ {
+ auto mi = mapWallet.find(txin.prevout.hash);
+ if (mi == mapWallet.end())
+ return false; // any unknown inputs can't be from us
+
+ const CWalletTx& prev = (*mi).second;
+
+ if (txin.prevout.n >= prev.tx->vout.size())
+ return false; // invalid input!
+
+ if (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter))
+ return false;
+ }
+ return true;
+}
+
CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const
{
CAmount nCredit = 0;
@@ -1958,6 +2017,37 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
if (nDepth == 0 && !pcoin->InMempool())
continue;
+ // We should not consider coins from transactions that are replacing
+ // other transactions.
+ //
+ // Example: There is a transaction A which is replaced by bumpfee
+ // transaction B. In this case, we want to prevent creation of
+ // a transaction B' which spends an output of B.
+ //
+ // Reason: If transaction A were initially confirmed, transactions B
+ // and B' would no longer be valid, so the user would have to create
+ // a new transaction C to replace B'. However, in the case of a
+ // one-block reorg, transactions B' and C might BOTH be accepted,
+ // when the user only wanted one of them. Specifically, there could
+ // be a 1-block reorg away from the chain where transactions A and C
+ // were accepted to another chain where B, B', and C were all
+ // accepted.
+ if (nDepth == 0 && fOnlyConfirmed && pcoin->mapValue.count("replaces_txid")) {
+ continue;
+ }
+
+ // Similarly, we should not consider coins from transactions that
+ // have been replaced. In the example above, we would want to prevent
+ // creation of a transaction A' spending an output of A, because if
+ // transaction B were initially confirmed, conflicting with A and
+ // A', we wouldn't want to the user to create a transaction D
+ // intending to replace A', but potentially resulting in a scenario
+ // where A, A', and D could all be accepted (instead of just B and
+ // D, or just A and A' like the user would want).
+ if (nDepth == 0 && fOnlyConfirmed && pcoin->mapValue.count("replaced_by_txid")) {
+ continue;
+ }
+
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
isminetype mine = IsMine(pcoin->tx->vout[i]);
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index fb20062b96..a7fc05b62d 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -825,6 +825,10 @@ public:
std::set<CTxDestination> GetAccountAddresses(const std::string& strAccount) const;
isminetype IsMine(const CTxIn& txin) const;
+ /**
+ * Returns amount of debit if the input matches the
+ * filter, otherwise returns 0
+ */
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
isminetype IsMine(const CTxOut& txout) const;
CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const;
@@ -834,6 +838,8 @@ public:
/** should probably be renamed to IsRelevantToMe */
bool IsFromMe(const CTransaction& tx) const;
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;
+ /** Returns whether all of the inputs match the filter */
+ bool IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetChange(const CTransaction& tx) const;
void SetBestChain(const CBlockLocator& loc);
@@ -885,6 +891,9 @@ public:
//! Get wallet transactions that conflict with given transaction (spend same outputs)
std::set<uint256> GetConflicts(const uint256& txid) const;
+ //! Check if a given transaction has any of its outputs spent by another transaction in the wallet
+ bool HasWalletSpend(const uint256& txid) const;
+
//! Flush wallet (bitdb flush)
void Flush(bool shutdown=false);
@@ -921,6 +930,9 @@ public:
/* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
bool AbandonTransaction(const uint256& hashTx);
+ /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */
+ bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
+
/* Returns the wallets help message */
static std::string GetWalletHelpString(bool showDebug);