aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rw-r--r--test/functional/data/wallets/high_minversion/.walletlock0
-rw-r--r--test/functional/data/wallets/high_minversion/GENERATE.md8
-rw-r--r--test/functional/data/wallets/high_minversion/db.log0
-rw-r--r--test/functional/data/wallets/high_minversion/wallet.datbin16384 -> 0 bytes
-rwxr-xr-xtest/functional/feature_proxy.py30
-rwxr-xr-xtest/functional/rpc_estimatefee.py2
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py137
-rwxr-xr-xtest/functional/rpc_psbt.py100
-rw-r--r--test/functional/test_framework/bdb.py152
-rw-r--r--test/functional/test_framework/util.py9
-rwxr-xr-xtest/functional/wallet_basic.py178
-rwxr-xr-xtest/functional/wallet_bumpfee.py89
-rwxr-xr-xtest/functional/wallet_importdescriptors.py9
-rwxr-xr-xtest/functional/wallet_listsinceblock.py1
-rwxr-xr-xtest/functional/wallet_multiwallet.py20
-rwxr-xr-xtest/functional/wallet_send.py96
-rwxr-xr-xtest/functional/wallet_upgradewallet.py257
17 files changed, 730 insertions, 358 deletions
diff --git a/test/functional/data/wallets/high_minversion/.walletlock b/test/functional/data/wallets/high_minversion/.walletlock
deleted file mode 100644
index e69de29bb2..0000000000
--- a/test/functional/data/wallets/high_minversion/.walletlock
+++ /dev/null
diff --git a/test/functional/data/wallets/high_minversion/GENERATE.md b/test/functional/data/wallets/high_minversion/GENERATE.md
deleted file mode 100644
index e55c4557ca..0000000000
--- a/test/functional/data/wallets/high_minversion/GENERATE.md
+++ /dev/null
@@ -1,8 +0,0 @@
-The wallet has been created by starting Bitcoin Core with the options
-`-regtest -datadir=/tmp -nowallet -walletdir=$(pwd)/test/functional/data/wallets/`.
-
-In the source code, `WalletFeature::FEATURE_LATEST` has been modified to be large, so that the minversion is too high
-for a current build of the wallet.
-
-The wallet has then been created with the RPC `createwallet high_minversion true true`, so that a blank wallet with
-private keys disabled is created.
diff --git a/test/functional/data/wallets/high_minversion/db.log b/test/functional/data/wallets/high_minversion/db.log
deleted file mode 100644
index e69de29bb2..0000000000
--- a/test/functional/data/wallets/high_minversion/db.log
+++ /dev/null
diff --git a/test/functional/data/wallets/high_minversion/wallet.dat b/test/functional/data/wallets/high_minversion/wallet.dat
deleted file mode 100644
index 99ab809263..0000000000
--- a/test/functional/data/wallets/high_minversion/wallet.dat
+++ /dev/null
Binary files differ
diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py
index dfae58e860..05b658ed87 100755
--- a/test/functional/feature_proxy.py
+++ b/test/functional/feature_proxy.py
@@ -26,6 +26,8 @@ addnode connect to IPv4
addnode connect to IPv6
addnode connect to onion
addnode connect to generic DNS name
+
+- Test getnetworkinfo for each node
"""
import socket
@@ -41,12 +43,16 @@ from test_framework.util import (
from test_framework.netutil import test_ipv6_local
RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports
-# From GetNetworkName() in netbase.cpp:
-NET_UNROUTABLE = ""
+
+# Networks returned by RPC getpeerinfo, defined in src/netbase.cpp::GetNetworkName()
+NET_UNROUTABLE = "unroutable"
NET_IPV4 = "ipv4"
NET_IPV6 = "ipv6"
NET_ONION = "onion"
+# Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo()
+NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION})
+
class ProxyTest(BitcoinTestFramework):
def set_test_params(self):
@@ -84,14 +90,14 @@ class ProxyTest(BitcoinTestFramework):
self.serv3 = Socks5Server(self.conf3)
self.serv3.start()
- # Note: proxies are not used to connect to local nodes
- # this is because the proxy to use is based on CService.GetNetwork(), which return NET_UNROUTABLE for localhost
+ # Note: proxies are not used to connect to local nodes. This is because the proxy to
+ # use is based on CService.GetNetwork(), which returns NET_UNROUTABLE for localhost.
args = [
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'],
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'],
['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'],
[]
- ]
+ ]
if self.have_ipv6:
args[3] = ['-listen', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0', '-noonion']
self.add_nodes(self.num_nodes, extra_args=args)
@@ -189,15 +195,17 @@ class ProxyTest(BitcoinTestFramework):
r[x['name']] = x
return r
- # test RPC getnetworkinfo
+ self.log.info("Test RPC getnetworkinfo")
n0 = networks_dict(self.nodes[0].getnetworkinfo())
- for net in ['ipv4','ipv6','onion']:
+ assert_equal(NETWORKS, n0.keys())
+ for net in NETWORKS:
assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n0[net]['proxy_randomize_credentials'], True)
assert_equal(n0['onion']['reachable'], True)
n1 = networks_dict(self.nodes[1].getnetworkinfo())
- for net in ['ipv4','ipv6']:
+ assert_equal(NETWORKS, n1.keys())
+ for net in ['ipv4', 'ipv6']:
assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n1[net]['proxy_randomize_credentials'], False)
assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr))
@@ -205,14 +213,16 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(n1['onion']['reachable'], True)
n2 = networks_dict(self.nodes[2].getnetworkinfo())
- for net in ['ipv4','ipv6','onion']:
+ assert_equal(NETWORKS, n2.keys())
+ for net in NETWORKS:
assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr))
assert_equal(n2[net]['proxy_randomize_credentials'], True)
assert_equal(n2['onion']['reachable'], True)
if self.have_ipv6:
n3 = networks_dict(self.nodes[3].getnetworkinfo())
- for net in ['ipv4','ipv6']:
+ assert_equal(NETWORKS, n3.keys())
+ for net in NETWORKS:
assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr))
assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False)
diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py
index 1fff9e1512..3b76c7dd1e 100755
--- a/test/functional/rpc_estimatefee.py
+++ b/test/functional/rpc_estimatefee.py
@@ -28,7 +28,7 @@ class EstimateFeeTest(BitcoinTestFramework):
# wrong type for estimatesmartfee(estimate_mode)
assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].estimatesmartfee, 1, 1)
- assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", self.nodes[0].estimatesmartfee, 1, 'foo')
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', self.nodes[0].estimatesmartfee, 1, 'foo')
# wrong type for estimaterawfee(threshold)
assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo')
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 503993162b..f987867e61 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -90,7 +90,6 @@ class RawTransactionsTest(BitcoinTestFramework):
self.test_op_return()
self.test_watchonly()
self.test_all_watched_funds()
- self.test_feerate_with_conf_target_and_estimate_mode()
self.test_option_feerate()
self.test_address_reuse()
self.test_option_subtract_fee_from_outputs()
@@ -708,74 +707,86 @@ class RawTransactionsTest(BitcoinTestFramework):
wwatch.unloadwallet()
def test_option_feerate(self):
- self.log.info("Test fundrawtxn feeRate option")
-
- # Make sure there is exactly one input so coin selection can't skew the result.
- assert_equal(len(self.nodes[3].listunspent(1)), 1)
-
- inputs = []
- outputs = {self.nodes[3].getnewaddress() : 1}
- rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
- result = self.nodes[3].fundrawtransaction(rawtx) # uses self.min_relay_tx_fee (set by settxfee)
- result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee})
- result3 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee})
- assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", self.nodes[3].fundrawtransaction, rawtx, {"feeRate": 1})
- result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex'])
- assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
- assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
-
- def test_feerate_with_conf_target_and_estimate_mode(self):
- self.log.info("Test fundrawtxn passing an explicit fee rate using conf_target and estimate_mode")
+ self.log.info("Test fundrawtxn with explicit fee rates (fee_rate sat/vB and feeRate BTC/kvB)")
node = self.nodes[3]
# Make sure there is exactly one input so coin selection can't skew the result.
- assert_equal(len(node.listunspent(1)), 1)
+ assert_equal(len(self.nodes[3].listunspent(1)), 1)
inputs = []
outputs = {node.getnewaddress() : 1}
rawtx = node.createrawtransaction(inputs, outputs)
- for unit, fee_rate in {"btc/kb": 0.1, "sat/b": 10000}.items():
- self.log.info("Test fundrawtxn with conf_target {} estimate_mode {} produces expected fee".format(fee_rate, unit))
- # With no arguments passed, expect fee of 141 sats/b.
- assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001)
- # Expect fee to be 10,000x higher when explicit fee 10,000x greater is specified.
- result = node.fundrawtransaction(rawtx, {"conf_target": fee_rate, "estimate_mode": unit})
- assert_approx(result["fee"], vexp=0.0141, vspan=0.0001)
+ result = node.fundrawtransaction(rawtx) # uses self.min_relay_tx_fee (set by settxfee)
+ btc_kvb_to_sat_vb = 100000 # (1e5)
+ result1 = node.fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee})
+ result2 = node.fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee})
+ result3 = node.fundrawtransaction(rawtx, {"fee_rate": 10 * btc_kvb_to_sat_vb * self.min_relay_tx_fee})
+ result4 = node.fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee})
+ result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex'])
+ assert_fee_amount(result1['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
+ assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
+ assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
+ assert_fee_amount(result4['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
- for field, fee_rate in {"conf_target": 0.1, "estimate_mode": "sat/b"}.items():
- self.log.info("Test fundrawtxn raises RPC error if both feeRate and {} are passed".format(field))
- assert_raises_rpc_error(
- -8, "Cannot specify both {} and feeRate".format(field),
- lambda: node.fundrawtransaction(rawtx, {"feeRate": 0.1, field: fee_rate}))
+ # With no arguments passed, expect fee of 141 satoshis.
+ assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001)
+ # Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified.
+ result = node.fundrawtransaction(rawtx, {"fee_rate": 10000})
+ assert_approx(result["fee"], vexp=0.0141, vspan=0.0001)
self.log.info("Test fundrawtxn with invalid estimate_mode settings")
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
- lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": v, "conf_target": 0.1}))
- for mode in ["foo", Decimal("3.141592")]:
- assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
- lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": 0.1}))
+ node.fundrawtransaction, rawtx, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True})
+ for mode in ["", "foo", Decimal("3.141592")]:
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True})
self.log.info("Test fundrawtxn with invalid conf_target settings")
- for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
+ for mode in ["unset", "economical", "conservative"]:
self.log.debug("{}".format(mode))
for k, v in {"string": "", "object": {"foo": "bar"}}.items():
assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
- lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": v}))
- if mode in ["btc/kb", "sat/b"]:
- assert_raises_rpc_error(-3, "Amount out of range",
- lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": -1}))
- assert_raises_rpc_error(-4, "Fee rate (0.00000000 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
- lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": 0}))
- else:
- for n in [-1, 0, 1009]:
- assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008",
- lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": n}))
-
- for unit, fee_rate in {"sat/B": 0.99999999, "BTC/kB": 0.00000999}.items():
- self.log.info("- raises RPC error 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
- assert_raises_rpc_error(-4, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
- lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": unit, "conf_target": fee_rate, "add_inputs": True}))
-
+ node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
+ for n in [-1, 0, 1009]:
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
+ node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": n, "add_inputs": True})
+
+ self.log.info("Test invalid fee rate settings")
+ assert_raises_rpc_error(-8, "Invalid fee_rate 0.000 sat/vB (must be greater than 0)",
+ node.fundrawtransaction, rawtx, {"fee_rate": 0, "add_inputs": True})
+ assert_raises_rpc_error(-8, "Invalid feeRate 0.00000000 BTC/kvB (must be greater than 0)",
+ node.fundrawtransaction, rawtx, {"feeRate": 0, "add_inputs": True})
+ for param, value in {("fee_rate", 100000), ("feeRate", 1.000)}:
+ assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
+ node.fundrawtransaction, rawtx, {param: value, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Amount out of range",
+ node.fundrawtransaction, rawtx, {"fee_rate": -1, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Amount is not a number or string",
+ node.fundrawtransaction, rawtx, {"fee_rate": {"foo": "bar"}, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Invalid amount",
+ node.fundrawtransaction, rawtx, {"fee_rate": "", "add_inputs": True})
+
+ self.log.info("Test min fee rate checks are bypassed with fundrawtxn, e.g. a fee_rate under 1 sat/vB is allowed")
+ node.fundrawtransaction(rawtx, {"fee_rate": 0.99999999, "add_inputs": True})
+ node.fundrawtransaction(rawtx, {"feeRate": 0.00000999, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both feeRate and fee_rate are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both fee_rate (sat/vB) and feeRate (BTC/kvB)",
+ node.fundrawtransaction, rawtx, {"fee_rate": 0.1, "feeRate": 0.1, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both feeRate and estimate_mode passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and feeRate",
+ node.fundrawtransaction, rawtx, {"estimate_mode": "economical", "feeRate": 0.1, "add_inputs": True})
+
+ for param in ["feeRate", "fee_rate"]:
+ self.log.info("- raises RPC error if both {} and conf_target are passed".format(param))
+ assert_raises_rpc_error(-8, "Cannot specify both conf_target and {}. Please provide either a confirmation "
+ "target in blocks for automatic fee estimation, or an explicit fee rate.".format(param),
+ node.fundrawtransaction, rawtx, {param: 1, "conf_target": 1, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both fee_rate and estimate_mode are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and fee_rate",
+ node.fundrawtransaction, rawtx, {"fee_rate": 1, "estimate_mode": "economical", "add_inputs": True})
def test_address_reuse(self):
"""Test no address reuse occurs."""
@@ -803,12 +814,32 @@ class RawTransactionsTest(BitcoinTestFramework):
outputs = {self.nodes[2].getnewaddress(): 1}
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
+ # Test subtract fee from outputs with feeRate (BTC/kvB)
result = [self.nodes[3].fundrawtransaction(rawtx), # uses self.min_relay_tx_fee (set by settxfee)
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses self.min_relay_tx_fee (set by settxfee)
self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee}),
self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}),]
+ dec_tx = [self.nodes[3].decoderawtransaction(tx_['hex']) for tx_ in result]
+ output = [d['vout'][1 - r['changepos']]['value'] for d, r in zip(dec_tx, result)]
+ change = [d['vout'][r['changepos']]['value'] for d, r in zip(dec_tx, result)]
+
+ assert_equal(result[0]['fee'], result[1]['fee'], result[2]['fee'])
+ assert_equal(result[3]['fee'], result[4]['fee'])
+ assert_equal(change[0], change[1])
+ assert_equal(output[0], output[1])
+ assert_equal(output[0], output[2] + result[2]['fee'])
+ assert_equal(change[0] + result[0]['fee'], change[2])
+ assert_equal(output[3], output[4] + result[4]['fee'])
+ assert_equal(change[3] + result[3]['fee'], change[4])
+ # Test subtract fee from outputs with fee_rate (sat/vB)
+ btc_kvb_to_sat_vb = 100000 # (1e5)
+ result = [self.nodes[3].fundrawtransaction(rawtx), # uses self.min_relay_tx_fee (set by settxfee)
+ self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list
+ self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses self.min_relay_tx_fee (set by settxfee)
+ self.nodes[3].fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee}),
+ self.nodes[3].fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}),]
dec_tx = [self.nodes[3].decoderawtransaction(tx_['hex']) for tx_ in result]
output = [d['vout'][1 - r['changepos']]['value'] for d, r in zip(dec_tx, result)]
change = [d['vout'][r['changepos']]['value'] for d, r in zip(dec_tx, result)]
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index ca75bcb9bb..498197b5e5 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -187,60 +187,74 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(walletprocesspsbt_out['complete'], True)
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
- self.log.info("Test walletcreatefundedpsbt feeRate of 0.1 BTC/kB produces a total fee at or slightly below -maxtxfee (~0.05290000)")
- res = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, "add_inputs": True})
- assert_approx(res["fee"], 0.055, 0.005)
-
- self.log.info("Test walletcreatefundedpsbt explicit fee rate with conf_target and estimate_mode")
- for unit, fee_rate in {"btc/kb": 0.1, "sat/b": 10000}.items():
- fee = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"conf_target": fee_rate, "estimate_mode": unit, "add_inputs": True})["fee"]
- self.log.info("- conf_target {}, estimate_mode {} produces fee {} at or slightly below -maxtxfee (~0.05290000)".format(fee_rate, unit, fee))
- assert_approx(fee, vexp=0.055, vspan=0.005)
-
- for field, fee_rate in {"conf_target": 0.1, "estimate_mode": "sat/b"}.items():
- self.log.info("- raises RPC error if both feeRate and {} are passed".format(field))
- assert_raises_rpc_error(-8, "Cannot specify both {} and feeRate".format(field),
- lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, field: fee_rate, "add_inputs": True}))
+ self.log.info("Test walletcreatefundedpsbt fee rate of 10000 sat/vB and 0.1 BTC/kvB produces a total fee at or slightly below -maxtxfee (~0.05290000)")
+ res1 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": 10000, "add_inputs": True})
+ assert_approx(res1["fee"], 0.055, 0.005)
+ res2 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, "add_inputs": True})
+ assert_approx(res2["fee"], 0.055, 0.005)
+ self.log.info("Test min fee rate checks with walletcreatefundedpsbt are bypassed, e.g. a fee_rate under 1 sat/vB is allowed")
+ res3 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": 0.99999999, "add_inputs": True})
+ assert_approx(res3["fee"], 0.00000381, 0.0000001)
+ res4 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.00000999, "add_inputs": True})
+ assert_approx(res4["fee"], 0.00000381, 0.0000001)
+
+ self.log.info("Test invalid fee rate settings")
+ assert_raises_rpc_error(-8, "Invalid fee_rate 0.000 sat/vB (must be greater than 0)",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": 0, "add_inputs": True})
+ assert_raises_rpc_error(-8, "Invalid feeRate 0.00000000 BTC/kvB (must be greater than 0)",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"feeRate": 0, "add_inputs": True})
+ for param, value in {("fee_rate", 100000), ("feeRate", 1)}:
+ assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: value, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Amount out of range",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": -1, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Amount is not a number or string",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": {"foo": "bar"}, "add_inputs": True})
+ assert_raises_rpc_error(-3, "Invalid amount",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": "", "add_inputs": True})
+
+ self.log.info("- raises RPC error if both feeRate and fee_rate are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both fee_rate (sat/vB) and feeRate (BTC/kvB)",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": 0.1, "feeRate": 0.1, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both feeRate and estimate_mode passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and feeRate",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": "economical", "feeRate": 0.1, "add_inputs": True})
+
+ for param in ["feeRate", "fee_rate"]:
+ self.log.info("- raises RPC error if both {} and conf_target are passed".format(param))
+ assert_raises_rpc_error(-8, "Cannot specify both conf_target and {}. Please provide either a confirmation "
+ "target in blocks for automatic fee estimation, or an explicit fee rate.".format(param),
+ self.nodes[1].walletcreatefundedpsbt ,inputs, outputs, 0, {param: 1, "conf_target": 1, "add_inputs": True})
+
+ self.log.info("- raises RPC error if both fee_rate and estimate_mode are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and fee_rate",
+ self.nodes[1].walletcreatefundedpsbt ,inputs, outputs, 0, {"fee_rate": 1, "estimate_mode": "economical", "add_inputs": True})
self.log.info("- raises RPC error with invalid estimate_mode settings")
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
- lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True}))
- for mode in ["foo", Decimal("3.141592")]:
- assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
- lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True}))
-
- self.log.info("- raises RPC error if estimate_mode is passed without a conf_target")
- for unit in ["SAT/B", "BTC/KB"]:
- assert_raises_rpc_error(-8, "Selected estimate_mode {} requires a fee rate to be specified in conf_target".format(unit),
- lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": unit}))
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True})
+ for mode in ["", "foo", Decimal("3.141592")]:
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True})
self.log.info("- raises RPC error with invalid conf_target settings")
- for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
+ for mode in ["unset", "economical", "conservative"]:
self.log.debug("{}".format(mode))
for k, v in {"string": "", "object": {"foo": "bar"}}.items():
assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
- lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": v, "add_inputs": True}))
- if mode in ["btc/kb", "sat/b"]:
- assert_raises_rpc_error(-3, "Amount out of range",
- lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": -1, "add_inputs": True}))
- assert_raises_rpc_error(-4, "Fee rate (0.00000000 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
- lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": 0, "add_inputs": True}))
- else:
- for n in [-1, 0, 1009]:
- assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008",
- lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": n, "add_inputs": True}))
-
- for unit, fee_rate in {"SAT/B": 0.99999999, "BTC/KB": 0.00000999}.items():
- self.log.info("- raises RPC error 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
- assert_raises_rpc_error(-4, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
- lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": unit, "conf_target": fee_rate, "add_inputs": True}))
-
- self.log.info("Test walletcreatefundedpsbt feeRate of 10 BTC/kB produces total fee well above -maxtxfee and raises RPC error")
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
+ for n in [-1, 0, 1009]:
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": n, "add_inputs": True})
+
+ self.log.info("Test walletcreatefundedpsbt with too-high fee rate produces total fee well above -maxtxfee and raises RPC error")
# previously this was silently capped at -maxtxfee
for bool_add, outputs_array in {True: outputs, False: [{self.nodes[1].getnewaddress(): 1}]}.items():
- assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
- self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"feeRate": 10, "add_inputs": bool_add})
+ msg = "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"
+ assert_raises_rpc_error(-4, msg, self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"fee_rate": 1000000, "add_inputs": bool_add})
+ assert_raises_rpc_error(-4, msg, self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"feeRate": 1, "add_inputs": bool_add})
self.log.info("Test various PSBT operations")
# partially sign multisig things with node 1
diff --git a/test/functional/test_framework/bdb.py b/test/functional/test_framework/bdb.py
new file mode 100644
index 0000000000..9de358aa0a
--- /dev/null
+++ b/test/functional/test_framework/bdb.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""
+Utilities for working directly with the wallet's BDB database file
+
+This is specific to the configuration of BDB used in this project:
+ - pagesize: 4096 bytes
+ - Outer database contains single subdatabase named 'main'
+ - btree
+ - btree leaf pages
+
+Each key-value pair is two entries in a btree leaf. The first is the key, the one that follows
+is the value. And so on. Note that the entry data is itself not in the correct order. Instead
+entry offsets are stored in the correct order and those offsets are needed to then retrieve
+the data itself.
+
+Page format can be found in BDB source code dbinc/db_page.h
+This only implements the deserialization of btree metadata pages and normal btree pages. Overflow
+pages are not implemented but may be needed in the future if dealing with wallets with large
+transactions.
+
+`db_dump -da wallet.dat` is useful to see the data in a wallet.dat BDB file
+"""
+
+import binascii
+import struct
+
+# Important constants
+PAGESIZE = 4096
+OUTER_META_PAGE = 0
+INNER_META_PAGE = 2
+
+# Page type values
+BTREE_INTERNAL = 3
+BTREE_LEAF = 5
+BTREE_META = 9
+
+# Some magic numbers for sanity checking
+BTREE_MAGIC = 0x053162
+DB_VERSION = 9
+
+# Deserializes a leaf page into a dict.
+# Btree internal pages have the same header, for those, return None.
+# For the btree leaf pages, deserialize them and put all the data into a dict
+def dump_leaf_page(data):
+ page_info = {}
+ page_header = data[0:26]
+ _, pgno, prev_pgno, next_pgno, entries, hf_offset, level, pg_type = struct.unpack('QIIIHHBB', page_header)
+ page_info['pgno'] = pgno
+ page_info['prev_pgno'] = prev_pgno
+ page_info['next_pgno'] = next_pgno
+ page_info['entries'] = entries
+ page_info['hf_offset'] = hf_offset
+ page_info['level'] = level
+ page_info['pg_type'] = pg_type
+ page_info['entry_offsets'] = struct.unpack('{}H'.format(entries), data[26:26 + entries * 2])
+ page_info['entries'] = []
+
+ if pg_type == BTREE_INTERNAL:
+ # Skip internal pages. These are the internal nodes of the btree and don't contain anything relevant to us
+ return None
+
+ assert pg_type == BTREE_LEAF, 'A non-btree leaf page has been encountered while dumping leaves'
+
+ for i in range(0, entries):
+ offset = page_info['entry_offsets'][i]
+ entry = {'offset': offset}
+ page_data_header = data[offset:offset + 3]
+ e_len, pg_type = struct.unpack('HB', page_data_header)
+ entry['len'] = e_len
+ entry['pg_type'] = pg_type
+ entry['data'] = data[offset + 3:offset + 3 + e_len]
+ page_info['entries'].append(entry)
+
+ return page_info
+
+# Deserializes a btree metadata page into a dict.
+# Does a simple sanity check on the magic value, type, and version
+def dump_meta_page(page):
+ # metadata page
+ # general metadata
+ metadata = {}
+ meta_page = page[0:72]
+ _, pgno, magic, version, pagesize, encrypt_alg, pg_type, metaflags, _, free, last_pgno, nparts, key_count, record_count, flags, uid = struct.unpack('QIIIIBBBBIIIIII20s', meta_page)
+ metadata['pgno'] = pgno
+ metadata['magic'] = magic
+ metadata['version'] = version
+ metadata['pagesize'] = pagesize
+ metadata['encrypt_alg'] = encrypt_alg
+ metadata['pg_type'] = pg_type
+ metadata['metaflags'] = metaflags
+ metadata['free'] = free
+ metadata['last_pgno'] = last_pgno
+ metadata['nparts'] = nparts
+ metadata['key_count'] = key_count
+ metadata['record_count'] = record_count
+ metadata['flags'] = flags
+ metadata['uid'] = binascii.hexlify(uid)
+
+ assert magic == BTREE_MAGIC, 'bdb magic does not match bdb btree magic'
+ assert pg_type == BTREE_META, 'Metadata page is not a btree metadata page'
+ assert version == DB_VERSION, 'Database too new'
+
+ # btree metadata
+ btree_meta_page = page[72:512]
+ _, minkey, re_len, re_pad, root, _, crypto_magic, _, iv, chksum = struct.unpack('IIIII368sI12s16s20s', btree_meta_page)
+ metadata['minkey'] = minkey
+ metadata['re_len'] = re_len
+ metadata['re_pad'] = re_pad
+ metadata['root'] = root
+ metadata['crypto_magic'] = crypto_magic
+ metadata['iv'] = binascii.hexlify(iv)
+ metadata['chksum'] = binascii.hexlify(chksum)
+ return metadata
+
+# Given the dict from dump_leaf_page, get the key-value pairs and put them into a dict
+def extract_kv_pairs(page_data):
+ out = {}
+ last_key = None
+ for i, entry in enumerate(page_data['entries']):
+ # By virtue of these all being pairs, even number entries are keys, and odd are values
+ if i % 2 == 0:
+ out[entry['data']] = b''
+ last_key = entry['data']
+ else:
+ out[last_key] = entry['data']
+ return out
+
+# Extract the key-value pairs of the BDB file given in filename
+def dump_bdb_kv(filename):
+ # Read in the BDB file and start deserializing it
+ pages = []
+ with open(filename, 'rb') as f:
+ data = f.read(PAGESIZE)
+ while len(data) > 0:
+ pages.append(data)
+ data = f.read(PAGESIZE)
+
+ # Sanity check the meta pages
+ dump_meta_page(pages[OUTER_META_PAGE])
+ dump_meta_page(pages[INNER_META_PAGE])
+
+ # Fetch the kv pairs from the leaf pages
+ kv = {}
+ for i in range(3, len(pages)):
+ info = dump_leaf_page(pages[i])
+ if info is not None:
+ info_kv = extract_kv_pairs(info)
+ kv = {**kv, **info_kv}
+ return kv
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 3356f1ab10..62ff5c6e33 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -8,6 +8,7 @@ from base64 import b64encode
from binascii import unhexlify
from decimal import Decimal, ROUND_DOWN
from subprocess import CalledProcessError
+import hashlib
import inspect
import json
import logging
@@ -260,6 +261,14 @@ def wait_until_helper(predicate, *, attempts=float('inf'), timeout=float('inf'),
raise AssertionError("Predicate {} not true after {} seconds".format(predicate_source, timeout))
raise RuntimeError('Unreachable')
+def sha256sum_file(filename):
+ h = hashlib.sha256()
+ with open(filename, 'rb') as f:
+ d = f.read(4096)
+ while len(d) > 0:
+ h.update(d)
+ d = f.read(4096)
+ return h.digest()
# RPC/P2P connection constants and functions
############################################
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 40daffd2c2..ead56046a4 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the wallet."""
from decimal import Decimal
+from itertools import product
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -14,6 +15,8 @@ from test_framework.util import (
)
from test_framework.wallet_util import test_address
+OUT_OF_RANGE = "Amount out of range"
+
class WalletTest(BitcoinTestFramework):
def set_test_params(self):
@@ -74,7 +77,7 @@ class WalletTest(BitcoinTestFramework):
assert_equal(len(self.nodes[1].listunspent()), 1)
assert_equal(len(self.nodes[2].listunspent()), 0)
- self.log.info("test gettxout")
+ self.log.info("Test gettxout")
confirmed_txid, confirmed_index = utxos[0]["txid"], utxos[0]["vout"]
# First, outputs that are unspent both in the chain and in the
# mempool should appear with or without include_mempool
@@ -87,7 +90,7 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
mempool_txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)
- self.log.info("test gettxout (second part)")
+ self.log.info("Test gettxout (second part)")
# utxo spent in mempool should be visible if you exclude mempool
# but invisible if you include mempool
txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False)
@@ -227,65 +230,41 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
- self.log.info("Test case-insensitive explicit fee rate (sendmany as BTC/kB)")
- # Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode bTc/kB requires a fee rate to be specified in conf_target",
- self.nodes[2].sendmany,
- amounts={ address: 10 },
- estimate_mode='bTc/kB')
- # Throw if negative feerate
- assert_raises_rpc_error(-3, "Amount out of range",
- self.nodes[2].sendmany,
- amounts={ address: 10 },
- conf_target=-1,
- estimate_mode='bTc/kB')
- fee_per_kb = 0.0002500
- explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
- txid = self.nodes[2].sendmany(
- amounts={ address: 10 },
- conf_target=fee_per_kb,
- estimate_mode='bTc/kB',
- )
- self.nodes[2].generate(1)
- self.sync_all(self.nodes[0:3])
- node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
- assert_equal(self.nodes[2].getbalance(), node_2_bal)
- node_0_bal += Decimal('10')
- assert_equal(self.nodes[0].getbalance(), node_0_bal)
+ self.log.info("Test sendmany with fee_rate param (explicit fee rate in sat/vB)")
+ fee_rate_sat_vb = 2
+ fee_rate_btc_kvb = fee_rate_sat_vb * 1e3 / 1e8
+ explicit_fee_rate_btc_kvb = Decimal(fee_rate_btc_kvb) / 1000
- self.log.info("Test case-insensitive explicit fee rate (sendmany as sat/B)")
- # Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode sat/b requires a fee rate to be specified in conf_target",
- self.nodes[2].sendmany,
- amounts={ address: 10 },
- estimate_mode='sat/b')
- # Throw if negative feerate
- assert_raises_rpc_error(-3, "Amount out of range",
- self.nodes[2].sendmany,
- amounts={ address: 10 },
- conf_target=-1,
- estimate_mode='sat/b')
- fee_sat_per_b = 2
- fee_per_kb = fee_sat_per_b / 100000.0
- explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
- txid = self.nodes[2].sendmany(
- amounts={ address: 10 },
- conf_target=fee_sat_per_b,
- estimate_mode='sAT/b',
- )
+ # Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
+ txid = self.nodes[2].sendmany(amounts={address: 10}, conf_target=0, estimate_mode="", fee_rate=fee_rate_sat_vb)
self.nodes[2].generate(1)
self.sync_all(self.nodes[0:3])
balance = self.nodes[2].getbalance()
- node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_rate_btc_kvb, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
assert_equal(balance, node_2_bal)
node_0_bal += Decimal('10')
assert_equal(self.nodes[0].getbalance(), node_0_bal)
+ for key in ["totalFee", "feeRate"]:
+ assert_raises_rpc_error(-8, "Unknown named parameter key", self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=1, key=1)
+
# Test setting explicit fee rate just below the minimum.
- for unit, fee_rate in {"BTC/kB": 0.00000999, "sat/B": 0.99999999}.items():
- self.log.info("Test sendmany raises 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
- assert_raises_rpc_error(-6, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
- self.nodes[2].sendmany, amounts={address: 10}, estimate_mode=unit, conf_target=fee_rate)
+ self.log.info("Test sendmany raises 'fee rate too low' if fee_rate of 0.99999999 is passed")
+ assert_raises_rpc_error(-6, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
+ self.nodes[2].sendmany, amounts={address: 10}, fee_rate=0.99999999)
+
+ self.log.info("Test sendmany raises if fee_rate of 0 or -1 is passed")
+ assert_raises_rpc_error(-6, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
+ self.nodes[2].sendmany, amounts={address: 10}, fee_rate=0)
+ assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendmany, amounts={address: 10}, fee_rate=-1)
+
+ self.log.info("Test sendmany raises if an invalid conf_target or estimate_mode is passed")
+ for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
+ self.nodes[2].sendmany, amounts={address: 1}, conf_target=target, estimate_mode=mode)
+ for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ self.nodes[2].sendmany, amounts={address: 1}, conf_target=target, estimate_mode=mode)
self.start_node(3, self.nodes[3].extra_args)
self.connect_nodes(0, 3)
@@ -318,7 +297,7 @@ class WalletTest(BitcoinTestFramework):
assert_equal(uTx['amount'], Decimal('0'))
assert found
- # do some -walletbroadcast tests
+ self.log.info("Test -walletbroadcast")
self.stop_nodes()
self.start_node(0, ["-walletbroadcast=0"])
self.start_node(1, ["-walletbroadcast=0"])
@@ -378,7 +357,7 @@ class WalletTest(BitcoinTestFramework):
# General checks for errors from incorrect inputs
# This will raise an exception because the amount is negative
- assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
+ assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
# This will raise an exception because the amount type is wrong
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
@@ -420,78 +399,43 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
- self.log.info("Test case-insensitive explicit fee rate (sendtoaddress as BTC/kB)")
- self.nodes[0].generate(1)
- self.sync_all(self.nodes[0:3])
+ self.log.info("Test sendtoaddress with fee_rate param (explicit fee rate in sat/vB)")
prebalance = self.nodes[2].getbalance()
assert prebalance > 2
address = self.nodes[1].getnewaddress()
- # Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode BTc/Kb requires a fee rate to be specified in conf_target",
- self.nodes[2].sendtoaddress,
- address=address,
- amount=1.0,
- estimate_mode='BTc/Kb')
- # Throw if negative feerate
- assert_raises_rpc_error(-3, "Amount out of range",
- self.nodes[2].sendtoaddress,
- address=address,
- amount=1.0,
- conf_target=-1,
- estimate_mode='btc/kb')
- txid = self.nodes[2].sendtoaddress(
- address=address,
- amount=1.0,
- conf_target=0.00002500,
- estimate_mode='btc/kb',
- )
- tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
- self.sync_all(self.nodes[0:3])
- self.nodes[0].generate(1)
- self.sync_all(self.nodes[0:3])
- postbalance = self.nodes[2].getbalance()
- fee = prebalance - postbalance - Decimal('1')
- assert_fee_amount(fee, tx_size, Decimal('0.00002500'))
+ amount = 3
+ fee_rate_sat_vb = 2
+ fee_rate_btc_kvb = fee_rate_sat_vb * 1e3 / 1e8
- self.sync_all(self.nodes[0:3])
-
- self.log.info("Test case-insensitive explicit fee rate (sendtoaddress as sat/B)")
- self.nodes[0].generate(1)
- prebalance = self.nodes[2].getbalance()
- assert prebalance > 2
- address = self.nodes[1].getnewaddress()
- # Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode SAT/b requires a fee rate to be specified in conf_target",
- self.nodes[2].sendtoaddress,
- address=address,
- amount=1.0,
- estimate_mode='SAT/b')
- # Throw if negative feerate
- assert_raises_rpc_error(-3, "Amount out of range",
- self.nodes[2].sendtoaddress,
- address=address,
- amount=1.0,
- conf_target=-1,
- estimate_mode='SAT/b')
- txid = self.nodes[2].sendtoaddress(
- address=address,
- amount=1.0,
- conf_target=2,
- estimate_mode='SAT/B',
- )
+ # Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
+ txid = self.nodes[2].sendtoaddress(address=address, amount=amount, conf_target=0, estimate_mode="", fee_rate=fee_rate_sat_vb)
tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
- self.sync_all(self.nodes[0:3])
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
postbalance = self.nodes[2].getbalance()
- fee = prebalance - postbalance - Decimal('1')
- assert_fee_amount(fee, tx_size, Decimal('0.00002000'))
+ fee = prebalance - postbalance - Decimal(amount)
+ assert_fee_amount(fee, tx_size, Decimal(fee_rate_btc_kvb))
+
+ for key in ["totalFee", "feeRate"]:
+ assert_raises_rpc_error(-8, "Unknown named parameter key", self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=1, key=1)
# Test setting explicit fee rate just below the minimum.
- for unit, fee_rate in {"BTC/kB": 0.00000999, "sat/B": 0.99999999}.items():
- self.log.info("Test sendtoaddress raises 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
- assert_raises_rpc_error(-6, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
- self.nodes[2].sendtoaddress, address=address, amount=1, estimate_mode=unit, conf_target=fee_rate)
+ self.log.info("Test sendtoaddress raises 'fee rate too low' if fee_rate of 0.99999999 is passed")
+ assert_raises_rpc_error(-6, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
+ self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=0.99999999)
+
+ self.log.info("Test sendtoaddress raises if fee_rate of 0 or -1 is passed")
+ assert_raises_rpc_error(-6, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
+ self.nodes[2].sendtoaddress, address=address, amount=10, fee_rate=0)
+ assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendtoaddress, address=address, amount=1.0, fee_rate=-1)
+
+ self.log.info("Test sendtoaddress raises if an invalid conf_target or estimate_mode is passed")
+ for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
+ self.nodes[2].sendtoaddress, address=address, amount=1, conf_target=target, estimate_mode=mode)
+ for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ self.nodes[2].sendtoaddress, address=address, amount=1, conf_target=target, estimate_mode=mode)
# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)
@@ -549,7 +493,7 @@ class WalletTest(BitcoinTestFramework):
]
chainlimit = 6
for m in maintenance:
- self.log.info("check " + m)
+ self.log.info("Test " + m)
self.stop_nodes()
# set lower ancestor limit for later
self.start_node(0, [m, "-limitancestorcount=" + str(chainlimit)])
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 88a7778e19..c323aac3cc 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -17,7 +17,7 @@ from decimal import Decimal
import io
from test_framework.blocktools import add_witness_commitment, create_block, create_coinbase, send_to_witness
-from test_framework.messages import BIP125_SEQUENCE_NUMBER, COIN, CTransaction
+from test_framework.messages import BIP125_SEQUENCE_NUMBER, CTransaction
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -29,15 +29,13 @@ from test_framework.util import (
WALLET_PASSPHRASE = "test"
WALLET_PASSPHRASE_TIMEOUT = 3600
-# Fee rates (in BTC per 1000 vbytes)
-INSUFFICIENT = 0.00001000
-ECONOMICAL = 0.00050000
-NORMAL = 0.00100000
-HIGH = 0.00500000
-TOO_HIGH = 1.00000000
+# Fee rates (sat/vB)
+INSUFFICIENT = 1
+ECONOMICAL = 50
+NORMAL = 100
+HIGH = 500
+TOO_HIGH = 100000
-BTC_MODE = "BTC/kB"
-SAT_MODE = "sat/B"
class BumpFeeTest(BitcoinTestFramework):
def set_test_params(self):
@@ -78,7 +76,7 @@ class BumpFeeTest(BitcoinTestFramework):
self.log.info("Running tests")
dest_address = peer_node.getnewaddress()
- for mode in ["default", "fee_rate", BTC_MODE, SAT_MODE]:
+ for mode in ["default", "fee_rate"]:
test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address)
self.test_invalid_parameters(rbf_node, peer_node, dest_address)
test_segwit_bumpfee_succeeds(self, rbf_node, dest_address)
@@ -105,50 +103,44 @@ class BumpFeeTest(BitcoinTestFramework):
self.sync_mempools((rbf_node, peer_node))
assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
- assert_raises_rpc_error(-3, "Unexpected key totalFee", rbf_node.bumpfee, rbfid, {"totalFee": NORMAL})
- assert_raises_rpc_error(-4, "is too high (cannot be higher than", rbf_node.bumpfee, rbfid, {"fee_rate": TOO_HIGH})
+ for key in ["totalFee", "feeRate"]:
+ assert_raises_rpc_error(-3, "Unexpected key {}".format(key), rbf_node.bumpfee, rbfid, {key: NORMAL})
- # For each fee mode, bumping to just above minrelay should fail to increase the total fee enough.
- for options in [{"fee_rate": INSUFFICIENT}, {"conf_target": INSUFFICIENT, "estimate_mode": BTC_MODE}, {"conf_target": 1, "estimate_mode": SAT_MODE}]:
- assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, rbfid, options)
+ # Bumping to just above minrelay should fail to increase the total fee enough.
+ assert_raises_rpc_error(-8, "Insufficient total fee 0.00000141, must be at least 0.00001704 (oldFee 0.00000999 + incrementalFee 0.00000705)",
+ rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
- self.log.info("Test explicit fee rate raises RPC error if estimate_mode is passed without a conf_target")
- for unit, fee_rate in {"SAT/B": 100, "BTC/KB": NORMAL}.items():
- assert_raises_rpc_error(-8, "Selected estimate_mode {} requires a fee rate to be specified in conf_target".format(unit),
- rbf_node.bumpfee, rbfid, {"fee_rate": fee_rate, "estimate_mode": unit})
+ self.log.info("Test invalid fee rate settings")
+ assert_raises_rpc_error(-8, "Insufficient total fee 0.00, must be at least 0.00001704 (oldFee 0.00000999 + incrementalFee 0.00000705)",
+ rbf_node.bumpfee, rbfid, {"fee_rate": 0})
+ assert_raises_rpc_error(-4, "Specified or calculated fee 0.141 is too high (cannot be higher than -maxtxfee 0.10",
+ rbf_node.bumpfee, rbfid, {"fee_rate": TOO_HIGH})
+ assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, {"fee_rate": -1})
+ for value in [{"foo": "bar"}, True]:
+ assert_raises_rpc_error(-3, "Amount is not a number or string", rbf_node.bumpfee, rbfid, {"fee_rate": value})
+ assert_raises_rpc_error(-3, "Invalid amount", rbf_node.bumpfee, rbfid, {"fee_rate": ""})
self.log.info("Test explicit fee rate raises RPC error if both fee_rate and conf_target are passed")
- error_both = "Cannot specify both conf_target and fee_rate. Please provide either a confirmation " \
- "target in blocks for automatic fee estimation, or an explicit fee rate."
- assert_raises_rpc_error(-8, error_both, rbf_node.bumpfee, rbfid, {"conf_target": NORMAL, "fee_rate": NORMAL})
+ assert_raises_rpc_error(-8, "Cannot specify both conf_target and fee_rate. Please provide either a confirmation "
+ "target in blocks for automatic fee estimation, or an explicit fee rate.",
+ rbf_node.bumpfee, rbfid, {"conf_target": NORMAL, "fee_rate": NORMAL})
+
+ self.log.info("Test explicit fee rate raises RPC error if both fee_rate and estimate_mode are passed")
+ assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and fee_rate",
+ rbf_node.bumpfee, rbfid, {"estimate_mode": "economical", "fee_rate": NORMAL})
self.log.info("Test invalid conf_target settings")
assert_raises_rpc_error(-8, "confTarget and conf_target options should not both be set",
- rbf_node.bumpfee, rbfid, {"confTarget": 123, "conf_target": 456})
- for field in ["confTarget", "conf_target"]:
- assert_raises_rpc_error(-4, "is too high (cannot be higher than -maxtxfee",
- lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": BTC_MODE, "conf_target": 2009}))
- assert_raises_rpc_error(-4, "is too high (cannot be higher than -maxtxfee",
- lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": SAT_MODE, "conf_target": 2009 * 10000}))
+ rbf_node.bumpfee, rbfid, {"confTarget": 123, "conf_target": 456})
self.log.info("Test invalid estimate_mode settings")
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
- lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": v, "fee_rate": NORMAL}))
- for mode in ["foo", Decimal("3.141592")]:
- assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
- lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": mode, "fee_rate": NORMAL}))
-
- self.log.info("Test invalid fee_rate settings")
- for mode in ["unset", "economical", "conservative", BTC_MODE, SAT_MODE]:
- self.log.debug("{}".format(mode))
- for k, v in {"string": "", "object": {"foo": "bar"}}.items():
- assert_raises_rpc_error(-3, "Expected type number for fee_rate, got {}".format(k),
- lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": mode, "fee_rate": v}))
- assert_raises_rpc_error(-3, "Amount out of range",
- lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": mode, "fee_rate": -1}))
- assert_raises_rpc_error(-8, "Invalid fee_rate 0.00000000 BTC/kB (must be greater than 0)",
- lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": mode, "fee_rate": 0}))
+ rbf_node.bumpfee, rbfid, {"estimate_mode": v})
+ for mode in ["foo", Decimal("3.1415"), "sat/B", "BTC/kB"]:
+ assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
+ rbf_node.bumpfee, rbfid, {"estimate_mode": mode})
+
self.clear_mempool()
@@ -161,13 +153,6 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):
if mode == "fee_rate":
bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"fee_rate": NORMAL})
bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": NORMAL})
- elif mode == BTC_MODE:
- bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"conf_target": NORMAL, "estimate_mode": BTC_MODE})
- bumped_tx = rbf_node.bumpfee(rbfid, {"conf_target": NORMAL, "estimate_mode": BTC_MODE})
- elif mode == SAT_MODE:
- sat_fee = NORMAL * COIN / 1000 # convert NORMAL from BTC/kB to sat/B
- bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"conf_target": sat_fee, "estimate_mode": SAT_MODE})
- bumped_tx = rbf_node.bumpfee(rbfid, {"conf_target": sat_fee, "estimate_mode": SAT_MODE})
else:
bumped_psbt = rbf_node.psbtbumpfee(rbfid)
bumped_tx = rbf_node.bumpfee(rbfid)
@@ -319,11 +304,11 @@ def test_dust_to_fee(self, rbf_node, dest_address):
# boundary. Thus expected transaction size (p2wpkh, 1 input, 2 outputs) is 140-141 vbytes, usually 141.
if not 140 <= fulltx["vsize"] <= 141:
raise AssertionError("Invalid tx vsize of {} (140-141 expected), full tx: {}".format(fulltx["vsize"], fulltx))
- # Bump with fee_rate of 0.00350250 BTC per 1000 vbytes to create dust.
+ # Bump with fee_rate of 350.25 sat/vB vbytes to create dust.
# Expected fee is 141 vbytes * fee_rate 0.00350250 BTC / 1000 vbytes = 0.00049385 BTC.
# or occasionally 140 vbytes * fee_rate 0.00350250 BTC / 1000 vbytes = 0.00049035 BTC.
# Dust should be dropped to the fee, so actual bump fee is 0.00050000 BTC.
- bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": 0.00350250})
+ bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": 350.25})
full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1)
assert_equal(bumped_tx["fee"], Decimal("0.00050000"))
assert_equal(len(fulltx["vout"]), 2)
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index 74a9994258..2903a84998 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -221,6 +221,15 @@ class ImportDescriptorsTest(BitcoinTestFramework):
success=False,
error_code=-4,
error_message='Cannot import private keys to a wallet with private keys disabled')
+
+ self.log.info("Should not import a descriptor with hardened derivations when private keys are disabled")
+ self.test_importdesc({"desc": descsum_create("wpkh(" + xpub + "/1h/*)"),
+ "timestamp": "now",
+ "range": 1},
+ success=False,
+ error_code=-4,
+ error_message='Cannot expand descriptor. Probably because of hardened derivations without private keys provided')
+
for address in addresses:
test_address(w1,
address,
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index 07c14db6b1..6a1b9097c5 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -191,6 +191,7 @@ class ListSinceBlockTest(BitcoinTestFramework):
address = key_to_p2wpkh(eckey.get_pubkey().get_bytes())
self.nodes[2].sendtoaddress(address, 10)
self.nodes[2].generate(6)
+ self.sync_all()
self.nodes[2].importprivkey(privkey)
utxos = self.nodes[2].listunspent()
utxo = [u for u in utxos if u["address"] == address][0]
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index abdc279197..df16ec741f 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -10,6 +10,7 @@ from decimal import Decimal
from threading import Thread
import os
import shutil
+import stat
import time
from test_framework.authproxy import JSONRPCException
@@ -78,6 +79,11 @@ class MultiWalletTest(BitcoinTestFramework):
os.mkdir(wallet_dir('w7'))
os.symlink('w7', wallet_dir('w7_symlink'))
+ os.symlink('..', wallet_dir('recursive_dir_symlink'))
+
+ os.mkdir(wallet_dir('self_walletdat_symlink'))
+ os.symlink('wallet.dat', wallet_dir('self_walletdat_symlink/wallet.dat'))
+
# rename wallet.dat to make sure plain wallet file paths (as opposed to
# directory paths) can be loaded
# create another dummy wallet for use in testing backups later
@@ -117,7 +123,16 @@ class MultiWalletTest(BitcoinTestFramework):
self.nodes[0].createwallet(wallet_name)
for wallet_name in to_load:
self.nodes[0].loadwallet(wallet_name)
- assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), sorted(in_wallet_dir))
+
+ os.mkdir(wallet_dir('no_access'))
+ os.chmod(wallet_dir('no_access'), 0)
+ try:
+ with self.nodes[0].assert_debug_log(expected_msgs=['Too many levels of symbolic links', 'Error scanning']):
+ walletlist = self.nodes[0].listwalletdir()['wallets']
+ finally:
+ # Need to ensure access is restored for cleanup
+ os.chmod(wallet_dir('no_access'), stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
+ assert_equal(sorted(map(lambda w: w['name'], walletlist)), sorted(in_wallet_dir))
assert_equal(set(node.listwallets()), set(wallet_names))
@@ -156,6 +171,9 @@ class MultiWalletTest(BitcoinTestFramework):
open(not_a_dir, 'a', encoding="utf8").close()
self.nodes[0].assert_start_raises_init_error(['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory')
+ self.log.info("Do not allow -upgradewallet with multiwallet")
+ self.nodes[0].assert_start_raises_init_error(['-upgradewallet'], "Error: Error parsing command line arguments: Invalid parameter -upgradewallet")
+
# if wallets/ doesn't exist, datadir should be the default wallet dir
wallet_dir2 = data_dir('walletdir')
os.rename(wallet_dir(), wallet_dir2)
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index 921a726d4b..5da71c85f9 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -5,13 +5,15 @@
"""Test the send RPC command."""
from decimal import Decimal, getcontext
+from itertools import product
+
from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_fee_amount,
assert_greater_than,
- assert_raises_rpc_error
+ assert_raises_rpc_error,
)
class WalletSendTest(BitcoinTestFramework):
@@ -28,8 +30,8 @@ class WalletSendTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
- arg_conf_target=None, arg_estimate_mode=None,
- conf_target=None, estimate_mode=None, add_to_wallet=None, psbt=None,
+ arg_conf_target=None, arg_estimate_mode=None, arg_fee_rate=None,
+ conf_target=None, estimate_mode=None, fee_rate=None, add_to_wallet=None, psbt=None,
inputs=None, add_inputs=None, change_address=None, change_position=None, change_type=None,
include_watching=None, locktime=None, lock_unspents=None, replaceable=None, subtract_fee_from_outputs=None,
expect_error=None):
@@ -62,6 +64,8 @@ class WalletSendTest(BitcoinTestFramework):
options["conf_target"] = conf_target
if estimate_mode is not None:
options["estimate_mode"] = estimate_mode
+ if fee_rate is not None:
+ options["fee_rate"] = fee_rate
if inputs is not None:
options["inputs"] = inputs
if add_inputs is not None:
@@ -89,18 +93,19 @@ class WalletSendTest(BitcoinTestFramework):
options = None
if expect_error is None:
- res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
+ res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
else:
try:
assert_raises_rpc_error(expect_error[0], expect_error[1], from_wallet.send,
- outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
+ outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
except AssertionError:
# Provide debug info if the test fails
self.log.error("Unexpected successful result:")
self.log.error(arg_conf_target)
self.log.error(arg_estimate_mode)
+ self.log.error(arg_fee_rate)
self.log.error(options)
- res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
+ res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
self.log.error(res)
if "txid" in res and add_to_wallet:
self.log.error("Transaction details:")
@@ -226,10 +231,10 @@ class WalletSendTest(BitcoinTestFramework):
assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"],
self.nodes[1].decodepsbt(res2["psbt"])["fee"])
# but not at the same time
- for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
+ for mode in ["unset", "economical", "conservative"]:
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical",
conf_target=1, estimate_mode=mode, add_to_wallet=False,
- expect_error=(-8, "Use either conf_target and estimate_mode or the options dictionary to control fee rate"))
+ expect_error=(-8, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both"))
self.log.info("Create PSBT from watch-only wallet w3, sign with w2...")
res = self.test_send(from_wallet=w3, to_wallet=w1, amount=1)
@@ -251,60 +256,57 @@ class WalletSendTest(BitcoinTestFramework):
assert res["complete"]
self.log.info("Test setting explicit fee rate")
- res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical", add_to_wallet=False)
- res2 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=1, estimate_mode="economical", add_to_wallet=False)
+ res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, add_to_wallet=False)
+ res2 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=1, add_to_wallet=False)
assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"], self.nodes[1].decodepsbt(res2["psbt"])["fee"])
- res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.00007, estimate_mode="btc/kb", add_to_wallet=False)
+ # Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0, estimate_mode="", fee_rate=7, add_to_wallet=False)
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00007"))
- res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=2, estimate_mode="sat/b", add_to_wallet=False)
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=2, add_to_wallet=False)
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00002"))
- res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.00004531, arg_estimate_mode="btc/kb", add_to_wallet=False)
+ # Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0, arg_estimate_mode="", arg_fee_rate=4.531, add_to_wallet=False)
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00004531"))
- res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=3, arg_estimate_mode="sat/b", add_to_wallet=False)
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=3, add_to_wallet=False)
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00003"))
- # TODO: This test should pass with all modes, e.g. with the next line uncommented, for consistency with the other explicit feerate RPCs.
- # for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
- for mode in ["btc/kb", "sat/b"]:
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=-1, estimate_mode=mode,
- expect_error=(-3, "Amount out of range"))
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0, estimate_mode=mode,
- expect_error=(-4, "Fee rate (0.00000000 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)"))
-
- for mode in ["foo", Decimal("3.141592")]:
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode=mode,
- expect_error=(-8, "Invalid estimate_mode parameter"))
- # TODO: these 2 equivalent sends with an invalid estimate_mode arg should both fail, but they do not...why?
- # self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.1, arg_estimate_mode=mode,
- # expect_error=(-8, "Invalid estimate_mode parameter"))
- # assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", lambda: w0.send({w1.getnewaddress(): 1}, 0.1, mode))
-
- # TODO: These tests should pass for consistency with the other explicit feerate RPCs, but they do not.
- # for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
- # self.log.debug("{}".format(mode))
- # for k, v in {"string": "", "object": {"foo": "bar"}}.items():
- # self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode,
- # expect_error=(-3, "Expected type number for conf_target, got {}".format(k)))
-
- # TODO: error should use sat/B instead of BTC/kB if sat/B is selected.
+ # Test that passing fee_rate as both an argument and an option raises.
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, fee_rate=1, add_to_wallet=False,
+ expect_error=(-8, "Pass the fee_rate either as an argument, or in the options object, but not both"))
+
+ assert_raises_rpc_error(-8, "Use fee_rate (sat/vB) instead of feeRate", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"feeRate": 0.01})
+
+ assert_raises_rpc_error(-3, "Unexpected key totalFee", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"totalFee": 0.01})
+
+ for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode,
+ expect_error=(-8, "Invalid conf_target, must be between 1 and 1008")) # max value of 1008 per src/policy/fees.h
+ msg = 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"'
+ for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode, expect_error=(-8, msg))
+ for mode in ["", "foo", Decimal("3.141592")]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode=mode, expect_error=(-8, msg))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.1, arg_estimate_mode=mode, expect_error=(-8, msg))
+ assert_raises_rpc_error(-8, msg, w0.send, {w1.getnewaddress(): 1}, 0.1, mode)
+
+ for mode in ["economical", "conservative", "btc/kb", "sat/b"]:
+ self.log.debug("{}".format(mode))
+ for k, v in {"string": "true", "object": {"foo": "bar"}}.items():
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode,
+ expect_error=(-3, "Expected type number for conf_target, got {}".format(k)))
+
# Test setting explicit fee rate just below the minimum.
- for unit, fee_rate in {"sat/B": 0.99999999, "BTC/kB": 0.00000999}.items():
- self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=fee_rate, estimate_mode=unit,
- expect_error=(-4, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)"))
-
- self.log.info("Explicit fee rate raises RPC error if estimate_mode is passed without a conf_target")
- for unit, fee_rate in {"sat/B": 100, "BTC/kB": 0.001}.items():
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, estimate_mode=unit,
- expect_error=(-8, "Selected estimate_mode {} requires a fee rate to be specified in conf_target".format(unit)))
+ self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if fee_rate of 0.99999999 is passed")
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0.99999999,
+ expect_error=(-4, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
# TODO: Return hex if fee rate is below -maxmempool
# res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="sat/b", add_to_wallet=False)
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index 15d9b109c5..8ab4b3f76c 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -13,23 +13,47 @@ Only v0.15.2 and v0.16.3 are required by this test. The others are used in featu
import os
import shutil
+import struct
+from io import BytesIO
+
+from test_framework.bdb import dump_bdb_kv
+from test_framework.messages import deser_compact_size, deser_string
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_is_hex_string,
+ assert_raises_rpc_error,
+ sha256sum_file,
)
+UPGRADED_KEYMETA_VERSION = 12
+
+def deser_keymeta(f):
+ ver, create_time = struct.unpack('<Iq', f.read(12))
+ kp_str = deser_string(f)
+ seed_id = f.read(20)
+ fpr = f.read(4)
+ path_len = 0
+ path = []
+ has_key_orig = False
+ if ver == UPGRADED_KEYMETA_VERSION:
+ path_len = deser_compact_size(f)
+ for i in range(0, path_len):
+ path.append(struct.unpack('<I', f.read(4))[0])
+ has_key_orig = bool(f.read(1))
+ return ver, create_time, kp_str, seed_id, fpr, path_len, path, has_key_orig
+
class UpgradeWalletTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.extra_args = [
- ["-addresstype=bech32"], # current wallet version
- ["-usehd=1"], # v0.16.3 wallet
- ["-usehd=0"] # v0.15.2 wallet
+ ["-addresstype=bech32", "-keypool=2"], # current wallet version
+ ["-usehd=1", "-keypool=2"], # v0.16.3 wallet
+ ["-usehd=0", "-keypool=2"] # v0.15.2 wallet
]
self.wallet_names = [self.default_wallet_name, None, None]
@@ -87,22 +111,53 @@ class UpgradeWalletTest(BitcoinTestFramework):
self.log.info("Test upgradewallet RPC...")
# Prepare for copying of the older wallet
- node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets")
+ node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets", self.default_wallet_name)
+ node_master_wallet = os.path.join(node_master_wallet_dir, self.default_wallet_name, self.wallet_data_filename)
v16_3_wallet = os.path.join(v16_3_node.datadir, "regtest/wallets/wallet.dat")
v15_2_wallet = os.path.join(v15_2_node.datadir, "regtest/wallet.dat")
+ split_hd_wallet = os.path.join(v15_2_node.datadir, "regtest/splithd")
self.stop_nodes()
- # Copy the 0.16.3 wallet to the last Bitcoin Core version and open it:
- shutil.rmtree(node_master_wallet_dir)
- os.mkdir(node_master_wallet_dir)
- shutil.copy(
- v16_3_wallet,
- node_master_wallet_dir
- )
- self.restart_node(0, ['-nowallet'])
- node_master.loadwallet('')
+ # Make split hd wallet
+ self.start_node(2, ['-usehd=1', '-keypool=2', '-wallet=splithd'])
+ self.stop_node(2)
+
+ def copy_v16():
+ node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
+ # Copy the 0.16.3 wallet to the last Bitcoin Core version and open it:
+ shutil.rmtree(node_master_wallet_dir)
+ os.mkdir(node_master_wallet_dir)
+ shutil.copy(
+ v16_3_wallet,
+ node_master_wallet_dir
+ )
+ node_master.loadwallet(self.default_wallet_name)
+
+ def copy_non_hd():
+ node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
+ # Copy the 0.15.2 non hd wallet to the last Bitcoin Core version and open it:
+ shutil.rmtree(node_master_wallet_dir)
+ os.mkdir(node_master_wallet_dir)
+ shutil.copy(
+ v15_2_wallet,
+ node_master_wallet_dir
+ )
+ node_master.loadwallet(self.default_wallet_name)
- wallet = node_master.get_wallet_rpc('')
+ def copy_split_hd():
+ node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
+ # Copy the 0.15.2 split hd wallet to the last Bitcoin Core version and open it:
+ shutil.rmtree(node_master_wallet_dir)
+ os.mkdir(node_master_wallet_dir)
+ shutil.copy(
+ split_hd_wallet,
+ os.path.join(node_master_wallet_dir, 'wallet.dat')
+ )
+ node_master.loadwallet(self.default_wallet_name)
+
+ self.restart_node(0)
+ copy_v16()
+ wallet = node_master.get_wallet_rpc(self.default_wallet_name)
old_version = wallet.getwalletinfo()["walletversion"]
# calling upgradewallet without version arguments
@@ -114,18 +169,8 @@ class UpgradeWalletTest(BitcoinTestFramework):
# wallet should still contain the same balance
assert_equal(wallet.getbalance(), v16_3_balance)
- self.stop_node(0)
- # Copy the 0.15.2 wallet to the last Bitcoin Core version and open it:
- shutil.rmtree(node_master_wallet_dir)
- os.mkdir(node_master_wallet_dir)
- shutil.copy(
- v15_2_wallet,
- node_master_wallet_dir
- )
- self.restart_node(0, ['-nowallet'])
- node_master.loadwallet('')
-
- wallet = node_master.get_wallet_rpc('')
+ copy_non_hd()
+ wallet = node_master.get_wallet_rpc(self.default_wallet_name)
# should have no master key hash before conversion
assert_equal('hdseedid' in wallet.getwalletinfo(), False)
# calling upgradewallet with explicit version number
@@ -137,5 +182,165 @@ class UpgradeWalletTest(BitcoinTestFramework):
# after conversion master key hash should be present
assert_is_hex_string(wallet.getwalletinfo()['hdseedid'])
+ self.log.info('Intermediary versions don\'t effect anything')
+ copy_non_hd()
+ # Wallet starts with 60000
+ assert_equal(60000, wallet.getwalletinfo()['walletversion'])
+ wallet.unloadwallet()
+ before_checksum = sha256sum_file(node_master_wallet)
+ node_master.loadwallet('')
+ # Can "upgrade" to 129999 which should have no effect on the wallet
+ wallet.upgradewallet(129999)
+ assert_equal(60000, wallet.getwalletinfo()['walletversion'])
+ wallet.unloadwallet()
+ assert_equal(before_checksum, sha256sum_file(node_master_wallet))
+ node_master.loadwallet('')
+
+ self.log.info('Wallets cannot be downgraded')
+ copy_non_hd()
+ assert_raises_rpc_error(-4, 'Cannot downgrade wallet', wallet.upgradewallet, 40000)
+ wallet.unloadwallet()
+ assert_equal(before_checksum, sha256sum_file(node_master_wallet))
+ node_master.loadwallet('')
+
+ self.log.info('Can upgrade to HD')
+ # Inspect the old wallet and make sure there is no hdchain
+ orig_kvs = dump_bdb_kv(node_master_wallet)
+ assert b'\x07hdchain' not in orig_kvs
+ # Upgrade to HD, no split
+ wallet.upgradewallet(130000)
+ assert_equal(130000, wallet.getwalletinfo()['walletversion'])
+ # Check that there is now a hd chain and it is version 1, no internal chain counter
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ assert b'\x07hdchain' in new_kvs
+ hd_chain = new_kvs[b'\x07hdchain']
+ assert_equal(28, len(hd_chain))
+ hd_chain_version, external_counter, seed_id = struct.unpack('<iI20s', hd_chain)
+ assert_equal(1, hd_chain_version)
+ seed_id = bytearray(seed_id)
+ seed_id.reverse()
+ old_kvs = new_kvs
+ # First 2 keys should still be non-HD
+ for i in range(0, 2):
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ assert 'hdkeypath' not in info
+ assert 'hdseedid' not in info
+ # Next key should be HD
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ assert_equal(seed_id.hex(), info['hdseedid'])
+ assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])
+ prev_seed_id = info['hdseedid']
+ # Change key should be the same keypool
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert_equal(prev_seed_id, info['hdseedid'])
+ assert_equal('m/0\'/0\'/1\'', info['hdkeypath'])
+
+ self.log.info('Cannot upgrade to HD Split, needs Pre Split Keypool')
+ assert_raises_rpc_error(-4, 'Cannot upgrade a non HD split wallet without upgrading to support pre split keypool', wallet.upgradewallet, 139900)
+ assert_equal(130000, wallet.getwalletinfo()['walletversion'])
+ assert_raises_rpc_error(-4, 'Cannot upgrade a non HD split wallet without upgrading to support pre split keypool', wallet.upgradewallet, 159900)
+ assert_equal(130000, wallet.getwalletinfo()['walletversion'])
+ assert_raises_rpc_error(-4, 'Cannot upgrade a non HD split wallet without upgrading to support pre split keypool', wallet.upgradewallet, 169899)
+ assert_equal(130000, wallet.getwalletinfo()['walletversion'])
+
+ self.log.info('Upgrade HD to HD chain split')
+ wallet.upgradewallet(169900)
+ assert_equal(169900, wallet.getwalletinfo()['walletversion'])
+ # Check that the hdchain updated correctly
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ hd_chain = new_kvs[b'\x07hdchain']
+ assert_equal(32, len(hd_chain))
+ hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
+ assert_equal(2, hd_chain_version)
+ assert_equal(0, internal_counter)
+ seed_id = bytearray(seed_id)
+ seed_id.reverse()
+ assert_equal(seed_id.hex(), prev_seed_id)
+ # Next change address is the same keypool
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert_equal(prev_seed_id, info['hdseedid'])
+ assert_equal('m/0\'/0\'/2\'', info['hdkeypath'])
+ # Next change address is the new keypool
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert_equal(prev_seed_id, info['hdseedid'])
+ assert_equal('m/0\'/1\'/0\'', info['hdkeypath'])
+ # External addresses use the same keypool
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ assert_equal(prev_seed_id, info['hdseedid'])
+ assert_equal('m/0\'/0\'/3\'', info['hdkeypath'])
+
+ self.log.info('Upgrade non-HD to HD chain split')
+ copy_non_hd()
+ wallet.upgradewallet(169900)
+ assert_equal(169900, wallet.getwalletinfo()['walletversion'])
+ # Check that the hdchain updated correctly
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ hd_chain = new_kvs[b'\x07hdchain']
+ assert_equal(32, len(hd_chain))
+ hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
+ assert_equal(2, hd_chain_version)
+ assert_equal(2, internal_counter)
+ # Drain the keypool by fetching one external key and one change key. Should still be the same keypool
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ assert 'hdseedid' not in info
+ assert 'hdkeypath' not in info
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert 'hdseedid' not in info
+ assert 'hdkeypath' not in info
+ # The next addresses are HD and should be on different HD chains
+ info = wallet.getaddressinfo(wallet.getnewaddress())
+ ext_id = info['hdseedid']
+ assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])
+ info = wallet.getaddressinfo(wallet.getrawchangeaddress())
+ assert_equal(ext_id, info['hdseedid'])
+ assert_equal('m/0\'/1\'/0\'', info['hdkeypath'])
+
+ self.log.info('KeyMetadata should upgrade when loading into master')
+ copy_v16()
+ old_kvs = dump_bdb_kv(v16_3_wallet)
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ for k, old_v in old_kvs.items():
+ if k.startswith(b'\x07keymeta'):
+ new_ver, new_create_time, new_kp_str, new_seed_id, new_fpr, new_path_len, new_path, new_has_key_orig = deser_keymeta(BytesIO(new_kvs[k]))
+ old_ver, old_create_time, old_kp_str, old_seed_id, old_fpr, old_path_len, old_path, old_has_key_orig = deser_keymeta(BytesIO(old_v))
+ assert_equal(10, old_ver)
+ if old_kp_str == b"": # imported things that don't have keymeta (i.e. imported coinbase privkeys) won't be upgraded
+ assert_equal(new_kvs[k], old_v)
+ continue
+ assert_equal(12, new_ver)
+ assert_equal(new_create_time, old_create_time)
+ assert_equal(new_kp_str, old_kp_str)
+ assert_equal(new_seed_id, old_seed_id)
+ assert_equal(0, old_path_len)
+ assert_equal(new_path_len, len(new_path))
+ assert_equal([], old_path)
+ assert_equal(False, old_has_key_orig)
+ assert_equal(True, new_has_key_orig)
+
+ # Check that the path is right
+ built_path = []
+ for s in new_kp_str.decode().split('/')[1:]:
+ h = 0
+ if s[-1] == '\'':
+ s = s[:-1]
+ h = 0x80000000
+ p = int(s) | h
+ built_path.append(p)
+ assert_equal(new_path, built_path)
+
+ self.log.info('Upgrading to NO_DEFAULT_KEY should not remove the defaultkey')
+ copy_split_hd()
+ # Check the wallet has a default key initially
+ old_kvs = dump_bdb_kv(node_master_wallet)
+ defaultkey = old_kvs[b'\x0adefaultkey']
+ # Upgrade the wallet. Should still have the same default key
+ wallet.upgradewallet(159900)
+ new_kvs = dump_bdb_kv(node_master_wallet)
+ up_defaultkey = new_kvs[b'\x0adefaultkey']
+ assert_equal(defaultkey, up_defaultkey)
+ # 0.16.3 doesn't have a default key
+ v16_3_kvs = dump_bdb_kv(v16_3_wallet)
+ assert b'\x0adefaultkey' not in v16_3_kvs
+
if __name__ == '__main__':
UpgradeWalletTest().main()