Merge main into checkpoint/main_from_release_1.6.0_12fcaf0e982590efcf26a954c67cd760035d9e6e

This commit is contained in:
Amine Khaldi 2022-08-30 13:25:43 +01:00
commit aacad8fecf
No known key found for this signature in database
GPG Key ID: B1C074FFC904E2D9
20 changed files with 817 additions and 165 deletions

View File

@ -21,6 +21,8 @@ keychain_commands = [
"get_key_for_fingerprint", "get_key_for_fingerprint",
"get_key", "get_key",
"get_keys", "get_keys",
"set_label",
"delete_label",
] ]
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -32,6 +34,12 @@ KEYCHAIN_ERR_KEY_NOT_FOUND = "key not found"
KEYCHAIN_ERR_MALFORMED_REQUEST = "malformed request" KEYCHAIN_ERR_MALFORMED_REQUEST = "malformed request"
@streamable
@dataclass(frozen=True)
class EmptyResponse(Streamable):
pass
@streamable @streamable
@dataclass(frozen=True) @dataclass(frozen=True)
class GetKeyResponse(Streamable): class GetKeyResponse(Streamable):
@ -63,6 +71,27 @@ class GetKeysRequest(Streamable):
return GetKeysResponse(keys=keychain.get_keys(self.include_secrets)) return GetKeysResponse(keys=keychain.get_keys(self.include_secrets))
@streamable
@dataclass(frozen=True)
class SetLabelRequest(Streamable):
fingerprint: uint32
label: str
def run(self, keychain: Keychain) -> EmptyResponse:
keychain.set_label(int(self.fingerprint), self.label)
return EmptyResponse()
@streamable
@dataclass(frozen=True)
class DeleteLabelRequest(Streamable):
fingerprint: uint32
def run(self, keychain: Keychain) -> EmptyResponse:
keychain.delete_label(self.fingerprint)
return EmptyResponse()
class KeychainServer: class KeychainServer:
""" """
Implements a remote keychain service for clients to perform key operations on Implements a remote keychain service for clients to perform key operations on
@ -113,6 +142,10 @@ class KeychainServer:
return await self.run_request(data, GetKeyRequest) return await self.run_request(data, GetKeyRequest)
elif command == "get_keys": elif command == "get_keys":
return await self.run_request(data, GetKeysRequest) return await self.run_request(data, GetKeysRequest)
elif command == "set_label":
return await self.run_request(data, SetLabelRequest)
elif command == "delete_label":
return await self.run_request(data, DeleteLabelRequest)
return {} return {}
except Exception as e: except Exception as e:
log.exception(e) log.exception(e)
@ -123,6 +156,8 @@ class KeychainServer:
return {"success": False, "error": KEYCHAIN_ERR_LOCKED} return {"success": False, "error": KEYCHAIN_ERR_LOCKED}
mnemonic = request.get("mnemonic", None) mnemonic = request.get("mnemonic", None)
label = request.get("label", None)
if mnemonic is None: if mnemonic is None:
return { return {
"success": False, "success": False,
@ -131,7 +166,7 @@ class KeychainServer:
} }
try: try:
self.get_keychain_for_request(request).add_private_key(mnemonic) self.get_keychain_for_request(request).add_private_key(mnemonic, label)
except KeyError as e: except KeyError as e:
return { return {
"success": False, "success": False,

View File

@ -1,3 +1,4 @@
from __future__ import annotations
import io import io
from typing import Callable, Dict, List, Set, Tuple, Optional, Any from typing import Callable, Dict, List, Set, Tuple, Optional, Any
@ -6,7 +7,6 @@ from clvm.casts import int_from_bytes
from clvm.EvalError import EvalError from clvm.EvalError import EvalError
from clvm.serialize import sexp_from_stream, sexp_to_stream from clvm.serialize import sexp_from_stream, sexp_to_stream
from chia_rs import MEMPOOL_MODE, run_chia_program, serialized_length, run_generator, tree_hash from chia_rs import MEMPOOL_MODE, run_chia_program, serialized_length, run_generator, tree_hash
from clvm_tools.curry import uncurry
from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.hash import std_hash from chia.util.hash import std_hash
@ -32,11 +32,18 @@ class Program(SExp):
sexp_to_stream(self, f) sexp_to_stream(self, f)
@classmethod @classmethod
def from_bytes(cls, blob: bytes) -> "Program": def from_bytes(cls, blob: bytes) -> Program:
f = io.BytesIO(blob) # this runs the program "1", which just returns the first arugment.
result = cls.parse(f) # noqa # the first argument is the buffer we want to parse. This effectively
assert f.read() == b"" # leverages the rust parser and LazyNode, making it a lot faster to
return result # parse serialized programs into a python compatible structure
cost, ret = run_chia_program(
b"\x01",
blob,
50,
0,
)
return Program.to(ret)
@classmethod @classmethod
def fromhex(cls, hexstr: str) -> "Program": def fromhex(cls, hexstr: str) -> "Program":
@ -136,11 +143,37 @@ class Program(SExp):
fixed_args = [4, (1, arg), fixed_args] fixed_args = [4, (1, arg), fixed_args]
return Program.to([2, (1, self), fixed_args]) return Program.to([2, (1, self), fixed_args])
def uncurry(self) -> Tuple["Program", "Program"]: def uncurry(self) -> Tuple[Program, Program]:
r = uncurry(self) def match(o: SExp, expected: bytes) -> None:
if r is None: if o.atom != expected:
raise ValueError(f"expected: {expected.hex()}")
try:
# (2 (1 . <mod>) <args>)
ev, quoted_inner, args_list = self.as_iter()
match(ev, b"\x02")
match(quoted_inner.pair[0], b"\x01")
mod = quoted_inner.pair[1]
args = []
while args_list.pair is not None:
# (4 (1 . <arg>) <rest>)
cons, quoted_arg, rest = args_list.as_iter()
match(cons, b"\x04")
match(quoted_arg.pair[0], b"\x01")
args.append(quoted_arg.pair[1])
args_list = rest
match(args_list, b"\x01")
return Program.to(mod), Program.to(args)
except ValueError: # too many values to unpack
# when unpacking as_iter()
# or when a match() fails
return self, self.to(0)
except TypeError: # NoneType not subscriptable
# when an object is not a pair or atom as expected
return self, self.to(0)
except EvalError: # first of non-cons
# when as_iter() fails
return self, self.to(0) return self, self.to(0)
return r
def as_int(self) -> int: def as_int(self) -> int:
return int_from_bytes(self.as_atom()) return int_from_bytes(self.as_atom())

View File

@ -192,6 +192,7 @@ class KeyDataSecrets(Streamable):
class KeyData(Streamable): class KeyData(Streamable):
fingerprint: uint32 fingerprint: uint32
public_key: G1Element public_key: G1Element
label: Optional[str]
secrets: Optional[KeyDataSecrets] secrets: Optional[KeyDataSecrets]
def __post_init__(self) -> None: def __post_init__(self) -> None:
@ -203,21 +204,22 @@ class KeyData(Streamable):
raise KeychainKeyDataMismatch("fingerprint") raise KeychainKeyDataMismatch("fingerprint")
@classmethod @classmethod
def from_mnemonic(cls, mnemonic: str) -> KeyData: def from_mnemonic(cls, mnemonic: str, label: Optional[str] = None) -> KeyData:
private_key = AugSchemeMPL.key_gen(mnemonic_to_seed(mnemonic)) private_key = AugSchemeMPL.key_gen(mnemonic_to_seed(mnemonic))
return cls( return cls(
fingerprint=private_key.get_g1().get_fingerprint(), fingerprint=private_key.get_g1().get_fingerprint(),
public_key=private_key.get_g1(), public_key=private_key.get_g1(),
label=label,
secrets=KeyDataSecrets.from_mnemonic(mnemonic), secrets=KeyDataSecrets.from_mnemonic(mnemonic),
) )
@classmethod @classmethod
def from_entropy(cls, entropy: bytes) -> KeyData: def from_entropy(cls, entropy: bytes, label: Optional[str] = None) -> KeyData:
return cls.from_mnemonic(bytes_to_mnemonic(entropy)) return cls.from_mnemonic(bytes_to_mnemonic(entropy), label)
@classmethod @classmethod
def generate(cls) -> KeyData: def generate(cls, label: Optional[str] = None) -> KeyData:
return cls.from_mnemonic(generate_mnemonic()) return cls.from_mnemonic(generate_mnemonic(), label)
@property @property
def mnemonic(self) -> List[str]: def mnemonic(self) -> List[str]:
@ -279,11 +281,13 @@ class Keychain:
str_bytes = bytes.fromhex(read_str) str_bytes = bytes.fromhex(read_str)
public_key = G1Element.from_bytes(str_bytes[: G1Element.SIZE]) public_key = G1Element.from_bytes(str_bytes[: G1Element.SIZE])
fingerprint = public_key.get_fingerprint()
entropy = str_bytes[G1Element.SIZE : G1Element.SIZE + 32] entropy = str_bytes[G1Element.SIZE : G1Element.SIZE + 32]
return KeyData( return KeyData(
fingerprint=public_key.get_fingerprint(), fingerprint=fingerprint,
public_key=public_key, public_key=public_key,
label=self.keyring_wrapper.get_label(fingerprint),
secrets=KeyDataSecrets.from_entropy(entropy) if include_secrets else None, secrets=KeyDataSecrets.from_entropy(entropy) if include_secrets else None,
) )
@ -299,7 +303,7 @@ class Keychain:
except KeychainUserNotFound: except KeychainUserNotFound:
return index return index
def add_private_key(self, mnemonic: str) -> PrivateKey: def add_private_key(self, mnemonic: str, label: Optional[str] = None) -> PrivateKey:
""" """
Adds a private key to the keychain, with the given entropy and passphrase. The Adds a private key to the keychain, with the given entropy and passphrase. The
keychain itself will store the public key, and the entropy bytes, keychain itself will store the public key, and the entropy bytes,
@ -315,13 +319,37 @@ class Keychain:
# Prevents duplicate add # Prevents duplicate add
raise KeychainFingerprintExists(fingerprint) raise KeychainFingerprintExists(fingerprint)
# Try to set the label first, it may fail if the label is invalid or already exists.
# This can probably just be moved into `FileKeyring.set_passphrase` after the legacy keyring stuff was dropped.
if label is not None:
self.keyring_wrapper.set_label(fingerprint, label)
try:
self.keyring_wrapper.set_passphrase( self.keyring_wrapper.set_passphrase(
self.service, self.service,
get_private_key_user(self.user, index), get_private_key_user(self.user, index),
bytes(key.get_g1()).hex() + entropy.hex(), bytes(key.get_g1()).hex() + entropy.hex(),
) )
except Exception:
if label is not None:
self.keyring_wrapper.delete_label(fingerprint)
raise
return key return key
def set_label(self, fingerprint: int, label: str) -> None:
"""
Assigns the given label to the first key with the given fingerprint.
"""
self.get_key(fingerprint) # raise if the fingerprint doesn't exist
self.keyring_wrapper.set_label(fingerprint, label)
def delete_label(self, fingerprint: int) -> None:
"""
Removes the label assigned to the key with the given fingerprint.
"""
self.keyring_wrapper.delete_label(fingerprint)
def get_first_private_key(self) -> Optional[Tuple[PrivateKey, bytes]]: def get_first_private_key(self) -> Optional[Tuple[PrivateKey, bytes]]:
""" """
Returns the first key in the keychain that has one of the passed in passphrases. Returns the first key in the keychain that has one of the passed in passphrases.

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, List, Optional from typing import Callable, List, Optional
from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.program import Program
@ -19,12 +19,11 @@ from chia.wallet.puzzles.cat_loader import CAT_MOD
@dataclass(frozen=True) @dataclass(frozen=True)
class CATOuterPuzzle: class CATOuterPuzzle:
_match: Any _match: Callable[[Program], Optional[PuzzleInfo]]
_asset_id: Any _construct: Callable[[PuzzleInfo, Program], Program]
_construct: Any _solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
_solve: Any _get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_puzzle: Any _get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]: def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
args = match_cat_puzzle(*puzzle.uncurry()) args = match_cat_puzzle(*puzzle.uncurry())
@ -45,16 +44,18 @@ class CATOuterPuzzle:
if args is None: if args is None:
raise ValueError("This driver is not for the specified puzzle reveal") raise ValueError("This driver is not for the specified puzzle reveal")
_, _, inner_puzzle = args _, _, inner_puzzle = args
if constructor.also() is not None: also = constructor.also()
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle) if also is not None:
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(also, inner_puzzle)
return deep_inner_puzzle return deep_inner_puzzle
else: else:
return inner_puzzle return inner_puzzle
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]: def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
my_inner_solution: Program = solution.first() my_inner_solution: Program = solution.first()
if constructor.also(): also = constructor.also()
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution) if also:
deep_inner_solution: Optional[Program] = self._get_inner_solution(also, my_inner_solution)
return deep_inner_solution return deep_inner_solution
else: else:
return my_inner_solution return my_inner_solution
@ -63,8 +64,9 @@ class CATOuterPuzzle:
return bytes32(constructor["tail"]) return bytes32(constructor["tail"])
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program: def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
if constructor.also() is not None: also = constructor.also()
inner_puzzle = self._construct(constructor.also(), inner_puzzle) if also is not None:
inner_puzzle = self._construct(also, inner_puzzle)
return construct_cat_puzzle(CAT_MOD, constructor["tail"], inner_puzzle) return construct_cat_puzzle(CAT_MOD, constructor["tail"], inner_puzzle)
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program: def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
@ -91,9 +93,10 @@ class CATOuterPuzzle:
target_coin = coin target_coin = coin
parent_spend: CoinSpend = CoinSpend.from_bytes(spend_prog.as_python()) parent_spend: CoinSpend = CoinSpend.from_bytes(spend_prog.as_python())
parent_coin: Coin = parent_spend.coin parent_coin: Coin = parent_spend.coin
if constructor.also() is not None: also = constructor.also()
puzzle = self._construct(constructor.also(), puzzle) if also is not None:
solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution) puzzle = self._construct(also, puzzle)
solution = self._solve(also, solver, inner_puzzle, inner_solution)
args = match_cat_puzzle(*parent_spend.puzzle_reveal.to_program().uncurry()) args = match_cat_puzzle(*parent_spend.puzzle_reveal.to_program().uncurry())
assert args is not None assert args is not None
_, _, parent_inner_puzzle = args _, _, parent_inner_puzzle = args

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, List, Optional, Tuple from typing import Callable, List, Optional, Tuple
from clvm_tools.binutils import disassemble from clvm_tools.binutils import disassemble
@ -31,12 +31,11 @@ def solution_for_metadata_layer(amount: uint64, inner_solution: Program) -> Prog
@dataclass(frozen=True) @dataclass(frozen=True)
class MetadataOuterPuzzle: class MetadataOuterPuzzle:
_match: Any _match: Callable[[Program], Optional[PuzzleInfo]]
_asset_id: Any _construct: Callable[[PuzzleInfo, Program], Program]
_construct: Any _solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
_solve: Any _get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_puzzle: Any _get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]: def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_metadata_layer_puzzle(puzzle) matched, curried_args = match_metadata_layer_puzzle(puzzle)
@ -59,16 +58,18 @@ class MetadataOuterPuzzle:
return bytes32(constructor["updater_hash"]) return bytes32(constructor["updater_hash"])
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program: def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
if constructor.also() is not None: also = constructor.also()
inner_puzzle = self._construct(constructor.also(), inner_puzzle) if also is not None:
inner_puzzle = self._construct(also, inner_puzzle)
return puzzle_for_metadata_layer(constructor["metadata"], constructor["updater_hash"], inner_puzzle) return puzzle_for_metadata_layer(constructor["metadata"], constructor["updater_hash"], inner_puzzle)
def get_inner_puzzle(self, constructor: PuzzleInfo, puzzle_reveal: Program) -> Optional[Program]: def get_inner_puzzle(self, constructor: PuzzleInfo, puzzle_reveal: Program) -> Optional[Program]:
matched, curried_args = match_metadata_layer_puzzle(puzzle_reveal) matched, curried_args = match_metadata_layer_puzzle(puzzle_reveal)
if matched: if matched:
_, _, _, inner_puzzle = curried_args _, _, _, inner_puzzle = curried_args
if constructor.also() is not None: also = constructor.also()
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle) if also is not None:
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(also, inner_puzzle)
return deep_inner_puzzle return deep_inner_puzzle
else: else:
return inner_puzzle return inner_puzzle
@ -77,8 +78,9 @@ class MetadataOuterPuzzle:
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]: def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
my_inner_solution: Program = solution.first() my_inner_solution: Program = solution.first()
if constructor.also(): also = constructor.also()
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution) if also:
deep_inner_solution: Optional[Program] = self._get_inner_solution(also, my_inner_solution)
return deep_inner_solution return deep_inner_solution
else: else:
return my_inner_solution return my_inner_solution
@ -86,8 +88,9 @@ class MetadataOuterPuzzle:
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program: def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
coin_bytes: bytes = solver["coin"] coin_bytes: bytes = solver["coin"]
coin: Coin = Coin(bytes32(coin_bytes[0:32]), bytes32(coin_bytes[32:64]), uint64.from_bytes(coin_bytes[64:72])) coin: Coin = Coin(bytes32(coin_bytes[0:32]), bytes32(coin_bytes[32:64]), uint64.from_bytes(coin_bytes[64:72]))
if constructor.also() is not None: also = constructor.also()
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution) if also is not None:
inner_solution = self._solve(also, solver, inner_puzzle, inner_solution)
return solution_for_metadata_layer( return solution_for_metadata_layer(
uint64(coin.amount), uint64(coin.amount),
inner_solution, inner_solution,

View File

@ -21,6 +21,7 @@ SINGLETON_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash() NFT_STATE_LAYER_MOD_HASH = NFT_STATE_LAYER_MOD.get_tree_hash()
NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm") NFT_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.clvm")
NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm") NFT_OWNERSHIP_LAYER = load_clvm("nft_ownership_layer.clvm")
NFT_OWNERSHIP_LAYER_HASH = NFT_OWNERSHIP_LAYER.get_tree_hash()
NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.clvm") NFT_TRANSFER_PROGRAM_DEFAULT = load_clvm("nft_ownership_transfer_program_one_way_claim_with_royalties.clvm")
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm") STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
@ -39,6 +40,7 @@ def create_nft_layer_puzzle_with_curry_params(
def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Program) -> Program: def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Program) -> Program:
if log.isEnabledFor(logging.DEBUG):
log.debug( log.debug(
"Creating full NFT puzzle with inner puzzle: \n%r\n%r", "Creating full NFT puzzle with inner puzzle: \n%r\n%r",
singleton_id, singleton_id,
@ -47,6 +49,7 @@ def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Prog
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH))) singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, inner_puzzle) full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, inner_puzzle)
if log.isEnabledFor(logging.DEBUG):
log.debug("Created NFT full puzzle with inner: %s", full_puzzle.get_tree_hash()) log.debug("Created NFT full puzzle with inner: %s", full_puzzle.get_tree_hash())
return full_puzzle return full_puzzle
@ -54,6 +57,7 @@ def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Prog
def create_full_puzzle( def create_full_puzzle(
singleton_id: bytes32, metadata: Program, metadata_updater_puzhash: bytes32, inner_puzzle: Program singleton_id: bytes32, metadata: Program, metadata_updater_puzhash: bytes32, inner_puzzle: Program
) -> Program: ) -> Program:
if log.isEnabledFor(logging.DEBUG):
log.debug( log.debug(
"Creating full NFT puzzle with: \n%r\n%r\n%r\n%r", "Creating full NFT puzzle with: \n%r\n%r\n%r\n%r",
singleton_id, singleton_id,
@ -65,6 +69,7 @@ def create_full_puzzle(
singleton_inner_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, metadata_updater_puzhash, inner_puzzle) singleton_inner_puzzle = create_nft_layer_puzzle_with_curry_params(metadata, metadata_updater_puzhash, inner_puzzle)
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, singleton_inner_puzzle) full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, singleton_inner_puzzle)
if log.isEnabledFor(logging.DEBUG):
log.debug("Created NFT full puzzle: %s", full_puzzle.get_tree_hash()) log.debug("Created NFT full puzzle: %s", full_puzzle.get_tree_hash())
return full_puzzle return full_puzzle
@ -171,7 +176,7 @@ def construct_ownership_layer(
inner_puzzle: Program, inner_puzzle: Program,
) -> Program: ) -> Program:
return NFT_OWNERSHIP_LAYER.curry( return NFT_OWNERSHIP_LAYER.curry(
NFT_OWNERSHIP_LAYER.get_tree_hash(), NFT_OWNERSHIP_LAYER_HASH,
current_owner, current_owner,
transfer_program, transfer_program,
inner_puzzle, inner_puzzle,

View File

@ -31,7 +31,7 @@ from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
calculate_synthetic_secret_key, calculate_synthetic_secret_key,
puzzle_for_pk, puzzle_for_pk,
) )
from chia.wallet.trading.offer import OFFER_MOD, NotarizedPayment, Offer from chia.wallet.trading.offer import OFFER_MOD, OFFER_MOD_HASH, NotarizedPayment, Offer
from chia.wallet.transaction_record import TransactionRecord from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.util.compute_memos import compute_memos from chia.wallet.util.compute_memos import compute_memos
from chia.wallet.util.debug_spend_bundle import disassemble from chia.wallet.util.debug_spend_bundle import disassemble
@ -168,8 +168,11 @@ class NFTWallet:
uncurried_nft = UncurriedNFT.uncurry(*puzzle.uncurry()) uncurried_nft = UncurriedNFT.uncurry(*puzzle.uncurry())
assert uncurried_nft is not None assert uncurried_nft is not None
self.log.info( self.log.debug(
f"found the info for NFT coin {coin_name} {uncurried_nft.inner_puzzle} {uncurried_nft.singleton_struct}" "found the info for NFT coin %s %s %s",
coin_name.hex(),
uncurried_nft.inner_puzzle,
uncurried_nft.singleton_struct,
) )
singleton_id = uncurried_nft.singleton_launcher_id singleton_id = uncurried_nft.singleton_launcher_id
parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash() parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash()
@ -181,7 +184,7 @@ class NFTWallet:
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(p2_puzzle_hash) ] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(p2_puzzle_hash)
self.log.debug("Record for %s is: %s", p2_puzzle_hash, derivation_record) self.log.debug("Record for %s is: %s", p2_puzzle_hash, derivation_record)
if derivation_record is None: if derivation_record is None:
self.log.debug(f"Not our NFT, pointing to {p2_puzzle_hash}, skipping") self.log.debug("Not our NFT, pointing to %s, skipping", p2_puzzle_hash)
return return
p2_puzzle = puzzle_for_pk(derivation_record.pubkey) p2_puzzle = puzzle_for_pk(derivation_record.pubkey)
if uncurried_nft.supports_did: if uncurried_nft.supports_did:
@ -199,14 +202,15 @@ class NFTWallet:
"Created NFT full puzzle with inner: %s", "Created NFT full puzzle with inner: %s",
nft_puzzles.create_full_puzzle_with_nft_puzzle(singleton_id, uncurried_nft.inner_puzzle), nft_puzzles.create_full_puzzle_with_nft_puzzle(singleton_id, uncurried_nft.inner_puzzle),
) )
child_puzzle_hash = child_puzzle.get_tree_hash()
for new_coin in coin_spend.additions(): for new_coin in coin_spend.additions():
self.log.debug( self.log.debug(
"Comparing addition: %s with %s, amount: %s ", "Comparing addition: %s with %s, amount: %s ",
new_coin.puzzle_hash, new_coin.puzzle_hash,
child_puzzle.get_tree_hash(), child_puzzle_hash,
new_coin.amount, new_coin.amount,
) )
if new_coin.puzzle_hash == child_puzzle.get_tree_hash(): if new_coin.puzzle_hash == child_puzzle_hash:
child_coin = new_coin child_coin = new_coin
break break
else: else:
@ -322,7 +326,7 @@ class NFTWallet:
origin = coins.copy().pop() origin = coins.copy().pop()
genesis_launcher_puz = nft_puzzles.LAUNCHER_PUZZLE genesis_launcher_puz = nft_puzzles.LAUNCHER_PUZZLE
# nft_id == singleton_id == launcher_id == launcher_coin.name() # nft_id == singleton_id == launcher_id == launcher_coin.name()
launcher_coin = Coin(origin.name(), genesis_launcher_puz.get_tree_hash(), uint64(amount)) launcher_coin = Coin(origin.name(), nft_puzzles.LAUNCHER_PUZZLE_HASH, uint64(amount))
self.log.debug("Generating NFT with launcher coin %s and metadata: %s", launcher_coin, metadata) self.log.debug("Generating NFT with launcher coin %s and metadata: %s", launcher_coin, metadata)
p2_inner_puzzle = await self.standard_wallet.get_new_puzzle() p2_inner_puzzle = await self.standard_wallet.get_new_puzzle()
@ -346,9 +350,10 @@ class NFTWallet:
eve_fullpuz = nft_puzzles.create_full_puzzle( eve_fullpuz = nft_puzzles.create_full_puzzle(
launcher_coin.name(), metadata, NFT_METADATA_UPDATER.get_tree_hash(), inner_puzzle launcher_coin.name(), metadata, NFT_METADATA_UPDATER.get_tree_hash(), inner_puzzle
) )
eve_fullpuz_hash = eve_fullpuz.get_tree_hash()
# launcher announcement # launcher announcement
announcement_set: Set[Announcement] = set() announcement_set: Set[Announcement] = set()
announcement_message = Program.to([eve_fullpuz.get_tree_hash(), amount, []]).get_tree_hash() announcement_message = Program.to([eve_fullpuz_hash, amount, []]).get_tree_hash()
announcement_set.add(Announcement(launcher_coin.name(), announcement_message)) announcement_set.add(Announcement(launcher_coin.name(), announcement_message))
self.log.debug( self.log.debug(
@ -357,7 +362,7 @@ class NFTWallet:
# store the launcher transaction in the wallet state # store the launcher transaction in the wallet state
tx_record: Optional[TransactionRecord] = await self.standard_wallet.generate_signed_transaction( tx_record: Optional[TransactionRecord] = await self.standard_wallet.generate_signed_transaction(
uint64(amount), uint64(amount),
genesis_launcher_puz.get_tree_hash(), nft_puzzles.LAUNCHER_PUZZLE_HASH,
fee, fee,
origin.name(), origin.name(),
coins, coins,
@ -365,13 +370,13 @@ class NFTWallet:
False, False,
announcement_set, announcement_set,
) )
genesis_launcher_solution = Program.to([eve_fullpuz.get_tree_hash(), amount, []]) genesis_launcher_solution = Program.to([eve_fullpuz_hash, amount, []])
# launcher spend to generate the singleton # launcher spend to generate the singleton
launcher_cs = CoinSpend(launcher_coin, genesis_launcher_puz, genesis_launcher_solution) launcher_cs = CoinSpend(launcher_coin, genesis_launcher_puz, genesis_launcher_solution)
launcher_sb = SpendBundle([launcher_cs], AugSchemeMPL.aggregate([])) launcher_sb = SpendBundle([launcher_cs], AugSchemeMPL.aggregate([]))
eve_coin = Coin(launcher_coin.name(), eve_fullpuz.get_tree_hash(), uint64(amount)) eve_coin = Coin(launcher_coin.name(), eve_fullpuz_hash, uint64(amount))
if tx_record is None or tx_record.spend_bundle is None: if tx_record is None or tx_record.spend_bundle is None:
self.log.error("Couldn't produce a launcher spend") self.log.error("Couldn't produce a launcher spend")
@ -793,9 +798,7 @@ class NFTWallet:
for asset, amount in fungible_asset_dict.items(): # requested fungible items for asset, amount in fungible_asset_dict.items(): # requested fungible items
if amount > 0: if amount > 0:
settlement_ph: bytes32 = ( settlement_ph: bytes32 = (
OFFER_MOD.get_tree_hash() OFFER_MOD_HASH if asset is None else construct_puzzle(driver_dict[asset], OFFER_MOD).get_tree_hash()
if asset is None
else construct_puzzle(driver_dict[asset], OFFER_MOD).get_tree_hash()
) )
trade_prices.append([uint64(math.floor(amount / offer_side_royalty_split)), settlement_ph]) trade_prices.append([uint64(math.floor(amount / offer_side_royalty_split)), settlement_ph])
@ -863,6 +866,7 @@ class NFTWallet:
for asset, payments in royalty_payments.items(): for asset, payments in royalty_payments.items():
if asset is None: # xch offer if asset is None: # xch offer
offer_puzzle = OFFER_MOD offer_puzzle = OFFER_MOD
royalty_ph = OFFER_MOD_HASH
else: else:
offer_puzzle = construct_puzzle(driver_dict[asset], OFFER_MOD) offer_puzzle = construct_puzzle(driver_dict[asset], OFFER_MOD)
royalty_ph = offer_puzzle.get_tree_hash() royalty_ph = offer_puzzle.get_tree_hash()
@ -930,6 +934,7 @@ class NFTWallet:
inner_royalty_sol = Program.to([(launcher_id, [payment.as_condition_args()])]) inner_royalty_sol = Program.to([(launcher_id, [payment.as_condition_args()])])
if asset is None: # xch offer if asset is None: # xch offer
offer_puzzle = OFFER_MOD offer_puzzle = OFFER_MOD
royalty_ph = OFFER_MOD_HASH
else: else:
offer_puzzle = construct_puzzle(driver_dict[asset], OFFER_MOD) offer_puzzle = construct_puzzle(driver_dict[asset], OFFER_MOD)
royalty_ph = offer_puzzle.get_tree_hash() royalty_ph = offer_puzzle.get_tree_hash()

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, List, Optional, Tuple, Union from typing import Callable, List, Optional, Tuple, Union
from clvm_tools.binutils import disassemble from clvm_tools.binutils import disassemble
@ -30,12 +30,11 @@ def solution_for_ownership_layer(inner_solution: Program) -> Program:
@dataclass(frozen=True) @dataclass(frozen=True)
class OwnershipOuterPuzzle: class OwnershipOuterPuzzle:
_match: Any _match: Callable[[Program], Optional[PuzzleInfo]]
_asset_id: Any _construct: Callable[[PuzzleInfo, Program], Program]
_construct: Any _solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
_solve: Any _get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_puzzle: Any _get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]: def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_ownership_layer_puzzle(puzzle) matched, curried_args = match_ownership_layer_puzzle(puzzle)
@ -61,8 +60,9 @@ class OwnershipOuterPuzzle:
return None return None
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program: def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
if constructor.also() is not None: also = constructor.also()
inner_puzzle = self._construct(constructor.also(), inner_puzzle) if also is not None:
inner_puzzle = self._construct(also, inner_puzzle)
transfer_program_info: Union[PuzzleInfo, Program] = constructor["transfer_program"] transfer_program_info: Union[PuzzleInfo, Program] = constructor["transfer_program"]
if isinstance(transfer_program_info, Program): if isinstance(transfer_program_info, Program):
transfer_program: Program = transfer_program_info transfer_program: Program = transfer_program_info
@ -74,8 +74,9 @@ class OwnershipOuterPuzzle:
matched, curried_args = match_ownership_layer_puzzle(puzzle_reveal) matched, curried_args = match_ownership_layer_puzzle(puzzle_reveal)
if matched: if matched:
_, _, _, inner_puzzle = curried_args _, _, _, inner_puzzle = curried_args
if constructor.also() is not None: also = constructor.also()
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle) if also is not None:
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(also, inner_puzzle)
return deep_inner_puzzle return deep_inner_puzzle
else: else:
return inner_puzzle return inner_puzzle
@ -84,13 +85,15 @@ class OwnershipOuterPuzzle:
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]: def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
my_inner_solution: Program = solution.first() my_inner_solution: Program = solution.first()
if constructor.also(): also = constructor.also()
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution) if also:
deep_inner_solution: Optional[Program] = self._get_inner_solution(also, my_inner_solution)
return deep_inner_solution return deep_inner_solution
else: else:
return my_inner_solution return my_inner_solution
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program: def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
if constructor.also() is not None: also = constructor.also()
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution) if also is not None:
inner_solution = self._solve(also, solver, inner_puzzle, inner_solution)
return solution_for_ownership_layer(inner_solution) return solution_for_ownership_layer(inner_solution)

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Optional from typing import Callable, Optional
from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.program import Program
@ -18,12 +18,11 @@ from chia.wallet.puzzles.singleton_top_layer_v1_1 import (
@dataclass(frozen=True) @dataclass(frozen=True)
class SingletonOuterPuzzle: class SingletonOuterPuzzle:
_match: Any _match: Callable[[Program], Optional[PuzzleInfo]]
_asset_id: Any _construct: Callable[[PuzzleInfo, Program], Program]
_construct: Any _solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
_solve: Any _get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_puzzle: Any _get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]: def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_singleton_puzzle(puzzle) matched, curried_args = match_singleton_puzzle(puzzle)
@ -45,8 +44,9 @@ class SingletonOuterPuzzle:
return bytes32(constructor["launcher_id"]) return bytes32(constructor["launcher_id"])
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program: def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
if constructor.also() is not None: also = constructor.also()
inner_puzzle = self._construct(constructor.also(), inner_puzzle) if also is not None:
inner_puzzle = self._construct(also, inner_puzzle)
launcher_hash = constructor["launcher_ph"] if "launcher_ph" in constructor else SINGLETON_LAUNCHER_HASH launcher_hash = constructor["launcher_ph"] if "launcher_ph" in constructor else SINGLETON_LAUNCHER_HASH
return puzzle_for_singleton(constructor["launcher_id"], inner_puzzle, launcher_hash) return puzzle_for_singleton(constructor["launcher_id"], inner_puzzle, launcher_hash)
@ -54,8 +54,9 @@ class SingletonOuterPuzzle:
matched, curried_args = match_singleton_puzzle(puzzle_reveal) matched, curried_args = match_singleton_puzzle(puzzle_reveal)
if matched: if matched:
_, inner_puzzle = curried_args _, inner_puzzle = curried_args
if constructor.also() is not None: also = constructor.also()
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle) if also is not None:
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(also, inner_puzzle)
return deep_inner_puzzle return deep_inner_puzzle
else: else:
return inner_puzzle return inner_puzzle
@ -64,8 +65,9 @@ class SingletonOuterPuzzle:
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]: def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
my_inner_solution: Program = solution.at("rrf") my_inner_solution: Program = solution.at("rrf")
if constructor.also(): also = constructor.also()
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution) if also:
deep_inner_solution: Optional[Program] = self._get_inner_solution(also, my_inner_solution)
return deep_inner_solution return deep_inner_solution
else: else:
return my_inner_solution return my_inner_solution
@ -75,8 +77,9 @@ class SingletonOuterPuzzle:
coin: Coin = Coin(bytes32(coin_bytes[0:32]), bytes32(coin_bytes[32:64]), uint64.from_bytes(coin_bytes[64:72])) coin: Coin = Coin(bytes32(coin_bytes[0:32]), bytes32(coin_bytes[32:64]), uint64.from_bytes(coin_bytes[64:72]))
parent_spend: CoinSpend = CoinSpend.from_bytes(solver["parent_spend"]) parent_spend: CoinSpend = CoinSpend.from_bytes(solver["parent_spend"])
parent_coin: Coin = parent_spend.coin parent_coin: Coin = parent_spend.coin
if constructor.also() is not None: also = constructor.also()
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution) if also is not None:
inner_solution = self._solve(also, solver, inner_puzzle, inner_solution)
matched, curried_args = match_singleton_puzzle(parent_spend.puzzle_reveal.to_program()) matched, curried_args = match_singleton_puzzle(parent_spend.puzzle_reveal.to_program())
assert matched assert matched
_, parent_inner_puzzle = curried_args _, parent_inner_puzzle = curried_args

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, List, Optional, Tuple from typing import Callable, List, Optional, Tuple
from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.blockchain_format.sized_bytes import bytes32
@ -39,12 +39,11 @@ def solution_for_transfer_program(
@dataclass(frozen=True) @dataclass(frozen=True)
class TransferProgramPuzzle: class TransferProgramPuzzle:
_match: Any _match: Callable[[Program], Optional[PuzzleInfo]]
_asset_id: Any _construct: Callable[[PuzzleInfo, Program], Program]
_construct: Any _solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
_solve: Any _get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_puzzle: Any _get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
_get_inner_solution: Any
def match(self, puzzle: Program) -> Optional[PuzzleInfo]: def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
matched, curried_args = match_transfer_program_puzzle(puzzle) matched, curried_args = match_transfer_program_puzzle(puzzle)

View File

@ -63,11 +63,11 @@ def get_inner_solution(constructor: PuzzleInfo, solution: Program) -> Optional[P
return driver_lookup[AssetType(constructor.type())].get_inner_solution(constructor, solution) return driver_lookup[AssetType(constructor.type())].get_inner_solution(constructor, solution)
def create_asset_id(constructor: PuzzleInfo) -> bytes32: def create_asset_id(constructor: PuzzleInfo) -> Optional[bytes32]:
return driver_lookup[AssetType(constructor.type())].asset_id(constructor) # type: ignore return driver_lookup[AssetType(constructor.type())].asset_id(constructor)
function_args = [match_puzzle, create_asset_id, construct_puzzle, solve_puzzle, get_inner_puzzle, get_inner_solution] function_args = (match_puzzle, construct_puzzle, solve_puzzle, get_inner_puzzle, get_inner_solution)
driver_lookup: Dict[AssetType, DriverProtocol] = { driver_lookup: Dict[AssetType, DriverProtocol] = {
AssetType.CAT: CATOuterPuzzle(*function_args), AssetType.CAT: CATOuterPuzzle(*function_args),

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
@ -51,7 +53,7 @@ class PuzzleInfo:
def type(self) -> str: def type(self) -> str:
return str(self.info["type"]) return str(self.info["type"])
def also(self) -> Optional["PuzzleInfo"]: def also(self) -> Optional[PuzzleInfo]:
if "also" in self.info: if "also" in self.info:
return PuzzleInfo(self.info["also"]) return PuzzleInfo(self.info["also"])
else: else:

View File

@ -199,7 +199,7 @@ class TradeManager:
coins_of_interest = [] coins_of_interest = []
for trade_offer in all_pending: for trade_offer in all_pending:
coins_of_interest.extend([c.name() for c in Offer.from_bytes(trade_offer.offer).get_involved_coins()]) coins_of_interest.extend([c.name() for c in trade_offer.coins_of_interest])
result = {} result = {}
coin_records = await self.wallet_state_manager.coin_store.get_multiple_coin_records(coins_of_interest) coin_records = await self.wallet_state_manager.coin_store.get_multiple_coin_records(coins_of_interest)

View File

@ -31,6 +31,7 @@ from chia.wallet.util.puzzle_compression import (
) )
OFFER_MOD = load_clvm("settlement_payments.clvm") OFFER_MOD = load_clvm("settlement_payments.clvm")
OFFER_MOD_HASH = OFFER_MOD.get_tree_hash()
ZERO_32 = bytes32([0] * 32) ZERO_32 = bytes32([0] * 32)
@ -69,7 +70,7 @@ class Offer:
@staticmethod @staticmethod
def ph() -> bytes32: def ph() -> bytes32:
return OFFER_MOD.get_tree_hash() return OFFER_MOD_HASH
@staticmethod @staticmethod
def notarize_payments( def notarize_payments(
@ -102,7 +103,7 @@ class Offer:
raise ValueError("Cannot calculate announcements without driver of requested item") raise ValueError("Cannot calculate announcements without driver of requested item")
settlement_ph: bytes32 = construct_puzzle(driver_dict[asset_id], OFFER_MOD).get_tree_hash() settlement_ph: bytes32 = construct_puzzle(driver_dict[asset_id], OFFER_MOD).get_tree_hash()
else: else:
settlement_ph = OFFER_MOD.get_tree_hash() settlement_ph = OFFER_MOD_HASH
msg: bytes32 = Program.to((payments[0].nonce, [p.as_condition_args() for p in payments])).get_tree_hash() msg: bytes32 = Program.to((payments[0].nonce, [p.as_condition_args() for p in payments])).get_tree_hash()
announcements.append(Announcement(settlement_ph, msg)) announcements.append(Announcement(settlement_ph, msg))
@ -110,13 +111,6 @@ class Offer:
return announcements return announcements
def __post_init__(self) -> None: def __post_init__(self) -> None:
# Verify that there is at least something being offered
offered_coins: Dict[Optional[bytes32], List[Coin]] = self.get_offered_coins()
if offered_coins == {}:
raise ValueError("Bundle is not offering anything")
if self.get_requested_payments() == {}:
raise ValueError("Bundle is not requesting anything")
# Verify that there are no duplicate payments # Verify that there are no duplicate payments
for payments in self.requested_payments.values(): for payments in self.requested_payments.values():
payment_programs: List[bytes32] = [p.name() for p in payments] payment_programs: List[bytes32] = [p.name() for p in payments]
@ -154,7 +148,7 @@ class Offer:
def get_offered_coins(self) -> Dict[Optional[bytes32], List[Coin]]: def get_offered_coins(self) -> Dict[Optional[bytes32], List[Coin]]:
offered_coins: Dict[Optional[bytes32], List[Coin]] = {} offered_coins: Dict[Optional[bytes32], List[Coin]] = {}
OFFER_HASH: bytes32 = OFFER_MOD.get_tree_hash() OFFER_HASH: bytes32 = OFFER_MOD_HASH
for parent_spend in self.bundle.coin_spends: for parent_spend in self.bundle.coin_spends:
coins_for_this_spend: List[Coin] = [] coins_for_this_spend: List[Coin] = []

View File

@ -2,7 +2,6 @@ from unittest import TestCase
from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.program import Program
from clvm.EvalError import EvalError from clvm.EvalError import EvalError
from clvm_tools.curry import uncurry
from clvm.operators import KEYWORD_TO_ATOM from clvm.operators import KEYWORD_TO_ATOM
from clvm_tools.binutils import assemble, disassemble from clvm_tools.binutils import assemble, disassemble
@ -49,7 +48,7 @@ def check_idempotency(f, *args):
curried = prg.curry(*args) curried = prg.curry(*args)
r = disassemble(curried) r = disassemble(curried)
f_0, args_0 = uncurry(curried) f_0, args_0 = curried.uncurry()
assert disassemble(f_0) == disassemble(f) assert disassemble(f_0) == disassemble(f)
assert disassemble(args_0) == disassemble(Program.to(list(args))) assert disassemble(args_0) == disassemble(Program.to(list(args)))
@ -67,3 +66,33 @@ def test_curry_uncurry():
# passing "args" here wraps the arguments in a list # passing "args" here wraps the arguments in a list
actual_disassembly = check_idempotency(f, args) actual_disassembly = check_idempotency(f, args)
assert actual_disassembly == f"(a (q {PLUS} 2 5) (c (q {PLUS} (q . 50) (q . 60)) 1))" assert actual_disassembly == f"(a (q {PLUS} 2 5) (c (q {PLUS} (q . 50) (q . 60)) 1))"
def test_uncurry_not_curried():
# this function has not been curried
plus = Program.to(assemble("(+ 2 5)"))
assert plus.uncurry() == (plus, Program.to(0))
def test_uncurry():
# this is a positive test
plus = Program.to(assemble("(2 (q . (+ 2 5)) (c (q . 1) 1))"))
assert plus.uncurry() == (Program.to(assemble("(+ 2 5)")), Program.to([1]))
def test_uncurry_top_level_garbage():
# there's garbage at the end of the top-level list
plus = Program.to(assemble("(2 (q . 1) (c (q . 1) (q . 1)) (q . 0x1337))"))
assert plus.uncurry() == (plus, Program.to(0))
def test_uncurry_not_pair():
# the second item in the list is expected to be a pair, with a qoute
plus = Program.to(assemble("(2 1 (c (q . 1) (q . 1)))"))
assert plus.uncurry() == (plus, Program.to(0))
def test_uncurry_args_garbage():
# there's garbage at the end of the args list
plus = Program.to(assemble("(2 (q . 1) (c (q . 1) (q . 1) (q . 0x1337)))"))
assert plus.uncurry() == (plus, Program.to(0))

View File

@ -7,6 +7,7 @@ import pytest
from dataclasses import replace from dataclasses import replace
from typing import Any, Dict, List, Type from typing import Any, Dict, List, Type
from chia.daemon.keychain_server import DeleteLabelRequest, SetLabelRequest
from chia.server.outbound_message import NodeType from chia.server.outbound_message import NodeType
from chia.types.peer_info import PeerInfo from chia.types.peer_info import PeerInfo
from chia.util.ints import uint16 from chia.util.ints import uint16
@ -56,6 +57,41 @@ def get_keys_response_data(keys: List[KeyData]) -> Dict[str, object]:
return {"success": True, **GetKeysResponse(keys=keys).to_json_dict()} return {"success": True, **GetKeysResponse(keys=keys).to_json_dict()}
def label_missing_response_data(request_type: Type[Any]) -> Dict[str, Any]:
return {
"success": False,
"error": "malformed request",
"error_details": {"message": f"1 field missing for {request_type.__name__}: label"},
}
def label_exists_response_data(fingerprint: int, label: str) -> Dict[str, Any]:
return {
"success": False,
"error": "malformed request",
"error_details": {"message": f"label {label!r} already exists for fingerprint {str(fingerprint)!r}"},
}
label_empty_response_data = {
"success": False,
"error": "malformed request",
"error_details": {"message": "label can't be empty or whitespace only"},
}
label_too_long_response_data = {
"success": False,
"error": "malformed request",
"error_details": {"message": "label exceeds max length: 66/65"},
}
label_newline_or_tab_response_data = {
"success": False,
"error": "malformed request",
"error_details": {"message": "label can't contain newline or tab"},
}
def assert_response(response: aiohttp.http_websocket.WSMessage, expected_response_data: Dict[str, Any]) -> None: def assert_response(response: aiohttp.http_websocket.WSMessage, expected_response_data: Dict[str, Any]) -> None:
# Expect: JSON response # Expect: JSON response
assert response.type == aiohttp.WSMsgType.TEXT assert response.type == aiohttp.WSMsgType.TEXT
@ -237,6 +273,31 @@ async def test_add_private_key(daemon_connection_and_temp_keychain):
assert_response(await ws.receive(), invalid_mnemonic_response_data) assert_response(await ws.receive(), invalid_mnemonic_response_data)
@pytest.mark.asyncio
async def test_add_private_key_label(daemon_connection_and_temp_keychain):
ws, keychain = daemon_connection_and_temp_keychain
async def assert_add_private_key_with_label(key_data: KeyData, request: Dict[str, object]) -> None:
await ws.send_str(create_payload("add_private_key", request, "test", "daemon"))
assert_response(await ws.receive(), success_response_data)
await ws.send_str(
create_payload("get_key", {"fingerprint": key_data.fingerprint, "include_secrets": True}, "test", "daemon")
)
assert_response(await ws.receive(), get_key_response_data(key_data))
# without `label` parameter
key_data_0 = KeyData.generate()
await assert_add_private_key_with_label(key_data_0, {"mnemonic": key_data_0.mnemonic_str()})
# with `label=None`
key_data_1 = KeyData.generate()
await assert_add_private_key_with_label(key_data_1, {"mnemonic": key_data_1.mnemonic_str(), "label": None})
# with `label="key_2"`
key_data_2 = KeyData.generate("key_2")
await assert_add_private_key_with_label(
key_data_1, {"mnemonic": key_data_2.mnemonic_str(), "label": key_data_2.label}
)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_key(daemon_connection_and_temp_keychain): async def test_get_key(daemon_connection_and_temp_keychain):
ws, keychain = daemon_connection_and_temp_keychain ws, keychain = daemon_connection_and_temp_keychain
@ -300,3 +361,110 @@ async def test_get_keys(daemon_connection_and_temp_keychain):
# with `include_secrets=True` # with `include_secrets=True`
await ws.send_str(create_payload("get_keys", {"include_secrets": True}, "test", "daemon")) await ws.send_str(create_payload("get_keys", {"include_secrets": True}, "test", "daemon"))
assert_response(await ws.receive(), get_keys_response_data(keys_added)) assert_response(await ws.receive(), get_keys_response_data(keys_added))
@pytest.mark.asyncio
async def test_key_renaming(daemon_connection_and_temp_keychain):
ws, keychain = daemon_connection_and_temp_keychain
keychain.add_private_key(test_key_data.mnemonic_str())
# Rename the key three times
for i in range(3):
key_data = replace(test_key_data_no_secrets, label=f"renaming_{i}")
await ws.send_str(
create_payload(
"set_label", {"fingerprint": key_data.fingerprint, "label": key_data.label}, "test", "daemon"
)
)
assert_response(await ws.receive(), success_response_data)
await ws.send_str(create_payload("get_key", {"fingerprint": key_data.fingerprint}, "test", "daemon"))
assert_response(
await ws.receive(),
{
"success": True,
"key": key_data.to_json_dict(),
},
)
@pytest.mark.asyncio
async def test_key_label_deletion(daemon_connection_and_temp_keychain):
ws, keychain = daemon_connection_and_temp_keychain
keychain.add_private_key(test_key_data.mnemonic_str(), "key_0")
assert keychain.get_key(test_key_data.fingerprint).label == "key_0"
await ws.send_str(create_payload("delete_label", {"fingerprint": test_key_data.fingerprint}, "test", "daemon"))
assert_response(await ws.receive(), success_response_data)
assert keychain.get_key(test_key_data.fingerprint).label is None
await ws.send_str(create_payload("delete_label", {"fingerprint": test_key_data.fingerprint}, "test", "daemon"))
assert_response(await ws.receive(), fingerprint_not_found_response_data(test_key_data.fingerprint))
@pytest.mark.parametrize(
"method, parameter, response_data_dict",
[
(
"set_label",
{"fingerprint": test_key_data.fingerprint, "label": "new_label"},
success_response_data,
),
(
"set_label",
{"label": "new_label"},
fingerprint_missing_response_data(SetLabelRequest),
),
(
"set_label",
{"fingerprint": test_key_data.fingerprint},
label_missing_response_data(SetLabelRequest),
),
(
"set_label",
{"fingerprint": test_key_data.fingerprint, "label": ""},
label_empty_response_data,
),
(
"set_label",
{"fingerprint": test_key_data.fingerprint, "label": "a" * 66},
label_too_long_response_data,
),
(
"set_label",
{"fingerprint": test_key_data.fingerprint, "label": "a\nb"},
label_newline_or_tab_response_data,
),
(
"set_label",
{"fingerprint": test_key_data.fingerprint, "label": "a\tb"},
label_newline_or_tab_response_data,
),
(
"set_label",
{"fingerprint": test_key_data.fingerprint, "label": "key_0"},
label_exists_response_data(test_key_data.fingerprint, "key_0"),
),
(
"delete_label",
{"fingerprint": test_key_data.fingerprint},
success_response_data,
),
(
"delete_label",
{},
fingerprint_missing_response_data(DeleteLabelRequest),
),
(
"delete_label",
{"fingerprint": 123456},
fingerprint_not_found_response_data(123456),
),
],
)
@pytest.mark.asyncio
async def test_key_label_methods(
daemon_connection_and_temp_keychain, method: str, parameter: Dict[str, Any], response_data_dict: Dict[str, Any]
) -> None:
ws, keychain = daemon_connection_and_temp_keychain
keychain.add_private_key(test_key_data.mnemonic_str(), "key_0")
await ws.send_str(create_payload(method, parameter, "test", "daemon"))
assert_response(await ws.receive(), response_data_dict)

View File

@ -13,6 +13,8 @@ from chia.util.errors import (
KeychainKeyDataMismatch, KeychainKeyDataMismatch,
KeychainSecretsMissing, KeychainSecretsMissing,
KeychainFingerprintNotFound, KeychainFingerprintNotFound,
KeychainLabelExists,
KeychainLabelInvalid,
) )
from chia.util.ints import uint32 from chia.util.ints import uint32
from chia.util.keychain import ( from chia.util.keychain import (
@ -106,6 +108,41 @@ class TestKeychain(unittest.TestCase):
kc.add_private_key(bytes_to_mnemonic(token_bytes(32))) kc.add_private_key(bytes_to_mnemonic(token_bytes(32)))
assert kc.get_first_public_key() is not None assert kc.get_first_public_key() is not None
@using_temp_file_keyring()
def test_add_private_key_label(self):
keychain: Keychain = Keychain(user="testing-1.8.0", service="chia-testing-1.8.0")
key_data_0 = KeyData.generate(label="key_0")
key_data_1 = KeyData.generate(label="key_1")
key_data_2 = KeyData.generate(label=None)
keychain.add_private_key(mnemonic=key_data_0.mnemonic_str(), label=key_data_0.label)
assert key_data_0 == keychain.get_key(key_data_0.fingerprint, include_secrets=True)
# Try to add a new key with an existing label should raise
with pytest.raises(KeychainLabelExists) as e:
keychain.add_private_key(mnemonic=key_data_1.mnemonic_str(), label=key_data_0.label)
assert e.value.fingerprint == key_data_0.fingerprint
assert e.value.label == key_data_0.label
# Adding the same key with a valid label should work fine
keychain.add_private_key(mnemonic=key_data_1.mnemonic_str(), label=key_data_1.label)
assert key_data_1 == keychain.get_key(key_data_1.fingerprint, include_secrets=True)
# Trying to add an existing key should not have an impact on the existing label
with pytest.raises(KeychainFingerprintExists):
keychain.add_private_key(mnemonic=key_data_0.mnemonic_str(), label="other label")
assert key_data_0 == keychain.get_key(key_data_0.fingerprint, include_secrets=True)
# Adding a key with no label should not assign any label
keychain.add_private_key(mnemonic=key_data_2.mnemonic_str(), label=key_data_2.label)
assert key_data_2 == keychain.get_key(key_data_2.fingerprint, include_secrets=True)
# All added keys should still be valid with their label
assert all(
key_data in [key_data_0, key_data_1, key_data_2] for key_data in keychain.get_keys(include_secrets=True)
)
@using_temp_file_keyring() @using_temp_file_keyring()
def test_bip39_eip2333_test_vector(self): def test_bip39_eip2333_test_vector(self):
kc: Keychain = Keychain(user="testing-1.8.0", service="chia-testing-1.8.0") kc: Keychain = Keychain(user="testing-1.8.0", service="chia-testing-1.8.0")
@ -171,29 +208,33 @@ def test_key_data_secrets_creation(input_data: object, from_method: Callable[...
assert secrets.private_key == private_key assert secrets.private_key == private_key
def test_key_data_generate() -> None: @pytest.mark.parametrize("label", [None, "key"])
key_data = KeyData.generate() def test_key_data_generate(label: Optional[str]) -> None:
key_data = KeyData.generate(label)
assert key_data.private_key == AugSchemeMPL.key_gen(mnemonic_to_seed(key_data.mnemonic_str())) assert key_data.private_key == AugSchemeMPL.key_gen(mnemonic_to_seed(key_data.mnemonic_str()))
assert key_data.entropy == bytes_from_mnemonic(key_data.mnemonic_str()) assert key_data.entropy == bytes_from_mnemonic(key_data.mnemonic_str())
assert key_data.public_key == key_data.private_key.get_g1() assert key_data.public_key == key_data.private_key.get_g1()
assert key_data.fingerprint == key_data.private_key.get_g1().get_fingerprint() assert key_data.fingerprint == key_data.private_key.get_g1().get_fingerprint()
assert key_data.label == label
@pytest.mark.parametrize("label", [None, "key"])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input_data, from_method", [(mnemonic, KeyData.from_mnemonic), (entropy, KeyData.from_entropy)] "input_data, from_method", [(mnemonic, KeyData.from_mnemonic), (entropy, KeyData.from_entropy)]
) )
def test_key_data_creation(input_data: object, from_method: Callable[..., KeyData]) -> None: def test_key_data_creation(input_data: object, from_method: Callable[..., KeyData], label: Optional[str]) -> None:
key_data = from_method(input_data) key_data = from_method(input_data, label)
assert key_data.fingerprint == fingerprint assert key_data.fingerprint == fingerprint
assert key_data.public_key == public_key assert key_data.public_key == public_key
assert key_data.mnemonic == mnemonic.split() assert key_data.mnemonic == mnemonic.split()
assert key_data.mnemonic_str() == mnemonic assert key_data.mnemonic_str() == mnemonic
assert key_data.entropy == entropy assert key_data.entropy == entropy
assert key_data.private_key == private_key assert key_data.private_key == private_key
assert key_data.label == label
def test_key_data_without_secrets() -> None: def test_key_data_without_secrets() -> None:
key_data = KeyData(fingerprint, public_key, None) key_data = KeyData(fingerprint, public_key, None, None)
assert key_data.secrets is None assert key_data.secrets is None
with pytest.raises(KeychainSecretsMissing): with pytest.raises(KeychainSecretsMissing):
@ -225,11 +266,13 @@ def test_key_data_secrets_post_init(input_data: Tuple[List[str], bytes, PrivateK
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input_data, data_type", "input_data, data_type",
[ [
((fingerprint, G1Element(), KeyDataSecrets(mnemonic.split(), entropy, private_key)), "public_key"), ((fingerprint, G1Element(), None, KeyDataSecrets(mnemonic.split(), entropy, private_key)), "public_key"),
((fingerprint, G1Element(), None), "fingerprint"), ((fingerprint, G1Element(), None, None), "fingerprint"),
], ],
) )
def test_key_data_post_init(input_data: Tuple[uint32, G1Element, Optional[KeyDataSecrets]], data_type: str) -> None: def test_key_data_post_init(
input_data: Tuple[uint32, G1Element, Optional[str], Optional[KeyDataSecrets]], data_type: str
) -> None:
with pytest.raises(KeychainKeyDataMismatch, match=data_type): with pytest.raises(KeychainKeyDataMismatch, match=data_type):
KeyData(*input_data) KeyData(*input_data)
@ -283,3 +326,92 @@ def test_get_keys(include_secrets: bool, get_temp_keyring: Keychain):
assert keychain.get_keys(include_secrets) == expected_keys assert keychain.get_keys(include_secrets) == expected_keys
# Should be empty again # Should be empty again
assert keychain.get_keys(include_secrets) == [] assert keychain.get_keys(include_secrets) == []
def test_set_label(get_temp_keyring: Keychain) -> None:
keychain: Keychain = get_temp_keyring
# Generate a key and add it without label
key_data_0 = KeyData.generate(label=None)
keychain.add_private_key(mnemonic=key_data_0.mnemonic_str(), label=None)
assert key_data_0 == keychain.get_key(key_data_0.fingerprint, include_secrets=True)
# Set a label and validate it
key_data_0 = replace(key_data_0, label="key_0")
assert key_data_0.label is not None
keychain.set_label(fingerprint=key_data_0.fingerprint, label=key_data_0.label)
assert key_data_0 == keychain.get_key(fingerprint=key_data_0.fingerprint, include_secrets=True)
# Try to add the same label for a fingerprint where don't have a key for
with pytest.raises(KeychainFingerprintNotFound):
keychain.set_label(fingerprint=123456, label=key_data_0.label)
# Add a second key
key_data_1 = KeyData.generate(label="key_1")
assert key_data_1.label is not None
keychain.add_private_key(key_data_1.mnemonic_str())
# Try to set the already existing label for the second key
with pytest.raises(KeychainLabelExists) as e:
keychain.set_label(fingerprint=key_data_1.fingerprint, label=key_data_0.label)
assert e.value.fingerprint == key_data_0.fingerprint
assert e.value.label == key_data_0.label
# Set a different label to the second key and validate it
keychain.set_label(fingerprint=key_data_1.fingerprint, label=key_data_1.label)
assert key_data_0 == keychain.get_key(fingerprint=key_data_0.fingerprint, include_secrets=True)
# All added keys should still be valid with their label
assert all(key_data in [key_data_0, key_data_1] for key_data in keychain.get_keys(include_secrets=True))
@pytest.mark.parametrize(
"label, message",
[
("", "label can't be empty or whitespace only"),
(" ", "label can't be empty or whitespace only"),
("a\nb", "label can't contain newline or tab"),
("a\tb", "label can't contain newline or tab"),
("a" * 66, "label exceeds max length: 66/65"),
("a" * 70, "label exceeds max length: 70/65"),
],
)
def test_set_label_invalid_labels(label: str, message: str, get_temp_keyring: Keychain) -> None:
keychain: Keychain = get_temp_keyring
key_data = KeyData.generate()
keychain.add_private_key(key_data.mnemonic_str())
with pytest.raises(KeychainLabelInvalid, match=message) as e:
keychain.set_label(key_data.fingerprint, label)
assert e.value.label == label
def test_delete_label(get_temp_keyring: Keychain) -> None:
keychain: Keychain = get_temp_keyring
# Generate two keys and add them to the keychain
key_data_0 = KeyData.generate(label="key_0")
key_data_1 = KeyData.generate(label="key_1")
def assert_delete_raises():
# Try to delete the labels should fail now since they are gone already
for key_data in [key_data_0, key_data_1]:
with pytest.raises(KeychainFingerprintNotFound) as e:
keychain.delete_label(key_data.fingerprint)
assert e.value.fingerprint == key_data.fingerprint
# Should pass here since the keys are not added yet
assert_delete_raises()
for key in [key_data_0, key_data_1]:
keychain.add_private_key(mnemonic=key.mnemonic_str(), label=key.label)
assert key == keychain.get_key(key.fingerprint, include_secrets=True)
# Delete the label of the first key, validate it was removed and make sure the other key retains its label
keychain.delete_label(key_data_0.fingerprint)
assert replace(key_data_0, label=None) == keychain.get_key(key_data_0.fingerprint, include_secrets=True)
assert key_data_1 == keychain.get_key(key_data_1.fingerprint, include_secrets=True)
# Re-add the label of the first key
assert key_data_0.label is not None
keychain.set_label(key_data_0.fingerprint, key_data_0.label)
# Delete the label of the second key
keychain.delete_label(key_data_1.fingerprint)
assert key_data_0 == keychain.get_key(key_data_0.fingerprint, include_secrets=True)
assert replace(key_data_1, label=None) == keychain.get_key(key_data_1.fingerprint, include_secrets=True)
# Delete the label of the first key again, now both should have no label
keychain.delete_label(key_data_0.fingerprint)
assert replace(key_data_0, label=None) == keychain.get_key(key_data_0.fingerprint, include_secrets=True)
assert replace(key_data_1, label=None) == keychain.get_key(key_data_1.fingerprint, include_secrets=True)
# Should pass here since the key labels are both removed here
assert_delete_raises()

View File

@ -9,14 +9,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend from chia.types.coin_spend import CoinSpend
from chia.util.ints import uint64 from chia.util.ints import uint64
from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle
from chia.wallet.outer_puzzles import ( from chia.wallet.outer_puzzles import construct_puzzle, get_inner_puzzle, get_inner_solution, match_puzzle, solve_puzzle
construct_puzzle,
create_asset_id,
get_inner_puzzle,
get_inner_solution,
match_puzzle,
solve_puzzle,
)
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
from chia.wallet.puzzles.cat_loader import CAT_MOD from chia.wallet.puzzles.cat_loader import CAT_MOD
@ -37,7 +30,6 @@ def test_cat_outer_puzzle() -> None:
assert inside_cat_driver["tail"] == tail assert inside_cat_driver["tail"] == tail
assert construct_puzzle(cat_driver, ACS) == double_cat_puzzle assert construct_puzzle(cat_driver, ACS) == double_cat_puzzle
assert get_inner_puzzle(cat_driver, double_cat_puzzle) == ACS assert get_inner_puzzle(cat_driver, double_cat_puzzle) == ACS
assert create_asset_id(cat_driver) == tail
# Set up for solve # Set up for solve
parent_coin = Coin(tail, double_cat_puzzle.get_tree_hash(), uint64(100)) parent_coin = Coin(tail, double_cat_puzzle.get_tree_hash(), uint64(100))

View File

@ -7,14 +7,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint16 from chia.util.ints import uint16
from chia.wallet.nft_wallet.ownership_outer_puzzle import puzzle_for_ownership_layer from chia.wallet.nft_wallet.ownership_outer_puzzle import puzzle_for_ownership_layer
from chia.wallet.nft_wallet.transfer_program_puzzle import puzzle_for_transfer_program from chia.wallet.nft_wallet.transfer_program_puzzle import puzzle_for_transfer_program
from chia.wallet.outer_puzzles import ( from chia.wallet.outer_puzzles import construct_puzzle, get_inner_puzzle, get_inner_solution, match_puzzle, solve_puzzle
construct_puzzle,
create_asset_id,
get_inner_puzzle,
get_inner_solution,
match_puzzle,
solve_puzzle,
)
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
@ -56,7 +49,6 @@ def test_ownership_outer_puzzle() -> None:
assert construct_puzzle(ownership_driver_empty, ACS) == ownership_puzzle_empty assert construct_puzzle(ownership_driver_empty, ACS) == ownership_puzzle_empty
assert construct_puzzle(ownership_driver_default, ACS) == ownership_puzzle_default assert construct_puzzle(ownership_driver_default, ACS) == ownership_puzzle_default
assert get_inner_puzzle(ownership_driver, ownership_puzzle) == ACS assert get_inner_puzzle(ownership_driver, ownership_puzzle) == ACS
assert create_asset_id(ownership_driver) is None
# Set up for solve # Set up for solve
inner_solution = Program.to( inner_solution = Program.to(

View File

@ -0,0 +1,226 @@
import cProfile
from contextlib import contextmanager
from typing import Iterator
import pytest
from chia.wallet.trading.offer import Offer
from tests.util.misc import assert_runtime
with_profile = False
# gprof2dot -f pstats offer-parsing.profile >p.dot && dot -Tpng p.dot >offer-parsing.png
# gprof2dot -f pstats offered-coins.profile >c.dot && dot -Tpng c.dot >offered-coins.png
@contextmanager
def enable_profiler(name: str) -> Iterator[None]:
if not with_profile:
yield
return
with cProfile.Profile() as pr:
yield
pr.create_stats()
pr.dump_stats(f"{name}.profile")
@pytest.mark.benchmark
def test_offer_parsing_performance() -> None:
offer_bytes = bytes.fromhex(test_offer)
with assert_runtime(seconds=16, label="Offer.from_bytes()"):
with enable_profiler("offer-parsing"):
for _ in range(100):
o = Offer.from_bytes(offer_bytes)
assert o is not None
@pytest.mark.benchmark
def test_offered_coins_performance() -> None:
offer_bytes = bytes.fromhex(test_offer)
o = Offer.from_bytes(offer_bytes)
with assert_runtime(seconds=10, label="Offer.from_bytes()"):
with enable_profiler("offered-coins"):
for _ in range(100):
c = o.get_offered_coins()
assert len(c.items()) > 0
test_offer = str(
"0000000200000000000000000000000000000000000000000000000000000000"
"00000000bae24162efbd568f89bc7a340798a6118df0189eb9e3f8697bcea27a"
"f99f8f790000000000000000ff02ffff01ff02ff0affff04ff02ffff04ff03ff"
"80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0c"
"ffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16"
"ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff808080"
"80ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff04ffff04ff08"
"ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff"
"010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1eff"
"ff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080"
"808080ffff01ff0bffff0101ff058080ff0180ff018080ffffa0113e4b68cb75"
"5a6e4347f4d93e3d942ad1d89aadef6536dad229fe5fbe6ab232ffffa0e13f56"
"72075e5ac9a50bcf080fca54762b2a59e22f37951f56802603ec2fe6e1ff64ff"
"80808080ee2b6845e1c317976b002adc4d1dc48d2b752b9f47a3c1ecad4df36a"
"2905d5add1ee4d5798f94b10f9487e314a9561a7a757d2fcf29d8d461106f04b"
"8e303b790000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2f"
"ff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff"
"04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02"
"ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff"
"02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff01"
"80ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080"
"ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff"
"02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ff"
"ff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01"
"ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff"
"0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff"
"8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ff"
"ff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080"
"ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01"
"ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff"
"03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02"
"ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01"
"ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff"
"04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080"
"ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080"
"8080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02"
"ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02"
"ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff"
"09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff"
"0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff"
"0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04"
"ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ff"
"ff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff8080"
"8080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101"
"ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff"
"30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa0"
"7faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9f"
"ffa02268aba6ee7b6a26b6f8abc2c00938e413a8aa128d1ba1bdc4a9bfb84e62"
"aa2da0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cd"
"c13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff"
"05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff"
"17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffff"
"ff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff"
"04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0b"
"ff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080"
"ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ff"
"ff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff"
"04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03"
"ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80"
"808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff01"
"01ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ff"
"ff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff"
"01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff"
"04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02"
"ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5f"
"ff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e8"
"80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff"
"ff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff"
"82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fff"
"ff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080"
"808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff"
"04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ff"
"ff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27"
"ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818f"
"ff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af"
"8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d8002"
"6e870519aaa66334aef8304f5d0393c2ffff04ffff01ffff75ff9d6874747073"
"3a2f2f70696373756d2e70686f746f732f3337372f38313180ffff68a0452062"
"a44018653e22198e70a0e756641361b8ec3bc466c1924a38d372e1a945ffff82"
"6d75ff9668747470733a2f2f7777772e6d656a69612e636f6d2f80ffff826c75"
"ff93687474703a2f2f616775697272652e6e65742f80ffff82736e01ffff8273"
"7401ffff826d68a01f462ea72e639eca6ebe792caeb296491177454fe2c763cb"
"9b08e52e85c02712ffff826c68a0b794d0dfa36ac60ff17b0b3649adbc44a703"
"8713bf8acfeaf4bb57dd276dd7ec80ffff04ffff01a0fe8a4b4e27a2e29a4d3f"
"c7ce9d527adbcaccbab6ada3903ccf3ba9a769d2d78bffff04ffff01ff02ffff"
"01ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff"
"04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff82ad4cff0233"
"ffff3e04ff81f601ffffff0102ffff02ffff03ff05ffff01ff02ff2affff04ff"
"02ffff04ff0dffff04ffff0bff32ffff0bff3cff3480ffff0bff32ffff0bff32"
"ffff0bff3cff2280ff0980ffff0bff32ff0bffff0bff3cff8080808080ff8080"
"808080ffff010b80ff0180ff04ffff04ff38ffff04ffff02ff36ffff04ff02ff"
"ff04ff05ffff04ff27ffff04ffff02ff2effff04ff02ffff04ffff02ffff03ff"
"81afffff0181afffff010b80ff0180ff80808080ffff04ffff0bff3cff4f80ff"
"ff04ffff0bff3cff0580ff8080808080808080ff378080ff82016f80ffffff02"
"ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2f"
"ffff01ff80ff808080808080808080ff0bff32ffff0bff3cff2880ffff0bff32"
"ffff0bff32ffff0bff3cff2280ff0580ffff0bff32ffff02ff2affff04ff02ff"
"ff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080"
"ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ff"
"ff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff"
"01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09"
"ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01"
"ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04"
"ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff82019fffff04ff82017f"
"ff80808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02"
"ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81"
"dfffff04ff81bfffff04ff82017fff808080808080808080808080ff0180ffff"
"01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80"
"ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff"
"82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff"
"0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff02ff0bff"
"ff04ff17ffff04ff2fffff04ff82019fff8080808080ff808080808080808080"
"8080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff"
"01ff02ffff03ffff20ffff02ffff03ffff09ffff0122ffff0dff82029f8080ff"
"ff01ff02ffff03ffff09ffff0cff82029fff80ffff010280ff1080ffff01ff01"
"01ff8080ff0180ff8080ff018080ffff01ff04ff819fffff02ff3effff04ff02"
"ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bf"
"ffff04ff82017fff8080808080808080808080ffff01ff088080ff0180ffff01"
"ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff"
"04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff808080808080808080"
"808080ff018080ff018080ff0180ffff01ff02ff3affff04ff02ffff04ff05ff"
"ff04ff0bffff04ff81bfffff04ffff02ffff03ff82017fffff0182017fffff01"
"ff02ff0bffff04ff17ffff04ff2fffff01ff808080808080ff0180ff80808080"
"80808080ff0180ff018080ffff04ffff01a0c5abea79afaa001b5427dfa0c8cf"
"42ca6f38f5841b78f9b3c252733eb2de2726ffff04ffff0180ffff04ffff01ff"
"02ffff01ff02ffff01ff02ffff03ff81bfffff01ff04ff82013fffff04ff80ff"
"ff04ffff02ffff03ffff22ff82013fffff20ffff09ff82013fff2f808080ffff"
"01ff04ffff04ff10ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04"
"ff8205bfffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff82013f"
"ff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff16ffff04ff"
"02ffff04ff0bffff04ff17ffff04ff8202bfffff04ff15ff8080808080808080"
"ffff01ff02ff16ffff04ff02ffff04ff0bffff04ff17ffff04ff8202bfffff04"
"ff15ff8080808080808080ff0180ff80808080ffff01ff04ff2fffff01ff80ff"
"80808080ff0180ffff04ffff01ffffff3f02ff04ff0101ffff822710ff02ff02"
"ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff"
"0bff2cff1480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2a"
"ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff"
"03ff17ffff01ff04ffff04ff10ffff04ffff0bff81a7ffff02ff3effff04ff02"
"ffff04ffff04ff2fffff04ffff04ff05ffff04ffff05ffff14ffff12ff47ff0b"
"80ff128080ffff04ffff04ff05ff8080ff80808080ff808080ff8080808080ff"
"808080ffff02ff16ffff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff"
"2fff8080808080808080ff8080ff0180ffff0bff2affff0bff2cff1880ffff0b"
"ff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff"
"02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff808080"
"8080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02"
"ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ff"
"ff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01ffa07faa3253bf"
"ddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa02268ab"
"a6ee7b6a26b6f8abc2c00938e413a8aa128d1ba1bdc4a9bfb84e62aa2da0eff0"
"7522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff"
"04ffff01a003d5d19244dfe1fffc3de5f9e1ded13bd5fb47340e798c9d042d7c"
"d9a101ca09ffff04ffff0182012cff0180808080ffff04ffff01ff02ffff01ff"
"02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1e"
"ffff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01"
"ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05"
"ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff"
"17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff"
"0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff"
"04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff01"
"8080ffff04ffff01b0b4652a5c069d8498cd4b0dd1bd5198176078b466264651"
"7d51e28344b2e714d8cefb781e4dbb5d4cc7e0cba7b4b50d77ff018080ff0180"
"80808080ff018080808080ff01808080ffffa02268aba6ee7b6a26b6f8abc2c0"
"0938e413a8aa128d1ba1bdc4a9bfb84e62aa2dffa012b92f169ae6991c481b3f"
"43e17b821cd8aec6bb1614bbe9042e6f9c734979aeff0180ff01ffffffff80ff"
"ff01ffff81f6ff80ffffff64ffa0bae24162efbd568f89bc7a340798a6118df0"
"189eb9e3f8697bcea27af99f8f798080ff8080ffff33ffa0bae24162efbd568f"
"89bc7a340798a6118df0189eb9e3f8697bcea27af99f8f79ff01ffffa0bae241"
"62efbd568f89bc7a340798a6118df0189eb9e3f8697bcea27af99f8f798080ff"
"ff3fffa01791f8e6d86d66bca42867c0be163909c07c46dfb3bb6660f1fe8b6b"
"0cb952e48080ff808080808096a0c4136217c2c2cc4eb525ba7aa14d166d0353"
"9e5d1ce733a28592fc4adf52a92f873e96ac8a3dfc02964f102dca750768cade"
"7acbf0055da31d080b9894768971906509062e2255634f14e4e6f7acd68b7c40"
"d1526e5ca0b489b7afd60762",
)