Add valid_times to Offer object (#16255)

This commit is contained in:
Matt Hauff 2023-09-13 18:07:18 -07:00 committed by GitHub
parent ab07de72d5
commit b6dae184a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 116 additions and 11 deletions

View File

@ -613,7 +613,7 @@ async def print_trade_record(record: TradeRecord, wallet_client: WalletRpcClient
if summaries:
print("Summary:")
offer = Offer.from_bytes(record.offer)
offered, requested, _ = offer.summary()
offered, requested, _, _ = offer.summary()
outbound_balances: Dict[str, int] = offer.get_pending_amounts()
fees: Decimal = Decimal(offer.fees())
cat_name_resolver = wallet_client.cat_asset_id_to_name
@ -701,7 +701,7 @@ async def take_offer(
print("Please enter a valid offer file or hex blob")
return
offered, requested, _ = offer.summary()
offered, requested, _, _ = offer.summary()
cat_name_resolver = wallet_client.cat_asset_id_to_name
network_xch = AddressType.XCH.hrp(config).upper()
print("Summary:")

View File

@ -8,7 +8,7 @@ import aiohttp
from chia.types.blockchain_format.coin import Coin
from chia.util.json_util import obj_to_response
from chia.wallet.conditions import Condition, ConditionValidTimes, conditions_from_json_dicts
from chia.wallet.conditions import Condition, ConditionValidTimes, conditions_from_json_dicts, parse_timelock_info
from chia.wallet.util.tx_config import TXConfig, TXConfigLoader
log = logging.getLogger(__name__)
@ -69,6 +69,16 @@ def tx_endpoint(
if "extra_conditions" in request:
extra_conditions = tuple(conditions_from_json_dicts(request["extra_conditions"]))
extra_conditions = (*extra_conditions, *ConditionValidTimes.from_json_dict(request).to_conditions())
valid_times: ConditionValidTimes = parse_timelock_info(extra_conditions)
if (
valid_times.max_secs_after_created is not None
or valid_times.min_secs_since_created is not None
or valid_times.max_blocks_after_created is not None
or valid_times.min_blocks_since_created is not None
):
raise ValueError("Relative timelocks are not currently supported in the RPC")
return await func(self, request, *args, tx_config=tx_config, extra_conditions=extra_conditions, **kwargs)
return rpc_endpoint

View File

@ -1730,11 +1730,27 @@ class WalletRpcApi:
###
offer = Offer.from_bech32(offer_hex)
offered, requested, infos = offer.summary()
offered, requested, infos, valid_times = offer.summary()
if request.get("advanced", False):
response = {
"summary": {"offered": offered, "requested": requested, "fees": offer.fees(), "infos": infos},
"summary": {
"offered": offered,
"requested": requested,
"fees": offer.fees(),
"infos": infos,
"valid_times": {
k: v
for k, v in valid_times.to_json_dict().items()
if k
not in (
"max_secs_after_created",
"min_secs_since_created",
"max_blocks_after_created",
"min_blocks_since_created",
)
},
},
"id": offer.name(),
}
else:

View File

@ -880,8 +880,24 @@ class TradeManager:
):
return await DataLayerWallet.get_offer_summary(offer)
# Otherwise just return the same thing as the RPC normally does
offered, requested, infos = offer.summary()
return {"offered": offered, "requested": requested, "fees": offer.fees(), "infos": infos}
offered, requested, infos, valid_times = offer.summary()
return {
"offered": offered,
"requested": requested,
"fees": offer.fees(),
"infos": infos,
"valid_times": {
k: v
for k, v in valid_times.to_json_dict().items()
if k
not in (
"max_secs_after_created",
"min_secs_since_created",
"max_blocks_after_created",
"min_blocks_since_created",
)
},
}
async def check_for_final_modifications(
self, offer: Offer, solver: Solver, tx_config: TXConfig

View File

@ -38,7 +38,7 @@ class TradeRecordOld(Streamable):
formatted["status"] = TradeStatus(self.status).name
offer_to_summarize: bytes = self.offer if self.taken_offer is None else self.taken_offer
offer = Offer.from_bytes(offer_to_summarize)
offered, requested, infos = offer.summary()
offered, requested, infos, _ = offer.summary()
formatted["summary"] = {
"offered": offered,
"requested": requested,

View File

@ -16,6 +16,7 @@ from chia.types.spend_bundle import SpendBundle
from chia.util.bech32m import bech32_decode, bech32_encode, convertbits
from chia.util.errors import Err, ValidationError
from chia.util.ints import uint64
from chia.wallet.conditions import Condition, ConditionValidTimes, parse_conditions_non_consensus, parse_timelock_info
from chia.wallet.outer_puzzles import (
construct_puzzle,
create_asset_id,
@ -77,6 +78,7 @@ class Offer:
_additions: Dict[Coin, List[Coin]] = field(init=False)
_offered_coins: Dict[Optional[bytes32], List[Coin]] = field(init=False)
_final_spend_bundle: Optional[SpendBundle] = field(init=False)
_conditions: Optional[Dict[Coin, List[Condition]]] = field(init=False)
@staticmethod
def ph() -> bytes32:
@ -148,6 +150,40 @@ class Offer:
if max_cost < 0:
raise ValidationError(Err.BLOCK_COST_EXCEEDS_MAX, "compute_additions for CoinSpend")
object.__setattr__(self, "_additions", adds)
object.__setattr__(self, "_conditions", None)
def conditions(self) -> Dict[Coin, List[Condition]]:
if self._conditions is None:
conditions: Dict[Coin, List[Condition]] = {}
max_cost = DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM
for cs in self._bundle.coin_spends:
try:
cost, conds = cs.puzzle_reveal.run_with_cost(max_cost, cs.solution)
max_cost -= cost
conditions[cs.coin] = parse_conditions_non_consensus(conds.as_iter())
except Exception: # pragma: no cover
continue
if max_cost < 0: # pragma: no cover
raise ValidationError(Err.BLOCK_COST_EXCEEDS_MAX, "computing conditions for CoinSpend")
object.__setattr__(self, "_conditions", conditions)
assert self._conditions is not None, "self._conditions is None"
return self._conditions
def valid_times(self) -> Dict[Coin, ConditionValidTimes]:
return {coin: parse_timelock_info(conditions) for coin, conditions in self.conditions().items()}
def absolute_valid_times_ban_relatives(self) -> ConditionValidTimes:
valid_times: ConditionValidTimes = parse_timelock_info(
[c for conditions in self.conditions().values() for c in conditions]
)
if (
valid_times.max_secs_after_created is not None
or valid_times.min_secs_since_created is not None
or valid_times.max_blocks_after_created is not None
or valid_times.min_blocks_since_created is not None
):
raise ValueError("Offers with relative timelocks are not currently supported")
return valid_times
def additions(self) -> List[Coin]:
return [c for additions in self._additions.values() for c in additions]
@ -270,7 +306,7 @@ class Offer:
return arbitrage_dict
# This is a method mostly for the UI that creates a JSON summary of the offer
def summary(self) -> Tuple[Dict[str, int], Dict[str, int], Dict[str, Dict[str, Any]]]:
def summary(self) -> Tuple[Dict[str, int], Dict[str, int], Dict[str, Dict[str, Any]], ConditionValidTimes]:
offered_amounts: Dict[Optional[bytes32], int] = self.get_offered_amounts()
requested_amounts: Dict[Optional[bytes32], int] = self.get_requested_amounts()
@ -287,7 +323,12 @@ class Offer:
for key, value in self.driver_dict.items():
driver_dict[key.hex()] = value.info
return keys_to_strings(offered_amounts), keys_to_strings(requested_amounts), driver_dict
return (
keys_to_strings(offered_amounts),
keys_to_strings(requested_amounts),
driver_dict,
self.absolute_valid_times_ban_relatives(),
)
# Also mostly for the UI, returns a dictionary of assets and how much of them is pended for this offer
# This method is also imperfect for sufficiently complex spends

View File

@ -21,6 +21,7 @@ from chia.wallet.cat_wallet.cat_utils import (
construct_cat_puzzle,
unsigned_spend_bundle_for_spendable_cats,
)
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.payment import Payment
from chia.wallet.puzzle_drivers import PuzzleInfo
@ -288,6 +289,7 @@ class TestOfferLifecycle:
},
{"xch": 900, str_to_tail_hash("red").hex(): 350},
driver_dict_as_infos,
ConditionValidTimes(),
)
assert new_offer.get_pending_amounts() == {
"xch": 1200,

View File

@ -42,6 +42,7 @@ from chia.util.streamable import ConversionError, InvalidTypeError
from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
from chia.wallet.cat_wallet.cat_utils import CAT_MOD, construct_cat_puzzle
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
@ -1148,7 +1149,18 @@ async def test_offer_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment)
assert id == offer.name()
id, advanced_summary = await wallet_1_rpc.get_offer_summary(offer, advanced=True)
assert id == offer.name()
assert summary == {"offered": {"xch": 5}, "requested": {cat_asset_id.hex(): 1}, "infos": driver_dict, "fees": 1}
assert summary == {
"offered": {"xch": 5},
"requested": {cat_asset_id.hex(): 1},
"infos": driver_dict,
"fees": 1,
"valid_times": {
"max_height": None,
"max_time": None,
"min_height": None,
"min_time": None,
},
}
assert advanced_summary == summary
id, valid = await wallet_1_rpc.check_offer_validity(offer)
@ -1298,6 +1310,14 @@ async def test_offer_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment)
assert len([o for o in await wallet_1_rpc.get_all_offers() if o.status == TradeStatus.PENDING_ACCEPT.value]) == 0
await time_out_assert(5, check_mempool_spend_count, True, full_node_api, 1)
with pytest.raises(ValueError, match="not currently supported"):
await wallet_1_rpc.create_offer_for_ids(
{uint32(1): -5, cat_asset_id.hex(): 1},
DEFAULT_TX_CONFIG,
driver_dict=driver_dict,
timelock_info=ConditionValidTimes(min_secs_since_created=uint64(1)),
)
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN, ConsensusMode.HARD_FORK_2_0], reason="save time")
@pytest.mark.asyncio