chia-blockchain/chia/wallet/cc_wallet/cc_utils.py

253 lines
8.2 KiB
Python

import dataclasses
from typing import List, Optional, Tuple
from blspy import AugSchemeMPL, G2Element
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program, INFINITE_COST
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.condition_opcodes import ConditionOpcode
from chia.types.spend_bundle import CoinSolution, SpendBundle
from chia.util.condition_tools import conditions_dict_for_solution
from chia.util.ints import uint64
from chia.wallet.puzzles.cc_loader import CC_MOD, LOCK_INNER_PUZZLE
from chia.wallet.puzzles.genesis_by_coin_id_with_0 import (
genesis_coin_id_for_genesis_coin_checker,
lineage_proof_for_coin,
lineage_proof_for_genesis,
lineage_proof_for_zero,
)
NULL_SIGNATURE = G2Element()
ANYONE_CAN_SPEND_PUZZLE = Program.to(1) # simply return the conditions
# information needed to spend a cc
# if we ever support more genesis conditions, like a re-issuable coin,
# we may need also to save the `genesis_coin_mod` or its hash
@dataclasses.dataclass
class SpendableCC:
coin: Coin
genesis_coin_id: bytes32
inner_puzzle: Program
lineage_proof: Program
def cc_puzzle_for_inner_puzzle(mod_code, genesis_coin_checker, inner_puzzle) -> Program:
"""
Given an inner puzzle, generate a puzzle program for a specific cc.
"""
return mod_code.curry(mod_code.get_tree_hash(), genesis_coin_checker, inner_puzzle)
# return mod_code.curry([mod_code.get_tree_hash(), genesis_coin_checker, inner_puzzle])
def cc_puzzle_hash_for_inner_puzzle_hash(mod_code, genesis_coin_checker, inner_puzzle_hash) -> bytes32:
"""
Given an inner puzzle hash, calculate a puzzle program hash for a specific cc.
"""
gcc_hash = genesis_coin_checker.get_tree_hash()
return mod_code.curry(mod_code.get_tree_hash(), gcc_hash, inner_puzzle_hash).get_tree_hash(
gcc_hash, inner_puzzle_hash
)
def lineage_proof_for_cc_parent(parent_coin: Coin, parent_inner_puzzle_hash: bytes32) -> Program:
return Program.to(
(
1,
[parent_coin.parent_coin_info, parent_inner_puzzle_hash, parent_coin.amount],
)
)
def subtotals_for_deltas(deltas) -> List[int]:
"""
Given a list of deltas corresponding to input coins, create the "subtotals" list
needed in solutions spending those coins.
"""
subtotals = []
subtotal = 0
for delta in deltas:
subtotals.append(subtotal)
subtotal += delta
# tweak the subtotals so the smallest value is 0
subtotal_offset = min(subtotals)
subtotals = [_ - subtotal_offset for _ in subtotals]
return subtotals
def coin_solution_for_lock_coin(
prev_coin: Coin,
subtotal: int,
coin: Coin,
) -> CoinSolution:
puzzle_reveal = LOCK_INNER_PUZZLE.curry(prev_coin.as_list(), subtotal)
coin = Coin(coin.name(), puzzle_reveal.get_tree_hash(), uint64(0))
coin_solution = CoinSolution(coin, puzzle_reveal, Program.to(0))
return coin_solution
def bundle_for_spendable_cc_list(spendable_cc: SpendableCC) -> Program:
pair = (spendable_cc.coin.as_list(), spendable_cc.lineage_proof)
return Program.to(pair)
def spend_bundle_for_spendable_ccs(
mod_code: Program,
genesis_coin_checker: Program,
spendable_cc_list: List[SpendableCC],
inner_solutions: List[Program],
sigs: Optional[List[G2Element]] = [],
) -> SpendBundle:
"""
Given a list of `SpendableCC` objects and inner solutions for those objects, create a `SpendBundle`
that spends all those coins. Note that it the signature is not calculated it, so the caller is responsible
for fixing it.
"""
N = len(spendable_cc_list)
if len(inner_solutions) != N:
raise ValueError("spendable_cc_list and inner_solutions are different lengths")
input_coins = [_.coin for _ in spendable_cc_list]
# figure out what the output amounts are by running the inner puzzles & solutions
output_amounts = []
for cc_spend_info, inner_solution in zip(spendable_cc_list, inner_solutions):
error, conditions, cost = conditions_dict_for_solution(
cc_spend_info.inner_puzzle, inner_solution, INFINITE_COST
)
total = 0
if conditions:
for _ in conditions.get(ConditionOpcode.CREATE_COIN, []):
total += Program.to(_.vars[1]).as_int()
output_amounts.append(total)
coin_solutions = []
deltas = [input_coins[_].amount - output_amounts[_] for _ in range(N)]
subtotals = subtotals_for_deltas(deltas)
if sum(deltas) != 0:
raise ValueError("input and output amounts don't match")
bundles = [bundle_for_spendable_cc_list(_) for _ in spendable_cc_list]
for index in range(N):
cc_spend_info = spendable_cc_list[index]
puzzle_reveal = cc_puzzle_for_inner_puzzle(mod_code, genesis_coin_checker, cc_spend_info.inner_puzzle)
prev_index = (index - 1) % N
next_index = (index + 1) % N
prev_bundle = bundles[prev_index]
my_bundle = bundles[index]
next_bundle = bundles[next_index]
solution = [
inner_solutions[index],
prev_bundle,
my_bundle,
next_bundle,
subtotals[index],
]
coin_solution = CoinSolution(input_coins[index], puzzle_reveal, Program.to(solution))
coin_solutions.append(coin_solution)
if sigs is None or sigs == []:
return SpendBundle(coin_solutions, NULL_SIGNATURE)
else:
return SpendBundle(coin_solutions, AugSchemeMPL.aggregate(sigs))
def is_cc_mod(inner_f: Program):
"""
You may want to generalize this if different `CC_MOD` templates are supported.
"""
return inner_f == CC_MOD
def check_is_cc_puzzle(puzzle: Program):
r = puzzle.uncurry()
if r is None:
return False
inner_f, args = r
return is_cc_mod(inner_f)
def uncurry_cc(puzzle: Program) -> Optional[Tuple[Program, Program, Program]]:
"""
Take a puzzle and return `None` if it's not a `CC_MOD` cc, or
a triple of `mod_hash, genesis_coin_checker, inner_puzzle` if it is.
"""
r = puzzle.uncurry()
if r is None:
return r
inner_f, args = r
if not is_cc_mod(inner_f):
return None
mod_hash, genesis_coin_checker, inner_puzzle = list(args.as_iter())
return mod_hash, genesis_coin_checker, inner_puzzle
def get_lineage_proof_from_coin_and_puz(parent_coin, parent_puzzle):
r = uncurry_cc(parent_puzzle)
if r:
mod_hash, genesis_checker, inner_puzzle = r
lineage_proof = lineage_proof_for_cc_parent(parent_coin, inner_puzzle.get_tree_hash())
else:
if parent_coin.amount == 0:
lineage_proof = lineage_proof_for_zero(parent_coin)
else:
lineage_proof = lineage_proof_for_genesis(parent_coin)
return lineage_proof
def spendable_cc_list_from_coin_solution(coin_solution: CoinSolution, hash_to_puzzle_f) -> List[SpendableCC]:
"""
Given a `CoinSolution`, extract out a list of `SpendableCC` objects.
Since `SpendableCC` needs to track the inner puzzles and a `Coin` only includes
puzzle hash, we also need a `hash_to_puzzle_f` function that turns puzzle hashes into
the corresponding puzzles. This is generally either a `dict` or some kind of DB
(if it's large or persistent).
"""
spendable_cc_list = []
coin = coin_solution.coin
puzzle = Program.from_bytes(bytes(coin_solution.puzzle_reveal))
r = uncurry_cc(puzzle)
if r:
mod_hash, genesis_coin_checker, inner_puzzle = r
lineage_proof = lineage_proof_for_cc_parent(coin, inner_puzzle.get_tree_hash())
else:
lineage_proof = lineage_proof_for_coin(coin)
for new_coin in coin_solution.additions():
puzzle = hash_to_puzzle_f(new_coin.puzzle_hash)
if puzzle is None:
# we don't recognize this puzzle hash, skip it
continue
r = uncurry_cc(puzzle)
if r is None:
# this isn't a cc puzzle
continue
mod_hash, genesis_coin_checker, inner_puzzle = r
genesis_coin_id = genesis_coin_id_for_genesis_coin_checker(genesis_coin_checker)
cc_spend_info = SpendableCC(new_coin, genesis_coin_id, inner_puzzle, lineage_proof)
spendable_cc_list.append(cc_spend_info)
return spendable_cc_list