aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAntoine Poinsot <darosior@protonmail.com>2021-09-21 15:12:42 +0200
committerAntoine Poinsot <darosior@protonmail.com>2021-09-29 17:24:28 +0200
commit45564065627ada5dfadff13bc32bc672a4edf152 (patch)
tree3104e24193a374f431ac29bc9b5af3a3584b1aac /test
parent053415b297b8665f2d2c4dce7c2c54bcc5298ef4 (diff)
downloadbitcoin-45564065627ada5dfadff13bc32bc672a4edf152.tar.xz
qa: test fee estimation with replacement transactions
Signed-off-by: Antoine Poinsot <darosior@protonmail.com>
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/feature_fee_estimation.py81
1 files changed, 81 insertions, 0 deletions
diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py
index 613ed41654..ac00db8ff0 100755
--- a/test/functional/feature_fee_estimation.py
+++ b/test/functional/feature_fee_estimation.py
@@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test fee estimation code."""
from decimal import Decimal
+import os
import random
from test_framework.messages import (
@@ -155,6 +156,21 @@ def check_estimates(node, fees_seen):
check_raw_estimates(node, fees_seen)
check_smart_estimates(node, fees_seen)
+
+def send_tx(node, utxo, feerate):
+ """Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb)."""
+ overhead, op, scriptsig, nseq, value, spk = 10, 36, 5, 4, 8, 24
+ tx_size = overhead + op + scriptsig + nseq + value + spk
+ fee = tx_size * feerate
+
+ tx = CTransaction()
+ tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), SCRIPT_SIG[utxo["vout"]])]
+ tx.vout = [CTxOut(int(utxo["amount"] * COIN) - fee, P2SH_1)]
+ txid = node.sendrawtransaction(tx.serialize().hex())
+
+ return txid
+
+
class EstimateFeeTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 3
@@ -271,6 +287,60 @@ class EstimateFeeTest(BitcoinTestFramework):
high_val = 3*self.nodes[1].estimatesmartfee(1)['feerate']
self.restart_node(1, extra_args=[f'-minrelaytxfee={high_val}'])
check_estimates(self.nodes[1], self.fees_per_kb)
+ self.restart_node(1)
+
+ def sanity_check_rbf_estimates(self, utxos):
+ """During 5 blocks, broadcast low fee transactions. Only 10% of them get
+ confirmed and the remaining ones get RBF'd with a high fee transaction at
+ the next block.
+ The block policy estimator should return the high feerate.
+ """
+ # The broadcaster and block producer
+ node = self.nodes[0]
+ miner = self.nodes[1]
+ # In sat/vb
+ low_feerate = 1
+ high_feerate = 10
+ # Cache the utxos of which to replace the spender after it failed to get
+ # confirmed
+ utxos_to_respend = []
+ txids_to_replace = []
+
+ assert len(utxos) >= 250
+ for _ in range(5):
+ # Broadcast 45 low fee transactions that will need to be RBF'd
+ for _ in range(45):
+ u = utxos.pop(0)
+ txid = send_tx(node, u, low_feerate)
+ utxos_to_respend.append(u)
+ txids_to_replace.append(txid)
+ # Broadcast 5 low fee transaction which don't need to
+ for _ in range(5):
+ send_tx(node, utxos.pop(0), low_feerate)
+ # Mine the transactions on another node
+ self.sync_mempools(wait=.1, nodes=[node, miner])
+ for txid in txids_to_replace:
+ miner.prioritisetransaction(txid=txid, fee_delta=-COIN)
+ self.generate(miner, 1)
+ self.sync_blocks(wait=.1, nodes=[node, miner])
+ # RBF the low-fee transactions
+ while True:
+ try:
+ u = utxos_to_respend.pop(0)
+ send_tx(node, u, high_feerate)
+ except IndexError:
+ break
+
+ # Mine the last replacement txs
+ self.sync_mempools(wait=.1, nodes=[node, miner])
+ self.generate(miner, 1)
+ self.sync_blocks(wait=.1, nodes=[node, miner])
+
+ # Only 10% of the transactions were really confirmed with a low feerate,
+ # the rest needed to be RBF'd. We must return the 90% conf rate feerate.
+ high_feerate_kvb = Decimal(high_feerate) / COIN * 10**3
+ est_feerate = node.estimatesmartfee(2)["feerate"]
+ assert est_feerate == high_feerate_kvb
def run_test(self):
self.log.info("This test is time consuming, please be patient")
@@ -297,6 +367,17 @@ class EstimateFeeTest(BitcoinTestFramework):
self.log.info("Test fee rate estimation after restarting node with high MempoolMinFee")
self.test_feerate_mempoolminfee()
+ self.log.info("Restarting node with fresh estimation")
+ self.stop_node(0)
+ fee_dat = os.path.join(self.nodes[0].datadir, self.chain, "fee_estimates.dat")
+ os.remove(fee_dat)
+ self.start_node(0)
+ self.connect_nodes(0, 1)
+ self.connect_nodes(0, 2)
+
+ self.log.info("Testing estimates with RBF.")
+ self.sanity_check_rbf_estimates(self.confutxo + self.memutxo)
+
self.log.info("Testing that fee estimation is disabled in blocksonly.")
self.restart_node(0, ["-blocksonly"])
assert_raises_rpc_error(-32603, "Fee estimation disabled",