mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-20 16:08:51 +03:00
Merge main into checkpoint/main_from_release_1.6.0_12fcaf0e982590efcf26a954c67cd760035d9e6e
This commit is contained in:
commit
aacad8fecf
@ -21,6 +21,8 @@ keychain_commands = [
|
||||
"get_key_for_fingerprint",
|
||||
"get_key",
|
||||
"get_keys",
|
||||
"set_label",
|
||||
"delete_label",
|
||||
]
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -32,6 +34,12 @@ KEYCHAIN_ERR_KEY_NOT_FOUND = "key not found"
|
||||
KEYCHAIN_ERR_MALFORMED_REQUEST = "malformed request"
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class EmptyResponse(Streamable):
|
||||
pass
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class GetKeyResponse(Streamable):
|
||||
@ -63,6 +71,27 @@ class GetKeysRequest(Streamable):
|
||||
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:
|
||||
"""
|
||||
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)
|
||||
elif command == "get_keys":
|
||||
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 {}
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@ -123,6 +156,8 @@ class KeychainServer:
|
||||
return {"success": False, "error": KEYCHAIN_ERR_LOCKED}
|
||||
|
||||
mnemonic = request.get("mnemonic", None)
|
||||
label = request.get("label", None)
|
||||
|
||||
if mnemonic is None:
|
||||
return {
|
||||
"success": False,
|
||||
@ -131,7 +166,7 @@ class KeychainServer:
|
||||
}
|
||||
|
||||
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:
|
||||
return {
|
||||
"success": False,
|
||||
|
@ -1,3 +1,4 @@
|
||||
from __future__ import annotations
|
||||
import io
|
||||
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.serialize import sexp_from_stream, sexp_to_stream
|
||||
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.util.hash import std_hash
|
||||
@ -32,11 +32,18 @@ class Program(SExp):
|
||||
sexp_to_stream(self, f)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, blob: bytes) -> "Program":
|
||||
f = io.BytesIO(blob)
|
||||
result = cls.parse(f) # noqa
|
||||
assert f.read() == b""
|
||||
return result
|
||||
def from_bytes(cls, blob: bytes) -> Program:
|
||||
# this runs the program "1", which just returns the first arugment.
|
||||
# the first argument is the buffer we want to parse. This effectively
|
||||
# leverages the rust parser and LazyNode, making it a lot faster to
|
||||
# parse serialized programs into a python compatible structure
|
||||
cost, ret = run_chia_program(
|
||||
b"\x01",
|
||||
blob,
|
||||
50,
|
||||
0,
|
||||
)
|
||||
return Program.to(ret)
|
||||
|
||||
@classmethod
|
||||
def fromhex(cls, hexstr: str) -> "Program":
|
||||
@ -136,11 +143,37 @@ class Program(SExp):
|
||||
fixed_args = [4, (1, arg), fixed_args]
|
||||
return Program.to([2, (1, self), fixed_args])
|
||||
|
||||
def uncurry(self) -> Tuple["Program", "Program"]:
|
||||
r = uncurry(self)
|
||||
if r is None:
|
||||
def uncurry(self) -> Tuple[Program, Program]:
|
||||
def match(o: SExp, expected: bytes) -> 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 r
|
||||
|
||||
def as_int(self) -> int:
|
||||
return int_from_bytes(self.as_atom())
|
||||
|
@ -192,6 +192,7 @@ class KeyDataSecrets(Streamable):
|
||||
class KeyData(Streamable):
|
||||
fingerprint: uint32
|
||||
public_key: G1Element
|
||||
label: Optional[str]
|
||||
secrets: Optional[KeyDataSecrets]
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@ -203,21 +204,22 @@ class KeyData(Streamable):
|
||||
raise KeychainKeyDataMismatch("fingerprint")
|
||||
|
||||
@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))
|
||||
return cls(
|
||||
fingerprint=private_key.get_g1().get_fingerprint(),
|
||||
public_key=private_key.get_g1(),
|
||||
label=label,
|
||||
secrets=KeyDataSecrets.from_mnemonic(mnemonic),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_entropy(cls, entropy: bytes) -> KeyData:
|
||||
return cls.from_mnemonic(bytes_to_mnemonic(entropy))
|
||||
def from_entropy(cls, entropy: bytes, label: Optional[str] = None) -> KeyData:
|
||||
return cls.from_mnemonic(bytes_to_mnemonic(entropy), label)
|
||||
|
||||
@classmethod
|
||||
def generate(cls) -> KeyData:
|
||||
return cls.from_mnemonic(generate_mnemonic())
|
||||
def generate(cls, label: Optional[str] = None) -> KeyData:
|
||||
return cls.from_mnemonic(generate_mnemonic(), label)
|
||||
|
||||
@property
|
||||
def mnemonic(self) -> List[str]:
|
||||
@ -279,11 +281,13 @@ class Keychain:
|
||||
str_bytes = bytes.fromhex(read_str)
|
||||
|
||||
public_key = G1Element.from_bytes(str_bytes[: G1Element.SIZE])
|
||||
fingerprint = public_key.get_fingerprint()
|
||||
entropy = str_bytes[G1Element.SIZE : G1Element.SIZE + 32]
|
||||
|
||||
return KeyData(
|
||||
fingerprint=public_key.get_fingerprint(),
|
||||
fingerprint=fingerprint,
|
||||
public_key=public_key,
|
||||
label=self.keyring_wrapper.get_label(fingerprint),
|
||||
secrets=KeyDataSecrets.from_entropy(entropy) if include_secrets else None,
|
||||
)
|
||||
|
||||
@ -299,7 +303,7 @@ class Keychain:
|
||||
except KeychainUserNotFound:
|
||||
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
|
||||
keychain itself will store the public key, and the entropy bytes,
|
||||
@ -315,13 +319,37 @@ class Keychain:
|
||||
# Prevents duplicate add
|
||||
raise KeychainFingerprintExists(fingerprint)
|
||||
|
||||
self.keyring_wrapper.set_passphrase(
|
||||
self.service,
|
||||
get_private_key_user(self.user, index),
|
||||
bytes(key.get_g1()).hex() + entropy.hex(),
|
||||
)
|
||||
# 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.service,
|
||||
get_private_key_user(self.user, index),
|
||||
bytes(key.get_g1()).hex() + entropy.hex(),
|
||||
)
|
||||
except Exception:
|
||||
if label is not None:
|
||||
self.keyring_wrapper.delete_label(fingerprint)
|
||||
raise
|
||||
|
||||
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]]:
|
||||
"""
|
||||
Returns the first key in the keychain that has one of the passed in passphrases.
|
||||
|
@ -1,5 +1,5 @@
|
||||
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.program import Program
|
||||
@ -19,12 +19,11 @@ from chia.wallet.puzzles.cat_loader import CAT_MOD
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CATOuterPuzzle:
|
||||
_match: Any
|
||||
_asset_id: Any
|
||||
_construct: Any
|
||||
_solve: Any
|
||||
_get_inner_puzzle: Any
|
||||
_get_inner_solution: Any
|
||||
_match: Callable[[Program], Optional[PuzzleInfo]]
|
||||
_construct: Callable[[PuzzleInfo, Program], Program]
|
||||
_solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
|
||||
_get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
_get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
|
||||
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
|
||||
args = match_cat_puzzle(*puzzle.uncurry())
|
||||
@ -45,16 +44,18 @@ class CATOuterPuzzle:
|
||||
if args is None:
|
||||
raise ValueError("This driver is not for the specified puzzle reveal")
|
||||
_, _, inner_puzzle = args
|
||||
if constructor.also() is not None:
|
||||
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(also, inner_puzzle)
|
||||
return deep_inner_puzzle
|
||||
else:
|
||||
return inner_puzzle
|
||||
|
||||
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
|
||||
my_inner_solution: Program = solution.first()
|
||||
if constructor.also():
|
||||
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution)
|
||||
also = constructor.also()
|
||||
if also:
|
||||
deep_inner_solution: Optional[Program] = self._get_inner_solution(also, my_inner_solution)
|
||||
return deep_inner_solution
|
||||
else:
|
||||
return my_inner_solution
|
||||
@ -63,8 +64,9 @@ class CATOuterPuzzle:
|
||||
return bytes32(constructor["tail"])
|
||||
|
||||
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
|
||||
if constructor.also() is not None:
|
||||
inner_puzzle = self._construct(constructor.also(), inner_puzzle)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
inner_puzzle = self._construct(also, 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:
|
||||
@ -91,9 +93,10 @@ class CATOuterPuzzle:
|
||||
target_coin = coin
|
||||
parent_spend: CoinSpend = CoinSpend.from_bytes(spend_prog.as_python())
|
||||
parent_coin: Coin = parent_spend.coin
|
||||
if constructor.also() is not None:
|
||||
puzzle = self._construct(constructor.also(), puzzle)
|
||||
solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
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())
|
||||
assert args is not None
|
||||
_, _, parent_inner_puzzle = args
|
||||
|
@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, List, Optional, Tuple
|
||||
from typing import Callable, List, Optional, Tuple
|
||||
|
||||
from clvm_tools.binutils import disassemble
|
||||
|
||||
@ -31,12 +31,11 @@ def solution_for_metadata_layer(amount: uint64, inner_solution: Program) -> Prog
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MetadataOuterPuzzle:
|
||||
_match: Any
|
||||
_asset_id: Any
|
||||
_construct: Any
|
||||
_solve: Any
|
||||
_get_inner_puzzle: Any
|
||||
_get_inner_solution: Any
|
||||
_match: Callable[[Program], Optional[PuzzleInfo]]
|
||||
_construct: Callable[[PuzzleInfo, Program], Program]
|
||||
_solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
|
||||
_get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
_get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
|
||||
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
|
||||
matched, curried_args = match_metadata_layer_puzzle(puzzle)
|
||||
@ -59,16 +58,18 @@ class MetadataOuterPuzzle:
|
||||
return bytes32(constructor["updater_hash"])
|
||||
|
||||
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
|
||||
if constructor.also() is not None:
|
||||
inner_puzzle = self._construct(constructor.also(), inner_puzzle)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
inner_puzzle = self._construct(also, 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]:
|
||||
matched, curried_args = match_metadata_layer_puzzle(puzzle_reveal)
|
||||
if matched:
|
||||
_, _, _, inner_puzzle = curried_args
|
||||
if constructor.also() is not None:
|
||||
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(also, inner_puzzle)
|
||||
return deep_inner_puzzle
|
||||
else:
|
||||
return inner_puzzle
|
||||
@ -77,8 +78,9 @@ class MetadataOuterPuzzle:
|
||||
|
||||
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
|
||||
my_inner_solution: Program = solution.first()
|
||||
if constructor.also():
|
||||
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution)
|
||||
also = constructor.also()
|
||||
if also:
|
||||
deep_inner_solution: Optional[Program] = self._get_inner_solution(also, my_inner_solution)
|
||||
return deep_inner_solution
|
||||
else:
|
||||
return my_inner_solution
|
||||
@ -86,8 +88,9 @@ class MetadataOuterPuzzle:
|
||||
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
|
||||
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]))
|
||||
if constructor.also() is not None:
|
||||
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
inner_solution = self._solve(also, solver, inner_puzzle, inner_solution)
|
||||
return solution_for_metadata_layer(
|
||||
uint64(coin.amount),
|
||||
inner_solution,
|
||||
|
@ -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_METADATA_UPDATER = load_clvm("nft_metadata_updater_default.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")
|
||||
STANDARD_PUZZLE_MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm")
|
||||
|
||||
@ -39,33 +40,37 @@ def create_nft_layer_puzzle_with_curry_params(
|
||||
|
||||
|
||||
def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Program) -> Program:
|
||||
log.debug(
|
||||
"Creating full NFT puzzle with inner puzzle: \n%r\n%r",
|
||||
singleton_id,
|
||||
inner_puzzle.get_tree_hash(),
|
||||
)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.debug(
|
||||
"Creating full NFT puzzle with inner puzzle: \n%r\n%r",
|
||||
singleton_id,
|
||||
inner_puzzle.get_tree_hash(),
|
||||
)
|
||||
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
|
||||
|
||||
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, inner_puzzle)
|
||||
log.debug("Created NFT full puzzle with inner: %s", full_puzzle.get_tree_hash())
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.debug("Created NFT full puzzle with inner: %s", full_puzzle.get_tree_hash())
|
||||
return full_puzzle
|
||||
|
||||
|
||||
def create_full_puzzle(
|
||||
singleton_id: bytes32, metadata: Program, metadata_updater_puzhash: bytes32, inner_puzzle: Program
|
||||
) -> Program:
|
||||
log.debug(
|
||||
"Creating full NFT puzzle with: \n%r\n%r\n%r\n%r",
|
||||
singleton_id,
|
||||
metadata.get_tree_hash(),
|
||||
metadata_updater_puzhash,
|
||||
inner_puzzle.get_tree_hash(),
|
||||
)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.debug(
|
||||
"Creating full NFT puzzle with: \n%r\n%r\n%r\n%r",
|
||||
singleton_id,
|
||||
metadata.get_tree_hash(),
|
||||
metadata_updater_puzhash,
|
||||
inner_puzzle.get_tree_hash(),
|
||||
)
|
||||
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
|
||||
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)
|
||||
log.debug("Created NFT full puzzle: %s", full_puzzle.get_tree_hash())
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.debug("Created NFT full puzzle: %s", full_puzzle.get_tree_hash())
|
||||
return full_puzzle
|
||||
|
||||
|
||||
@ -171,7 +176,7 @@ def construct_ownership_layer(
|
||||
inner_puzzle: Program,
|
||||
) -> Program:
|
||||
return NFT_OWNERSHIP_LAYER.curry(
|
||||
NFT_OWNERSHIP_LAYER.get_tree_hash(),
|
||||
NFT_OWNERSHIP_LAYER_HASH,
|
||||
current_owner,
|
||||
transfer_program,
|
||||
inner_puzzle,
|
||||
|
@ -31,7 +31,7 @@ from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
|
||||
calculate_synthetic_secret_key,
|
||||
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.util.compute_memos import compute_memos
|
||||
from chia.wallet.util.debug_spend_bundle import disassemble
|
||||
@ -168,8 +168,11 @@ class NFTWallet:
|
||||
|
||||
uncurried_nft = UncurriedNFT.uncurry(*puzzle.uncurry())
|
||||
assert uncurried_nft is not None
|
||||
self.log.info(
|
||||
f"found the info for NFT coin {coin_name} {uncurried_nft.inner_puzzle} {uncurried_nft.singleton_struct}"
|
||||
self.log.debug(
|
||||
"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
|
||||
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)
|
||||
self.log.debug("Record for %s is: %s", p2_puzzle_hash, derivation_record)
|
||||
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
|
||||
p2_puzzle = puzzle_for_pk(derivation_record.pubkey)
|
||||
if uncurried_nft.supports_did:
|
||||
@ -199,14 +202,15 @@ class NFTWallet:
|
||||
"Created NFT full puzzle with inner: %s",
|
||||
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():
|
||||
self.log.debug(
|
||||
"Comparing addition: %s with %s, amount: %s ",
|
||||
new_coin.puzzle_hash,
|
||||
child_puzzle.get_tree_hash(),
|
||||
child_puzzle_hash,
|
||||
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
|
||||
break
|
||||
else:
|
||||
@ -322,7 +326,7 @@ class NFTWallet:
|
||||
origin = coins.copy().pop()
|
||||
genesis_launcher_puz = nft_puzzles.LAUNCHER_PUZZLE
|
||||
# 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)
|
||||
|
||||
p2_inner_puzzle = await self.standard_wallet.get_new_puzzle()
|
||||
@ -346,9 +350,10 @@ class NFTWallet:
|
||||
eve_fullpuz = nft_puzzles.create_full_puzzle(
|
||||
launcher_coin.name(), metadata, NFT_METADATA_UPDATER.get_tree_hash(), inner_puzzle
|
||||
)
|
||||
eve_fullpuz_hash = eve_fullpuz.get_tree_hash()
|
||||
# launcher announcement
|
||||
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))
|
||||
|
||||
self.log.debug(
|
||||
@ -357,7 +362,7 @@ class NFTWallet:
|
||||
# store the launcher transaction in the wallet state
|
||||
tx_record: Optional[TransactionRecord] = await self.standard_wallet.generate_signed_transaction(
|
||||
uint64(amount),
|
||||
genesis_launcher_puz.get_tree_hash(),
|
||||
nft_puzzles.LAUNCHER_PUZZLE_HASH,
|
||||
fee,
|
||||
origin.name(),
|
||||
coins,
|
||||
@ -365,13 +370,13 @@ class NFTWallet:
|
||||
False,
|
||||
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_cs = CoinSpend(launcher_coin, genesis_launcher_puz, genesis_launcher_solution)
|
||||
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:
|
||||
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
|
||||
if amount > 0:
|
||||
settlement_ph: bytes32 = (
|
||||
OFFER_MOD.get_tree_hash()
|
||||
if asset is None
|
||||
else construct_puzzle(driver_dict[asset], OFFER_MOD).get_tree_hash()
|
||||
OFFER_MOD_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])
|
||||
|
||||
@ -863,9 +866,10 @@ class NFTWallet:
|
||||
for asset, payments in royalty_payments.items():
|
||||
if asset is None: # xch offer
|
||||
offer_puzzle = OFFER_MOD
|
||||
royalty_ph = OFFER_MOD_HASH
|
||||
else:
|
||||
offer_puzzle = construct_puzzle(driver_dict[asset], OFFER_MOD)
|
||||
royalty_ph = offer_puzzle.get_tree_hash()
|
||||
royalty_ph = offer_puzzle.get_tree_hash()
|
||||
announcements_to_assert.extend(
|
||||
[
|
||||
Announcement(royalty_ph, Program.to((launcher_id, [p.as_condition_args()])).get_tree_hash())
|
||||
@ -930,9 +934,10 @@ class NFTWallet:
|
||||
inner_royalty_sol = Program.to([(launcher_id, [payment.as_condition_args()])])
|
||||
if asset is None: # xch offer
|
||||
offer_puzzle = OFFER_MOD
|
||||
royalty_ph = OFFER_MOD_HASH
|
||||
else:
|
||||
offer_puzzle = construct_puzzle(driver_dict[asset], OFFER_MOD)
|
||||
royalty_ph = offer_puzzle.get_tree_hash()
|
||||
royalty_ph = offer_puzzle.get_tree_hash()
|
||||
royalty_coin: Coin
|
||||
for tx in txs:
|
||||
if tx.spend_bundle is not None:
|
||||
|
@ -1,5 +1,5 @@
|
||||
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
|
||||
|
||||
@ -30,12 +30,11 @@ def solution_for_ownership_layer(inner_solution: Program) -> Program:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OwnershipOuterPuzzle:
|
||||
_match: Any
|
||||
_asset_id: Any
|
||||
_construct: Any
|
||||
_solve: Any
|
||||
_get_inner_puzzle: Any
|
||||
_get_inner_solution: Any
|
||||
_match: Callable[[Program], Optional[PuzzleInfo]]
|
||||
_construct: Callable[[PuzzleInfo, Program], Program]
|
||||
_solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
|
||||
_get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
_get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
|
||||
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
|
||||
matched, curried_args = match_ownership_layer_puzzle(puzzle)
|
||||
@ -61,8 +60,9 @@ class OwnershipOuterPuzzle:
|
||||
return None
|
||||
|
||||
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
|
||||
if constructor.also() is not None:
|
||||
inner_puzzle = self._construct(constructor.also(), inner_puzzle)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
inner_puzzle = self._construct(also, inner_puzzle)
|
||||
transfer_program_info: Union[PuzzleInfo, Program] = constructor["transfer_program"]
|
||||
if isinstance(transfer_program_info, Program):
|
||||
transfer_program: Program = transfer_program_info
|
||||
@ -74,8 +74,9 @@ class OwnershipOuterPuzzle:
|
||||
matched, curried_args = match_ownership_layer_puzzle(puzzle_reveal)
|
||||
if matched:
|
||||
_, _, _, inner_puzzle = curried_args
|
||||
if constructor.also() is not None:
|
||||
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(also, inner_puzzle)
|
||||
return deep_inner_puzzle
|
||||
else:
|
||||
return inner_puzzle
|
||||
@ -84,13 +85,15 @@ class OwnershipOuterPuzzle:
|
||||
|
||||
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
|
||||
my_inner_solution: Program = solution.first()
|
||||
if constructor.also():
|
||||
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution)
|
||||
also = constructor.also()
|
||||
if also:
|
||||
deep_inner_solution: Optional[Program] = self._get_inner_solution(also, my_inner_solution)
|
||||
return deep_inner_solution
|
||||
else:
|
||||
return my_inner_solution
|
||||
|
||||
def solve(self, constructor: PuzzleInfo, solver: Solver, inner_puzzle: Program, inner_solution: Program) -> Program:
|
||||
if constructor.also() is not None:
|
||||
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
inner_solution = self._solve(also, solver, inner_puzzle, inner_solution)
|
||||
return solution_for_ownership_layer(inner_solution)
|
||||
|
@ -1,5 +1,5 @@
|
||||
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.program import Program
|
||||
@ -18,12 +18,11 @@ from chia.wallet.puzzles.singleton_top_layer_v1_1 import (
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SingletonOuterPuzzle:
|
||||
_match: Any
|
||||
_asset_id: Any
|
||||
_construct: Any
|
||||
_solve: Any
|
||||
_get_inner_puzzle: Any
|
||||
_get_inner_solution: Any
|
||||
_match: Callable[[Program], Optional[PuzzleInfo]]
|
||||
_construct: Callable[[PuzzleInfo, Program], Program]
|
||||
_solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
|
||||
_get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
_get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
|
||||
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
|
||||
matched, curried_args = match_singleton_puzzle(puzzle)
|
||||
@ -45,8 +44,9 @@ class SingletonOuterPuzzle:
|
||||
return bytes32(constructor["launcher_id"])
|
||||
|
||||
def construct(self, constructor: PuzzleInfo, inner_puzzle: Program) -> Program:
|
||||
if constructor.also() is not None:
|
||||
inner_puzzle = self._construct(constructor.also(), inner_puzzle)
|
||||
also = constructor.also()
|
||||
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
|
||||
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)
|
||||
if matched:
|
||||
_, inner_puzzle = curried_args
|
||||
if constructor.also() is not None:
|
||||
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(constructor.also(), inner_puzzle)
|
||||
also = constructor.also()
|
||||
if also is not None:
|
||||
deep_inner_puzzle: Optional[Program] = self._get_inner_puzzle(also, inner_puzzle)
|
||||
return deep_inner_puzzle
|
||||
else:
|
||||
return inner_puzzle
|
||||
@ -64,8 +65,9 @@ class SingletonOuterPuzzle:
|
||||
|
||||
def get_inner_solution(self, constructor: PuzzleInfo, solution: Program) -> Optional[Program]:
|
||||
my_inner_solution: Program = solution.at("rrf")
|
||||
if constructor.also():
|
||||
deep_inner_solution: Optional[Program] = self._get_inner_solution(constructor.also(), my_inner_solution)
|
||||
also = constructor.also()
|
||||
if also:
|
||||
deep_inner_solution: Optional[Program] = self._get_inner_solution(also, my_inner_solution)
|
||||
return deep_inner_solution
|
||||
else:
|
||||
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]))
|
||||
parent_spend: CoinSpend = CoinSpend.from_bytes(solver["parent_spend"])
|
||||
parent_coin: Coin = parent_spend.coin
|
||||
if constructor.also() is not None:
|
||||
inner_solution = self._solve(constructor.also(), solver, inner_puzzle, inner_solution)
|
||||
also = constructor.also()
|
||||
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())
|
||||
assert matched
|
||||
_, parent_inner_puzzle = curried_args
|
||||
|
@ -1,5 +1,5 @@
|
||||
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.sized_bytes import bytes32
|
||||
@ -39,12 +39,11 @@ def solution_for_transfer_program(
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TransferProgramPuzzle:
|
||||
_match: Any
|
||||
_asset_id: Any
|
||||
_construct: Any
|
||||
_solve: Any
|
||||
_get_inner_puzzle: Any
|
||||
_get_inner_solution: Any
|
||||
_match: Callable[[Program], Optional[PuzzleInfo]]
|
||||
_construct: Callable[[PuzzleInfo, Program], Program]
|
||||
_solve: Callable[[PuzzleInfo, Solver, Program, Program], Program]
|
||||
_get_inner_puzzle: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
_get_inner_solution: Callable[[PuzzleInfo, Program], Optional[Program]]
|
||||
|
||||
def match(self, puzzle: Program) -> Optional[PuzzleInfo]:
|
||||
matched, curried_args = match_transfer_program_puzzle(puzzle)
|
||||
|
@ -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)
|
||||
|
||||
|
||||
def create_asset_id(constructor: PuzzleInfo) -> bytes32:
|
||||
return driver_lookup[AssetType(constructor.type())].asset_id(constructor) # type: ignore
|
||||
def create_asset_id(constructor: PuzzleInfo) -> Optional[bytes32]:
|
||||
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] = {
|
||||
AssetType.CAT: CATOuterPuzzle(*function_args),
|
||||
|
@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
@ -51,7 +53,7 @@ class PuzzleInfo:
|
||||
def type(self) -> str:
|
||||
return str(self.info["type"])
|
||||
|
||||
def also(self) -> Optional["PuzzleInfo"]:
|
||||
def also(self) -> Optional[PuzzleInfo]:
|
||||
if "also" in self.info:
|
||||
return PuzzleInfo(self.info["also"])
|
||||
else:
|
||||
|
@ -199,7 +199,7 @@ class TradeManager:
|
||||
|
||||
coins_of_interest = []
|
||||
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 = {}
|
||||
coin_records = await self.wallet_state_manager.coin_store.get_multiple_coin_records(coins_of_interest)
|
||||
|
@ -31,6 +31,7 @@ from chia.wallet.util.puzzle_compression import (
|
||||
)
|
||||
|
||||
OFFER_MOD = load_clvm("settlement_payments.clvm")
|
||||
OFFER_MOD_HASH = OFFER_MOD.get_tree_hash()
|
||||
ZERO_32 = bytes32([0] * 32)
|
||||
|
||||
|
||||
@ -69,7 +70,7 @@ class Offer:
|
||||
|
||||
@staticmethod
|
||||
def ph() -> bytes32:
|
||||
return OFFER_MOD.get_tree_hash()
|
||||
return OFFER_MOD_HASH
|
||||
|
||||
@staticmethod
|
||||
def notarize_payments(
|
||||
@ -102,7 +103,7 @@ class Offer:
|
||||
raise ValueError("Cannot calculate announcements without driver of requested item")
|
||||
settlement_ph: bytes32 = construct_puzzle(driver_dict[asset_id], OFFER_MOD).get_tree_hash()
|
||||
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()
|
||||
announcements.append(Announcement(settlement_ph, msg))
|
||||
@ -110,13 +111,6 @@ class Offer:
|
||||
return announcements
|
||||
|
||||
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
|
||||
for payments in self.requested_payments.values():
|
||||
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]]:
|
||||
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:
|
||||
coins_for_this_spend: List[Coin] = []
|
||||
|
||||
|
@ -2,7 +2,6 @@ from unittest import TestCase
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from clvm.EvalError import EvalError
|
||||
from clvm_tools.curry import uncurry
|
||||
from clvm.operators import KEYWORD_TO_ATOM
|
||||
from clvm_tools.binutils import assemble, disassemble
|
||||
|
||||
@ -49,7 +48,7 @@ def check_idempotency(f, *args):
|
||||
curried = prg.curry(*args)
|
||||
|
||||
r = disassemble(curried)
|
||||
f_0, args_0 = uncurry(curried)
|
||||
f_0, args_0 = curried.uncurry()
|
||||
|
||||
assert disassemble(f_0) == disassemble(f)
|
||||
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
|
||||
actual_disassembly = check_idempotency(f, args)
|
||||
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))
|
||||
|
@ -7,6 +7,7 @@ import pytest
|
||||
from dataclasses import replace
|
||||
from typing import Any, Dict, List, Type
|
||||
|
||||
from chia.daemon.keychain_server import DeleteLabelRequest, SetLabelRequest
|
||||
from chia.server.outbound_message import NodeType
|
||||
from chia.types.peer_info import PeerInfo
|
||||
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()}
|
||||
|
||||
|
||||
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:
|
||||
# Expect: JSON response
|
||||
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)
|
||||
|
||||
|
||||
@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
|
||||
async def test_get_key(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`
|
||||
await ws.send_str(create_payload("get_keys", {"include_secrets": True}, "test", "daemon"))
|
||||
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)
|
||||
|
@ -13,6 +13,8 @@ from chia.util.errors import (
|
||||
KeychainKeyDataMismatch,
|
||||
KeychainSecretsMissing,
|
||||
KeychainFingerprintNotFound,
|
||||
KeychainLabelExists,
|
||||
KeychainLabelInvalid,
|
||||
)
|
||||
from chia.util.ints import uint32
|
||||
from chia.util.keychain import (
|
||||
@ -106,6 +108,41 @@ class TestKeychain(unittest.TestCase):
|
||||
kc.add_private_key(bytes_to_mnemonic(token_bytes(32)))
|
||||
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()
|
||||
def test_bip39_eip2333_test_vector(self):
|
||||
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
|
||||
|
||||
|
||||
def test_key_data_generate() -> None:
|
||||
key_data = KeyData.generate()
|
||||
@pytest.mark.parametrize("label", [None, "key"])
|
||||
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.entropy == bytes_from_mnemonic(key_data.mnemonic_str())
|
||||
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.label == label
|
||||
|
||||
|
||||
@pytest.mark.parametrize("label", [None, "key"])
|
||||
@pytest.mark.parametrize(
|
||||
"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:
|
||||
key_data = from_method(input_data)
|
||||
def test_key_data_creation(input_data: object, from_method: Callable[..., KeyData], label: Optional[str]) -> None:
|
||||
key_data = from_method(input_data, label)
|
||||
assert key_data.fingerprint == fingerprint
|
||||
assert key_data.public_key == public_key
|
||||
assert key_data.mnemonic == mnemonic.split()
|
||||
assert key_data.mnemonic_str() == mnemonic
|
||||
assert key_data.entropy == entropy
|
||||
assert key_data.private_key == private_key
|
||||
assert key_data.label == label
|
||||
|
||||
|
||||
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
|
||||
|
||||
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(
|
||||
"input_data, data_type",
|
||||
[
|
||||
((fingerprint, G1Element(), KeyDataSecrets(mnemonic.split(), entropy, private_key)), "public_key"),
|
||||
((fingerprint, G1Element(), None), "fingerprint"),
|
||||
((fingerprint, G1Element(), None, KeyDataSecrets(mnemonic.split(), entropy, private_key)), "public_key"),
|
||||
((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):
|
||||
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
|
||||
# Should be empty again
|
||||
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()
|
||||
|
@ -9,14 +9,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.util.ints import uint64
|
||||
from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle
|
||||
from chia.wallet.outer_puzzles import (
|
||||
construct_puzzle,
|
||||
create_asset_id,
|
||||
get_inner_puzzle,
|
||||
get_inner_solution,
|
||||
match_puzzle,
|
||||
solve_puzzle,
|
||||
)
|
||||
from chia.wallet.outer_puzzles import construct_puzzle, get_inner_puzzle, get_inner_solution, match_puzzle, solve_puzzle
|
||||
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
|
||||
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 construct_puzzle(cat_driver, ACS) == double_cat_puzzle
|
||||
assert get_inner_puzzle(cat_driver, double_cat_puzzle) == ACS
|
||||
assert create_asset_id(cat_driver) == tail
|
||||
|
||||
# Set up for solve
|
||||
parent_coin = Coin(tail, double_cat_puzzle.get_tree_hash(), uint64(100))
|
||||
|
@ -7,14 +7,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.ints import uint16
|
||||
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.outer_puzzles import (
|
||||
construct_puzzle,
|
||||
create_asset_id,
|
||||
get_inner_puzzle,
|
||||
get_inner_solution,
|
||||
match_puzzle,
|
||||
solve_puzzle,
|
||||
)
|
||||
from chia.wallet.outer_puzzles import construct_puzzle, get_inner_puzzle, get_inner_solution, match_puzzle, solve_puzzle
|
||||
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_default, ACS) == ownership_puzzle_default
|
||||
assert get_inner_puzzle(ownership_driver, ownership_puzzle) == ACS
|
||||
assert create_asset_id(ownership_driver) is None
|
||||
|
||||
# Set up for solve
|
||||
inner_solution = Program.to(
|
||||
|
226
tests/wallet/test_offer_parsing_performance.py
Normal file
226
tests/wallet/test_offer_parsing_performance.py
Normal 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",
|
||||
)
|
Loading…
Reference in New Issue
Block a user