aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rwxr-xr-xtest/functional/p2p_invalid_messages.py10
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py55
-rwxr-xr-xtest/functional/rpc_psbt.py24
-rwxr-xr-xtest/functional/wallet_basic.py47
-rwxr-xr-xtest/functional/wallet_bumpfee.py15
-rwxr-xr-xtest/functional/wallet_send.py72
6 files changed, 177 insertions, 46 deletions
diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
index c0b3c2cb12..788a81d4af 100755
--- a/test/functional/p2p_invalid_messages.py
+++ b/test/functional/p2p_invalid_messages.py
@@ -37,7 +37,7 @@ VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5 # Account for the 5-byte len
class msg_unrecognized:
"""Nonsensical message. Modeled after similar types in test_framework.messages."""
- msgtype = b'badmsg'
+ msgtype = b'badmsg\x01'
def __init__(self, *, str_data):
self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data
@@ -104,7 +104,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_magic_bytes(self):
self.log.info("Test message with invalid magic bytes disconnects peer")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
- with self.nodes[0].assert_debug_log(['HEADER ERROR - MESSAGESTART (badmsg, 2 bytes), received ffffffff']):
+ with self.nodes[0].assert_debug_log(['Header error: Wrong MessageStart ffffffff received']):
msg = conn.build_message(msg_unrecognized(str_data="d"))
# modify magic bytes
msg = b'\xff' * 4 + msg[4:]
@@ -115,7 +115,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_checksum(self):
self.log.info("Test message with invalid checksum logs an error")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
- with self.nodes[0].assert_debug_log(['CHECKSUM ERROR (badmsg, 2 bytes), expected 78df0a04 was ffffffff']):
+ with self.nodes[0].assert_debug_log(['Header error: Wrong checksum (badmsg, 2 bytes), expected 78df0a04 was ffffffff']):
msg = conn.build_message(msg_unrecognized(str_data="d"))
# Checksum is after start bytes (4B), message type (12B), len (4B)
cut_len = 4 + 12 + 4
@@ -130,7 +130,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_size(self):
self.log.info("Test message with oversized payload disconnects peer")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
- with self.nodes[0].assert_debug_log(['HEADER ERROR - SIZE (badmsg, 4000001 bytes)']):
+ with self.nodes[0].assert_debug_log(['Header error: Size too large (badmsg, 4000001 bytes)']):
msg = msg_unrecognized(str_data="d" * (VALID_DATA_LIMIT + 1))
msg = conn.build_message(msg)
conn.send_raw_message(msg)
@@ -140,7 +140,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_msgtype(self):
self.log.info("Test message with invalid message type logs an error")
conn = self.nodes[0].add_p2p_connection(P2PDataStore())
- with self.nodes[0].assert_debug_log(['HEADER ERROR - COMMAND']):
+ with self.nodes[0].assert_debug_log(['Header error: Invalid message type']):
msg = msg_unrecognized(str_data="d")
msg = conn.build_message(msg)
# Modify msgtype
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 2e1b1e878f..4b07a32c54 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -5,6 +5,8 @@
"""Test the fundrawtransaction RPC."""
from decimal import Decimal
+from itertools import product
+
from test_framework.descriptors import descsum_create
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@@ -96,6 +98,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.test_option_subtract_fee_from_outputs()
self.test_subtract_fee_with_presets()
self.test_transaction_too_large()
+ self.test_include_unsafe()
def test_change_position(self):
"""Ensure setting changePosition in fundraw with an exact match is handled properly."""
@@ -723,17 +726,16 @@ class RawTransactionsTest(BitcoinTestFramework):
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": str(10 * self.min_relay_tx_fee)})
- # Test that funding non-standard "zero-fee" transactions is valid.
- result5 = self.nodes[3].fundrawtransaction(rawtx, {"fee_rate": 0})
- result6 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 0})
result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex'])
assert_fee_amount(result1['fee'], count_bytes(result1['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(result4['hex']), 10 * result_fee_rate)
- assert_fee_amount(result5['fee'], count_bytes(result5['hex']), 0)
- assert_fee_amount(result6['fee'], count_bytes(result6['hex']), 0)
+
+ # Test that funding non-standard "zero-fee" transactions is valid.
+ for param, zero_value in product(["fee_rate", "feeRate"], [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]):
+ assert_equal(self.nodes[3].fundrawtransaction(rawtx, {param: zero_value})["fee"], 0)
# With no arguments passed, expect fee of 141 satoshis.
assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001)
@@ -767,11 +769,16 @@ class RawTransactionsTest(BitcoinTestFramework):
node.fundrawtransaction, rawtx, {param: -1, "add_inputs": True})
assert_raises_rpc_error(-3, "Amount is not a number or string",
node.fundrawtransaction, rawtx, {param: {"foo": "bar"}, "add_inputs": True})
+ # Test fee rate values that don't pass fixed-point parsing checks.
+ for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
+ assert_raises_rpc_error(-3, "Invalid amount", node.fundrawtransaction, rawtx, {param: invalid_value, "add_inputs": True})
+ # Test fee_rate values that cannot be represented in sat/vB.
+ for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]:
assert_raises_rpc_error(-3, "Invalid amount",
- node.fundrawtransaction, rawtx, {param: "", "add_inputs": True})
+ node.fundrawtransaction, rawtx, {"fee_rate": invalid_value, "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, {"fee_rate": 0.999, "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")
@@ -928,6 +935,40 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[0].generate(10)
assert_raises_rpc_error(-4, "Transaction too large", recipient.fundrawtransaction, rawtx)
+ def test_include_unsafe(self):
+ self.log.info("Test fundrawtxn with unsafe inputs")
+
+ self.nodes[0].createwallet("unsafe")
+ wallet = self.nodes[0].get_wallet_rpc("unsafe")
+
+ # We receive unconfirmed funds from external keys (unsafe outputs).
+ addr = wallet.getnewaddress()
+ txid1 = self.nodes[2].sendtoaddress(addr, 6)
+ txid2 = self.nodes[2].sendtoaddress(addr, 4)
+ self.sync_all()
+ vout1 = find_vout_for_address(wallet, txid1, addr)
+ vout2 = find_vout_for_address(wallet, txid2, addr)
+
+ # Unsafe inputs are ignored by default.
+ rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 5}])
+ assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, rawtx)
+
+ # But we can opt-in to use them for funding.
+ fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": True})
+ tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
+ assert any([txin['txid'] == txid1 and txin['vout'] == vout1 for txin in tx_dec['vin']])
+ signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
+ wallet.sendrawtransaction(signedtx['hex'])
+
+ # And we can also use them once they're confirmed.
+ self.nodes[0].generate(1)
+ rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 3}])
+ fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": True})
+ tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
+ assert any([txin['txid'] == txid2 and txin['vout'] == vout2 for txin in tx_dec['vin']])
+ signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
+ wallet.sendrawtransaction(signedtx['hex'])
+
if __name__ == '__main__':
RawTransactionsTest().main()
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 079a3bd3ba..cf4d8a134c 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -6,6 +6,8 @@
"""
from decimal import Decimal
+from itertools import product
+
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
@@ -193,14 +195,14 @@ class PSBTTest(BitcoinTestFramework):
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})
+ res3 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": "0.999", "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 min fee rate checks with walletcreatefundedpsbt are bypassed and that funding non-standard 'zero-fee' transactions is valid")
- for param in ["fee_rate", "feeRate"]:
- assert_equal(self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {param: 0, "add_inputs": True})["fee"], 0)
+ for param, zero_value in product(["fee_rate", "feeRate"], [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]):
+ assert_equal(0, self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {param: zero_value, "add_inputs": True})["fee"])
self.log.info("Test invalid fee rate settings")
for param, value in {("fee_rate", 100000), ("feeRate", 1)}:
@@ -210,8 +212,14 @@ class PSBTTest(BitcoinTestFramework):
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: -1, "add_inputs": True})
assert_raises_rpc_error(-3, "Amount is not a number or string",
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: {"foo": "bar"}, "add_inputs": True})
+ # Test fee rate values that don't pass fixed-point parsing checks.
+ for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
+ assert_raises_rpc_error(-3, "Invalid amount",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: invalid_value, "add_inputs": True})
+ # Test fee_rate values that cannot be represented in sat/vB.
+ for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]:
assert_raises_rpc_error(-3, "Invalid amount",
- self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: "", "add_inputs": True})
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": invalid_value, "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)",
@@ -394,6 +402,14 @@ class PSBTTest(BitcoinTestFramework):
# We don't care about the decode result, but decoding must succeed.
self.nodes[0].decodepsbt(double_processed_psbt["psbt"])
+ # Make sure unsafe inputs are included if specified
+ self.nodes[2].createwallet(wallet_name="unsafe")
+ wunsafe = self.nodes[2].get_wallet_rpc("unsafe")
+ self.nodes[0].sendtoaddress(wunsafe.getnewaddress(), 2)
+ self.sync_mempools()
+ assert_raises_rpc_error(-4, "Insufficient funds", wunsafe.walletcreatefundedpsbt, [], [{self.nodes[0].getnewaddress(): 1}])
+ wunsafe.walletcreatefundedpsbt([], [{self.nodes[0].getnewaddress(): 1}], 0, {"include_unsafe": True})
+
# BIP 174 Test Vectors
# Check that unknown values are just passed through
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 46eecae611..4a1d25bbc5 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -15,6 +15,7 @@ from test_framework.util import (
)
from test_framework.wallet_util import test_address
+NOT_A_NUMBER_OR_STRING = "Amount is not a number or string"
OUT_OF_RANGE = "Amount out of range"
@@ -264,12 +265,25 @@ class WalletTest(BitcoinTestFramework):
# Test setting explicit fee rate just below the minimum.
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)
+ self.nodes[2].sendmany, amounts={address: 10}, fee_rate=0.999)
+
+ self.log.info("Test sendmany raises if an invalid fee_rate is passed")
+ # Test fee_rate with zero values.
+ msg = "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"
+ for zero_value in [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]:
+ assert_raises_rpc_error(-6, msg, self.nodes[2].sendmany, amounts={address: 1}, fee_rate=zero_value)
+ msg = "Invalid amount"
+ # Test fee_rate values that don't pass fixed-point parsing checks.
+ for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
+ assert_raises_rpc_error(-3, msg, self.nodes[2].sendmany, amounts={address: 1.0}, fee_rate=invalid_value)
+ # Test fee_rate values that cannot be represented in sat/vB.
+ for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]:
+ assert_raises_rpc_error(-3, msg, self.nodes[2].sendmany, amounts={address: 10}, fee_rate=invalid_value)
+ # Test fee_rate out of range (negative number).
assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendmany, amounts={address: 10}, fee_rate=-1)
+ # Test type error.
+ for invalid_value in [True, {"foo": "bar"}]:
+ assert_raises_rpc_error(-3, NOT_A_NUMBER_OR_STRING, self.nodes[2].sendmany, amounts={address: 10}, fee_rate=invalid_value)
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"]):
@@ -447,12 +461,25 @@ class WalletTest(BitcoinTestFramework):
# Test setting explicit fee rate just below the minimum.
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)
+ self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=0.999)
+
+ self.log.info("Test sendtoaddress raises if an invalid fee_rate is passed")
+ # Test fee_rate with zero values.
+ msg = "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"
+ for zero_value in [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]:
+ assert_raises_rpc_error(-6, msg, self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=zero_value)
+ msg = "Invalid amount"
+ # Test fee_rate values that don't pass fixed-point parsing checks.
+ for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
+ assert_raises_rpc_error(-3, msg, self.nodes[2].sendtoaddress, address=address, amount=1.0, fee_rate=invalid_value)
+ # Test fee_rate values that cannot be represented in sat/vB.
+ for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]:
+ assert_raises_rpc_error(-3, msg, self.nodes[2].sendtoaddress, address=address, amount=10, fee_rate=invalid_value)
+ # Test fee_rate out of range (negative number).
assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendtoaddress, address=address, amount=1.0, fee_rate=-1)
+ # Test type error.
+ for invalid_value in [True, {"foo": "bar"}]:
+ assert_raises_rpc_error(-3, NOT_A_NUMBER_OR_STRING, self.nodes[2].sendtoaddress, address=address, amount=1.0, fee_rate=invalid_value)
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"]):
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 0d1b6c54ce..ff5070c1fa 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -110,13 +110,24 @@ class BumpFeeTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "Insufficient total fee 0.00000141", rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
self.log.info("Test invalid fee rate settings")
- assert_raises_rpc_error(-8, "Insufficient total fee 0.00", 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})
+ # Test fee_rate with zero values.
+ msg = "Insufficient total fee 0.00"
+ for zero_value in [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]:
+ assert_raises_rpc_error(-8, msg, rbf_node.bumpfee, rbfid, {"fee_rate": zero_value})
+ msg = "Invalid amount"
+ # Test fee_rate values that don't pass fixed-point parsing checks.
+ for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
+ assert_raises_rpc_error(-3, msg, rbf_node.bumpfee, rbfid, {"fee_rate": invalid_value})
+ # Test fee_rate values that cannot be represented in sat/vB.
+ for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]:
+ assert_raises_rpc_error(-3, msg, rbf_node.bumpfee, rbfid, {"fee_rate": invalid_value})
+ # Test fee_rate out of range (negative number).
assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, {"fee_rate": -1})
+ # Test type error.
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")
assert_raises_rpc_error(-8, "Cannot specify both conf_target and fee_rate. Please provide either a confirmation "
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index 53553dcd80..d24d1693af 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -33,12 +33,15 @@ class WalletSendTest(BitcoinTestFramework):
def test_send(self, from_wallet, to_wallet=None, amount=None, data=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,
+ inputs=None, add_inputs=None, include_unsafe=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):
assert (amount is None) != (data is None)
- from_balance_before = from_wallet.getbalance()
+ from_balance_before = from_wallet.getbalances()["mine"]["trusted"]
+ if include_unsafe:
+ from_balance_before += from_wallet.getbalances()["mine"]["untrusted_pending"]
+
if to_wallet is None:
assert amount is None
else:
@@ -71,6 +74,8 @@ class WalletSendTest(BitcoinTestFramework):
options["inputs"] = inputs
if add_inputs is not None:
options["add_inputs"] = add_inputs
+ if include_unsafe is not None:
+ options["include_unsafe"] = include_unsafe
if change_address is not None:
options["change_address"] = change_address
if change_position is not None:
@@ -133,6 +138,10 @@ class WalletSendTest(BitcoinTestFramework):
assert not "txid" in res
assert "psbt" in res
+ from_balance = from_wallet.getbalances()["mine"]["trusted"]
+ if include_unsafe:
+ from_balance += from_wallet.getbalances()["mine"]["untrusted_pending"]
+
if add_to_wallet and not include_watching:
# Ensure transaction exists in the wallet:
tx = from_wallet.gettransaction(res["txid"])
@@ -143,13 +152,13 @@ class WalletSendTest(BitcoinTestFramework):
assert tx
if amount:
if subtract_fee_from_outputs:
- assert_equal(from_balance_before - from_wallet.getbalance(), amount)
+ assert_equal(from_balance_before - from_balance, amount)
else:
- assert_greater_than(from_balance_before - from_wallet.getbalance(), amount)
+ assert_greater_than(from_balance_before - from_balance, amount)
else:
assert next((out for out in tx["vout"] if out["scriptPubKey"]["asm"] == "OP_RETURN 35"), None)
else:
- assert_equal(from_balance_before, from_wallet.getbalance())
+ assert_equal(from_balance_before, from_balance)
if to_wallet:
self.sync_mempools()
@@ -343,22 +352,41 @@ class WalletSendTest(BitcoinTestFramework):
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():
+ for mode in ["economical", "conservative"]:
+ for k, v in {"string": "true", "bool": 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)))
+ expect_error=(-3, f"Expected type number for conf_target, got {k}"))
- # Test setting explicit fee rate just below the minimum and at zero.
+ # Test setting explicit fee rate just below the minimum of 1 sat/vB.
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)"))
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_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)"))
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0,
- expect_error=(-4, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=0,
- expect_error=(-4, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
+ msg = "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0.999, expect_error=(-4, msg))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=0.999, expect_error=(-4, msg))
+
+ self.log.info("Explicit fee rate raises if invalid fee_rate is passed")
+ # Test fee_rate with zero values.
+ msg = "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"
+ for zero_value in [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=zero_value, expect_error=(-4, msg))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=zero_value, expect_error=(-4, msg))
+ msg = "Invalid amount"
+ # Test fee_rate values that don't pass fixed-point parsing checks.
+ for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg))
+ # Test fee_rate values that cannot be represented in sat/vB.
+ for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999, "0.0001", "0.00000001", "0.00099999", "31.99999999"]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg))
+ # Test fee_rate out of range (negative number).
+ msg = "Amount out of range"
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=-1, expect_error=(-3, msg))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=-1, expect_error=(-3, msg))
+ # Test type error.
+ msg = "Amount is not a number or string"
+ for invalid_value in [True, {"foo": "bar"}]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=invalid_value, expect_error=(-3, msg))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=invalid_value, expect_error=(-3, msg))
# 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)
@@ -440,6 +468,14 @@ class WalletSendTest(BitcoinTestFramework):
self.log.info("Subtract fee from output")
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, subtract_fee_from_outputs=[0])
+ self.log.info("Include unsafe inputs")
+ self.nodes[1].createwallet(wallet_name="w5")
+ w5 = self.nodes[1].get_wallet_rpc("w5")
+ self.test_send(from_wallet=w0, to_wallet=w5, amount=2)
+ self.test_send(from_wallet=w5, to_wallet=w0, amount=1, expect_error=(-4, "Insufficient funds"))
+ res = self.test_send(from_wallet=w5, to_wallet=w0, amount=1, include_unsafe=True)
+ assert res["complete"]
+
if __name__ == '__main__':
WalletSendTest().main()