Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize CRCAT trades #16430

Merged
merged 2 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion chia/rpc/wallet_rpc_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2227,7 +2227,7 @@ async def did_find_lost_did(self, request: Dict[str, Any]) -> EndpointResult:
get_inner_puzzle_from_singleton(coin_spend.puzzle_reveal.to_program()),
coin_state,
)
hinted_coins = compute_spend_hints_and_additions(coin_spend)
hinted_coins, _ = compute_spend_hints_and_additions(coin_spend)
# Hint is required, if it doesn't have any hint then it should be invalid
hint: Optional[bytes32] = None
for hinted_coin in hinted_coins.values():
Expand Down
1 change: 1 addition & 0 deletions chia/wallet/cat_wallet/cat_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ANYONE_CAN_SPEND_PUZZLE = Program.to(1) # simply return the conditions
CAT_MOD = load_clvm_maybe_recompile("cat_v2.clsp", package_or_requirement="chia.wallet.cat_wallet.puzzles")
CAT_MOD_HASH = CAT_MOD.get_tree_hash()
CAT_MOD_HASH_HASH: bytes32 = Program.to(CAT_MOD_HASH).get_tree_hash()


def empty_program() -> Program:
Expand Down
29 changes: 22 additions & 7 deletions chia/wallet/trade_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class TradeManager:
wallet_state_manager: Any
log: logging.Logger
trade_store: TradeStore
most_recently_deserialized_trade: Optional[Tuple[bytes32, Offer]]

