0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-07 10:27:47 -05:00

test: add estimatefee functionality test

This commit is contained in:
ismaelsadeeq 2025-01-22 17:26:22 -05:00
parent a405eefac9
commit 8982cfea52
No known key found for this signature in database
GPG key ID: 0E3908F364989888
2 changed files with 90 additions and 5 deletions

View file

@ -11,6 +11,8 @@ import time
from test_framework.messages import (
COIN,
DEFAULT_BLOCK_MAX_WEIGHT,
WITNESS_SCALE_FACTOR,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
@ -24,6 +26,7 @@ from test_framework.wallet import MiniWallet
MAX_FILE_AGE = 60
SECONDS_PER_HOUR = 60 * 60
MEMPOOL_FORECASTER_CACHE_LIFE = 30 # Seconds
def small_txpuzzle_randfee(
wallet, from_node, conflist, unconflist, amount, min_fee, fee_increment, batch_reqs
@ -136,6 +139,14 @@ def check_fee_estimates_btw_modes(node, expected_conservative, expected_economic
assert_equal(fee_est_economical, expected_economical)
assert_equal(fee_est_default, expected_economical)
def verify_estimate_response(estimate, min_relay_fee, low_fee, high_fee, forecaster, errors):
assert_equal(estimate["minrelayfee"], min_relay_fee)
if low_fee and high_fee:
assert_equal(estimate["low"], low_fee)
assert_equal(estimate["high"], high_fee)
assert_equal(estimate["forecaster"], forecaster)
assert all(err in estimate["errors"] for err in errors)
class EstimateFeeTest(BitcoinTestFramework):
def set_test_params(self):
@ -408,24 +419,93 @@ class EstimateFeeTest(BitcoinTestFramework):
self.sync_mempools()
self.generate(miner, 1)
def setup_and_broadcast(self, fee_rate, blocks, txs):
"""Helper for broadcasting transactions from node 1 and mining them using node 2 consecutively."""
for _ in range(blocks):
self.broadcast_and_mine(self.nodes[1], self.nodes[2], fee_rate, txs)
def send_transactions(self, utxos, fee_rate, target_vsize):
for utxo in utxos:
self.wallet.send_self_transfer(
from_node=self.nodes[0],
utxo_to_spend=utxo,
fee_rate=fee_rate,
target_vsize=target_vsize,
)
def test_estimation_modes(self):
low_feerate = Decimal("0.001")
high_feerate = Decimal("0.005")
tx_count = 24
# Broadcast and mine high fee transactions for the first 12 blocks.
for _ in range(12):
self.broadcast_and_mine(self.nodes[1], self.nodes[2], high_feerate, tx_count)
check_fee_estimates_btw_modes(self.nodes[0], high_feerate, high_feerate)
self.setup_and_broadcast(high_feerate, blocks=12, txs=tx_count)
# We now track 12 blocks; short horizon stats will start decaying.
# Broadcast and mine low fee transactions for the next 4 blocks.
for _ in range(4):
self.broadcast_and_mine(self.nodes[1], self.nodes[2], low_feerate, tx_count)
self.setup_and_broadcast(low_feerate, blocks=4, txs=tx_count)
# conservative mode will consider longer time horizons while economical mode does not
# Check the fee estimates for both modes after mining low fee transactions.
check_fee_estimates_btw_modes(self.nodes[0], high_feerate, low_feerate)
def test_estimatefee(self):
node0 = self.nodes[0]
self.log.info("Ensure node0's mempool is empty at the start")
assert_equal(node0.getmempoolinfo()['size'], 0)
self.log.info("Test estimatefee after restart with empty mempool and no block policy estimator data")
mempool_forecast_error = "Mempool Forecast: No enough transactions in the mempool to provide a fee rate forecast"
block_policy_error = "Block Policy Estimator: Insufficient data or no feerate found"
generic_error = "Failed to get estimate from forecasters"
estimate_after_restart = node0.estimatefee(1)
min_relay_fee = Decimal("0.0000100")
verify_estimate_response(estimate_after_restart, min_relay_fee, None, None, None, [generic_error, block_policy_error])
self.log.info("Test estimatefee after gathering sufficient block policy estimator data")
# Generate high-feerate transactions and mine them over 6 blocks
high_feerate, tx_count = Decimal("0.004"), 24
self.setup_and_broadcast(high_feerate, blocks=6, txs=tx_count)
estimate_from_block_policy = node0.estimatefee(1)
verify_estimate_response(estimate_from_block_policy, min_relay_fee, high_feerate, high_feerate, "Block Policy Estimator", [mempool_forecast_error])
self.log.info("Verify we return block policy estimator estimate when mempool provides higher estimate")
# Add 10 large high-feerate transactions enough to generate a block template
num_txs = 10
target_vsize = int((DEFAULT_BLOCK_MAX_WEIGHT / WITNESS_SCALE_FACTOR - 4000) / num_txs) # Account for coinbase space
self.restart_node(0, extra_args=[f"-datacarriersize={target_vsize}"])
utxos = [self.wallet.get_utxo(confirmed_only=True) for _ in range(num_txs)]
insane_feerate = Decimal("0.01")
self.send_transactions(utxos, insane_feerate, target_vsize)
estimate_after_spike = node0.estimatefee(1)
verify_estimate_response(estimate_after_spike, min_relay_fee, high_feerate, high_feerate, "Block Policy Estimator", [])
self.log.info("Test caching of recent estimates")
# Restart node with empty mempool, then broadcast low-feerate transactions
# Check that estimate reflects the lower feerate even after higher-feerate transactions were recently broadcasted
self.stop_node(0)
os.remove(node0.chain_path / "mempool.dat")
self.restart_node(0, extra_args=[f"-datacarriersize={target_vsize}"])
low_feerate = Decimal("0.00004")
self.send_transactions(utxos, low_feerate, target_vsize)
lower_estimate = node0.estimatefee(1)
verify_estimate_response(lower_estimate, min_relay_fee, low_feerate, low_feerate, "Mempool Forecast", [])
# Verify cache persists low-feerate estimate after broadcasting higher-feerate transactions
med_feerate = Decimal("0.002")
self.send_transactions(utxos, med_feerate, target_vsize) # Double-spend UTXOs with medium feerate
cached_estimate = node0.estimatefee(1)
verify_estimate_response(cached_estimate, min_relay_fee, low_feerate, low_feerate, "Mempool Forecast", [])
self.log.info("Test estimate refresh after cache expiration")
current_timestamp = int(time.time())
node0.setmocktime(current_timestamp + (MEMPOOL_FORECASTER_CACHE_LIFE + 1))
new_estimate = node0.estimatefee(1)
verify_estimate_response(new_estimate, min_relay_fee, med_feerate, med_feerate, "Mempool Forecast", [])
def run_test(self):
self.log.info("This test is time consuming, please be patient")
self.log.info("Splitting inputs so we can generate tx's")
@ -472,6 +552,10 @@ class EstimateFeeTest(BitcoinTestFramework):
self.log.info("Test estimatesmartfee modes")
self.test_estimation_modes()
self.clear_estimates()
self.log.info("Test estimatefee RPC")
self.test_estimatefee()
self.log.info("Testing that fee estimation is disabled in blocksonly.")
self.restart_node(0, ["-blocksonly"])
assert_raises_rpc_error(

View file

@ -35,6 +35,7 @@ MAX_LOCATOR_SZ = 101
MAX_BLOCK_WEIGHT = 4000000
MAX_BLOOM_FILTER_SIZE = 36000
MAX_BLOOM_HASH_FUNCS = 50
DEFAULT_BLOCK_MAX_WEIGHT = MAX_BLOCK_WEIGHT - 4000
COIN = 100000000 # 1 btc in satoshis
MAX_MONEY = 21000000 * COIN