aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/mempool_persist.py2
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py28
-rw-r--r--test/functional/test_framework/util.py28
-rwxr-xr-xtest/functional/wallet_keypool.py2
-rwxr-xr-xtest/functional/wallet_send.py9
5 files changed, 59 insertions, 10 deletions
diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py
index 752b925b92..1ae95fced3 100755
--- a/test/functional/mempool_persist.py
+++ b/test/functional/mempool_persist.py
@@ -141,7 +141,7 @@ class MempoolPersistTest(BitcoinTestFramework):
self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 6 transactions")
os.rename(mempooldat0, mempooldat1)
self.stop_nodes()
- self.start_node(1, extra_args=[])
+ self.start_node(1, extra_args=["-persistmempool"])
assert self.nodes[1].getmempoolinfo()["loaded"]
assert_equal(len(self.nodes[1].getrawmempool()), 6)
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index fcc310394a..bbd3dbb10c 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -100,6 +100,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.test_transaction_too_large()
self.test_include_unsafe()
self.test_22670()
+ self.test_feerate_rounding()
def test_change_position(self):
"""Ensure setting changePosition in fundraw with an exact match is handled properly."""
@@ -1026,6 +1027,33 @@ class RawTransactionsTest(BitcoinTestFramework):
do_fund_send(upper_bound)
self.restart_node(0)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(0, 2)
+ self.connect_nodes(0, 3)
+
+ def test_feerate_rounding(self):
+ self.log.info("Test that rounding of GetFee does not result in an assertion")
+
+ self.nodes[1].createwallet("roundtest")
+ w = self.nodes[1].get_wallet_rpc("roundtest")
+
+ addr = w.getnewaddress(address_type="bech32")
+ self.nodes[0].sendtoaddress(addr, 1)
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ # A P2WPKH input costs 68 vbytes; With a single P2WPKH output, the rest of the tx is 42 vbytes for a total of 110 vbytes.
+ # At a feerate of 1.85 sat/vb, the input will need a fee of 125.8 sats and the rest 77.7 sats
+ # The entire tx fee should be 203.5 sats.
+ # Coin selection rounds the fee individually instead of at the end (due to how CFeeRate::GetFee works).
+ # If rounding down (which is the incorrect behavior), then the calculated fee will be 125 + 77 = 202.
+ # If rounding up, then the calculated fee will be 126 + 78 = 204.
+ # In the former case, the calculated needed fee is higher than the actual fee being paid, so an assertion is reached
+ # To test this does not happen, we subtract 202 sats from the input value. If working correctly, this should
+ # fail with insufficient funds rather than bitcoind asserting.
+ rawtx = w.createrawtransaction(inputs=[], outputs=[{self.nodes[0].getnewaddress(address_type="bech32"): 1 - 0.00000202}])
+ assert_raises_rpc_error(-4, "Insufficient funds", w.fundrawtransaction, rawtx, {"fee_rate": 1.85})
+
if __name__ == '__main__':
RawTransactionsTest().main()
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 35dbfbba8d..8a41ec4370 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -35,13 +35,15 @@ def assert_approx(v, vexp, vspan=0.00001):
raise AssertionError("%s > [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
-def assert_fee_amount(fee, tx_size, fee_per_kB):
- """Assert the fee was in range"""
- target_fee = round(tx_size * fee_per_kB / 1000, 8)
+def assert_fee_amount(fee, tx_size, feerate_BTC_kvB):
+ """Assert the fee is in range."""
+ assert isinstance(tx_size, int)
+ target_fee = get_fee(tx_size, feerate_BTC_kvB)
if fee < target_fee:
raise AssertionError("Fee of %s BTC too low! (Should be %s BTC)" % (str(fee), str(target_fee)))
# allow the wallet's estimation to be at most 2 bytes off
- if fee > (tx_size + 2) * fee_per_kB / 1000:
+ high_fee = get_fee(tx_size + 2, feerate_BTC_kvB)
+ if fee > high_fee:
raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)" % (str(fee), str(target_fee)))
@@ -222,6 +224,24 @@ def str_to_b64str(string):
return b64encode(string.encode('utf-8')).decode('ascii')
+def ceildiv(a, b):
+ """
+ Divide 2 ints and round up to next int rather than round down
+ Implementation requires python integers, which have a // operator that does floor division.
+ Other types like decimal.Decimal whose // operator truncates towards 0 will not work.
+ """
+ assert isinstance(a, int)
+ assert isinstance(b, int)
+ return -(-a // b)
+
+
+def get_fee(tx_size, feerate_btc_kvb):
+ """Calculate the fee in BTC given a feerate is BTC/kvB. Reflects CFeeRate::GetFee"""
+ feerate_sat_kvb = int(feerate_btc_kvb * Decimal(1e8)) # Fee in sat/kvb as an int to avoid float precision errors
+ target_fee_sat = ceildiv(feerate_sat_kvb * tx_size, 1000) # Round calculated fee up to nearest sat
+ return target_fee_sat / Decimal(1e8) # Return result in BTC
+
+
def satoshi_round(amount):
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py
index 28bfc9116f..9286387f96 100755
--- a/test/functional/wallet_keypool.py
+++ b/test/functional/wallet_keypool.py
@@ -179,7 +179,7 @@ class KeyPoolTest(BitcoinTestFramework):
assert_equal("psbt" in res, True)
# create a transaction without change at the maximum fee rate, such that the output is still spendable:
- res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.0008824})
+ res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.0008823})
assert_equal("psbt" in res, True)
assert_equal(res["fee"], Decimal("0.00009706"))
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index d24d1693af..d469af600e 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -15,6 +15,7 @@ from test_framework.util import (
assert_fee_amount,
assert_greater_than,
assert_raises_rpc_error,
+ count_bytes,
)
class WalletSendTest(BitcoinTestFramework):
@@ -318,20 +319,20 @@ class WalletSendTest(BitcoinTestFramework):
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, 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"))
+ assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00007"))
# "unset" and None are treated the same for estimate_mode
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=2, estimate_mode="unset", add_to_wallet=False)
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
- assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00002"))
+ assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00002"))
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, 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"))
+ assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00004531"))
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"))
+ assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00003"))
# 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,