def get_discrepancies_for_spend_bundle( trade_offer: SpendBundle, ) -> Tuple[bool, Optional[Dict], Optional[Exception]]: try: cc_discrepancies: Dict[str, int] = dict() for coinsol in trade_offer.coin_spends: puzzle: Program = Program.from_bytes(bytes(coinsol.puzzle_reveal)) solution: Program = Program.from_bytes(bytes(coinsol.solution)) # work out the deficits between coin amount and expected output for each r = cc_utils.uncurry_cc(puzzle) if r: # Calculate output amounts mod_hash, genesis_checker, inner_puzzle = r innersol = solution.first() total = get_output_amount_for_puzzle_and_solution(inner_puzzle, innersol) colour = bytes(genesis_checker).hex() if colour in cc_discrepancies: cc_discrepancies[colour] += coinsol.coin.amount - total else: cc_discrepancies[colour] = coinsol.coin.amount - total else: coin_amount = coinsol.coin.amount out_amount = get_output_amount_for_puzzle_and_solution(puzzle, solution) diff = coin_amount - out_amount if "chia" in cc_discrepancies: cc_discrepancies["chia"] = cc_discrepancies["chia"] + diff else: cc_discrepancies["chia"] = diff return True, cc_discrepancies, None except Exception as e: return False, None, e
def print_conditions(spend_bundle: SpendBundle): print("\nConditions:") for coin_solution in spend_bundle.coin_solutions: result = Program.from_bytes(bytes(coin_solution.puzzle_reveal)).run( Program.from_bytes(bytes(coin_solution.solution))) error, result_human = parse_sexp_to_conditions(result) assert error is None assert result_human is not None for cvp in result_human: print( f"{ConditionOpcode(cvp.opcode).name}: {[var.hex() for var in cvp.vars]}" ) print("")
def compress_coin_solution(coin_solution: CoinSolution): compressed_puzzle = compress_cse_puzzle(coin_solution.puzzle_reveal) return [ [coin_solution.coin.parent_coin_info, coin_solution.coin.amount], [compressed_puzzle, Program.from_bytes(bytes(coin_solution.solution))], ]
def solution_to_pool_state(full_spend: CoinSpend) -> Optional[PoolState]: full_solution_ser: SerializedProgram = full_spend.solution full_solution: Program = Program.from_bytes(bytes(full_solution_ser)) if full_spend.coin.puzzle_hash == SINGLETON_LAUNCHER_HASH: # Launcher spend extra_data: Program = full_solution.rest().rest().first() return pool_state_from_extra_data(extra_data) # Not launcher spend inner_solution: Program = full_solution.rest().rest().first() # Spend which is not absorb, and is not the launcher num_args = len(inner_solution.as_python()) assert num_args in (2, 3) if num_args == 2: # pool member if inner_solution.rest().first().as_int() != 0: return None # This is referred to as p1 in the chialisp code # spend_type is absorbing money if p1 is a cons box, spend_type is escape if p1 is an atom # TODO: The comment above, and in the CLVM, seems wrong extra_data = inner_solution.first() if isinstance(extra_data.as_python(), bytes): # Absorbing return None return pool_state_from_extra_data(extra_data) else: # pool waitingroom if inner_solution.first().as_int() == 0: return None extra_data = inner_solution.rest().first() return pool_state_from_extra_data(extra_data)
async def test_clvm_strict_mode(self, rust_checker: bool): block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program)) disassembly = binutils.disassemble(block) # this is a valid generator program except the first clvm # if-condition, that depends on executing an unknown operator # ("0xfe"). In strict mode, this should fail, but in non-strict # mode, the unknown operator should be treated as if it returns (). program = SerializedProgram.from_bytes( binutils.assemble( f"(i (0xfe (q . 0)) (q . ()) {disassembly})").as_bin()) generator = BlockGenerator(program, []) npc_result: NPCResult = get_name_puzzle_conditions( generator, test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, safe_mode=True, rust_checker=rust_checker, ) assert npc_result.error is not None npc_result = get_name_puzzle_conditions( generator, test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, safe_mode=False, rust_checker=rust_checker, ) assert npc_result.error is None
def test_deserialization_simple_list(self): # ("hello" "friend") b = hexstr_to_bytes("ff8568656c6c6fff86667269656e6480") cost, output = DESERIALIZE_MOD.run_with_cost([b]) print(cost, output) prog = Program.to(output) assert prog == Program.from_bytes(b)
async def test_clvm_max_cost(self): block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program)) disassembly = binutils.disassemble(block) # this is a valid generator program except the first clvm # if-condition, that depends on executing an unknown operator # ("0xfe"). In strict mode, this should fail, but in non-strict # mode, the unknown operator should be treated as if it returns (). # the CLVM program has a cost of 391969 program = SerializedProgram.from_bytes( binutils.assemble( f"(i (softfork (q . 10000000)) (q . ()) {disassembly})"). as_bin()) # ensure we fail if the program exceeds the cost generator = BlockGenerator(program, []) npc_result: NPCResult = get_name_puzzle_conditions( generator, 10000000, False) assert npc_result.error is not None assert npc_result.clvm_cost == 0 # raise the max cost to make sure this passes # ensure we pass if the program does not exceeds the cost npc_result: NPCResult = get_name_puzzle_conditions( generator, 20000000, False) assert npc_result.error is None assert npc_result.clvm_cost > 10000000
def test_shatrees_match(self): """Checks to see that all .sha256tree files match their .hex files""" for prog_path in wallet_program_files: # load the .hex file as a program hex_filename = path_with_ext(prog_path, ".hex") clvm_hex = hex_filename.read_text() # .decode("utf8") clvm_blob = bytes.fromhex(clvm_hex) s = SerializedProgram.from_bytes(clvm_blob) p = Program.from_bytes(clvm_blob) # load the checked-in shatree existing_sha = path_with_ext( prog_path, ".hex.sha256tree").read_text().strip() self.assertEqual( s.get_tree_hash().hex(), existing_sha, msg= f"Checked-in shatree hash file does not match shatree hash of loaded SerializedProgram: {prog_path}", # noqa ) self.assertEqual( p.get_tree_hash().hex(), existing_sha, msg= f"Checked-in shatree hash file does not match shatree hash of loaded Program: {prog_path}", )
def parse_program(program: str, include=[]): if '(' in program: prog = Program.to(assemble(program)) elif '.' not in program: prog = Program.from_bytes(bytes.fromhex(program)) else: with open(program, "r") as file: filestring = file.read() if '(' in filestring: if re.compile('\(mod\s').search(filestring): prog = Program.to( compile_clvm_text(filestring, append_include(include))) else: prog = Program.to(assemble(filestring)) else: prog = Program.from_bytes(bytes.fromhex(filestring)) return prog
def make_generator_args( generator_ref_list: List[SerializedProgram]) -> SerializedProgram: """ `make_generator_args`: The format and contents of these arguments affect consensus. """ gen_ref_list = [Program.from_bytes(bytes(g)) for g in generator_ref_list] return SerializedProgram.from_bytes( bytes(Program.to([DESERIALIZE_MOD, gen_ref_list])))
def test_make_generator_args(self): generator_ref_list = [gen1] gen_args = create_generator_args(generator_ref_list) gen_args_as_program = Program.from_bytes(bytes(gen_args)) # First Argument to the block generator is the first template generator arg2 = gen_args_as_program.first() print(arg2) assert arg2 == bytes(gen1)
def test_deserialization_large_numbers(self): # '(99999999999999999999999999999999999999999999999999999999999999999 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -99999999999999999999999999999999999999999999999999999999999999999999999999999)' # noqa b = hexstr_to_bytes( "ff9c00f316271c7fc3908a8bef464e3945ef7a253609ffffffffffffffffffb00fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1ff22ea0179500526edb610f148ec0c614155678491902d6000000000000000000180" # noqa ) # noqa cost, output = DESERIALIZE_MOD.run_with_cost([b]) print(cost, output) prog = Program.to(output) assert prog == Program.from_bytes(b)
def test_deserialization_password_coin(self): # (i (= (sha256 2) (q 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (c (q 51) (c 5 (c (q 100) (q ())))) (q "wrong password")) # noqa b = hexstr_to_bytes( "ff04ffff0affff0bff0280ffff01ffa02cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b98248080ffff05ffff01ff3380ffff05ff05ffff05ffff01ff6480ffff01ff8080808080ffff01ff8e77726f6e672070617373776f72648080" # noqa ) # noqa cost, output = DESERIALIZE_MOD.run_with_cost([b]) print(cost, output) prog = Program.to(output) assert prog == Program.from_bytes(b)
def test_make_generator_args(self): generator_ref_list = [gen1] gen_args = create_generator_args(generator_ref_list) gen_args_as_program = Program.from_bytes(bytes(gen_args)) d = gen_args_as_program.first() # First argument: clvm deserializer b = bytes.fromhex( "ff8568656c6c6fff86667269656e6480") # ("hello" "friend") cost, output = d.run_with_cost([b]) # print(cost, output) out = Program.to(output) assert out == Program.from_bytes(b) # Second Argument to the block generator is the first template generator arg2 = gen_args_as_program.rest().first() print(arg2) assert arg2 == bytes(gen1)
def test_make_generator_args(self): generator_ref_list = [gen1] gen_args = make_generator_args(generator_ref_list) gen_args_as_program = Program.from_bytes(bytes(gen_args)) d = gen_args_as_program.first() # First arguemnt: clvm deserializer b = hexstr_to_bytes( "ff8568656c6c6fff86667269656e6480") # ("hello" "friend") cost, output = d.run_with_cost([b]) # print(cost, output) out = Program.to(output) assert out == Program.from_bytes(b) # Second Argument arg2 = gen_args_as_program.rest().first().first() print(arg2) assert bytes(arg2) == bytes(gen1)
def get_inner_puzzle_from_puzzle(full_puzzle: Program) -> Optional[Program]: p = Program.from_bytes(bytes(full_puzzle)) r = p.uncurry() if r is None: return None _, args = r _, inner_puzzle = list(args.as_iter()) if not is_pool_singleton_inner_puzzle(inner_puzzle): return None return inner_puzzle
def get_delayed_puz_info_from_launcher_spend(coinsol: CoinSolution) -> Tuple[uint64, bytes32]: extra_data = Program.from_bytes(bytes(coinsol.solution)).rest().rest().first() # Extra data is (pool_state delayed_puz_info) # Delayed puz info is (seconds delayed_puzzle_hash) seconds: Optional[uint64] = None delayed_puzzle_hash: Optional[bytes32] = None for key, value in extra_data.as_python(): if key == b"t": seconds = int_from_bytes(value) if key == b"h": delayed_puzzle_hash = bytes32(value) assert seconds is not None assert delayed_puzzle_hash is not None return seconds, delayed_puzzle_hash
async def test_clvm_strict_mode(self): block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR)) disassembly = binutils.disassemble(block) # this is a valid generator program except the first clvm # if-condition, that depends on executing an unknown operator # ("0xfe"). In strict mode, this should fail, but in non-strict # mode, the unknown operator should be treated as if it returns (). program = SerializedProgram.from_bytes( binutils.assemble( f"(i (0xfe (q . 0)) (q . ()) {disassembly})").as_bin()) error, npc_list, cost = get_name_puzzle_conditions(program, True) assert error is not None error, npc_list, cost = get_name_puzzle_conditions(program, False) assert error is None
def lineage_proof_for_coin_spend(coin_spend: CoinSpend) -> Program: """Take a coin solution, return a lineage proof for their child to use in spends""" coin = coin_spend.coin parent_name = coin.parent_coin_info amount = coin.amount inner_puzzle_hash = None if coin.puzzle_hash == LAUNCHER_PUZZLE_HASH: return Program.to([parent_name, amount]) full_puzzle = Program.from_bytes(bytes(coin_spend.puzzle_reveal)) _, args = full_puzzle.uncurry() _, __, ___, inner_puzzle = list(args.as_iter()) inner_puzzle_hash = inner_puzzle.get_tree_hash() return Program.to([parent_name, inner_puzzle_hash, amount])
def spendable_cc_list_from_coin_spend(coin_spend: CoinSpend, hash_to_puzzle_f) -> List[SpendableCC]: """ Given a `CoinSpend`, 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_spend.coin puzzle = Program.from_bytes(bytes(coin_spend.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_spend.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) # TODO: address hint error and remove ignore # error: Argument 2 to "SpendableCC" has incompatible type "Optional[bytes32]"; expected "bytes32" # [arg-type] cc_spend_info = SpendableCC(new_coin, genesis_coin_id, inner_puzzle, lineage_proof) # type: ignore[arg-type] spendable_cc_list.append(cc_spend_info) return spendable_cc_list
def lineage_proof_for_coinsol(coin_spend: CoinSpend) -> LineageProof: parent_name: bytes32 = coin_spend.coin.parent_coin_info inner_puzzle_hash: Optional[bytes32] = None if coin_spend.coin.puzzle_hash != SINGLETON_LAUNCHER_HASH: full_puzzle = Program.from_bytes(bytes(coin_spend.puzzle_reveal)) r = full_puzzle.uncurry() if r is not None: _, args = r _, inner_puzzle = list(args.as_iter()) inner_puzzle_hash = inner_puzzle.get_tree_hash() amount: uint64 = coin_spend.coin.amount return LineageProof( parent_name, inner_puzzle_hash, amount, )
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
def find_interesting_singletons( puzzle_db: PuzzleDB, removals: List[CoinSpend]) -> List[SingletonWallet]: singletons = [] for coin_spend in removals: if coin_spend.coin.puzzle_hash == LAUNCHER_PUZZLE_HASH: r = Program.from_bytes(bytes(coin_spend.solution)) key_value_list = r.rest().rest().first() eve_coin = coin_spend.additions()[0] lineage_proof = lineage_proof_for_coin_spend(coin_spend) launcher_id = coin_spend.coin.name() singleton = SingletonWallet( launcher_id, coin_spend.coin.puzzle_hash, key_value_list, eve_coin, lineage_proof, ) singletons.append(singleton) return singletons
async def create_wallet_for_cc( wallet_state_manager: Any, wallet: Wallet, genesis_checker_hex: str, ) -> CCWallet: self = CCWallet() self.cost_of_single_tx = None self.base_puzzle_program = None self.base_inner_puzzle_hash = None self.standard_wallet = wallet self.log = logging.getLogger(__name__) self.wallet_state_manager = wallet_state_manager self.cc_info = CCInfo( Program.from_bytes(bytes.fromhex(genesis_checker_hex)), []) info_as_string = bytes(self.cc_info).hex() self.wallet_info = await wallet_state_manager.user_store.create_wallet( "CC Wallet", WalletType.COLOURED_COIN, info_as_string) if self.wallet_info is None: raise Exception("wallet_info is None") await self.wallet_state_manager.add_new_wallet(self, self.id()) return self
async def load_backup(self, filename: str): try: f = open(filename, "r") details = f.readline().split(":") f.close() origin = Coin(bytes.fromhex(details[0]), bytes.fromhex(details[1]), uint64(int(details[2]))) backup_ids = [] for d in details[3].split(","): backup_ids.append(bytes.fromhex(d)) num_of_backup_ids_needed = uint64(int(details[5])) if num_of_backup_ids_needed > len(backup_ids): raise Exception innerpuz = Program.from_bytes(bytes.fromhex(details[4])) did_info = DIDInfo( origin, backup_ids, num_of_backup_ids_needed, self.did_info.parent_info, innerpuz, None, None, None, ) await self.save_info(did_info, False) await self.wallet_state_manager.update_wallet_puzzle_hashes( self.wallet_info.id) full_puz = did_wallet_puzzles.create_fullpuz( innerpuz, origin.puzzle_hash) full_puzzle_hash = full_puz.get_tree_hash() ( sub_height, header_hash, ) = await self.wallet_state_manager.search_blockrecords_for_puzzlehash( full_puzzle_hash) assert sub_height is not None assert header_hash is not None full_nodes = self.wallet_state_manager.server.connection_by_type[ NodeType.FULL_NODE] additions: Union[RespondAdditions, RejectAdditionsRequest, None] = None for id, node in full_nodes.items(): request = wallet_protocol.RequestAdditions( sub_height, header_hash, None) additions = await node.request_additions(request) if additions is not None: break if isinstance(additions, RejectAdditionsRequest): continue assert additions is not None assert isinstance(additions, RespondAdditions) # All additions in this block here: new_puzhash = (await self.get_new_puzzle()).get_tree_hash() new_pubkey = bytes( (await self.wallet_state_manager.get_unused_derivation_record( self.wallet_info.id)).pubkey) all_parents: bytes32 = set() for puzzle_list_coin in additions.coins: puzzle_hash, coins = puzzle_list_coin for coin in coins: all_parents.add(coin.parent_coin_info) for puzzle_list_coin in additions.coins: puzzle_hash, coins = puzzle_list_coin if puzzle_hash == full_puzzle_hash: # our coin for coin in coins: future_parent = CCParent( coin.parent_coin_info, innerpuz.get_tree_hash(), coin.amount, ) await self.add_parent(coin.name(), future_parent, False) if coin.name() in all_parents: continue did_info = DIDInfo( origin, backup_ids, num_of_backup_ids_needed, self.did_info.parent_info, innerpuz, coin, new_puzhash, new_pubkey, ) await self.save_info(did_info, False) return None except Exception as e: raise e
package_or_requirement="chia.wallet.puzzles") DESERIALIZE_MOD = load_clvm("chialisp_deserialisation.clvm", package_or_requirement="chia.wallet.puzzles") DECOMPRESS_PUZZLE = load_clvm("decompress_puzzle.clvm", package_or_requirement="chia.wallet.puzzles") DECOMPRESS_CSE = load_clvm("decompress_coin_solution_entry.clvm", package_or_requirement="chia.wallet.puzzles") DECOMPRESS_CSE_WITH_PREFIX = load_clvm( "decompress_coin_solution_entry_with_prefix.clvm", package_or_requirement="chia.wallet.puzzles") DECOMPRESS_BLOCK = load_clvm("block_program_zero.clvm", package_or_requirement="chia.wallet.puzzles") Nil = Program.from_bytes(b"\x80") original_generator = hexstr_to_bytes( "ff01ffffffa00000000000000000000000000000000000000000000000000000000000000000ff830186a080ffffff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b081963921826355dcb6c355ccf9c2637c18adf7d38ee44d803ea9ca41587e48c913d8d46896eb830aeadfc13144a8eac3ff018080ffff80ffff01ffff33ffa06b7a83babea1eec790c947db4464ab657dbe9b887fe9acc247062847b8c2a8a9ff830186a08080ff8080808080" ) # noqa class TestCompression(TestCase): def test_spend_bundle_suitable(self): sb: SpendBundle = make_spend_bundle(1) assert bundle_suitable_for_compression(sb) def test_compress_spend_bundle(self): pass def test_compressed_block_results(self):
def test_serialization(self): s0 = SerializedProgram.from_bytes(b"\x00") p0 = Program.from_bytes(b"\x00") print(s0, p0)
async def respond_to_offer( self, file_path: Path ) -> Tuple[bool, Optional[TradeRecord], Optional[str]]: has_wallets = await self.maybe_create_wallets_for_offer(file_path) if not has_wallets: return False, None, "Unknown Error" trade_offer = None try: trade_offer_hex = file_path.read_text() trade_offer = TradeRecord.from_bytes( hexstr_to_bytes(trade_offer_hex)) except Exception as e: return False, None, f"Error: {e}" if trade_offer is not None: offer_spend_bundle: SpendBundle = trade_offer.spend_bundle coinsols: List[CoinSolution] = [] # [] of CoinSolutions cc_coinsol_outamounts: Dict[bytes32, List[Tuple[CoinSolution, int]]] = dict() aggsig = offer_spend_bundle.aggregated_signature cc_discrepancies: Dict[bytes32, int] = dict() chia_discrepancy = None wallets: Dict[bytes32, Any] = dict() # colour to wallet dict for coinsol in offer_spend_bundle.coin_solutions: puzzle: Program = Program.from_bytes(bytes(coinsol.puzzle_reveal)) solution: Program = Program.from_bytes(bytes(coinsol.solution)) # work out the deficits between coin amount and expected output for each r = cc_utils.uncurry_cc(puzzle) if r: # Calculate output amounts mod_hash, genesis_checker, inner_puzzle = r colour = bytes(genesis_checker).hex() if colour not in wallets: wallets[ colour] = await self.wallet_state_manager.get_wallet_for_colour( colour) unspent = await self.wallet_state_manager.get_spendable_coins_for_wallet( wallets[colour].id()) if coinsol.coin in [record.coin for record in unspent]: return False, None, "can't respond to own offer" innersol = solution.first() total = get_output_amount_for_puzzle_and_solution( inner_puzzle, innersol) if colour in cc_discrepancies: cc_discrepancies[colour] += coinsol.coin.amount - total else: cc_discrepancies[colour] = coinsol.coin.amount - total # Store coinsol and output amount for later if colour in cc_coinsol_outamounts: cc_coinsol_outamounts[colour].append((coinsol, total)) else: cc_coinsol_outamounts[colour] = [(coinsol, total)] else: # standard chia coin unspent = await self.wallet_state_manager.get_spendable_coins_for_wallet( 1) if coinsol.coin in [record.coin for record in unspent]: return False, None, "can't respond to own offer" if chia_discrepancy is None: chia_discrepancy = get_output_discrepancy_for_puzzle_and_solution( coinsol.coin, puzzle, solution) else: chia_discrepancy += get_output_discrepancy_for_puzzle_and_solution( coinsol.coin, puzzle, solution) coinsols.append(coinsol) chia_spend_bundle: Optional[SpendBundle] = None if chia_discrepancy is not None: chia_spend_bundle = await self.wallet_state_manager.main_wallet.create_spend_bundle_relative_chia( chia_discrepancy, []) if chia_spend_bundle is not None: for coinsol in coinsols: chia_spend_bundle.coin_solutions.append(coinsol) zero_spend_list: List[SpendBundle] = [] spend_bundle = None # create coloured coin self.log.info(cc_discrepancies) for colour in cc_discrepancies.keys(): if cc_discrepancies[colour] < 0: my_cc_spends = await wallets[colour].select_coins( abs(cc_discrepancies[colour])) else: if chia_spend_bundle is None: to_exclude: List = [] else: to_exclude = chia_spend_bundle.removals() my_cc_spends = await wallets[colour].select_coins(0) if my_cc_spends is None or my_cc_spends == set(): zero_spend_bundle: SpendBundle = await wallets[ colour].generate_zero_val_coin(False, to_exclude) if zero_spend_bundle is None: return ( False, None, "Unable to generate zero value coin. Confirm that you have chia available", ) zero_spend_list.append(zero_spend_bundle) additions = zero_spend_bundle.additions() removals = zero_spend_bundle.removals() my_cc_spends = set() for add in additions: if add not in removals and add.amount == 0: my_cc_spends.add(add) if my_cc_spends == set() or my_cc_spends is None: return False, None, "insufficient funds" # Create SpendableCC list and innersol_list with both my coins and the offered coins # Firstly get the output coin my_output_coin = my_cc_spends.pop() spendable_cc_list = [] innersol_list = [] genesis_id = genesis_coin_id_for_genesis_coin_checker( Program.from_bytes(bytes.fromhex(colour))) # Make the rest of the coins assert the output coin is consumed for coloured_coin in my_cc_spends: inner_solution = self.wallet_state_manager.main_wallet.make_solution( consumed=[my_output_coin.name()]) inner_puzzle = await self.get_inner_puzzle_for_puzzle_hash( coloured_coin.puzzle_hash) assert inner_puzzle is not None sigs = await wallets[colour].get_sigs(inner_puzzle, inner_solution, coloured_coin.name()) sigs.append(aggsig) aggsig = AugSchemeMPL.aggregate(sigs) lineage_proof = await wallets[ colour].get_lineage_proof_for_coin(coloured_coin) spendable_cc_list.append( SpendableCC(coloured_coin, genesis_id, inner_puzzle, lineage_proof)) innersol_list.append(inner_solution) # Create SpendableCC for each of the coloured coins received for cc_coinsol_out in cc_coinsol_outamounts[colour]: cc_coinsol = cc_coinsol_out[0] puzzle = Program.from_bytes(bytes(cc_coinsol.puzzle_reveal)) solution = Program.from_bytes(bytes(cc_coinsol.solution)) r = uncurry_cc(puzzle) if r: mod_hash, genesis_coin_checker, inner_puzzle = r inner_solution = solution.first() lineage_proof = solution.rest().rest().first() spendable_cc_list.append( SpendableCC(cc_coinsol.coin, genesis_id, inner_puzzle, lineage_proof)) innersol_list.append(inner_solution) # Finish the output coin SpendableCC with new information newinnerpuzhash = await wallets[colour].get_new_inner_hash() outputamount = sum([ c.amount for c in my_cc_spends ]) + cc_discrepancies[colour] + my_output_coin.amount inner_solution = self.wallet_state_manager.main_wallet.make_solution( primaries=[{ "puzzlehash": newinnerpuzhash, "amount": outputamount }]) inner_puzzle = await self.get_inner_puzzle_for_puzzle_hash( my_output_coin.puzzle_hash) assert inner_puzzle is not None lineage_proof = await wallets[colour].get_lineage_proof_for_coin( my_output_coin) spendable_cc_list.append( SpendableCC(my_output_coin, genesis_id, inner_puzzle, lineage_proof)) innersol_list.append(inner_solution) sigs = await wallets[colour].get_sigs(inner_puzzle, inner_solution, my_output_coin.name()) sigs.append(aggsig) aggsig = AugSchemeMPL.aggregate(sigs) if spend_bundle is None: spend_bundle = spend_bundle_for_spendable_ccs( CC_MOD, Program.from_bytes(bytes.fromhex(colour)), spendable_cc_list, innersol_list, [aggsig], ) else: new_spend_bundle = spend_bundle_for_spendable_ccs( CC_MOD, Program.from_bytes(bytes.fromhex(colour)), spendable_cc_list, innersol_list, [aggsig], ) spend_bundle = SpendBundle.aggregate( [spend_bundle, new_spend_bundle]) # reset sigs and aggsig so that they aren't included next time around sigs = [] aggsig = AugSchemeMPL.aggregate(sigs) my_tx_records = [] if zero_spend_list is not None and spend_bundle is not None: zero_spend_list.append(spend_bundle) spend_bundle = SpendBundle.aggregate(zero_spend_list) if spend_bundle is None: return False, None, "spend_bundle missing" # Add transaction history for this trade now = uint64(int(time.time())) if chia_spend_bundle is not None: spend_bundle = SpendBundle.aggregate( [spend_bundle, chia_spend_bundle]) # debug_spend_bundle(spend_bundle) if chia_discrepancy < 0: tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=now, to_puzzle_hash=token_bytes(), amount=uint64(abs(chia_discrepancy)), fee_amount=uint64(0), confirmed=False, sent=uint32(10), spend_bundle=chia_spend_bundle, additions=chia_spend_bundle.additions(), removals=chia_spend_bundle.removals(), wallet_id=uint32(1), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.OUTGOING_TRADE.value), name=chia_spend_bundle.name(), ) else: tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=token_bytes(), amount=uint64(abs(chia_discrepancy)), fee_amount=uint64(0), confirmed=False, sent=uint32(10), spend_bundle=chia_spend_bundle, additions=chia_spend_bundle.additions(), removals=chia_spend_bundle.removals(), wallet_id=uint32(1), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.INCOMING_TRADE.value), name=chia_spend_bundle.name(), ) my_tx_records.append(tx_record) for colour, amount in cc_discrepancies.items(): wallet = wallets[colour] if chia_discrepancy > 0: tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=token_bytes(), amount=uint64(abs(amount)), fee_amount=uint64(0), confirmed=False, sent=uint32(10), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=wallet.id(), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.OUTGOING_TRADE.value), name=spend_bundle.name(), ) else: tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=token_bytes(), amount=uint64(abs(amount)), fee_amount=uint64(0), confirmed=False, sent=uint32(10), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=wallet.id(), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.INCOMING_TRADE.value), name=token_bytes(), ) my_tx_records.append(tx_record) tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=token_bytes(), amount=uint64(0), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=uint32(0), sent_to=[], trade_id=std_hash(spend_bundle.name() + bytes(now)), type=uint32(TransactionType.OUTGOING_TRADE.value), name=spend_bundle.name(), ) now = uint64(int(time.time())) trade_record: TradeRecord = TradeRecord( confirmed_at_index=uint32(0), accepted_at_time=now, created_at_time=now, my_offer=False, sent=uint32(0), spend_bundle=offer_spend_bundle, tx_spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), trade_id=std_hash(spend_bundle.name() + bytes(now)), status=uint32(TradeStatus.PENDING_CONFIRM.value), sent_to=[], ) await self.save_trade(trade_record) await self.wallet_state_manager.add_pending_transaction(tx_record) for tx in my_tx_records: await self.wallet_state_manager.add_transaction(tx) return True, trade_record, None
This roughly corresponds to bitcoin's taproot. """ import hashlib from typing import Union from blspy import G1Element, PrivateKey from clvm.casts import int_from_bytes from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 from .load_clvm import load_clvm from .p2_conditions import puzzle_for_conditions DEFAULT_HIDDEN_PUZZLE = Program.from_bytes(bytes.fromhex("ff0980")) DEFAULT_HIDDEN_PUZZLE_HASH = DEFAULT_HIDDEN_PUZZLE.get_tree_hash( ) # this puzzle `(x)` always fails MOD = load_clvm("p2_delegated_puzzle_or_hidden_puzzle.clvm") SYNTHETIC_MOD = load_clvm("calculate_synthetic_public_key.clvm") PublicKeyProgram = Union[bytes, Program] GROUP_ORDER = 0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001 def calculate_synthetic_offset(public_key: G1Element, hidden_puzzle_hash: bytes32) -> int:
def debug_spend_bundle( spend_bundle, agg_sig_additional_data=DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA ) -> None: """ Print a lot of useful information about a `SpendBundle` that might help with debugging its clvm. """ pks = [] msgs = [] created_coin_announcements: List[List[bytes]] = [] asserted_coin_announcements = [] created_puzzle_announcements: List[List[bytes]] = [] asserted_puzzle_announcements = [] print("=" * 80) for coin_spend in spend_bundle.coin_spends: coin = coin_spend.coin puzzle_reveal = Program.from_bytes(bytes(coin_spend.puzzle_reveal)) solution = Program.from_bytes(bytes(coin_spend.solution)) coin_name = coin.name() if puzzle_reveal.get_tree_hash() != coin_spend.coin.puzzle_hash: print("*** BAD PUZZLE REVEAL") print( f"{puzzle_reveal.get_tree_hash().hex()} vs {coin_spend.coin.puzzle_hash.hex()}" ) print("*" * 80) continue print(f"consuming coin {dump_coin(coin)}") print(f" with id {coin_name}") print() print( f"\nbrun -y main.sym '{bu_disassemble(puzzle_reveal)}' '{bu_disassemble(solution)}'" ) error, conditions, cost = conditions_dict_for_solution( puzzle_reveal, solution, INFINITE_COST) if error: print(f"*** error {error}") elif conditions is not None: for pk_bytes, m in pkm_pairs_for_conditions_dict( conditions, coin_name, agg_sig_additional_data): pks.append(G1Element.from_bytes(pk_bytes)) msgs.append(m) print() cost, r = puzzle_reveal.run_with_cost(INFINITE_COST, solution) # type: ignore print(disassemble(r)) print() if conditions and len(conditions) > 0: print("grouped conditions:") for condition_programs in conditions.values(): print() for c in condition_programs: if len(c.vars) == 1: as_prog = Program.to([c.opcode, c.vars[0]]) if len(c.vars) == 2: as_prog = Program.to( [c.opcode, c.vars[0], c.vars[1]]) print(f" {disassemble(as_prog)}") created_coin_announcements.extend( [coin_name] + _.vars for _ in conditions.get( ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [])) asserted_coin_announcements.extend([ _.vars[0].hex() for _ in conditions.get( ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, []) ]) created_puzzle_announcements.extend( [puzzle_reveal.get_tree_hash()] + _.vars for _ in conditions.get( ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT, [])) asserted_puzzle_announcements.extend([ _.vars[0].hex() for _ in conditions.get( ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT, []) ]) print() else: print("(no output conditions generated)") print() print("-------") created = set(spend_bundle.additions()) spent = set(spend_bundle.removals()) zero_coin_set = set(coin.name() for coin in created if coin.amount == 0) ephemeral = created.intersection(spent) created.difference_update(ephemeral) spent.difference_update(ephemeral) print() print("spent coins") for coin in sorted(spent, key=lambda _: _.name()): print(f" {dump_coin(coin)}") print(f" => spent coin id {coin.name()}") print() print("created coins") for coin in sorted(created, key=lambda _: _.name()): print(f" {dump_coin(coin)}") print(f" => created coin id {coin.name()}") if ephemeral: print() print("ephemeral coins") for coin in sorted(ephemeral, key=lambda _: _.name()): print(f" {dump_coin(coin)}") print(f" => created coin id {coin.name()}") created_coin_announcement_pairs = [(_, std_hash(b"".join(_)).hex()) for _ in created_coin_announcements] if created_coin_announcement_pairs: print("created coin announcements") for announcement, hashed in sorted(created_coin_announcement_pairs, key=lambda _: _[-1]): as_hex = [f"0x{_.hex()}" for _ in announcement] print(f" {as_hex} =>\n {hashed}") eor_coin_announcements = sorted( set(_[-1] for _ in created_coin_announcement_pairs) ^ set(asserted_coin_announcements)) created_puzzle_announcement_pairs = [(_, std_hash(b"".join(_)).hex()) for _ in created_puzzle_announcements] if created_puzzle_announcements: print("created puzzle announcements") for announcement, hashed in sorted(created_puzzle_announcement_pairs, key=lambda _: _[-1]): as_hex = [f"0x{_.hex()}" for _ in announcement] print(f" {as_hex} =>\n {hashed}") eor_puzzle_announcements = sorted( set(_[-1] for _ in created_puzzle_announcement_pairs) ^ set(asserted_puzzle_announcements)) print() print() print(f"zero_coin_set = {sorted(zero_coin_set)}") print() if created_coin_announcement_pairs or asserted_coin_announcements: print( f"created coin announcements = {sorted([_[-1] for _ in created_coin_announcement_pairs])}" ) print() print( f"asserted coin announcements = {sorted(asserted_coin_announcements)}" ) print() print( f"symdiff of coin announcements = {sorted(eor_coin_announcements)}" ) print() if created_puzzle_announcement_pairs or asserted_puzzle_announcements: print( f"created puzzle announcements = {sorted([_[-1] for _ in created_puzzle_announcement_pairs])}" ) print() print( f"asserted puzzle announcements = {sorted(asserted_puzzle_announcements)}" ) print() print( f"symdiff of puzzle announcements = {sorted(eor_puzzle_announcements)}" ) print() print() print("=" * 80) print() validates = AugSchemeMPL.aggregate_verify( pks, msgs, spend_bundle.aggregated_signature) print(f"aggregated signature check pass: {validates}") print(f"pks: {pks}") print(f"msgs: {[msg.hex() for msg in msgs]}") print(f" msg_data: {[msg.hex()[:-128] for msg in msgs]}") print(f" coin_ids: {[msg.hex()[-128:-64] for msg in msgs]}") print(f" add_data: {[msg.hex()[-64:] for msg in msgs]}") print(f"signature: {spend_bundle.aggregated_signature}")