Move table of SpendBundle costs to backend (#14367)
This commit is contained in:
parent
a91265ffff
commit
0f2995e4a1
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
@ -759,19 +760,38 @@ class FullNodeRpcApi:
|
|||
|
||||
return {"mempool_item": item}
|
||||
|
||||
async def get_fee_estimate(self, request: Dict) -> Dict[str, Any]:
|
||||
if "spend_bundle" in request and "cost" in request:
|
||||
raise ValueError("Request must contain ONLY 'spend_bundle' or 'cost'")
|
||||
if "spend_bundle" not in request and "cost" not in request:
|
||||
raise ValueError("Request must contain 'spend_bundle' or 'cost'")
|
||||
if "target_times" not in request:
|
||||
raise ValueError("Request must contain 'target_times' array")
|
||||
if any(t < 0 for t in request["target_times"]):
|
||||
raise ValueError("'target_times' array members must be non-negative")
|
||||
def _get_spendbundle_type_cost(self, name: str):
|
||||
"""
|
||||
This is a stopgap until we modify the wallet RPCs to get exact costs for created SpendBundles
|
||||
before we send the mto the Mempool.
|
||||
"""
|
||||
maxBlockCostCLVM = 11_000_000_000
|
||||
|
||||
txCostEstimates = {
|
||||
"walletSendXCH": math.floor(maxBlockCostCLVM / 1170),
|
||||
"spendCATtx": 36_382_111,
|
||||
"acceptOffer": 721_393_265,
|
||||
"cancelOffer": 212_443_993,
|
||||
"burnNFT": 74_385_541,
|
||||
"assignDIDToNFT": 115_540_006,
|
||||
"transferNFT": 74_385_541,
|
||||
"createPlotNFT": 18_055_407,
|
||||
"claimPoolingReward": 82_668_466,
|
||||
"createDID": 57_360_396,
|
||||
}
|
||||
return txCostEstimates[name]
|
||||
|
||||
async def _validate_fee_estimate_cost(self, request: Dict) -> uint64:
|
||||
c = 0
|
||||
ns = ["spend_bundle", "cost", "spend_type"]
|
||||
for n in ns:
|
||||
if n in request:
|
||||
c += 1
|
||||
if c != 1:
|
||||
raise ValueError(f"Request must contain exactly one of {ns}")
|
||||
|
||||
cost = 0
|
||||
if "spend_bundle" in request:
|
||||
spend_bundle = SpendBundle.from_json_dict(request["spend_bundle"])
|
||||
spend_bundle: SpendBundle = SpendBundle.from_json_dict(request["spend_bundle"])
|
||||
spend_name = spend_bundle.name()
|
||||
npc_result: NPCResult = await self.service.mempool_manager.pre_validate_spendbundle(
|
||||
spend_bundle, None, spend_name
|
||||
|
@ -779,13 +799,27 @@ class FullNodeRpcApi:
|
|||
if npc_result.error is not None:
|
||||
raise RuntimeError(f"Spend Bundle failed validation: {npc_result.error}")
|
||||
cost = npc_result.cost
|
||||
if "cost" in request:
|
||||
cost = uint64(request["cost"])
|
||||
elif "cost" in request:
|
||||
cost = request["cost"]
|
||||
else:
|
||||
cost = self._get_spendbundle_type_cost(request["spend_type"])
|
||||
return uint64(cost)
|
||||
|
||||
def _validate_target_times(self, request: Dict) -> None:
|
||||
if "target_times" not in request:
|
||||
raise ValueError("Request must contain 'target_times' array")
|
||||
if any(t < 0 for t in request["target_times"]):
|
||||
raise ValueError("'target_times' array members must be non-negative")
|
||||
|
||||
async def get_fee_estimate(self, request: Dict) -> Dict[str, Any]:
|
||||
self._validate_target_times(request)
|
||||
spend_cost = await self._validate_fee_estimate_cost(request)
|
||||
|
||||
target_times = request["target_times"]
|
||||
estimator: FeeEstimatorInterface = self.service.mempool_manager.mempool.fee_estimator
|
||||
estimates = [
|
||||
estimator.estimate_fee_rate(time_offset_seconds=time).mojos_per_clvm_cost * cost for time in target_times
|
||||
estimator.estimate_fee_rate(time_offset_seconds=time).mojos_per_clvm_cost * spend_cost
|
||||
for time in target_times
|
||||
]
|
||||
current_fee_rate = estimator.estimate_fee_rate(time_offset_seconds=1)
|
||||
mempool_size = self.service.mempool_manager.mempool.total_mempool_cost
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import List, Tuple
|
||||
import re
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
@ -14,6 +15,7 @@ from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
|||
from chia.simulator.wallet_tools import WalletTool
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.ints import uint64
|
||||
from chia.wallet.wallet_node import WalletNode
|
||||
|
||||
|
@ -201,3 +203,54 @@ async def test_multiple(setup_node_and_rpc: Tuple[FullNodeRpcClient, FullNodeRpc
|
|||
response = await full_node_rpc_api.get_fee_estimate({"target_times": [1, 5, 10, 15, 60, 120, 180, 240], "cost": 1})
|
||||
assert response["estimates"] == [0, 0, 0, 0, 0, 0, 0, 0]
|
||||
assert response["target_times"] == [1, 5, 10, 15, 60, 120, 180, 240]
|
||||
|
||||
|
||||
def get_test_spendbundle(bt: BlockTools) -> SpendBundle:
|
||||
wallet_a: WalletTool = bt.get_pool_wallet_tool()
|
||||
my_puzzle_hash = wallet_a.get_new_puzzlehash()
|
||||
recevier_puzzle_hash = bytes32(b"0" * 32)
|
||||
coin_to_spend = Coin(bytes32(b"0" * 32), my_puzzle_hash, uint64(1750000000000))
|
||||
return wallet_a.generate_signed_transaction(uint64(coin_to_spend.amount), recevier_puzzle_hash, coin_to_spend)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_fee_estimate_cost_err(
|
||||
setup_node_and_rpc: Tuple[FullNodeRpcClient, FullNodeRpcApi], bt: BlockTools
|
||||
) -> None:
|
||||
spend_bundle = get_test_spendbundle(bt)
|
||||
client, full_node_rpc_api = setup_node_and_rpc
|
||||
bad_arglist: List[List[Any]] = [
|
||||
[["foo", "bar"]],
|
||||
[["spend_bundle", spend_bundle.to_json_dict()], ["cost", 1]],
|
||||
[["spend_bundle", spend_bundle.to_json_dict()], ["spend_type", "walletSendXCH"]],
|
||||
[["cost", 1], ["spend_type", "walletSendXCH"]],
|
||||
[["spend_bundle", spend_bundle.to_json_dict()], ["cost", 1], ["spend_type", "walletSendXCH"]],
|
||||
]
|
||||
for args in bad_arglist:
|
||||
print(args)
|
||||
request = {"target_times": [1]}
|
||||
for var, val in args:
|
||||
print(var)
|
||||
request[var] = val
|
||||
with pytest.raises(
|
||||
ValueError, match=re.escape("Request must contain exactly one of ['spend_bundle', 'cost', 'spend_type']")
|
||||
):
|
||||
_ = await full_node_rpc_api.get_fee_estimate(request)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_fee_estimate_cost_ok(
|
||||
setup_node_and_rpc: Tuple[FullNodeRpcClient, FullNodeRpcApi], bt: BlockTools
|
||||
) -> None:
|
||||
spend_bundle = get_test_spendbundle(bt)
|
||||
client, full_node_rpc_api = setup_node_and_rpc
|
||||
|
||||
good_arglist: List[List[Any]] = [
|
||||
["spend_bundle", spend_bundle.to_json_dict()],
|
||||
["cost", 1],
|
||||
["spend_type", "walletSendXCH"],
|
||||
]
|
||||
for var, val in good_arglist:
|
||||
request = {"target_times": [1]}
|
||||
request[var] = val
|
||||
_ = await full_node_rpc_api.get_fee_estimate(request)
|
||||
|
|
Loading…
Reference in a new issue