Skip to content

Commit 33a0212

Browse files
arvidnAmineKhaldi
andauthored
use per-puzzle cost to estimate DEDUP savings (#19811)
* use per-puzzle cost to estimate DEDUP savings, rather than rerunning puzzles * restore SpendBundleAddInfo to have an optional cost. remove unused max_cost parameter from get_deduplication_info() * restore call to make_test_conds() taking cost as int * update test_dedup_by_fee() and fix big in make_bundle_spends_map_and_fee() * review comments * review comments * Addendum to use per-puzzle cost to estimate DEDUP savings (#19816) Addendum to use per-puzzle cost to estimate DEDUP savings. --------- Co-authored-by: Amine Khaldi <[email protected]>
1 parent 6faab8f commit 33a0212

File tree

7 files changed

+128
-177
lines changed

7 files changed

+128
-177
lines changed

chia/_tests/core/mempool/test_mempool.py

Lines changed: 77 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
from chia.consensus.cost_calculator import NPCResult
4545
from chia.consensus.default_constants import DEFAULT_CONSTANTS
4646
from chia.full_node.bitcoin_fee_estimator import create_bitcoin_fee_estimator
47-
from chia.full_node.eligible_coin_spends import run_for_cost
4847
from chia.full_node.fee_estimation import EmptyMempoolInfo, MempoolInfo
4948
from chia.full_node.full_node_api import FullNodeAPI
5049
from chia.full_node.mempool import Mempool
@@ -2909,14 +2908,7 @@ async def test_invalid_coin_spend_coin(
29092908
],
29102909
)
29112910
def test_items_by_feerate(items: list[MempoolItem], expected: list[Coin]) -> None:
2912-
fee_estimator = create_bitcoin_fee_estimator(uint64(11000000000))
2913-
2914-
mempool_info = MempoolInfo(
2915-
CLVMCost(uint64(11000000000 * 3)),
2916-
FeeRate(uint64(1000000)),
2917-
CLVMCost(uint64(11000000000)),
2918-
)
2919-
mempool = Mempool(mempool_info, fee_estimator)
2911+
mempool = construct_mempool()
29202912
for i in items:
29212913
mempool.add_to_pool(i)
29222914

@@ -2934,13 +2926,7 @@ def test_items_by_feerate(items: list[MempoolItem], expected: list[Coin]) -> Non
29342926

29352927
@pytest.mark.parametrize("old", [True, False])
29362928
def test_timeout(old: bool) -> None:
2937-
fee_estimator = create_bitcoin_fee_estimator(uint64(11000000000))
2938-
mempool_info = MempoolInfo(
2939-
CLVMCost(uint64(11000000000 * 3)),
2940-
FeeRate(uint64(1000000)),
2941-
CLVMCost(uint64(11000000000)),
2942-
)
2943-
mempool = Mempool(mempool_info, fee_estimator)
2929+
mempool = construct_mempool()
29442930

29452931
for i in range(50):
29462932
item = mk_item(coins[i : i + 1], flags=[0], fee=0, cost=50)
@@ -3107,13 +3093,7 @@ def test_limit_expiring_transactions(height: bool, items: list[int], expected: l
31073093
],
31083094
)
31093095
def test_get_items_by_coin_ids(items: list[MempoolItem], coin_ids: list[bytes32], expected: list[MempoolItem]) -> None:
3110-
fee_estimator = create_bitcoin_fee_estimator(uint64(11000000000))
3111-
mempool_info = MempoolInfo(
3112-
CLVMCost(uint64(11000000000 * 3)),
3113-
FeeRate(uint64(1000000)),
3114-
CLVMCost(uint64(11000000000)),
3115-
)
3116-
mempool = Mempool(mempool_info, fee_estimator)
3096+
mempool = construct_mempool()
31173097
for i in items:
31183098
mempool.add_to_pool(i)
31193099
invariant_check_mempool(mempool)
@@ -3127,69 +3107,92 @@ def make_test_spendbundle(coin: Coin, *, fee: int = 0, with_higher_cost: bool =
31273107
if with_higher_cost:
31283108
conditions.extend([[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, i] for i in range(3)])
31293109
actual_fee += 3
3130-
conditions.append([ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, coin.amount - actual_fee])
3110+
conditions.append([ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, uint64(coin.amount - actual_fee)])
31313111
sb = spend_bundle_from_conditions(conditions, coin)
31323112
return sb
31333113

31343114

3135-
def test_aggregating_on_a_solution_then_a_more_cost_saving_one_appears() -> None:
3136-
def agg_and_add_sb_returning_cost_info(mempool: Mempool, spend_bundles: list[SpendBundle]) -> uint64:
3115+
def construct_mempool() -> Mempool:
3116+
fee_estimator = create_bitcoin_fee_estimator(test_constants.MAX_BLOCK_COST_CLVM)
3117+
mempool_info = MempoolInfo(
3118+
CLVMCost(uint64(test_constants.MAX_BLOCK_COST_CLVM * 3)),
3119+
FeeRate(uint64(1000000)),
3120+
CLVMCost(test_constants.MAX_BLOCK_COST_CLVM),
3121+
)
3122+
return Mempool(mempool_info, fee_estimator)
3123+
3124+
3125+
def make_coin(idx: int) -> Coin:
3126+
return Coin(IDENTITY_PUZZLE_HASH, IDENTITY_PUZZLE_HASH, uint64(2_000_000_000 + idx * 2))
3127+
3128+
3129+
@pytest.mark.parametrize("old", [True, False])
3130+
def test_dedup_by_fee(old: bool) -> None:
3131+
"""
3132+
We pick the solution to use for dedup based on the spendbundle with the highest
3133+
fee per cost, not based on which one would give the overall best fee per cost
3134+
"""
3135+
mempool = construct_mempool()
3136+
3137+
def add_spend_bundles(spend_bundles: list[SpendBundle]) -> None:
31373138
sb = SpendBundle.aggregate(spend_bundles)
31383139
mi = mempool_item_from_spendbundle(sb)
31393140
mempool.add_to_pool(mi)
31403141
invariant_check_mempool(mempool)
3141-
saved_cost = run_for_cost(
3142-
sb.coin_spends[0].puzzle_reveal, sb.coin_spends[0].solution, len(mi.additions), mi.cost
3143-
)
3144-
return saved_cost
31453142

3146-
fee_estimator = create_bitcoin_fee_estimator(uint64(11000000000))
3147-
mempool_info = MempoolInfo(
3148-
CLVMCost(uint64(11000000000 * 3)),
3149-
FeeRate(uint64(1000000)),
3150-
CLVMCost(uint64(11000000000)),
3151-
)
3152-
mempool = Mempool(mempool_info, fee_estimator)
3153-
coins = [
3154-
Coin(IDENTITY_PUZZLE_HASH, IDENTITY_PUZZLE_HASH, uint64(amount)) for amount in range(2000000000, 2000000020, 2)
3155-
]
3156-
# Create a ~10 FPC item that spends the eligible coin[0]
3157-
sb_A = make_test_spendbundle(coins[0])
3158-
highest_fee = 58282830
3159-
sb_high_rate = make_test_spendbundle(coins[1], fee=highest_fee)
3160-
agg_and_add_sb_returning_cost_info(mempool, [sb_A, sb_high_rate])
3161-
invariant_check_mempool(mempool)
3162-
# Create a ~2 FPC item that spends the eligible coin using the same solution A
3163-
sb_low_rate = make_test_spendbundle(coins[2], fee=highest_fee // 5)
3164-
saved_cost_on_solution_A = agg_and_add_sb_returning_cost_info(mempool, [sb_A, sb_low_rate])
3165-
invariant_check_mempool(mempool)
3166-
result = mempool.create_bundle_from_mempool_items(test_constants, uint32(0))
3143+
DEDUP_COIN = make_coin(0)
3144+
COIN_A1 = make_coin(1)
3145+
COIN_A2 = make_coin(2)
3146+
# all other coins belong to solution B, the dedup alternative to solution A
3147+
3148+
# Create a spend bundle with a high fee, spending sb_A, which supports dedup
3149+
sb_A = make_test_spendbundle(DEDUP_COIN)
3150+
sb_high_rate = make_test_spendbundle(COIN_A1, fee=10)
3151+
add_spend_bundles([sb_A, sb_high_rate])
3152+
3153+
# Create a spend bundle, with a low fee, that spends the dedup coin using the same solution A
3154+
sb_low_rate = make_test_spendbundle(COIN_A2, fee=10)
3155+
add_spend_bundles([sb_A, sb_low_rate])
3156+
3157+
create_block = mempool.create_block_generator if old else mempool.create_block_generator2
3158+
# validate that dedup happens at all for sb_A
3159+
result = create_block(test_constants, uint32(0), 5.0)
31673160
assert result is not None
3168-
agg, _ = result
31693161
# Make sure both items would be processed
3170-
assert [c.coin for c in agg.coin_spends] == [coins[0], coins[1], coins[2]]
3171-
# Now let's add 3 x ~3 FPC items that spend the eligible coin differently
3172-
# (solution B). It creates a higher (saved) cost than solution A
3173-
sb_B = make_test_spendbundle(coins[0], with_higher_cost=True)
3174-
for i in range(3, 6):
3175-
# We're picking this fee to get a ~3 FPC, and get picked after sb_A1
3176-
# (which has ~10 FPC) but before sb_A2 (which has ~2 FPC)
3177-
sb_mid_rate = make_test_spendbundle(coins[i], fee=38004852 - i)
3178-
saved_cost_on_solution_B = agg_and_add_sb_returning_cost_info(mempool, [sb_B, sb_mid_rate])
3179-
invariant_check_mempool(mempool)
3180-
# We'd save more cost if we went with solution B instead of A
3181-
assert saved_cost_on_solution_B > saved_cost_on_solution_A
3182-
# If we process everything now, the 3 x ~3 FPC items get skipped because
3183-
# sb_A1 gets picked before them (~10 FPC), so from then on only sb_A2 (~2 FPC)
3184-
# would get picked
3185-
result = mempool.create_bundle_from_mempool_items(test_constants, uint32(0))
3162+
assert result.removals == [DEDUP_COIN, COIN_A1, COIN_A2]
3163+
3164+
# Now we add a bunch of alternative spends for coin 0, with lower fees
3165+
# Even though the total fee would be higher if we deduped on this solution,
3166+
# we won't.
3167+
sb_B = make_test_spendbundle(DEDUP_COIN, with_higher_cost=True)
3168+
for i in range(3, 600):
3169+
sb_high_rate = make_test_spendbundle(make_coin(i), fee=10)
3170+
add_spend_bundles([sb_B, sb_high_rate])
3171+
3172+
result = create_block(test_constants, uint32(0), 5.0)
31863173
assert result is not None
3187-
agg, _ = result
3188-
# The 3 items got skipped here
31893174
# We ran with solution A and missed bigger savings on solution B
3190-
assert mempool.size() == 5
3191-
assert [c.coin for c in agg.coin_spends] == [coins[0], coins[1], coins[2]]
3192-
invariant_check_mempool(mempool)
3175+
# we've added 599 spend bundles now. 2 with solution A and 598 with solution B
3176+
assert mempool.size() == 599
3177+
assert result.removals == [DEDUP_COIN, COIN_A1, COIN_A2]
3178+
3179+
# Now, if we add a high fee per-cost-for sb_B, it should be picked
3180+
sb_high_rate = make_test_spendbundle(make_coin(600), fee=1_000_000_000)
3181+
add_spend_bundles([sb_B, sb_high_rate])
3182+
3183+
result = create_block(test_constants, uint32(0), 5.0)
3184+
assert result is not None
3185+
# The 3 items got skipped here
3186+
# We ran with solution B
3187+
# we've added 600 spend bundles now. 2 with solution A and 599 with solution B
3188+
assert mempool.size() == 600
3189+
spends_in_block = set(result.removals)
3190+
assert DEDUP_COIN in spends_in_block
3191+
assert COIN_A1 not in spends_in_block
3192+
assert COIN_A2 not in spends_in_block
3193+
3194+
for i in range(3, 601):
3195+
assert make_coin(i) in spends_in_block
31933196

31943197

31953198
def test_get_puzzle_and_solution_for_coin_failure() -> None:
@@ -3210,14 +3213,7 @@ def test_get_puzzle_and_solution_for_coin_failure() -> None:
32103213

32113214
@pytest.mark.parametrize("old", [True, False])
32123215
def test_create_block_generator(old: bool) -> None:
3213-
mempool_info = MempoolInfo(
3214-
CLVMCost(uint64(11000000000 * 3)),
3215-
FeeRate(uint64(1000000)),
3216-
CLVMCost(uint64(11000000000)),
3217-
)
3218-
3219-
fee_estimator = create_bitcoin_fee_estimator(test_constants.MAX_BLOCK_COST_CLVM)
3220-
mempool = Mempool(mempool_info, fee_estimator)
3216+
mempool = construct_mempool()
32213217
coins = [
32223218
Coin(IDENTITY_PUZZLE_HASH, IDENTITY_PUZZLE_HASH, uint64(amount)) for amount in range(2000000000, 2000000020, 2)
32233219
]

0 commit comments

Comments
 (0)