mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-20 16:08:51 +03:00
76ffd4c2df
* mint from did * fix did puzzle announcements * clean up params and test * mypy * add RPC endpoint * use rpc client for test * test create spends in loop * transfer to target * fix lineage proof for transfers * isolate block timing issue in test * DID lineage proof * test transfer to targets * add exclude list to select_coins RPC * coin json * move tests to did wallet * fix typing royalties * cleanup tests * DID_HRP to AddressType * clvm compilation * improve checks in rpc api * move mint_from_did to nft wallet * reduce number of blocks in tests * forgot to remove mint function from did_wallet.py * mypy * validate NFT metadata and edition numbers in test * fix puzzle announcements, refactor and comments * make tests work with new RPC server settings * create puzzle assertions for eve and transfer spends * rename vars for mint numbering * rename more edition -> mint variables * remove DID owner from eve spend * make coin announcement from did spend * make transfer from eve spend * corrections for address options in RPC * simplify intermediate launchers * announcments for xch xpend * re-order announcements for xch spend * test with fewer blocks * skip off chain metadata test with IPFS * add bulk mint puzzle and methods to nft wallet * add rpc endpoints * add tests * don't skip off chain metadata test * fix default case for xch_coin_list
241 lines
8.0 KiB
Python
241 lines
8.0 KiB
Python
from chia.types.blockchain_format.sized_bytes import bytes32
|
|
from chia.types.blockchain_format.program import Program
|
|
from typing import List, Optional, Tuple, Iterator, Dict
|
|
from blspy import G1Element
|
|
from chia.types.blockchain_format.coin import Coin
|
|
from chia.types.coin_spend import CoinSpend
|
|
from chia.util.ints import uint64
|
|
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
|
|
from chia.types.condition_opcodes import ConditionOpcode
|
|
from chia.wallet.util.curry_and_treehash import calculate_hash_of_quoted_mod_hash, curry_and_treehash
|
|
|
|
SINGLETON_TOP_LAYER_MOD = load_clvm_maybe_recompile("singleton_top_layer_v1_1.clvm")
|
|
SINGLETON_TOP_LAYER_MOD_HASH = SINGLETON_TOP_LAYER_MOD.get_tree_hash()
|
|
SINGLETON_TOP_LAYER_MOD_HASH_QUOTED = calculate_hash_of_quoted_mod_hash(SINGLETON_TOP_LAYER_MOD_HASH)
|
|
LAUNCHER_PUZZLE = load_clvm_maybe_recompile("singleton_launcher.clvm")
|
|
DID_INNERPUZ_MOD = load_clvm_maybe_recompile("did_innerpuz.clvm")
|
|
LAUNCHER_PUZZLE_HASH = LAUNCHER_PUZZLE.get_tree_hash()
|
|
DID_INNERPUZ_MOD_HASH = DID_INNERPUZ_MOD.get_tree_hash()
|
|
INTERMEDIATE_LAUNCHER_MOD = load_clvm_maybe_recompile("nft_intermediate_launcher.clvm")
|
|
|
|
|
|
def create_innerpuz(
|
|
p2_puzzle: Program,
|
|
recovery_list: List[bytes32],
|
|
num_of_backup_ids_needed: uint64,
|
|
launcher_id: bytes32,
|
|
metadata: Program = Program.to([]),
|
|
) -> Program:
|
|
"""
|
|
Create DID inner puzzle
|
|
:param p2_puzzle: Standard P2 puzzle
|
|
:param recovery_list: A list of DIDs used for the recovery
|
|
:param num_of_backup_ids_needed: Need how many DIDs for the recovery
|
|
:param launcher_id: ID of the launch coin
|
|
:param metadata: DID customized metadata
|
|
:return: DID inner puzzle
|
|
"""
|
|
backup_ids_hash = Program(Program.to(recovery_list)).get_tree_hash()
|
|
singleton_struct = Program.to((SINGLETON_TOP_LAYER_MOD_HASH, (launcher_id, LAUNCHER_PUZZLE_HASH)))
|
|
return DID_INNERPUZ_MOD.curry(p2_puzzle, backup_ids_hash, num_of_backup_ids_needed, singleton_struct, metadata)
|
|
|
|
|
|
def get_inner_puzhash_by_p2(
|
|
p2_puzhash: bytes32,
|
|
recovery_list: List[bytes32],
|
|
num_of_backup_ids_needed: uint64,
|
|
launcher_id: bytes32,
|
|
metadata: Program = Program.to([]),
|
|
) -> bytes32:
|
|
"""
|
|
Calculate DID inner puzzle hash based on a P2 puzzle hash
|
|
:param p2_puzhash: P2 puzzle hash
|
|
:param recovery_list: A list of DIDs used for the recovery
|
|
:param num_of_backup_ids_needed: Need how many DIDs for the recovery
|
|
:param launcher_id: ID of the launch coin
|
|
:param metadata: DID customized metadata
|
|
:return: DID inner puzzle hash
|
|
"""
|
|
|
|
backup_ids_hash = Program(Program.to(recovery_list)).get_tree_hash()
|
|
singleton_struct = Program.to((SINGLETON_TOP_LAYER_MOD_HASH, (launcher_id, LAUNCHER_PUZZLE_HASH)))
|
|
|
|
quoted_mod_hash = calculate_hash_of_quoted_mod_hash(DID_INNERPUZ_MOD_HASH)
|
|
|
|
return curry_and_treehash(
|
|
quoted_mod_hash,
|
|
p2_puzhash,
|
|
Program.to(backup_ids_hash).get_tree_hash(),
|
|
Program.to(num_of_backup_ids_needed).get_tree_hash(),
|
|
Program.to(singleton_struct).get_tree_hash(),
|
|
metadata.get_tree_hash(),
|
|
)
|
|
|
|
|
|
def create_fullpuz(innerpuz: Program, launcher_id: bytes32) -> Program:
|
|
"""
|
|
Create a full puzzle of DID
|
|
:param innerpuz: DID inner puzzle
|
|
:param launcher_id:
|
|
:return: DID full puzzle
|
|
"""
|
|
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
|
|
singleton_struct = Program.to((SINGLETON_TOP_LAYER_MOD_HASH, (launcher_id, LAUNCHER_PUZZLE_HASH)))
|
|
return SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, innerpuz)
|
|
|
|
|
|
def create_fullpuz_hash(innerpuz_hash: bytes32, launcher_id: bytes32) -> bytes32:
|
|
"""
|
|
Create a full puzzle of DID
|
|
:param innerpuz_hash: DID inner puzzle tree hash
|
|
:param launcher_id: launcher coin name
|
|
:return: DID full puzzle hash
|
|
"""
|
|
# singleton_struct = (MOD_HASH . (LAUNCHER_ID . LAUNCHER_PUZZLE_HASH))
|
|
singleton_struct = Program.to((SINGLETON_TOP_LAYER_MOD_HASH, (launcher_id, LAUNCHER_PUZZLE_HASH)))
|
|
|
|
return curry_and_treehash(SINGLETON_TOP_LAYER_MOD_HASH_QUOTED, singleton_struct.get_tree_hash(), innerpuz_hash)
|
|
|
|
|
|
def is_did_innerpuz(inner_f: Program) -> bool:
|
|
"""
|
|
Check if a puzzle is a DID inner mode
|
|
:param inner_f: puzzle
|
|
:return: Boolean
|
|
"""
|
|
return inner_f == DID_INNERPUZ_MOD
|
|
|
|
|
|
def is_did_core(inner_f: Program) -> bool:
|
|
"""
|
|
Check if a puzzle is a singleton mod
|
|
:param inner_f: puzzle
|
|
:return: Boolean
|
|
"""
|
|
return inner_f == SINGLETON_TOP_LAYER_MOD
|
|
|
|
|
|
def uncurry_innerpuz(puzzle: Program) -> Optional[Tuple[Program, Program, Program, Program, Program]]:
|
|
"""
|
|
Uncurry a DID inner puzzle
|
|
:param puzzle: DID puzzle
|
|
:return: Curried parameters
|
|
"""
|
|
r = puzzle.uncurry()
|
|
if r is None:
|
|
return r
|
|
inner_f, args = r
|
|
if not is_did_innerpuz(inner_f):
|
|
return None
|
|
|
|
p2_puzzle, id_list, num_of_backup_ids_needed, singleton_struct, metadata = list(args.as_iter())
|
|
return p2_puzzle, id_list, num_of_backup_ids_needed, singleton_struct, metadata
|
|
|
|
|
|
def get_innerpuzzle_from_puzzle(puzzle: Program) -> Optional[Program]:
|
|
"""
|
|
Extract the inner puzzle of a singleton
|
|
:param puzzle: Singleton puzzle
|
|
:return: Inner puzzle
|
|
"""
|
|
r = puzzle.uncurry()
|
|
if r is None:
|
|
return None
|
|
inner_f, args = r
|
|
if not is_did_core(inner_f):
|
|
return None
|
|
SINGLETON_STRUCT, INNER_PUZZLE = list(args.as_iter())
|
|
return INNER_PUZZLE
|
|
|
|
|
|
def create_recovery_message_puzzle(recovering_coin_id: bytes32, newpuz: bytes32, pubkey: G1Element) -> Program:
|
|
"""
|
|
Create attestment message puzzle
|
|
:param recovering_coin_id: ID of the DID coin needs to recover
|
|
:param newpuz: New wallet puzzle hash
|
|
:param pubkey: New wallet pubkey
|
|
:return: Message puzzle
|
|
"""
|
|
return Program.to(
|
|
(
|
|
1,
|
|
[
|
|
[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, recovering_coin_id],
|
|
[ConditionOpcode.AGG_SIG_UNSAFE, bytes(pubkey), newpuz],
|
|
],
|
|
)
|
|
)
|
|
|
|
|
|
def create_spend_for_message(
|
|
parent_of_message: bytes32, recovering_coin: bytes32, newpuz: bytes32, pubkey: G1Element
|
|
) -> CoinSpend:
|
|
"""
|
|
Create a CoinSpend for a atestment
|
|
:param parent_of_message: Parent coin ID
|
|
:param recovering_coin: ID of the DID coin needs to recover
|
|
:param newpuz: New wallet puzzle hash
|
|
:param pubkey: New wallet pubkey
|
|
:return: CoinSpend
|
|
"""
|
|
puzzle = create_recovery_message_puzzle(recovering_coin, newpuz, pubkey)
|
|
coin = Coin(parent_of_message, puzzle.get_tree_hash(), uint64(0))
|
|
solution = Program.to([])
|
|
coinsol = CoinSpend(coin, puzzle, solution)
|
|
return coinsol
|
|
|
|
|
|
def match_did_puzzle(mod: Program, curried_args: Program) -> Optional[Iterator[Program]]:
|
|
"""
|
|
Given a puzzle test if it's a DID, if it is, return the curried arguments
|
|
:param puzzle: Puzzle
|
|
:return: Curried parameters
|
|
"""
|
|
try:
|
|
if mod == SINGLETON_TOP_LAYER_MOD:
|
|
mod, curried_args = curried_args.rest().first().uncurry()
|
|
if mod == DID_INNERPUZ_MOD:
|
|
return curried_args.as_iter()
|
|
except Exception:
|
|
import traceback
|
|
|
|
print(f"exception: {traceback.format_exc()}")
|
|
return None
|
|
|
|
|
|
def check_is_did_puzzle(puzzle: Program) -> bool:
|
|
"""
|
|
Check if a puzzle is a DID puzzle
|
|
:param puzzle: Puzzle
|
|
:return: Boolean
|
|
"""
|
|
r = puzzle.uncurry()
|
|
if r is None:
|
|
return r
|
|
inner_f, args = r
|
|
return is_did_core(inner_f)
|
|
|
|
|
|
def metadata_to_program(metadata: Dict) -> Program:
|
|
"""
|
|
Convert the metadata dict to a Chialisp program
|
|
:param metadata: User defined metadata
|
|
:return: Chialisp program
|
|
"""
|
|
kv_list = []
|
|
for key, value in metadata.items():
|
|
kv_list.append((key, value))
|
|
return Program.to(kv_list)
|
|
|
|
|
|
def program_to_metadata(program: Program) -> Dict:
|
|
"""
|
|
Convert a program to a metadata dict
|
|
:param program: Chialisp program contains the metadata
|
|
:return: Metadata dict
|
|
"""
|
|
metadata = {}
|
|
for key, val in program.as_python():
|
|
metadata[str(key, "utf-8")] = str(val, "utf-8")
|
|
return metadata
|