Move table of SpendBundle costs to backend (#14367)

This commit is contained in:
Adam Kelly 2023-01-19 13:39:35 -08:00 committed by GitHub
parent a91265ffff
commit 0f2995e4a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 102 additions and 15 deletions

View file

@ -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

View file

@ -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)