feature_fee_estimation.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2014-2022 The Limenka developers
   3  # Distributed under the MIT software license, see the accompanying
   4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  """Test fee estimation code."""
   6  from copy import deepcopy
   7  from decimal import Decimal, ROUND_DOWN
   8  import http.client
   9  import json
  10  import os
  11  import random
  12  import time
  13  import urllib.parse
  14  
  15  from test_framework.messages import (
  16      COIN,
  17  )
  18  from test_framework.test_framework import LimenkaTestFramework
  19  from test_framework.util import (
  20      assert_equal,
  21      assert_greater_than,
  22      assert_greater_than_or_equal,
  23      assert_raises_rpc_error,
  24      satoshi_round,
  25  )
  26  from test_framework.wallet import MiniWallet
  27  
  28  MAX_FILE_AGE = 60
  29  SECONDS_PER_HOUR = 60 * 60
  30  target_success_threshold = 0.8
  31  
  32  def small_txpuzzle_randfee(
  33      wallet, from_node, conflist, unconflist, amount, min_fee, fee_increment, batch_reqs
  34  ):
  35      """Create and send a transaction with a random fee using MiniWallet.
  36  
  37      The function takes a list of confirmed outputs and unconfirmed outputs
  38      and attempts to use the confirmed list first for its inputs.
  39      It adds the newly created outputs to the unconfirmed list.
  40      Returns (raw transaction, fee)."""
  41  
  42      # It's best to exponentially distribute our random fees
  43      # because the buckets are exponentially spaced.
  44      # Exponentially distributed from 1-128 * fee_increment
  45      rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28))
  46      # Total fee ranges from min_fee to min_fee + 127*fee_increment
  47      fee = min_fee - fee_increment + satoshi_round(rand_fee, rounding=ROUND_DOWN)
  48      utxos_to_spend = []
  49      total_in = Decimal("0.00000000")
  50      while total_in <= (amount + fee) and len(conflist) > 0:
  51          t = conflist.pop(0)
  52          total_in += t["value"]
  53          utxos_to_spend.append(t)
  54      while total_in <= (amount + fee) and len(unconflist) > 0:
  55          t = unconflist.pop(0)
  56          total_in += t["value"]
  57          utxos_to_spend.append(t)
  58      if total_in <= amount + fee:
  59          raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}")
  60      tx = wallet.create_self_transfer_multi(
  61          utxos_to_spend=utxos_to_spend,
  62          fee_per_output=0,
  63      )["tx"]
  64      tx.vout[0].nValue = int((total_in - amount - fee) * COIN)
  65      tx.vout.append(deepcopy(tx.vout[0]))
  66      tx.vout[1].nValue = int(amount * COIN)
  67      tx.rehash()
  68      txid = tx.hash
  69      tx_hex = tx.serialize().hex()
  70  
  71      batch_reqs.append(from_node.sendrawtransaction.get_request(hexstring=tx_hex, maxfeerate=0))
  72      unconflist.append({"txid": txid, "vout": 0, "value": total_in - amount - fee})
  73      unconflist.append({"txid": txid, "vout": 1, "value": amount})
  74  
  75      return (tx.get_vsize(), fee)
  76  
  77  
  78  def rest_getfee(url, mode, target, status=200):
  79      rest_uri = '/rest/fee/%s/%s.json' % (mode, target)
  80  
  81      url = urllib.parse.urlparse(url)
  82      conn = http.client.HTTPConnection(url.hostname, url.port)
  83      conn.request('GET', rest_uri)
  84      resp = conn.getresponse()
  85      data = resp.read()
  86  
  87      assert_equal(resp.status, status)
  88  
  89      if status == 200:
  90          return json.loads(data.decode('utf-8'), parse_float=Decimal)
  91      else:
  92          return data
  93  
  94  def check_raw_estimates(node, fees_seen):
  95      """Call estimaterawfee and verify that the estimates meet certain invariants."""
  96  
  97      delta = 1.0e-6  # account for rounding error
  98      for i in range(1, 26):
  99          for _, e in node.estimaterawfee(i).items():
 100              feerate = float(e["feerate"])
 101              assert_greater_than(feerate, 0)
 102  
 103              if feerate + delta < min(fees_seen) or feerate - delta > max(fees_seen):
 104                  raise AssertionError(
 105                      f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{max(fees_seen)})"
 106                  )
 107  
 108  
 109  def check_smart_estimates(node, fees_seen):
 110      """Call estimatesmartfee and verify that the estimates meet certain invariants."""
 111  
 112      delta = 1.0e-6  # account for rounding error
 113      all_smart_estimates = [node.estimatesmartfee(i) for i in range(1, 26)]
 114      mempoolMinFee = node.getmempoolinfo()["mempoolminfee"]
 115      minRelaytxFee = node.getmempoolinfo()["minrelaytxfee"]
 116      feerate_ceiling = max(max(fees_seen), float(mempoolMinFee), float(minRelaytxFee))
 117      last_feerate = feerate_ceiling
 118      for i, e in enumerate(all_smart_estimates):  # estimate is for i+1
 119          assert_equal(e, rest_getfee(node.url, 'unset', i+1))
 120  
 121          feerate = float(e["feerate"])
 122          assert_greater_than(feerate, 0)
 123          assert_greater_than_or_equal(feerate, float(mempoolMinFee))
 124          assert_greater_than_or_equal(feerate, float(minRelaytxFee))
 125  
 126          if feerate + delta < min(fees_seen) or feerate - delta > feerate_ceiling:
 127              raise AssertionError(
 128                  f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{feerate_ceiling})"
 129              )
 130          if feerate - delta > last_feerate:
 131              raise AssertionError(
 132                  f"Estimated fee ({feerate}) larger than last fee ({last_feerate}) for lower number of confirms"
 133              )
 134          last_feerate = feerate
 135  
 136          if i == 0:
 137              assert_equal(e["blocks"], 2)
 138          else:
 139              assert_greater_than_or_equal(i + 1, e["blocks"])
 140  
 141  
 142  def check_estimates(node, fees_seen):
 143      check_raw_estimates(node, fees_seen)
 144      check_smart_estimates(node, fees_seen)
 145  
 146  
 147  def make_tx(wallet, utxo, feerate):
 148      """Create a 1in-1out transaction with a specific input and feerate (sat/vb)."""
 149      return wallet.create_self_transfer(
 150          utxo_to_spend=utxo,
 151          fee_rate=Decimal(feerate * 1000) / COIN,
 152      )
 153  
 154  def check_fee_estimates_btw_modes(node, expected_conservative, expected_economical):
 155      fee_est_conservative = node.estimatesmartfee(1, estimate_mode="conservative")['feerate']
 156      fee_est_economical = node.estimatesmartfee(1, estimate_mode="economical")['feerate']
 157      fee_est_default = node.estimatesmartfee(1)['feerate']
 158      assert_equal(fee_est_conservative, expected_conservative)
 159      assert_equal(fee_est_economical, expected_economical)
 160      assert_equal(fee_est_default, expected_economical)
 161      assert_equal(fee_est_conservative, rest_getfee(node.url, 'conservative', 1)['feerate'])
 162      assert_equal(fee_est_economical, rest_getfee(node.url, 'economical', 1)['feerate'])
 163      assert_equal(fee_est_default, rest_getfee(node.url, 'unset', 1)['feerate'])
 164  
 165  
 166  def get_feerate_into_mempool(node, kB):
 167      mempool_entries = list(node.getrawmempool(verbose=True).values())
 168      for entry in mempool_entries:
 169          entry['feerate_BTC/vB'] = entry['fees']['modified'] / entry['vsize']
 170      mempool_entries.sort(key=lambda entry: entry['feerate_BTC/vB'], reverse=True)
 171      bytes_remaining = kB * 1000
 172      for entry in mempool_entries:
 173          bytes_remaining -= entry['vsize']
 174          if bytes_remaining <= 0:
 175              return satoshi_round(entry['feerate_BTC/vB'] * 1000, rounding=ROUND_DOWN)
 176      raise AssertionError('Entire mempool is smaller than %s kB' % (kB,))
 177  
 178  
 179  class EstimateFeeTest(LimenkaTestFramework):
 180      def set_test_params(self):
 181          self.num_nodes = 3
 182          # whitelist peers to speed up tx relay / mempool sync
 183          self.noban_tx_relay = True
 184          self.extra_args = [
 185              ['-rest'],
 186              ["-blockmaxweight=72000", "-rest"],
 187              ["-blockmaxweight=36000"],
 188          ]
 189  
 190      def setup_network(self):
 191          """
 192          We'll setup the network to have 3 nodes that all mine with different parameters.
 193          But first we need to use one node to create a lot of outputs
 194          which we will use to generate our transactions.
 195          """
 196          self.add_nodes(3, extra_args=self.extra_args)
 197          # Use node0 to mine blocks for input splitting
 198          # Node1 mines small blocks but that are bigger than the expected transaction rate.
 199          # NOTE: the CreateNewBlock code starts counting block weight at 4,000 weight,
 200          # (68k weight is room enough for 120 or so transactions)
 201          # Node2 is a stingy miner, that
 202          # produces too small blocks (room for only 55 or so transactions)
 203  
 204      def transact_and_mine(self, numblocks, mining_node):
 205          min_fee = Decimal("0.00001")
 206          # We will now mine numblocks blocks generating on average 100 transactions between each block
 207          # We shuffle our confirmed txout set before each set of transactions
 208          # small_txpuzzle_randfee will use the transactions that have inputs already in the chain when possible
 209          # resorting to tx's that depend on the mempool when those run out
 210          for _ in range(numblocks):
 211              random.shuffle(self.confutxo)
 212              batch_sendtx_reqs = []
 213              for _ in range(random.randrange(100 - 50, 100 + 50)):
 214                  from_index = random.randint(1, 2)
 215                  (tx_bytes, fee) = small_txpuzzle_randfee(
 216                      self.wallet,
 217                      self.nodes[from_index],
 218                      self.confutxo,
 219                      self.memutxo,
 220                      Decimal("0.005"),
 221                      min_fee,
 222                      min_fee,
 223                      batch_sendtx_reqs,
 224                  )
 225                  tx_kbytes = tx_bytes / 1000.0
 226                  self.fees_per_kb.append(float(fee) / tx_kbytes)
 227              for node in self.nodes:
 228                  node.batch(batch_sendtx_reqs)
 229              self.sync_mempools(wait=0.1)
 230              mined = mining_node.getblock(self.generate(mining_node, 1)[0], True)["tx"]
 231              # update which txouts are confirmed
 232              newmem = []
 233              for utx in self.memutxo:
 234                  if utx["txid"] in mined:
 235                      self.confutxo.append(utx)
 236                  else:
 237                      newmem.append(utx)
 238              self.memutxo = newmem
 239  
 240      def initial_split(self, node):
 241          """Split two coinbase UTxOs into many small coins"""
 242          self.confutxo = self.wallet.send_self_transfer_multi(
 243              from_node=node,
 244              utxos_to_spend=[self.wallet.get_utxo() for _ in range(2)],
 245              num_outputs=2048)['new_utxos']
 246          while len(node.getrawmempool()) > 0:
 247              self.generate(node, 1, sync_fun=self.no_op)
 248  
 249      def sanity_check_estimates_range(self):
 250          """Populate estimation buckets, assert estimates are in a sane range and
 251          are strictly increasing as the target decreases."""
 252          self.fees_per_kb = []
 253          self.memutxo = []
 254          self.log.info("Will output estimates for 1/2/3/6/15/25 blocks")
 255  
 256          for _ in range(2):
 257              self.log.info(
 258                  "Creating transactions and mining them with a block size that can't keep up"
 259              )
 260              # Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine
 261              self.transact_and_mine(10, self.nodes[2])
 262              check_estimates(self.nodes[1], self.fees_per_kb)
 263  
 264              self.log.info(
 265                  "Creating transactions and mining them at a block size that is just big enough"
 266              )
 267              # Generate transactions while mining 10 more blocks, this time with node1
 268              # which mines blocks with capacity just above the rate that transactions are being created
 269              self.transact_and_mine(10, self.nodes[1])
 270              check_estimates(self.nodes[1], self.fees_per_kb)
 271  
 272          # Finish by mining a normal-sized block:
 273          while len(self.nodes[1].getrawmempool()) > 0:
 274              self.generate(self.nodes[1], 1)
 275  
 276          self.log.info("Final estimates after emptying mempools")
 277          check_estimates(self.nodes[1], self.fees_per_kb)
 278  
 279      def test_feerate_dustrelayfee_common(self, node, multiplier, dust_mode, desc, expected_base):
 280          dust_parameter = f"-dustdynamic={dust_mode}".replace('=3*', '=')
 281          self.log.info(f"Test dust limit setting {dust_parameter} (fee estimation for {desc})")
 282          self.restart_node(0, extra_args=[dust_parameter, '-dustrelayfee=0'])
 283          assert_equal(node.getmempoolinfo()['dustdynamic'], dust_mode)
 284          expected_dustrelayfee = satoshi_round(expected_base() * multiplier, rounding=ROUND_DOWN)
 285          with node.busy_wait_for_debug_log([b'Updating dust feerate']):
 286              mempool_info = node.getmempoolinfo()
 287              assert mempool_info['dustrelayfee'] != expected_dustrelayfee
 288              assert mempool_info['dustrelayfeefloor'] <= expected_dustrelayfee
 289              node.mockscheduler(SECONDS_PER_HOUR)
 290          mempool_info = node.getmempoolinfo()
 291          assert_equal(mempool_info['dustrelayfee'], expected_dustrelayfee)
 292          assert mempool_info['dustrelayfee'] > mempool_info['dustrelayfeefloor']
 293  
 294      def test_feerate_dustrelayfee_target(self, node, multiplier, dustfee_target):
 295          dust_mode = f"{multiplier}*target:{dustfee_target}"
 296          self.test_feerate_dustrelayfee_common(node, multiplier, dust_mode, f'{dustfee_target} blocks', lambda: node.estimaterawfee(dustfee_target, target_success_threshold)['long']['feerate'])
 297  
 298      def test_feerate_dustrelayfee_mempool(self, node, multiplier, dustfee_kB):
 299          dust_mode = f"{multiplier}*mempool:{dustfee_kB}"
 300          self.test_feerate_dustrelayfee_common(node, multiplier, dust_mode, f'{dustfee_kB} kB into mempool', lambda: get_feerate_into_mempool(node, dustfee_kB))
 301  
 302      def test_feerate_dustrelayfee(self):
 303          node = self.nodes[0]
 304  
 305          # test dustdynamic=target:<blocks>
 306          for dustfee_target in (2, 8, 1008):
 307              for multiplier in (Decimal('0.5'), 1, 3, Decimal('3.3'), 10, Decimal('10.001')):
 308                  self.test_feerate_dustrelayfee_target(node, multiplier, dustfee_target)
 309  
 310          # Fill mempool up
 311          mempool_size = 0
 312          batch_sendtx_reqs = []
 313          min_fee = Decimal("0.00001")
 314          while mempool_size < 52000:
 315              (tx_bytes, fee) = small_txpuzzle_randfee(
 316                  self.wallet,
 317                  self.nodes[0],
 318                  self.confutxo,
 319                  self.memutxo,
 320                  Decimal("0.005"),
 321                  min_fee,
 322                  min_fee,
 323                  batch_sendtx_reqs,
 324              )
 325              mempool_size += tx_bytes
 326          node.batch(batch_sendtx_reqs)
 327  
 328          # test dustdynamic=mempool:<kB>
 329          for dustfee_kB in (1, 10, 50):
 330              for multiplier in (Decimal('0.5'), 1, 3, Decimal('3.3'), 10, Decimal('10.001')):
 331                  self.test_feerate_dustrelayfee_mempool(node, multiplier, dustfee_kB)
 332  
 333          # Restore nodes to a normal state, wiping the mempool
 334          self.stop_node(0)
 335          (self.nodes[0].chain_path / 'mempool.dat').unlink()
 336          self.start_node(0)
 337          self.connect_nodes(1, 0)
 338          self.connect_nodes(0, 2)
 339  
 340      def test_estimates_with_highminrelaytxfee(self):
 341          high_val = 3 * self.nodes[1].estimatesmartfee(2)["feerate"]
 342          self.restart_node(1, extra_args=[f"-minrelaytxfee={high_val}", '-rest'])
 343          check_smart_estimates(self.nodes[1], self.fees_per_kb)
 344          self.restart_node(1)
 345  
 346      def sanity_check_rbf_estimates(self, utxos):
 347          """During 5 blocks, broadcast low fee transactions. Only 10% of them get
 348          confirmed and the remaining ones get RBF'd with a high fee transaction at
 349          the next block.
 350          The block policy estimator should return the high feerate.
 351          """
 352          # The broadcaster and block producer
 353          node = self.nodes[0]
 354          miner = self.nodes[1]
 355          # In sat/vb
 356          low_feerate = 1
 357          high_feerate = 10
 358          # Cache the utxos of which to replace the spender after it failed to get
 359          # confirmed
 360          utxos_to_respend = []
 361          txids_to_replace = []
 362  
 363          assert_greater_than_or_equal(len(utxos), 250)
 364          for _ in range(5):
 365              # Broadcast 45 low fee transactions that will need to be RBF'd
 366              txs = []
 367              for _ in range(45):
 368                  u = utxos.pop(0)
 369                  tx = make_tx(self.wallet, u, low_feerate)
 370                  utxos_to_respend.append(u)
 371                  txids_to_replace.append(tx["txid"])
 372                  txs.append(tx)
 373              # Broadcast 5 low fee transaction which don't need to
 374              for _ in range(5):
 375                  tx = make_tx(self.wallet, utxos.pop(0), low_feerate)
 376                  txs.append(tx)
 377              batch_send_tx = [node.sendrawtransaction.get_request(tx["hex"]) for tx in txs]
 378              for n in self.nodes:
 379                  n.batch(batch_send_tx)
 380              # Mine the transactions on another node
 381              self.sync_mempools(wait=0.1, nodes=[node, miner])
 382              for txid in txids_to_replace:
 383                  miner.prioritisetransaction(txid=txid, fee_delta=-COIN)
 384              self.generate(miner, 1)
 385              # RBF the low-fee transactions
 386              while len(utxos_to_respend) > 0:
 387                  u = utxos_to_respend.pop(0)
 388                  tx = make_tx(self.wallet, u, high_feerate)
 389                  node.sendrawtransaction(tx["hex"])
 390                  txs.append(tx)
 391              dec_txs = [res["result"] for res in node.batch([node.decoderawtransaction.get_request(tx["hex"]) for tx in txs])]
 392              self.wallet.scan_txs(dec_txs)
 393  
 394  
 395          # Mine the last replacement txs
 396          self.sync_mempools(wait=0.1, nodes=[node, miner])
 397          self.generate(miner, 1)
 398  
 399          # Only 10% of the transactions were really confirmed with a low feerate,
 400          # the rest needed to be RBF'd. We must return the 90% conf rate feerate.
 401          high_feerate_kvb = Decimal(high_feerate) / COIN * 10 ** 3
 402          est_feerate = node.estimatesmartfee(2)["feerate"]
 403          assert_equal(est_feerate, high_feerate_kvb)
 404  
 405      def test_old_fee_estimate_file(self):
 406          # Get the initial fee rate while node is running
 407          fee_rate = self.nodes[0].estimatesmartfee(1)["feerate"]
 408  
 409          # Restart node to ensure fee_estimate.dat file is read
 410          self.restart_node(0)
 411          assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)
 412  
 413          fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
 414  
 415          # Stop the node and backdate the fee_estimates.dat file more than MAX_FILE_AGE
 416          self.stop_node(0)
 417          last_modified_time = time.time() - (MAX_FILE_AGE + 1) * SECONDS_PER_HOUR
 418          os.utime(fee_dat, (last_modified_time, last_modified_time))
 419  
 420          # Start node and ensure the fee_estimates.dat file was not read
 421          self.start_node(0)
 422          assert_equal(self.nodes[0].estimatesmartfee(1)["errors"], ["Insufficient data or no feerate found"])
 423  
 424  
 425      def test_estimate_dat_is_flushed_periodically(self):
 426          fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
 427          os.remove(fee_dat) if os.path.exists(fee_dat) else None
 428  
 429          # Verify that fee_estimates.dat does not exist
 430          assert_equal(os.path.isfile(fee_dat), False)
 431  
 432          # Verify if the string "Flushed fee estimates to fee_estimates.dat." is present in the debug log file.
 433          # If present, it indicates that fee estimates have been successfully flushed to disk.
 434          with self.nodes[0].assert_debug_log(expected_msgs=["Flushed fee estimates to fee_estimates.dat."], timeout=1):
 435              # Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
 436              self.nodes[0].mockscheduler(SECONDS_PER_HOUR)
 437  
 438          # Verify that fee estimates were flushed and fee_estimates.dat file is created
 439          assert_equal(os.path.isfile(fee_dat), True)
 440  
 441          # Verify that the estimates remain the same if there are no blocks in the flush interval
 442          block_hash_before = self.nodes[0].getbestblockhash()
 443          fee_dat_initial_content = open(fee_dat, "rb").read()
 444          with self.nodes[0].assert_debug_log(expected_msgs=["Flushed fee estimates to fee_estimates.dat."], timeout=1):
 445              # Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
 446              self.nodes[0].mockscheduler(SECONDS_PER_HOUR)
 447  
 448          # Verify that there were no blocks in between the flush interval
 449          assert_equal(block_hash_before, self.nodes[0].getbestblockhash())
 450  
 451          fee_dat_current_content = open(fee_dat, "rb").read()
 452          assert_equal(fee_dat_current_content, fee_dat_initial_content)
 453  
 454          # Verify that the estimates remain the same after shutdown with no blocks before shutdown
 455          self.restart_node(0)
 456          fee_dat_current_content = open(fee_dat, "rb").read()
 457          assert_equal(fee_dat_current_content, fee_dat_initial_content)
 458  
 459          # Verify that the estimates are not the same if new blocks were produced in the flush interval
 460          with self.nodes[0].assert_debug_log(expected_msgs=["Flushed fee estimates to fee_estimates.dat."], timeout=1):
 461              # Mock the scheduler for an hour to flush fee estimates to fee_estimates.dat
 462              self.generate(self.nodes[0], 5, sync_fun=self.no_op)
 463              self.nodes[0].mockscheduler(SECONDS_PER_HOUR)
 464  
 465          fee_dat_current_content = open(fee_dat, "rb").read()
 466          assert fee_dat_current_content != fee_dat_initial_content
 467  
 468          fee_dat_initial_content = fee_dat_current_content
 469  
 470          # Generate blocks before shutdown and verify that the fee estimates are not the same
 471          self.generate(self.nodes[0], 5, sync_fun=self.no_op)
 472          self.restart_node(0)
 473          fee_dat_current_content = open(fee_dat, "rb").read()
 474          assert fee_dat_current_content != fee_dat_initial_content
 475  
 476  
 477      def test_acceptstalefeeestimates_option(self):
 478          # Get the initial fee rate while node is running
 479          fee_rate = self.nodes[0].estimatesmartfee(1)["feerate"]
 480  
 481          self.stop_node(0)
 482  
 483          fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
 484  
 485          # Stop the node and backdate the fee_estimates.dat file more than MAX_FILE_AGE
 486          last_modified_time = time.time() - (MAX_FILE_AGE + 1) * SECONDS_PER_HOUR
 487          os.utime(fee_dat, (last_modified_time, last_modified_time))
 488  
 489          # Restart node with -acceptstalefeeestimates option to ensure fee_estimate.dat file is read
 490          self.start_node(0,extra_args=["-acceptstalefeeestimates"])
 491          assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)
 492  
 493      def clear_estimates(self):
 494          self.log.info("Restarting node with fresh estimation")
 495          self.stop_node(0)
 496          fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"
 497          os.remove(fee_dat)
 498          self.start_node(0)
 499          self.connect_nodes(0, 1)
 500          self.connect_nodes(0, 2)
 501          self.sync_blocks()
 502          assert_equal(self.nodes[0].estimatesmartfee(1)["errors"], ["Insufficient data or no feerate found"])
 503  
 504      def broadcast_and_mine(self, broadcaster, miner, feerate, count):
 505          """Broadcast and mine some number of transactions with a specified fee rate."""
 506          for _ in range(count):
 507              self.wallet.send_self_transfer(from_node=broadcaster, fee_rate=feerate)
 508          self.sync_mempools()
 509          self.generate(miner, 1)
 510  
 511      def test_estimation_modes(self):
 512          low_feerate = Decimal("0.001")
 513          high_feerate = Decimal("0.005")
 514          tx_count = 24
 515          # Broadcast and mine high fee transactions for the first 12 blocks.
 516          for _ in range(12):
 517              self.broadcast_and_mine(self.nodes[1], self.nodes[2], high_feerate, tx_count)
 518          check_fee_estimates_btw_modes(self.nodes[0], high_feerate, high_feerate)
 519  
 520          # We now track 12 blocks; short horizon stats will start decaying.
 521          # Broadcast and mine low fee transactions for the next 4 blocks.
 522          for _ in range(4):
 523              self.broadcast_and_mine(self.nodes[1], self.nodes[2], low_feerate, tx_count)
 524          # conservative mode will consider longer time horizons while economical mode does not
 525          # Check the fee estimates for both modes after mining low fee transactions.
 526          check_fee_estimates_btw_modes(self.nodes[0], high_feerate, low_feerate)
 527  
 528  
 529      def run_test(self):
 530          self.log.info("This test is time consuming, please be patient")
 531          self.log.info("Splitting inputs so we can generate tx's")
 532  
 533          # Split two coinbases into many small utxos
 534          self.start_node(0)
 535          self.wallet = MiniWallet(self.nodes[0])
 536          self.initial_split(self.nodes[0])
 537          self.log.info("Finished splitting")
 538  
 539          # Now we can connect the other nodes, didn't want to connect them earlier
 540          # so the estimates would not be affected by the splitting transactions
 541          self.start_node(1)
 542          self.start_node(2)
 543          self.connect_nodes(1, 0)
 544          self.connect_nodes(0, 2)
 545          self.connect_nodes(2, 1)
 546          self.sync_all()
 547  
 548          self.log.info("Testing estimates with single transactions.")
 549          self.sanity_check_estimates_range()
 550  
 551          self.log.info("Test fee_estimates.dat is flushed periodically")
 552          self.test_estimate_dat_is_flushed_periodically()
 553  
 554          self.test_feerate_dustrelayfee()
 555  
 556          # check that estimatesmartfee feerate is greater than or equal to maximum of mempoolminfee and minrelaytxfee
 557          self.log.info(
 558              "Test fee rate estimation after restarting node with high minrelaytxfee"
 559          )
 560          self.test_estimates_with_highminrelaytxfee()
 561  
 562          self.log.info("Test acceptstalefeeestimates option")
 563          self.test_acceptstalefeeestimates_option()
 564  
 565          self.log.info("Test reading old fee_estimates.dat")
 566          self.test_old_fee_estimate_file()
 567  
 568          self.clear_estimates()
 569  
 570          self.log.info("Testing estimates with RBF.")
 571          self.sanity_check_rbf_estimates(self.confutxo + self.memutxo)
 572  
 573          self.clear_estimates()
 574          self.log.info("Test estimatesmartfee modes")
 575          self.test_estimation_modes()
 576  
 577          self.log.info("Testing that fee estimation is disabled in blocksonly.")
 578          self.restart_node(0, ["-blocksonly"])
 579          assert_raises_rpc_error(
 580              -32603, "Fee estimation disabled", self.nodes[0].estimatesmartfee, 2
 581          )
 582  
 583          self.log.info("Bad REST requests")
 584          assert rest_getfee(self.nodes[1].url, 'foobar', 2, 400).startswith(b'<MODE> must be one of <unset|economical|conservative>')
 585          assert rest_getfee(self.nodes[1].url, 'conservative', -1, 400).startswith(b'Unable to parse confirmation target to int')
 586          assert rest_getfee(self.nodes[1].url, 'conservative', 'abc', 400).startswith(b'Unable to parse confirmation target to int')
 587          assert rest_getfee(self.nodes[1].url, 'conservative', 2**65, 400).startswith(b'Unable to parse confirmation target to int')
 588          assert rest_getfee(self.nodes[1].url, 'conservative', 0, 400).startswith(b'Invalid confirmation target, must be in between ')
 589  
 590  
 591  if __name__ == "__main__":
 592      EstimateFeeTest(__file__).main()
 593