chia-blockchain/tests/fee_estimation/test_fee_estimation_integration.py

261 lines
10 KiB
Python

from __future__ import annotations
import types
from typing import Dict, List
import pytest
from chia_rs import Coin
from chia.consensus.cost_calculator import NPCResult
from chia.full_node.bitcoin_fee_estimator import create_bitcoin_fee_estimator
from chia.full_node.fee_estimation import (
EmptyFeeMempoolInfo,
EmptyMempoolInfo,
FeeBlockInfo,
FeeMempoolInfo,
MempoolInfo,
)
from chia.full_node.fee_estimator_interface import FeeEstimatorInterface
from chia.full_node.fee_tracker import FeeTracker
from chia.full_node.mempool import Mempool, MempoolRemoveReason
from chia.simulator.block_tools import test_constants
from chia.simulator.wallet_tools import WalletTool
from chia.types.clvm_cost import CLVMCost
from chia.types.fee_rate import FeeRate, FeeRateV2
from chia.types.mempool_item import MempoolItem
from chia.util.ints import uint32, uint64
from tests.core.mempool.test_mempool_manager import (
create_test_block_record,
instantiate_mempool_manager,
zero_calls_get_coin_record,
)
def make_mempoolitem() -> MempoolItem:
wallet_tool = WalletTool(test_constants)
ph = wallet_tool.get_new_puzzlehash()
coin = Coin(ph, ph, uint64(10000))
spend_bundle = wallet_tool.generate_signed_transaction(uint64(10000), ph, coin)
cost = uint64(5000000)
block_height = 1
fee = uint64(10000000)
mempool_item = MempoolItem(
spend_bundle,
fee,
NPCResult(None, None, cost),
cost,
spend_bundle.name(),
[],
uint32(block_height),
)
return mempool_item
class FeeEstimatorInterfaceIntegrationVerificationObject(FeeEstimatorInterface):
add_mempool_item_called_count: int = 0
remove_mempool_item_called_count: int = 0
new_block_called_count: int = 0
current_block_height: int = 0
def new_block_height(self, block_height: uint32) -> None:
self.current_block_height: int = block_height
def new_block(self, block_info: FeeBlockInfo) -> None:
"""A new block has been added to the blockchain"""
self.current_block_height = block_info.block_height
self.new_block_called_count += 1
def add_mempool_item(self, mempool_info: FeeMempoolInfo, mempool_item: MempoolItem) -> None:
"""A MempoolItem (transaction and associated info) has been added to the mempool"""
self.add_mempool_item_called_count += 1
def remove_mempool_item(self, mempool_info: FeeMempoolInfo, mempool_item: MempoolItem) -> None:
"""A MempoolItem (transaction and associated info) has been removed from the mempool"""
self.remove_mempool_item_called_count += 1
def estimate_fee_rate(self, *, time_offset_seconds: int) -> FeeRateV2:
"""time_offset_seconds: number of seconds into the future for which to estimate fee"""
return FeeRateV2(0)
def mempool_size(self) -> CLVMCost:
"""Report last seen mempool size"""
return CLVMCost(uint64(0))
def mempool_max_size(self) -> CLVMCost:
"""Report current mempool max "size" (i.e. CLVM cost)"""
return CLVMCost(uint64(0))
def get_mempool_info(self) -> FeeMempoolInfo:
"""Report Mempool current configuration and state"""
return EmptyFeeMempoolInfo
def test_mempool_fee_estimator_init() -> None:
max_block_cost = uint64(1000 * 1000)
fee_estimator = create_bitcoin_fee_estimator(max_block_cost)
mempool = Mempool(EmptyMempoolInfo, fee_estimator)
assert mempool.fee_estimator
test_mempool_info = MempoolInfo(
max_size_in_cost=CLVMCost(uint64(5000000)),
minimum_fee_per_cost_to_replace=FeeRate(uint64(5)),
max_block_clvm_cost=CLVMCost(uint64(1000000)),
)
def test_mempool_fee_estimator_add_item() -> None:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
item = make_mempoolitem()
mempool.add_to_pool(item)
assert mempool.fee_estimator.add_mempool_item_called_count == 1 # type: ignore[attr-defined]
def test_item_not_removed_if_not_added() -> None:
for reason in MempoolRemoveReason:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
item = make_mempoolitem()
mempool.remove_from_pool([item.name], reason)
assert mempool.fee_estimator.remove_mempool_item_called_count == 0 # type: ignore[attr-defined]
def test_mempool_fee_estimator_remove_item() -> None:
should_call_fee_estimator_remove: Dict[MempoolRemoveReason, int] = {
MempoolRemoveReason.BLOCK_INCLUSION: 0,
MempoolRemoveReason.CONFLICT: 1,
MempoolRemoveReason.POOL_FULL: 1,
}
for reason, call_count in should_call_fee_estimator_remove.items():
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
item = make_mempoolitem()
mempool.add_to_pool(item)
mempool.remove_from_pool([item.name], reason)
assert mempool.fee_estimator.remove_mempool_item_called_count == call_count # type: ignore[attr-defined]
def test_mempool_manager_fee_estimator_new_block() -> None:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
item = make_mempoolitem()
height = uint32(4)
included_items = [item]
mempool.fee_estimator.new_block(FeeBlockInfo(height, included_items))
assert mempool.fee_estimator.new_block_called_count == 1 # type: ignore[attr-defined]
def test_current_block_height_init() -> None:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
assert mempool.fee_estimator.current_block_height == uint32(0) # type: ignore[attr-defined]
def test_current_block_height_add() -> None:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
item = make_mempoolitem()
height = uint32(7)
fee_estimator.new_block_height(height)
mempool.add_to_pool(item)
assert mempool.fee_estimator.current_block_height == height # type: ignore[attr-defined]
def test_current_block_height_remove() -> None:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
item = make_mempoolitem()
height = uint32(8)
fee_estimator.new_block_height(height)
mempool.add_to_pool(item)
mempool.remove_from_pool([item.name], MempoolRemoveReason.CONFLICT)
assert mempool.fee_estimator.current_block_height == height # type: ignore[attr-defined]
def test_current_block_height_new_block_height() -> None:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
height = uint32(9)
mempool.fee_estimator.new_block_height(height)
assert mempool.fee_estimator.current_block_height == height # type: ignore[attr-defined]
def test_current_block_height_new_block() -> None:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
height = uint32(10)
included_items: List[MempoolItem] = []
mempool.fee_estimator.new_block(FeeBlockInfo(height, included_items))
assert mempool.fee_estimator.current_block_height == height # type: ignore[attr-defined]
def test_current_block_height_new_height_then_new_block() -> None:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
height = uint32(11)
included_items: List[MempoolItem] = []
fee_estimator.new_block_height(uint32(height - 1))
mempool.fee_estimator.new_block(FeeBlockInfo(height, included_items))
assert mempool.fee_estimator.current_block_height == height # type: ignore[attr-defined]
def test_current_block_height_new_block_then_new_height() -> None:
fee_estimator = FeeEstimatorInterfaceIntegrationVerificationObject()
mempool = Mempool(test_mempool_info, fee_estimator)
height = uint32(12)
included_items: List[MempoolItem] = []
fee_estimator.new_block_height(uint32(height - 1))
mempool.fee_estimator.new_block(FeeBlockInfo(height, included_items))
fee_estimator.new_block_height(uint32(height + 1))
assert mempool.fee_estimator.current_block_height == height + 1 # type: ignore[attr-defined]
@pytest.mark.asyncio
async def test_mm_new_peak_changes_fee_estimator_block_height() -> None:
mempool_manager = await instantiate_mempool_manager(zero_calls_get_coin_record)
block2 = create_test_block_record(height=uint32(2))
await mempool_manager.new_peak(block2, None)
assert mempool_manager.mempool.fee_estimator.block_height == uint32(2) # type: ignore[attr-defined]
@pytest.mark.asyncio
async def test_mm_calls_new_block_height() -> None:
mempool_manager = await instantiate_mempool_manager(zero_calls_get_coin_record)
new_block_height_called = False
def test_new_block_height_called(self: FeeEstimatorInterface, height: uint32) -> None:
nonlocal new_block_height_called
new_block_height_called = True
# Replace new_block_height with test function
mempool_manager.fee_estimator.new_block_height = types.MethodType( # type: ignore[assignment]
test_new_block_height_called, mempool_manager.fee_estimator
)
block2 = create_test_block_record(height=uint32(2))
await mempool_manager.new_peak(block2, None)
assert new_block_height_called
def test_add_tx_called() -> None:
max_block_cost = uint64(1000 * 1000)
fee_estimator = create_bitcoin_fee_estimator(max_block_cost)
mempool = Mempool(test_mempool_info, fee_estimator)
item = make_mempoolitem()
add_tx_called = False
def add_tx_called_fun(self: FeeTracker, mitem: MempoolItem) -> None:
nonlocal add_tx_called
add_tx_called = True
# Replace with test method
mempool.fee_estimator.tracker.add_tx = types.MethodType( # type: ignore[attr-defined]
add_tx_called_fun, mempool.fee_estimator.tracker # type: ignore[attr-defined]
)
mempool.add_to_pool(item)
assert add_tx_called