diff options
author | Antoine Poinsot <darosior@protonmail.com> | 2021-09-21 15:12:42 +0200 |
---|---|---|
committer | Antoine Poinsot <darosior@protonmail.com> | 2021-09-29 17:24:28 +0200 |
commit | 45564065627ada5dfadff13bc32bc672a4edf152 (patch) | |
tree | 3104e24193a374f431ac29bc9b5af3a3584b1aac /test | |
parent | 053415b297b8665f2d2c4dce7c2c54bcc5298ef4 (diff) | |
download | bitcoin-45564065627ada5dfadff13bc32bc672a4edf152.tar.xz |
qa: test fee estimation with replacement transactions
Signed-off-by: Antoine Poinsot <darosior@protonmail.com>
Diffstat (limited to 'test')
-rwxr-xr-x | test/functional/feature_fee_estimation.py | 81 |
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", |