Skip to content

Commit

Permalink
Optimize CRCAT trades
Browse files Browse the repository at this point in the history
  • Loading branch information
Quexington committed Oct 17, 2023
1 parent 2a34249 commit 1a94a8a
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 98 deletions.
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

0 comments on commit 1a94a8a

Please sign in to comment.