@staticmethod
async def create(
Expand All @@ -98,6 +99,7 @@ async def create(

self.wallet_state_manager = wallet_state_manager
self.trade_store = await TradeStore.create(db_wrapper)
self.most_recently_deserialized_trade = None
return self

async def get_offers_with_status(self, status: TradeStatus) -> List[TradeRecord]:
Expand Down Expand Up @@ -142,7 +144,14 @@ async def coins_of_interest_farmed(
if coin_state.spent_height is None:
self.log.error(f"Coin: {coin_state.coin}, has not been spent so trade can remain valid")
# Then let's filter the offer into coins that WE offered
offer = Offer.from_bytes(trade.offer)
if (
self.most_recently_deserialized_trade is not None
and trade.trade_id == self.most_recently_deserialized_trade[0]
):
offer = self.most_recently_deserialized_trade[1]
else:
offer = Offer.from_bytes(trade.offer)
self.most_recently_deserialized_trade = (trade.trade_id, offer)
primary_coin_ids = [c.name() for c in offer.removals()]
# TODO: Add `WalletCoinStore.get_coins`.
result = await self.wallet_state_manager.coin_store.get_coin_records(
Expand Down Expand Up @@ -613,18 +622,24 @@ async def check_offer_validity(self, offer: Offer, peer: WSChiaConnection) -> bo
async def calculate_tx_records_for_offer(self, offer: Offer, validate: bool) -> List[TransactionRecord]:
if validate:
final_spend_bundle: SpendBundle = offer.to_valid_spend()
hint_dict: Dict[bytes32, bytes32] = {}
additions_dict: Dict[bytes32, Coin] = {}
for hinted_coins, _ in (
compute_spend_hints_and_additions(spend) for spend in final_spend_bundle.coin_spends
):
hint_dict.update({id: hc.hint for id, hc in hinted_coins.items() if hc.hint is not None})
additions_dict.update({id: hc.coin for id, hc in hinted_coins.items()})
all_additions: List[Coin] = list(a for a in additions_dict.values())
else:
final_spend_bundle = offer._bundle
hint_dict = offer.hints()
all_additions = offer.additions()

settlement_coins: List[Coin] = [c for coins in offer.get_offered_coins().values() for c in coins]
settlement_coin_ids: List[bytes32] = [c.name() for c in settlement_coins]
hint_dict: Dict[bytes32, bytes32] = {}
additions_dict: Dict[bytes32, Coin] = {}
for hinted_coins in (compute_spend_hints_and_additions(spend) for spend in final_spend_bundle.coin_spends):
hint_dict.update({id: hc.hint for id, hc in hinted_coins.items() if hc.hint is not None})
additions_dict.update({id: hc.coin for id, hc in hinted_coins.items()})

removals: List[Coin] = final_spend_bundle.removals()
additions: List[Coin] = list(a for a in additions_dict.values() if a not in removals)
additions: List[Coin] = list(a for a in all_additions if a not in removals)
valid_times: ConditionValidTimes = parse_timelock_info(
parse_conditions_non_consensus(
condition
Expand Down
14 changes: 11 additions & 3 deletions chia/wallet/trading/offer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from chia.types.blockchain_format.coin import Coin, coin_as_list
from chia.types.blockchain_format.program import INFINITE_COST, Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend, compute_additions_with_cost
from chia.types.coin_spend import CoinSpend
from chia.types.spend_bundle import SpendBundle
from chia.util.bech32m import bech32_decode, bech32_encode, convertbits
from chia.util.errors import Err, ValidationError
Expand All @@ -29,6 +29,7 @@
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
from chia.wallet.uncurried_puzzle import UncurriedPuzzle, uncurry_puzzle
from chia.wallet.util.compute_hints import compute_spend_hints_and_additions
from chia.wallet.util.puzzle_compression import (
compress_object_with_puzzles,
decompress_object_with_puzzles,
Expand Down Expand Up @@ -76,6 +77,7 @@ class Offer:
# this is a cache of the coin additions made by the SpendBundle (_bundle)
# ordered by the coin being spent
_additions: Dict[Coin, List[Coin]] = field(init=False)
_hints: Dict[bytes32, bytes32] = 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)
Expand Down Expand Up @@ -137,19 +139,22 @@ def __post_init__(self) -> None:

# populate the _additions cache
adds: Dict[Coin, List[Coin]] = {}
hints: Dict[bytes32, bytes32] = {}
max_cost = DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM
for cs in self._bundle.coin_spends:
# you can't spend the same coin twice in the same SpendBundle
assert cs.coin not in adds
try:
coins, cost = compute_additions_with_cost(cs)
hinted_coins, cost = compute_spend_hints_and_additions(cs)
max_cost -= cost
adds[cs.coin] = coins
adds[cs.coin] = [hc.coin for hc in hinted_coins.values()]
hints = {**hints, **{id: hc.hint for id, hc in hinted_coins.items() if hc.hint is not None}}
except Exception:
continue
if max_cost < 0:
raise ValidationError(Err.BLOCK_COST_EXCEEDS_MAX, "compute_additions for CoinSpend")
object.__setattr__(self, "_additions", adds)
object.__setattr__(self, "_hints", hints)
object.__setattr__(self, "_conditions", None)

def conditions(self) -> Dict[Coin, List[Condition]]:
Expand Down Expand Up @@ -185,6 +190,9 @@ def absolute_valid_times_ban_relatives(self) -> ConditionValidTimes:
raise ValueError("Offers with relative timelocks are not currently supported")
return valid_times

def hints(self) -> Dict[bytes32, bytes32]:
return self._hints

def additions(self) -> List[Coin]:
return [c for additions in self._additions.values() for c in additions]

Expand Down
60 changes: 43 additions & 17 deletions chia/wallet/util/compute_hints.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, Optional
from typing import Dict, Optional, Tuple

from chia.consensus.condition_costs import ConditionCost
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import INFINITE_COST, Program
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.types.condition_opcodes import ConditionOpcode
from chia.util.errors import Err, ValidationError
from chia.util.ints import uint64


Expand All @@ -17,21 +20,44 @@ class HintedCoin:
hint: Optional[bytes32]


def compute_spend_hints_and_additions(cs: CoinSpend) -> Dict[bytes32, HintedCoin]:
_, result_program = cs.puzzle_reveal.run_with_cost(INFINITE_COST, cs.solution)
def compute_spend_hints_and_additions(
cs: CoinSpend,
*,
max_cost: int = DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
) -> Tuple[Dict[bytes32, HintedCoin], int]:
cost, result_program = cs.puzzle_reveal.run_with_cost(max_cost, cs.solution)

hinted_coins: Dict[bytes32, HintedCoin] = {}
for condition in result_program.as_iter():
if condition.at("f").atom == ConditionOpcode.CREATE_COIN: # It's a create coin:
coin: Coin = Coin(cs.coin.name(), bytes32(condition.at("rf").atom), uint64(condition.at("rrf").as_int()))
hint: Optional[bytes32] = None
if (
condition.at("rrr") != Program.to(None) # There's more than two arguments
and condition.at("rrrf").atom is None # The 3rd argument is a cons
):
potential_hint: bytes = condition.at("rrrff").atom
if len(potential_hint) == 32:
hint = bytes32(potential_hint)
hinted_coins[bytes32(coin.name())] = HintedCoin(coin, hint)

return hinted_coins
if cost > max_cost: # pragma: no cover
raise ValidationError(Err.BLOCK_COST_EXCEEDS_MAX, "compute_spend_hints_and_additions() for CoinSpend")
atoms = condition.as_iter()
op = next(atoms).atom
if op in [
ConditionOpcode.AGG_SIG_PARENT,
ConditionOpcode.AGG_SIG_PUZZLE,
ConditionOpcode.AGG_SIG_AMOUNT,
ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_PUZZLE,
ConditionOpcode.AGG_SIG_UNSAFE,
ConditionOpcode.AGG_SIG_ME,
]:
cost += ConditionCost.AGG_SIG.value
continue
if op != ConditionOpcode.CREATE_COIN.value:
continue
cost += ConditionCost.CREATE_COIN.value

coin: Coin = Coin(cs.coin.name(), bytes32(condition.at("rf").atom), uint64(condition.at("rrf").as_int()))
hint: Optional[bytes32] = None
if (
condition.at("rrr") != Program.to(None) # There's more than two arguments
and condition.at("rrrf").atom is None # The 3rd argument is a cons
):
potential_hint: bytes = condition.at("rrrff").atom
if len(potential_hint) == 32:
hint = bytes32(potential_hint)
hinted_coins[bytes32(coin.name())] = HintedCoin(coin, hint)

return hinted_coins, cost
109 changes: 64 additions & 45 deletions chia/wallet/vc_wallet/cr_cat_drivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
from chia.wallet.puzzles.singleton_top_layer_v1_1 import SINGLETON_LAUNCHER_HASH, SINGLETON_MOD_HASH
from chia.wallet.uncurried_puzzle import UncurriedPuzzle, uncurry_puzzle
from chia.wallet.util.curry_and_treehash import curry_and_treehash
from chia.wallet.vc_wallet.vc_drivers import (
COVENANT_LAYER_HASH,
EML_TP_COVENANT_ADAPTER_HASH,
EXTIGENT_METADATA_LAYER_HASH,
GUARANTEED_NIL_TP,
P2_ANNOUNCED_DELEGATED_PUZZLE,
GUARANTEED_NIL_TP_HASH,
P2_ANNOUNCED_DELEGATED_PUZZLE_HASH,
create_did_tp,
create_eml_covenant_morpher,
)
Expand All @@ -49,6 +50,46 @@
package_or_requirement="chia.wallet.vc_wallet.cr_puzzles",
include_standard_libraries=True,
)
CREDENTIAL_STRUCT: Program = Program.to(
(
(
(
SINGLETON_MOD_HASH,
SINGLETON_LAUNCHER_HASH,
),
(
EXTIGENT_METADATA_LAYER_HASH,
EML_TP_COVENANT_ADAPTER_HASH,
),
),
(
curry_and_treehash(
Program.to((1, EXTIGENT_METADATA_LAYER_HASH)).get_tree_hash_precalc(EXTIGENT_METADATA_LAYER_HASH),
Program.to(EXTIGENT_METADATA_LAYER_HASH).get_tree_hash(),
Program.to(None).get_tree_hash(),
GUARANTEED_NIL_TP_HASH,
Program.to(GUARANTEED_NIL_TP_HASH).get_tree_hash(),
P2_ANNOUNCED_DELEGATED_PUZZLE_HASH,
),
(
Program.to(
int_to_bytes(2) + Program.to((1, COVENANT_LAYER_HASH)).get_tree_hash_precalc(COVENANT_LAYER_HASH)
),
Program.to(
(
[
4,
(1, create_eml_covenant_morpher(create_did_tp().get_tree_hash())),
[4, (1, create_did_tp()), 1],
],
None,
)
).get_tree_hash(),
),
),
),
)
CREDENTIAL_STRUCT_HASH: bytes32 = CREDENTIAL_STRUCT.get_tree_hash()


# Basic drivers
Expand All @@ -58,55 +99,33 @@ def construct_cr_layer(
inner_puzzle: Program,
) -> Program:
first_curry: Program = CREDENTIAL_RESTRICTION.curry(
Program.to(
(
(
(
SINGLETON_MOD_HASH,
SINGLETON_LAUNCHER_HASH,
),
(
EXTIGENT_METADATA_LAYER_HASH,
EML_TP_COVENANT_ADAPTER_HASH,
),
),
(
Program.to(EXTIGENT_METADATA_LAYER_HASH)
.curry(
Program.to(EXTIGENT_METADATA_LAYER_HASH).get_tree_hash(),
Program.to(None),
GUARANTEED_NIL_TP,
GUARANTEED_NIL_TP.get_tree_hash(),
P2_ANNOUNCED_DELEGATED_PUZZLE,
)
.get_tree_hash_precalc(
EXTIGENT_METADATA_LAYER_HASH, Program.to(EXTIGENT_METADATA_LAYER_HASH).get_tree_hash()
),
(
Program.to(
int_to_bytes(2)
+ Program.to((1, COVENANT_LAYER_HASH)).get_tree_hash_precalc(COVENANT_LAYER_HASH)
),
Program.to(
(
[
4,
(1, create_eml_covenant_morpher(create_did_tp().get_tree_hash())),
[4, (1, create_did_tp()), 1],
],
None,
)
).get_tree_hash(),
),
),
),
),
CREDENTIAL_STRUCT,
authorized_providers,
proofs_checker,
)
return first_curry.curry(first_curry.get_tree_hash(), inner_puzzle)


def construct_cr_layer_hash(
authorized_providers_hash: bytes32,
proofs_checker_hash: bytes32,
inner_puzzle_hash: bytes32,
) -> bytes32:
first_curry_hash: bytes32 = curry_and_treehash(
Program.to((1, CREDENTIAL_RESTRICTION_HASH)).get_tree_hash_precalc(CREDENTIAL_RESTRICTION_HASH),
CREDENTIAL_STRUCT_HASH,
authorized_providers_hash,
proofs_checker_hash,
)
first_curry_hash_hash: bytes32 = Program.to(first_curry_hash).get_tree_hash()
final_hash: bytes32 = curry_and_treehash(
Program.to((1, first_curry_hash)).get_tree_hash_precalc(first_curry_hash),
first_curry_hash_hash,
inner_puzzle_hash,
)
return final_hash


def match_cr_layer(
uncurried_puzzle: UncurriedPuzzle,
) -> Optional[Tuple[List[bytes32], Program, Program]]:
Expand Down
Loading