def do_test_spend( puzzle_reveal: Program, solution: Program, payments: Iterable[Tuple[bytes32, int]], key_lookup: KeyTool, farm_time: CoinTimestamp = T1, spend_time: CoinTimestamp = T2, ) -> SpendBundle: """ This method will farm a coin paid to the hash of `puzzle_reveal`, then try to spend it with `solution`, and verify that the created coins correspond to `payments`. The `key_lookup` is used to create a signed version of the `SpendBundle`, although at this time, signatures are not verified. """ coin_db = CoinStore() puzzle_hash = puzzle_reveal.get_tree_hash() # farm it coin = coin_db.farm_coin(puzzle_hash, farm_time) # spend it coin_solution = CoinSolution(coin, puzzle_reveal, solution) spend_bundle = SpendBundle([coin_solution], G2Element()) coin_db.update_coin_store_for_spend_bundle(spend_bundle, spend_time, MAX_BLOCK_COST_CLVM) # ensure all outputs are there for puzzle_hash, amount in payments: for coin in coin_db.coins_for_puzzle_hash(puzzle_hash): if coin.amount == amount: break else: assert 0 # make sure we can actually sign the solution signatures = [] for coin_solution in spend_bundle.coin_solutions: signature = key_lookup.signature_for_solution(coin_solution, bytes([2] * 32)) signatures.append(signature) return SpendBundle(spend_bundle.coin_solutions, AugSchemeMPL.aggregate(signatures))
def tx(info): j = json.dumps(info) m: Dict = eval(j) inputs: List = m.get("inputs") outputs: List = m.get("outputs") primaries = [] for o in outputs: output: Dict = o address: str = output.get("address") value: float = output.get("value") primaries.append({ "puzzlehash": decode_puzzle_hash(address), "amount": value }) spends: List[CoinSolution] = [] pks: List[str] = [] first_spend = True for i in inputs: input: Dict = i pk: str = input.get("pk") pks.append(pk) txid: Dict = eval(input.get("txId")) parentCoinInfo = txid.get("parentCoinInfo") puzzleHash = txid.get("puzzleHash") amount = txid.get("amount") pa = bytes32(bytes.fromhex(parentCoinInfo[2:])) pu = bytes32(bytes.fromhex(puzzleHash[2:])) a = uint64(amount) coin: Coin = Coin(pa, pu, a) child_sk: PrivateKey = PrivateKey.from_bytes(bytes.fromhex(pk)) child_public_key = child_sk.get_g1() puzzle = puzzle_for_pk(child_public_key) if first_spend: solution: Program = Wallet().make_solution(primaries=primaries) first_spend = False else: solution = Wallet().make_solution() spends.append(CoinSolution(coin, puzzle, solution)) spend_bundle: SpendBundle = SpendBundle(spends, G2Element()) # return json.dumps(spend_bundle.to_json_dict()) return sign_tx(pks, spend_bundle)
def claim_p2_singleton( p2_singleton_coin: Coin, singleton_inner_puzhash: bytes32, launcher_id: bytes32, ) -> Tuple[Program, Program, CoinSolution]: assertion = Program.to([ ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, std_hash(p2_singleton_coin.name() + b"$") ]) announcement = Program.to( [ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT, p2_singleton_coin.name()]) claim_coinsol = CoinSolution( p2_singleton_coin, pay_to_singleton_puzzle(launcher_id), solution_for_p2_singleton(p2_singleton_coin, singleton_inner_puzhash), ) return assertion, announcement, claim_coinsol
async def rl_generate_unsigned_transaction(self, to_puzzlehash, amount, fee) -> List[CoinSolution]: spends = [] assert self.rl_coin_record is not None coin = self.rl_coin_record.coin puzzle_hash = coin.puzzle_hash pubkey = self.rl_info.user_pubkey rl_parent: Optional[Coin] = await self._get_rl_parent() if rl_parent is None: raise ValueError("No RL parent coin") # these lines make mypy happy assert pubkey is not None assert self.rl_info.limit is not None assert self.rl_info.interval is not None assert self.rl_info.rl_origin_id is not None assert self.rl_info.admin_pubkey is not None puzzle = rl_puzzle_for_pk( bytes(pubkey), self.rl_info.limit, self.rl_info.interval, self.rl_info.rl_origin_id, self.rl_info.admin_pubkey, ) solution = solution_for_rl( coin.parent_coin_info, puzzle_hash, coin.amount, to_puzzlehash, amount, rl_parent.parent_coin_info, rl_parent.amount, self.rl_info.interval, self.rl_info.limit, fee, ) spends.append(CoinSolution(coin, puzzle, solution)) return spends
def generate_unsigned_transaction( self, amount: uint64, new_puzzle_hash: bytes32, coin: Coin, condition_dic: Dict[ConditionOpcode, List[ConditionWithArgs]], fee: int = 0, secret_key: Optional[PrivateKey] = None, ) -> List[CoinSolution]: spends = [] spend_value = coin.amount puzzle_hash = coin.puzzle_hash if secret_key is None: secret_key = self.get_private_key_for_puzzle_hash(puzzle_hash) pubkey = secret_key.get_g1() puzzle = puzzle_for_pk(bytes(pubkey)) if ConditionOpcode.CREATE_COIN not in condition_dic: condition_dic[ConditionOpcode.CREATE_COIN] = [] output = ConditionWithArgs( ConditionOpcode.CREATE_COIN, [new_puzzle_hash, int_to_bytes(amount)]) condition_dic[output.opcode].append(output) amount_total = sum( int_from_bytes(cvp.vars[1]) for cvp in condition_dic[ConditionOpcode.CREATE_COIN]) change = spend_value - amount_total - fee if change > 0: change_puzzle_hash = self.get_new_puzzlehash() change_output = ConditionWithArgs( ConditionOpcode.CREATE_COIN, [change_puzzle_hash, int_to_bytes(change)]) condition_dic[output.opcode].append(change_output) solution = self.make_solution(condition_dic) else: solution = self.make_solution(condition_dic) spends.append(CoinSolution(coin, puzzle, solution)) return spends
def generate_unsigned_clawback_transaction(self, clawback_coin: Coin, clawback_puzzle_hash: bytes32, fee): if (self.rl_info.limit is None or self.rl_info.interval is None or self.rl_info.user_pubkey is None or self.rl_info.admin_pubkey is None): raise ValueError("One ore more of the elements of rl_info is None") spends = [] coin = clawback_coin if self.rl_info.rl_origin is None: raise ValueError("Origin not initialized") puzzle = rl_puzzle_for_pk( self.rl_info.user_pubkey, self.rl_info.limit, self.rl_info.interval, self.rl_info.rl_origin.name(), self.rl_info.admin_pubkey, ) solution = make_clawback_solution(clawback_puzzle_hash, clawback_coin.amount, fee) spends.append((puzzle, CoinSolution(coin, puzzle, solution))) return spends
def launch_conditions_and_coinsol( coin: Coin, inner_puzzle: Program, comment: List[Tuple[str, str]], amount: uint64, ) -> Tuple[List[Program], CoinSolution]: if (amount % 2) == 0: raise ValueError("Coin amount cannot be even. Subtract one mojo.") launcher_coin = generate_launcher_coin(coin, amount) curried_singleton = SINGLETON_MOD.curry( (SINGLETON_MOD_HASH, (launcher_coin.name(), SINGLETON_LAUNCHER_HASH)), inner_puzzle, ) launcher_solution = Program.to([ curried_singleton.get_tree_hash(), amount, comment, ]) create_launcher = Program.to([ ConditionOpcode.CREATE_COIN, SINGLETON_LAUNCHER_HASH, amount, ], ) assert_launcher_announcement = Program.to([ ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, std_hash(launcher_coin.name() + launcher_solution.get_tree_hash()), ], ) conditions = [create_launcher, assert_launcher_announcement] launcher_coin_solution = CoinSolution( launcher_coin, SINGLETON_LAUNCHER, launcher_solution, ) return conditions, launcher_coin_solution
async def create_spend_bundle_relative_chia( self, chia_amount: int, exclude: List[Coin]) -> SpendBundle: list_of_solutions = [] utxos = None # If we're losing value then get coins with at least that much value # If we're gaining value then our amount doesn't matter if chia_amount < 0: utxos = await self.select_coins(abs(chia_amount), exclude) else: utxos = await self.select_coins(0, exclude) assert len(utxos) > 0 # Calculate output amount given sum of utxos spend_value = sum([coin.amount for coin in utxos]) chia_amount = spend_value + chia_amount # Create coin solutions for each utxo output_created = None for coin in utxos: puzzle = await self.puzzle_for_puzzle_hash(coin.puzzle_hash) if output_created is None: newpuzhash = await self.get_new_puzzlehash() primaries = [{"puzzlehash": newpuzhash, "amount": chia_amount}] solution = self.make_solution(primaries=primaries) output_created = coin list_of_solutions.append(CoinSolution(coin, puzzle, solution)) await self.hack_populate_secret_keys_for_coin_solutions( list_of_solutions) spend_bundle = await sign_coin_solutions( list_of_solutions, self.secret_key_store.secret_key_for_public_key, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA, self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, ) return spend_bundle
async def generate_eve_spend(self, coin: Coin, full_puzzle: Program, origin_id: bytes, innerpuz: Program): # innerpuz solution is (mode amount message my_id my_puzhash parent_innerpuzhash_amounts_for_recovery_ids) innersol = Program.to([ 0, coin.amount, coin.puzzle_hash, coin.name(), coin.puzzle_hash, [] ]) # full solution is (parent_info my_amount innersolution) fullsol = Program.to([coin.parent_coin_info, coin.amount, innersol]) list_of_solutions = [CoinSolution(coin, full_puzzle, fullsol)] # sign for AGG_SIG_ME message = coin.puzzle_hash + coin.name( ) + self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA pubkey = did_wallet_puzzles.get_pubkey_from_innerpuz(innerpuz) index = await self.wallet_state_manager.puzzle_store.index_for_pubkey( pubkey) private = master_sk_to_wallet_sk(self.wallet_state_manager.private_key, index) signature = AugSchemeMPL.sign(private, message) sigs = [signature] aggsig = AugSchemeMPL.aggregate(sigs) spend_bundle = SpendBundle(list_of_solutions, aggsig) return spend_bundle
def launcher_conditions_and_spend_bundle( parent_coin_id: bytes32, launcher_amount: uint64, initial_singleton_inner_puzzle: Program, metadata: List[Tuple[str, str]], launcher_puzzle: Program = LAUNCHER_PUZZLE, ) -> Tuple[Program, bytes32, List[Program], SpendBundle]: launcher_puzzle_hash = launcher_puzzle.get_tree_hash() launcher_coin = Coin(parent_coin_id, launcher_puzzle_hash, launcher_amount) singleton_full_puzzle = SINGLETON_MOD.curry( SINGLETON_MOD_HASH, launcher_coin.name(), launcher_puzzle_hash, initial_singleton_inner_puzzle) singleton_full_puzzle_hash = singleton_full_puzzle.get_tree_hash() message_program = Program.to( [singleton_full_puzzle_hash, launcher_amount, metadata]) expected_announcement = Announcement(launcher_coin.name(), message_program.get_tree_hash()) expected_conditions = [] expected_conditions.append( Program.to( binutils.assemble( f"(0x{ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT.hex()} 0x{expected_announcement.name()})" ))) expected_conditions.append( Program.to( binutils.assemble( f"(0x{ConditionOpcode.CREATE_COIN.hex()} 0x{launcher_puzzle_hash} {launcher_amount})" ))) launcher_solution = Program.to( [singleton_full_puzzle_hash, launcher_amount, metadata]) coin_solution = CoinSolution(launcher_coin, launcher_puzzle, launcher_solution) spend_bundle = SpendBundle([coin_solution], G2Element()) lineage_proof = Program.to([parent_coin_id, launcher_amount]) return lineage_proof, launcher_coin.name( ), expected_conditions, spend_bundle
async def main() -> None: rpc_port: uint16 = uint16(8555) self_hostname = "localhost" path = DEFAULT_ROOT_PATH config = load_config(path, "config.yaml") client = await FullNodeRpcClient.create(self_hostname, rpc_port, path, config) try: farmer_prefarm = ( await client.get_block_record_by_height(1)).reward_claims_incorporated[1] pool_prefarm = ( await client.get_block_record_by_height(1)).reward_claims_incorporated[0] pool_amounts = int(calculate_pool_reward(uint32(0)) / 2) farmer_amounts = int(calculate_base_farmer_reward(uint32(0)) / 2) print(farmer_prefarm.amount, farmer_amounts) assert farmer_amounts == farmer_prefarm.amount // 2 assert pool_amounts == pool_prefarm.amount // 2 address1 = "xch1rdatypul5c642jkeh4yp933zu3hw8vv8tfup8ta6zfampnyhjnusxdgns6" # Key 1 address2 = "xch1duvy5ur5eyj7lp5geetfg84cj2d7xgpxt7pya3lr2y6ke3696w9qvda66e" # Key 2 ph1 = decode_puzzle_hash(address1) ph2 = decode_puzzle_hash(address2) p_farmer_2 = Program.to( binutils.assemble( f"(q . ((51 0x{ph1.hex()} {farmer_amounts}) (51 0x{ph2.hex()} {farmer_amounts})))" )) p_pool_2 = Program.to( binutils.assemble( f"(q . ((51 0x{ph1.hex()} {pool_amounts}) (51 0x{ph2.hex()} {pool_amounts})))" )) print(f"Ph1: {ph1.hex()}") print(f"Ph2: {ph2.hex()}") assert ph1.hex( ) == "1b7ab2079fa635554ad9bd4812c622e46ee3b1875a7813afba127bb0cc9794f9" assert ph2.hex( ) == "6f184a7074c925ef8688ce56941eb8929be320265f824ec7e351356cc745d38a" p_solution = Program.to(binutils.assemble("()")) sb_farmer = SpendBundle( [CoinSolution(farmer_prefarm, p_farmer_2, p_solution)], G2Element()) sb_pool = SpendBundle( [CoinSolution(pool_prefarm, p_pool_2, p_solution)], G2Element()) print("\n\n\nConditions") print_conditions(sb_pool) print("\n\n\n") print("Farmer to spend") print(sb_pool) print(sb_farmer) print("\n\n\n") # res = await client.push_tx(sb_farmer) # res = await client.push_tx(sb_pool) # print(res) up = await client.get_coin_records_by_puzzle_hash( farmer_prefarm.puzzle_hash, True) uf = await client.get_coin_records_by_puzzle_hash( pool_prefarm.puzzle_hash, True) print(up) print(uf) finally: client.close()
async def generate_unsigned_transaction( self, amount: uint64, newpuzzlehash: bytes32, fee: uint64 = uint64(0), origin_id: bytes32 = None, coins: Set[Coin] = None, primaries_input: Optional[List[Dict[str, Any]]] = None, ignore_max_send_amount: bool = False, ) -> List[CoinSolution]: """ Generates a unsigned transaction in form of List(Puzzle, Solutions) """ if primaries_input is None: primaries = None total_amount = amount + fee else: primaries = primaries_input.copy() primaries_amount = 0 for prim in primaries: primaries_amount += prim["amount"] total_amount = amount + fee + primaries_amount if not ignore_max_send_amount: max_send = await self.get_max_send_amount() if total_amount > max_send: raise ValueError( f"Can't send more than {max_send} in a single transaction") if coins is None: coins = await self.select_coins(total_amount) assert len(coins) > 0 self.log.info(f"coins is not None {coins}") spend_value = sum([coin.amount for coin in coins]) change = spend_value - total_amount spends: List[CoinSolution] = [] output_created = False for coin in coins: self.log.info(f"coin from coins {coin}") puzzle: Program = await self.puzzle_for_puzzle_hash( coin.puzzle_hash) # Only one coin creates outputs if not output_created and origin_id in (None, coin.name()): if primaries is None: primaries = [{ "puzzlehash": newpuzzlehash, "amount": amount }] else: primaries.append({ "puzzlehash": newpuzzlehash, "amount": amount }) if change > 0: changepuzzlehash = await self.get_new_puzzlehash() primaries.append({ "puzzlehash": changepuzzlehash, "amount": change }) solution = self.make_solution(primaries=primaries, fee=fee) output_created = True else: solution = self.make_solution() spends.append(CoinSolution(coin, puzzle, solution)) self.log.info(f"Spends is {spends}") return spends
async def create_absorb_transaction( self, farmer_record: FarmerRecord, singleton_coin: Coin, reward_coin_records: List[CoinRecord]) -> SpendBundle: # We assume that the farmer record singleton state is updated to the latest escape_inner_puzzle: Program = POOL_ESCAPING_MOD.curry( farmer_record.pool_puzzle_hash, self.relative_lock_height, bytes(farmer_record.owner_public_key), farmer_record.p2_singleton_puzzle_hash, ) committed_inner_puzzle: Program = POOL_COMMITED_MOD.curry( farmer_record.pool_puzzle_hash, escape_inner_puzzle.get_tree_hash(), farmer_record.p2_singleton_puzzle_hash, bytes(farmer_record.owner_public_key), ) aggregate_spend_bundle: SpendBundle = SpendBundle([], G2Element()) for reward_coin_record in reward_coin_records: found_block_index: Optional[uint32] = None for block_index in range( reward_coin_record.confirmed_block_index, reward_coin_record.confirmed_block_index - 100, -1): if block_index < 0: break pool_parent = pool_parent_id(uint32(block_index), self.constants.GENESIS_CHALLENGE) if pool_parent == reward_coin_record.coin.parent_coin_info: found_block_index = uint32(block_index) if not found_block_index: self.log.info( f"Received reward {reward_coin_record.coin} that is not a pool reward." ) singleton_full = SINGLETON_MOD.curry( singleton_mod_hash, farmer_record.singleton_genesis, committed_inner_puzzle) inner_sol = Program.to([ 0, singleton_full.get_tree_hash(), singleton_coin.amount, reward_coin_record.amount, found_block_index ]) full_sol = Program.to([ farmer_record.singleton_genesis, singleton_coin.amount, inner_sol ]) new_spend = SpendBundle( [ CoinSolution( singleton_coin, SerializedProgram.from_bytes(bytes(singleton_full)), full_sol) ], G2Element(), ) # TODO(pool): handle the case where the cost exceeds the size of the block aggregate_spend_bundle = SpendBundle.aggregate( [aggregate_spend_bundle, new_spend]) singleton_coin = await self.get_next_singleton_coin(new_spend) cost, result = singleton_full.run_with_cost( INFINITE_COST, full_sol) self.log.info(f"Cost: {cost}, result {result}") return aggregate_spend_bundle
async def generate_new_decentralised_id( self, amount: uint64) -> Optional[SpendBundle]: """ This must be called under the wallet state manager lock """ coins = await self.standard_wallet.select_coins(amount) if coins is None: return None origin = coins.copy().pop() genesis_launcher_puz = did_wallet_puzzles.SINGLETON_LAUNCHER launcher_coin = Coin(origin.name(), genesis_launcher_puz.get_tree_hash(), amount) did_inner: Program = await self.get_new_innerpuz() did_inner_hash = did_inner.get_tree_hash() did_full_puz = did_wallet_puzzles.create_fullpuz( did_inner, launcher_coin.name()) did_puzzle_hash = did_full_puz.get_tree_hash() announcement_set: Set[Announcement] = set() announcement_message = Program.to( [did_puzzle_hash, amount, bytes(0x80)]).get_tree_hash() announcement_set.add( Announcement(launcher_coin.name(), announcement_message).name()) tx_record: Optional[ TransactionRecord] = await self.standard_wallet.generate_signed_transaction( amount, genesis_launcher_puz.get_tree_hash(), uint64(0), origin.name(), coins, None, False, announcement_set) genesis_launcher_solution = Program.to( [did_puzzle_hash, amount, bytes(0x80)]) launcher_cs = CoinSolution(launcher_coin, genesis_launcher_puz, genesis_launcher_solution) launcher_sb = SpendBundle([launcher_cs], AugSchemeMPL.aggregate([])) eve_coin = Coin(launcher_coin.name(), did_puzzle_hash, amount) future_parent = LineageProof( eve_coin.parent_coin_info, did_inner_hash, eve_coin.amount, ) eve_parent = LineageProof( launcher_coin.parent_coin_info, launcher_coin.puzzle_hash, launcher_coin.amount, ) await self.add_parent(eve_coin.parent_coin_info, eve_parent, False) await self.add_parent(eve_coin.name(), future_parent, False) if tx_record is None or tx_record.spend_bundle is None: return None # Only want to save this information if the transaction is valid did_info: DIDInfo = DIDInfo( launcher_coin, self.did_info.backup_ids, self.did_info.num_of_backup_ids_needed, self.did_info.parent_info, did_inner, None, None, None, ) await self.save_info(did_info, False) eve_spend = await self.generate_eve_spend(eve_coin, did_full_puz, did_inner) full_spend = SpendBundle.aggregate( [tx_record.spend_bundle, eve_spend, launcher_sb]) return full_spend
async def test_make_fake_coin(self, two_wallet_nodes): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_1 = full_nodes[0] server_1 = full_node_1.server wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] await server_2.start_client( PeerInfo("localhost", uint16(server_1._port)), None) wallet = wallet_node.wallet_state_manager.main_wallet wallet2 = wallet_node_2.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() await server_3.start_client( PeerInfo("localhost", uint16(server_1._port)), None) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1) ]) await time_out_assert(15, wallet.get_confirmed_balance, funds) did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( wallet_node.wallet_state_manager, wallet, uint64(101)) ph2 = await wallet2.get_new_puzzlehash() for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(ph2)) await time_out_assert(15, did_wallet.get_confirmed_balance, 101) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 101) await time_out_assert(15, did_wallet.get_spendable_balance, 101) coins = await did_wallet.select_coins(1) coin = coins.pop() # copy info for later parent_info = await did_wallet.get_parent_for_coin(coin) id_puzhash = coin.puzzle_hash await did_wallet.create_spend(ph) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(ph)) await time_out_assert(15, did_wallet.get_confirmed_balance, 0) await time_out_assert(15, did_wallet.get_unconfirmed_balance, 0) tx_record = await wallet.generate_signed_transaction(101, id_puzhash) await wallet.push_transaction(tx_record) for i in range(1, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(ph)) await time_out_assert(15, wallet.get_confirmed_balance, 21999999999899) await time_out_assert(15, wallet.get_unconfirmed_balance, 21999999999899) coins = await did_wallet.select_coins(1) assert len(coins) >= 1 coin = coins.pop() # Write spend by hand # innerpuz solution is (mode amount new_puz identity my_puz) innersol = Program.to( [0, coin.amount, ph, coin.name(), coin.puzzle_hash]) # full solution is (corehash parent_info my_amount innerpuz_reveal solution) innerpuz = did_wallet.did_info.current_inner full_puzzle: Program = did_wallet_puzzles.create_fullpuz( innerpuz, did_wallet.did_info.my_did, ) fullsol = Program.to([ [ parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ], coin.amount, innersol, ]) list_of_solutions = [CoinSolution(coin, full_puzzle, fullsol)] # sign for AGG_SIG_ME message = coin.puzzle_hash + coin.name( ) + did_wallet.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA pubkey = did_wallet_puzzles.get_pubkey_from_innerpuz(innerpuz) index = await did_wallet.wallet_state_manager.puzzle_store.index_for_pubkey( pubkey) private = master_sk_to_wallet_sk( did_wallet.wallet_state_manager.private_key, index) signature = AugSchemeMPL.sign(private, message) sigs = [signature] aggsig = AugSchemeMPL.aggregate(sigs) spend_bundle = SpendBundle(list_of_solutions, aggsig) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=ph, amount=uint64(coin.amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=did_wallet.wallet_info.id, sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=token_bytes(), ) await did_wallet.standard_wallet.push_transaction(did_record) await time_out_assert(15, wallet.get_confirmed_balance, 21999999999899) await time_out_assert(15, wallet.get_unconfirmed_balance, 21999999999899) ph2 = Program.to(binutils.assemble("()")).get_tree_hash() for i in range(1, num_blocks + 3): await full_node_1.farm_new_block(FarmNewBlockProtocol(ph2)) # It ends in 900 so it's not gone through # Assert coin ID is failing await time_out_assert(15, wallet.get_confirmed_balance, 23999999999899) await time_out_assert(15, wallet.get_unconfirmed_balance, 23999999999899)
def test_pool_lifecycle(self): # START TESTS # Generate starting info key_lookup = KeyTool() sk: PrivateKey = PrivateKey.from_bytes( secret_exponent_for_index(1).to_bytes(32, "big"), ) pk: G1Element = G1Element.from_bytes( public_key_for_index(1, key_lookup)) starting_puzzle: Program = puzzle_for_pk(pk) starting_ph: bytes32 = starting_puzzle.get_tree_hash() # Get our starting standard coin created START_AMOUNT: uint64 = 1023 coin_db = CoinStore() time = CoinTimestamp(10000000, 1) coin_db.farm_coin(starting_ph, time, START_AMOUNT) starting_coin: Coin = next(coin_db.all_unspent_coins()) # LAUNCHING # Create the escaping inner puzzle GENESIS_CHALLENGE = bytes32.fromhex( "ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb") launcher_coin = singleton_top_layer.generate_launcher_coin( starting_coin, START_AMOUNT, ) DELAY_TIME = uint64(60800) DELAY_PH = starting_ph launcher_id = launcher_coin.name() relative_lock_height: uint32 = uint32(5000) # use a dummy pool state pool_state = PoolState( owner_pubkey=pk, pool_url="", relative_lock_height=relative_lock_height, state=3, # farming to pool target_puzzle_hash=starting_ph, version=1, ) # create a new dummy pool state for travelling target_pool_state = PoolState( owner_pubkey=pk, pool_url="", relative_lock_height=relative_lock_height, state=2, # Leaving pool target_puzzle_hash=starting_ph, version=1, ) # Standard format comment comment = Program.to([("p", bytes(pool_state)), ("t", DELAY_TIME), ("h", DELAY_PH)]) pool_wr_innerpuz: bytes32 = create_waiting_room_inner_puzzle( starting_ph, relative_lock_height, pk, launcher_id, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) pool_wr_inner_hash = pool_wr_innerpuz.get_tree_hash() pooling_innerpuz: Program = create_pooling_inner_puzzle( starting_ph, pool_wr_inner_hash, pk, launcher_id, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # Driver tests assert is_pool_singleton_inner_puzzle(pooling_innerpuz) assert is_pool_singleton_inner_puzzle(pool_wr_innerpuz) assert get_pubkey_from_member_inner_puzzle(pooling_innerpuz) == pk # Generating launcher information conditions, launcher_coinsol = singleton_top_layer.launch_conditions_and_coinsol( starting_coin, pooling_innerpuz, comment, START_AMOUNT) # Creating solution for standard transaction delegated_puzzle: Program = puzzle_for_conditions(conditions) full_solution: Program = solution_for_conditions(conditions) starting_coinsol = CoinSolution( starting_coin, starting_puzzle, full_solution, ) # Create the spend bundle sig: G2Element = sign_delegated_puz(delegated_puzzle, starting_coin) spend_bundle = SpendBundle( [starting_coinsol, launcher_coinsol], sig, ) # Spend it! coin_db.update_coin_store_for_spend_bundle( spend_bundle, time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # Test that we can retrieve the extra data assert get_delayed_puz_info_from_launcher_spend(launcher_coinsol) == ( DELAY_TIME, DELAY_PH) assert solution_to_extra_data(launcher_coinsol) == pool_state # TEST TRAVEL AFTER LAUNCH # fork the state fork_coin_db: CoinStore = copy.deepcopy(coin_db) post_launch_coinsol, _ = create_travel_spend( launcher_coinsol, launcher_coin, pool_state, target_pool_state, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # Spend it! fork_coin_db.update_coin_store_for_spend_bundle( SpendBundle([post_launch_coinsol], G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # HONEST ABSORB time = CoinTimestamp(10000030, 2) # create the farming reward p2_singleton_puz: Program = create_p2_singleton_puzzle( SINGLETON_MOD_HASH, launcher_id, DELAY_TIME, DELAY_PH, ) p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash() assert uncurry_pool_waitingroom_inner_puzzle(pool_wr_innerpuz) == ( starting_ph, relative_lock_height, pk, p2_singleton_ph, ) assert launcher_id_to_p2_puzzle_hash(launcher_id, DELAY_TIME, DELAY_PH) == p2_singleton_ph assert get_seconds_and_delayed_puzhash_from_p2_singleton_puzzle( p2_singleton_puz) == (DELAY_TIME, DELAY_PH) coin_db.farm_coin(p2_singleton_ph, time, 1750000000000) coin_sols: List[CoinSolution] = create_absorb_spend( launcher_coinsol, pool_state, launcher_coin, 2, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, # height ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle(coin_sols, G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # ABSORB A NON EXISTENT REWARD (Negative test) last_coinsol: CoinSolution = list( filter( lambda e: e.coin.amount == START_AMOUNT, coin_sols, ))[0] coin_sols: List[CoinSolution] = create_absorb_spend( last_coinsol, pool_state, launcher_coin, 2, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, # height ) # filter for only the singleton solution singleton_coinsol: CoinSolution = list( filter( lambda e: e.coin.amount == START_AMOUNT, coin_sols, ))[0] # Spend it and hope it fails! try: coin_db.update_coin_store_for_spend_bundle( SpendBundle([singleton_coinsol], G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) except BadSpendBundleError as e: assert str( e ) == "condition validation failure Err.ASSERT_ANNOUNCE_CONSUMED_FAILED" # SPEND A NON-REWARD P2_SINGLETON (Negative test) # create the dummy coin non_reward_p2_singleton = Coin( bytes32(32 * b"3"), p2_singleton_ph, uint64(1337), ) coin_db._add_coin_entry(non_reward_p2_singleton, time) # construct coin solution for the p2_singleton coin bad_coinsol = CoinSolution( non_reward_p2_singleton, p2_singleton_puz, Program.to([ pooling_innerpuz.get_tree_hash(), non_reward_p2_singleton.name(), ]), ) # Spend it and hope it fails! try: coin_db.update_coin_store_for_spend_bundle( SpendBundle([singleton_coinsol, bad_coinsol], G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) except BadSpendBundleError as e: assert str( e ) == "condition validation failure Err.ASSERT_ANNOUNCE_CONSUMED_FAILED" # ENTER WAITING ROOM # find the singleton singleton = get_most_recent_singleton_coin_from_coin_solution( last_coinsol) # get the relevant coin solution travel_coinsol, _ = create_travel_spend( last_coinsol, launcher_coin, pool_state, target_pool_state, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # Test that we can retrieve the extra data assert solution_to_extra_data(travel_coinsol) == target_pool_state # sign the serialized state data = Program.to(bytes(target_pool_state)).get_tree_hash() sig: G2Element = AugSchemeMPL.sign( sk, (data + singleton.name() + DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA), ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle([travel_coinsol], sig), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # ESCAPE TOO FAST (Negative test) # find the singleton singleton = get_most_recent_singleton_coin_from_coin_solution( travel_coinsol) # get the relevant coin solution return_coinsol, _ = create_travel_spend( travel_coinsol, launcher_coin, target_pool_state, pool_state, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # sign the serialized target state sig = AugSchemeMPL.sign( sk, (data + singleton.name() + DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA), ) # Spend it and hope it fails! try: coin_db.update_coin_store_for_spend_bundle( SpendBundle([return_coinsol], sig), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) except BadSpendBundleError as e: assert str( e ) == "condition validation failure Err.ASSERT_HEIGHT_RELATIVE_FAILED" # ABSORB WHILE IN WAITING ROOM time = CoinTimestamp(10000060, 3) # create the farming reward coin_db.farm_coin(p2_singleton_ph, time, 1750000000000) # generate relevant coin solutions coin_sols: List[CoinSolution] = create_absorb_spend( travel_coinsol, target_pool_state, launcher_coin, 3, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, # height ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle(coin_sols, G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # LEAVE THE WAITING ROOM time = CoinTimestamp(20000000, 10000) # find the singleton singleton_coinsol: CoinSolution = list( filter( lambda e: e.coin.amount == START_AMOUNT, coin_sols, ))[0] singleton: Coin = get_most_recent_singleton_coin_from_coin_solution( singleton_coinsol) # get the relevant coin solution return_coinsol, _ = create_travel_spend( singleton_coinsol, launcher_coin, target_pool_state, pool_state, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # Test that we can retrieve the extra data assert solution_to_extra_data(return_coinsol) == pool_state # sign the serialized target state data = Program.to([ pooling_innerpuz.get_tree_hash(), START_AMOUNT, bytes(pool_state) ]).get_tree_hash() sig: G2Element = AugSchemeMPL.sign( sk, (data + singleton.name() + DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA), ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle([return_coinsol], sig), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # ABSORB ONCE MORE FOR GOOD MEASURE time = CoinTimestamp(20000000, 10005) # create the farming reward coin_db.farm_coin(p2_singleton_ph, time, 1750000000000) coin_sols: List[CoinSolution] = create_absorb_spend( return_coinsol, pool_state, launcher_coin, 10005, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, # height ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle(coin_sols, G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, )
def create_absorb_spend( last_coin_solution: CoinSolution, current_state: PoolState, launcher_coin: Coin, height: uint32, genesis_challenge: bytes32, delay_time: uint64, delay_ph: bytes32, ) -> List[CoinSolution]: inner_puzzle: Program = pool_state_to_inner_puzzle( current_state, launcher_coin.name(), genesis_challenge, delay_time, delay_ph ) reward_amount: uint64 = calculate_pool_reward(height) if is_pool_member_inner_puzzle(inner_puzzle): # inner sol is (spend_type, pool_reward_amount, pool_reward_height, extra_data) inner_sol: Program = Program.to([reward_amount, height]) elif is_pool_waitingroom_inner_puzzle(inner_puzzle): # inner sol is (spend_type, destination_puzhash, pool_reward_amount, pool_reward_height, extra_data) inner_sol = Program.to([0, reward_amount, height]) else: raise ValueError # full sol = (parent_info, my_amount, inner_solution) coin: Optional[Coin] = get_most_recent_singleton_coin_from_coin_solution(last_coin_solution) assert coin is not None if coin.parent_coin_info == launcher_coin.name(): parent_info: Program = Program.to([launcher_coin.parent_coin_info, launcher_coin.amount]) else: p = Program.from_bytes(bytes(last_coin_solution.puzzle_reveal)) last_coin_solution_inner_puzzle: Optional[Program] = get_inner_puzzle_from_puzzle(p) assert last_coin_solution_inner_puzzle is not None parent_info = Program.to( [ last_coin_solution.coin.parent_coin_info, last_coin_solution_inner_puzzle.get_tree_hash(), last_coin_solution.coin.amount, ] ) full_solution: SerializedProgram = SerializedProgram.from_program( Program.to([parent_info, last_coin_solution.coin.amount, inner_sol]) ) full_puzzle: SerializedProgram = SerializedProgram.from_program( create_full_puzzle(inner_puzzle, launcher_coin.name()) ) assert coin.puzzle_hash == full_puzzle.get_tree_hash() reward_parent: bytes32 = pool_parent_id(height, genesis_challenge) p2_singleton_puzzle: SerializedProgram = SerializedProgram.from_program( create_p2_singleton_puzzle(SINGLETON_MOD_HASH, launcher_coin.name(), delay_time, delay_ph) ) reward_coin: Coin = Coin(reward_parent, p2_singleton_puzzle.get_tree_hash(), reward_amount) p2_singleton_solution: SerializedProgram = SerializedProgram.from_program( Program.to([inner_puzzle.get_tree_hash(), reward_coin.name()]) ) assert p2_singleton_puzzle.get_tree_hash() == reward_coin.puzzle_hash assert full_puzzle.get_tree_hash() == coin.puzzle_hash assert get_inner_puzzle_from_puzzle(Program.from_bytes(bytes(full_puzzle))) is not None coin_solutions = [ CoinSolution(coin, full_puzzle, full_solution), CoinSolution(reward_coin, p2_singleton_puzzle, p2_singleton_solution), ] return coin_solutions
def get_most_recent_singleton_coin_from_coin_solution(coin_sol: CoinSolution) -> Optional[Coin]: additions: List[Coin] = coin_sol.additions() for coin in additions: if coin.amount % 2 == 1: return coin return None
def create_travel_spend( last_coin_solution: CoinSolution, launcher_coin: Coin, current: PoolState, target: PoolState, genesis_challenge: bytes32, delay_time: uint64, delay_ph: bytes32, ) -> Tuple[CoinSolution, Program]: inner_puzzle: Program = pool_state_to_inner_puzzle( current, launcher_coin.name(), genesis_challenge, delay_time, delay_ph, ) if is_pool_member_inner_puzzle(inner_puzzle): # inner sol is key_value_list () # key_value_list is: # "ps" -> poolstate as bytes inner_sol: Program = Program.to([[("p", bytes(target))], 0]) elif is_pool_waitingroom_inner_puzzle(inner_puzzle): # inner sol is (spend_type, key_value_list, pool_reward_height) destination_inner: Program = pool_state_to_inner_puzzle( target, launcher_coin.name(), genesis_challenge, delay_time, delay_ph ) log.warning( f"create_travel_spend: waitingroom: target PoolState bytes:\n{bytes(target).hex()}\n" f"{target}" f"hash:{Program.to(bytes(target)).get_tree_hash()}" ) # key_value_list is: # "ps" -> poolstate as bytes inner_sol = Program.to([1, [("p", bytes(target))], destination_inner.get_tree_hash()]) # current or target else: raise ValueError current_singleton: Optional[Coin] = get_most_recent_singleton_coin_from_coin_solution(last_coin_solution) assert current_singleton is not None if current_singleton.parent_coin_info == launcher_coin.name(): parent_info_list = Program.to([launcher_coin.parent_coin_info, launcher_coin.amount]) else: p = Program.from_bytes(bytes(last_coin_solution.puzzle_reveal)) last_coin_solution_inner_puzzle: Optional[Program] = get_inner_puzzle_from_puzzle(p) assert last_coin_solution_inner_puzzle is not None parent_info_list = Program.to( [ last_coin_solution.coin.parent_coin_info, last_coin_solution_inner_puzzle.get_tree_hash(), last_coin_solution.coin.amount, ] ) full_solution: Program = Program.to([parent_info_list, current_singleton.amount, inner_sol]) full_puzzle: Program = create_full_puzzle(inner_puzzle, launcher_coin.name()) return ( CoinSolution( current_singleton, SerializedProgram.from_program(full_puzzle), SerializedProgram.from_program(full_solution), ), inner_puzzle, )
async def create_attestment(self, recovering_coin_name: bytes32, newpuz: bytes32, pubkey: G1Element, filename=None) -> SpendBundle: assert self.did_info.current_inner is not None assert self.did_info.origin_coin is not None coins = await self.select_coins(1) assert coins is not None and coins != set() coin = coins.pop() message = did_wallet_puzzles.create_recovery_message_puzzle( recovering_coin_name, newpuz, pubkey) innermessage = message.get_tree_hash() # innerpuz solution is (mode amount new_puz identity my_puz) innersol = Program.to([ 1, coin.amount, innermessage, recovering_coin_name, coin.puzzle_hash ]) # full solution is (corehash parent_info my_amount innerpuz_reveal solution) innerpuz: Program = self.did_info.current_inner full_puzzle: Program = did_wallet_puzzles.create_fullpuz( innerpuz, self.did_info.origin_coin.puzzle_hash, ) parent_info = await self.get_parent_for_coin(coin) assert parent_info is not None fullsol = Program.to([ [ self.did_info.origin_coin.parent_coin_info, self.did_info.origin_coin.amount ], [ parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ], coin.amount, innersol, ]) list_of_solutions = [CoinSolution(coin, full_puzzle, fullsol)] message_spend = did_wallet_puzzles.create_spend_for_message( coin.name(), recovering_coin_name, newpuz, pubkey) message_spend_bundle = SpendBundle([message_spend], AugSchemeMPL.aggregate([])) # sign for AGG_SIG_ME to_sign = Program.to([coin.puzzle_hash, coin.amount, innermessage]).get_tree_hash() message = to_sign + coin.name( ) + self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA pubkey = did_wallet_puzzles.get_pubkey_from_innerpuz(innerpuz) index = await self.wallet_state_manager.puzzle_store.index_for_pubkey( pubkey) private = master_sk_to_wallet_sk(self.wallet_state_manager.private_key, index) signature = AugSchemeMPL.sign(private, message) # assert signature.validate([signature.PkMessagePair(pubkey, message)]) spend_bundle = SpendBundle(list_of_solutions, signature) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=coin.puzzle_hash, amount=uint64(coin.amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.wallet_info.id, sent_to=[], trade_id=None, type=uint32(TransactionType.INCOMING_TX.value), name=token_bytes(), ) await self.standard_wallet.push_transaction(did_record) if filename is not None: f = open(filename, "w") f.write(self.get_my_DID()) f.write(":") f.write(bytes(message_spend_bundle).hex()) f.write(":") parent = coin.parent_coin_info.hex() innerpuzhash = self.did_info.current_inner.get_tree_hash().hex() amount = coin.amount f.write(parent) f.write(":") f.write(innerpuzhash) f.write(":") f.write(str(amount)) f.close() return message_spend_bundle
def generate_unsigned_transaction( self, amount: uint64, new_puzzle_hash: bytes32, coins: List[Coin], condition_dic: Dict[ConditionOpcode, List[ConditionWithArgs]], fee: int = 0, secret_key: Optional[PrivateKey] = None, ) -> List[CoinSolution]: spends = [] spend_value = sum([c.amount for c in coins]) if ConditionOpcode.CREATE_COIN not in condition_dic: condition_dic[ConditionOpcode.CREATE_COIN] = [] if ConditionOpcode.CREATE_COIN_ANNOUNCEMENT not in condition_dic: condition_dic[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT] = [] output = ConditionWithArgs( ConditionOpcode.CREATE_COIN, [new_puzzle_hash, int_to_bytes(amount)]) condition_dic[output.opcode].append(output) amount_total = sum( int_from_bytes(cvp.vars[1]) for cvp in condition_dic[ConditionOpcode.CREATE_COIN]) change = spend_value - amount_total - fee if change > 0: change_puzzle_hash = self.get_new_puzzlehash() change_output = ConditionWithArgs( ConditionOpcode.CREATE_COIN, [change_puzzle_hash, int_to_bytes(change)]) condition_dic[output.opcode].append(change_output) secondary_coins_cond_dic: Dict[ConditionOpcode, List[ConditionWithArgs]] = dict() secondary_coins_cond_dic[ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT] = [] for n, coin in enumerate(coins): puzzle_hash = coin.puzzle_hash if secret_key is None: secret_key = self.get_private_key_for_puzzle_hash(puzzle_hash) pubkey = secret_key.get_g1() puzzle = puzzle_for_pk(bytes(pubkey)) if n == 0: message_list = [c.name() for c in coins] for outputs in condition_dic[ConditionOpcode.CREATE_COIN]: message_list.append( Coin(coin.name(), outputs.vars[0], int_from_bytes(outputs.vars[1])).name()) message = std_hash(b"".join(message_list)) condition_dic[ConditionOpcode.CREATE_COIN_ANNOUNCEMENT].append( ConditionWithArgs(ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [message])) primary_announcement_hash = Announcement(coin.name(), message).name() secondary_coins_cond_dic[ ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT].append( ConditionWithArgs( ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, [primary_announcement_hash])) main_solution = self.make_solution(condition_dic) spends.append(CoinSolution(coin, puzzle, main_solution)) else: spends.append( CoinSolution(coin, puzzle, self.make_solution(secondary_coins_cond_dic))) return spends
async def create_spend(self, puzhash: bytes32): assert self.did_info.current_inner is not None assert self.did_info.origin_coin is not None coins = await self.select_coins(1) assert coins is not None coin = coins.pop() # innerpuz solution is (mode amount new_puz identity my_puz) innersol: Program = Program.to( [0, coin.amount, puzhash, coin.name(), coin.puzzle_hash]) # full solution is (corehash parent_info my_amount innerpuz_reveal solution) innerpuz: Program = self.did_info.current_inner full_puzzle: Program = did_wallet_puzzles.create_fullpuz( innerpuz, self.did_info.origin_coin.puzzle_hash, ) parent_info = await self.get_parent_for_coin(coin) assert parent_info is not None fullsol = Program.to([ [ self.did_info.origin_coin.parent_coin_info, self.did_info.origin_coin.amount ], [ parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ], coin.amount, innersol, ]) list_of_solutions = [CoinSolution(coin, full_puzzle, fullsol)] # sign for AGG_SIG_ME message = ( Program.to([coin.amount, puzhash]).get_tree_hash() + coin.name() + self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA) pubkey = did_wallet_puzzles.get_pubkey_from_innerpuz(innerpuz) index = await self.wallet_state_manager.puzzle_store.index_for_pubkey( pubkey) private = master_sk_to_wallet_sk(self.wallet_state_manager.private_key, index) signature = AugSchemeMPL.sign(private, message) # assert signature.validate([signature.PkMessagePair(pubkey, message)]) sigs = [signature] aggsig = AugSchemeMPL.aggregate(sigs) spend_bundle = SpendBundle(list_of_solutions, aggsig) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzhash, amount=uint64(coin.amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.wallet_info.id, sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=token_bytes(), ) await self.standard_wallet.push_transaction(did_record) return spend_bundle
async def _generate_unsigned_transaction( self, amount: uint64, newpuzzlehash: bytes32, fee: uint64 = uint64(0), origin_id: bytes32 = None, coins: Set[Coin] = None, primaries_input: Optional[List[Dict[str, Any]]] = None, ignore_max_send_amount: bool = False, ) -> List[CoinSolution]: """ Generates a unsigned transaction in form of List(Puzzle, Solutions) Note: this must be called under a wallet state manager lock """ if primaries_input is None: primaries: Optional[List[Dict]] = None total_amount = amount + fee else: primaries = primaries_input.copy() primaries_amount = 0 for prim in primaries: primaries_amount += prim["amount"] total_amount = amount + fee + primaries_amount if not ignore_max_send_amount: max_send = await self.get_max_send_amount() if total_amount > max_send: raise ValueError( f"Can't send more than {max_send} in a single transaction") if coins is None: coins = await self.select_coins(total_amount) assert len(coins) > 0 self.log.info(f"coins is not None {coins}") spend_value = sum([coin.amount for coin in coins]) change = spend_value - total_amount assert change >= 0 spends: List[CoinSolution] = [] primary_announcement_hash: Optional[bytes32] = None # Check for duplicates if primaries is not None: all_primaries_list = [ (p["puzzlehash"], p["amount"]) for p in primaries ] + [(newpuzzlehash, amount)] if len(set(all_primaries_list)) != len(all_primaries_list): raise ValueError("Cannot create two identical coins") for coin in coins: self.log.info(f"coin from coins {coin}") puzzle: Program = await self.puzzle_for_puzzle_hash( coin.puzzle_hash) # Only one coin creates outputs if primary_announcement_hash is None and origin_id in ( None, coin.name()): if primaries is None: primaries = [{ "puzzlehash": newpuzzlehash, "amount": amount }] else: primaries.append({ "puzzlehash": newpuzzlehash, "amount": amount }) if change > 0: change_puzzle_hash: bytes32 = await self.get_new_puzzlehash( ) primaries.append({ "puzzlehash": change_puzzle_hash, "amount": change }) message_list: List[bytes32] = [c.name() for c in coins] for primary in primaries: message_list.append( Coin(coin.name(), primary["puzzlehash"], primary["amount"]).name()) message: bytes32 = std_hash(b"".join(message_list)) solution: Program = self.make_solution( primaries=primaries, fee=fee, coin_announcements=[message]) primary_announcement_hash = Announcement(coin.name(), message).name() else: solution = self.make_solution( coin_announcements_to_assert=[primary_announcement_hash]) spends.append( CoinSolution(coin, SerializedProgram.from_bytes(bytes(puzzle)), SerializedProgram.from_bytes(bytes(solution)))) self.log.info(f"Spends is {spends}") return spends
async def recovery_spend( self, coin: Coin, puzhash: bytes, parent_innerpuzhash_amounts_for_recovery_ids: List[Tuple[bytes, bytes, int]], pubkey: G1Element, spend_bundle: SpendBundle, ) -> SpendBundle: assert self.did_info.origin_coin is not None # innerpuz solution is (mode amount new_puz identity my_puz parent_innerpuzhash_amounts_for_recovery_ids) innersol = Program.to([ 2, coin.amount, puzhash, coin.name(), coin.puzzle_hash, parent_innerpuzhash_amounts_for_recovery_ids, bytes(pubkey), self.did_info.backup_ids, self.did_info.num_of_backup_ids_needed, ]) # full solution is (parent_info my_amount solution) innerpuz = self.did_info.current_inner full_puzzle: Program = did_wallet_puzzles.create_fullpuz( innerpuz, self.did_info.origin_coin.puzzle_hash, ) parent_info = await self.get_parent_for_coin(coin) assert parent_info is not None fullsol = Program.to([ [ self.did_info.origin_coin.parent_coin_info, self.did_info.origin_coin.amount ], [ parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ], coin.amount, innersol, ]) list_of_solutions = [CoinSolution(coin, full_puzzle, fullsol)] index = await self.wallet_state_manager.puzzle_store.index_for_pubkey( pubkey) if index is None: raise ValueError("Unknown pubkey.") private = master_sk_to_wallet_sk(self.wallet_state_manager.private_key, index) message = bytes(puzhash) sigs = [AugSchemeMPL.sign(private, message)] for _ in spend_bundle.coin_solutions: sigs.append(AugSchemeMPL.sign(private, message)) aggsig = AugSchemeMPL.aggregate(sigs) # assert AugSchemeMPL.verify(pubkey, message, aggsig) if spend_bundle is None: spend_bundle = SpendBundle(list_of_solutions, aggsig) else: spend_bundle = spend_bundle.aggregate( [spend_bundle, SpendBundle(list_of_solutions, aggsig)]) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzhash, amount=uint64(coin.amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.wallet_info.id, sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=token_bytes(), ) await self.standard_wallet.push_transaction(did_record) return spend_bundle
def test_singleton_top_layer(self): # START TESTS # Generate starting info key_lookup = KeyTool() pk: G1Element = public_key_for_index(1, key_lookup) starting_puzzle: Program = p2_delegated_puzzle_or_hidden_puzzle.puzzle_for_pk( pk) # noqa adapted_puzzle: Program = singleton_top_layer.adapt_inner_to_singleton( starting_puzzle) # noqa adapted_puzzle_hash: bytes32 = adapted_puzzle.get_tree_hash() # Get our starting standard coin created START_AMOUNT: uint64 = 1023 coin_db = CoinStore() coin_db.farm_coin(starting_puzzle.get_tree_hash(), T1, START_AMOUNT) starting_coin: Coin = next(coin_db.all_unspent_coins()) comment: List[Tuple[str, str]] = [("hello", "world")] # LAUNCHING # Try to create an even singleton (driver test) try: conditions, launcher_coinsol = singleton_top_layer.launch_conditions_and_coinsol( # noqa starting_coin, adapted_puzzle, comment, (START_AMOUNT - 1)) raise AssertionError("This should fail due to an even amount") except ValueError as msg: assert str(msg) == "Coin amount cannot be even. Subtract one mojo." conditions, launcher_coinsol = singleton_top_layer.launch_conditions_and_coinsol( # noqa starting_coin, adapted_puzzle, comment, START_AMOUNT) # Creating solution for standard transaction delegated_puzzle: Program = p2_conditions.puzzle_for_conditions( conditions) # noqa full_solution: Program = p2_delegated_puzzle_or_hidden_puzzle.solution_for_conditions( conditions) # noqa starting_coinsol = CoinSolution( starting_coin, starting_puzzle, full_solution, ) make_and_spend_bundle( coin_db, starting_coin, delegated_puzzle, [starting_coinsol, launcher_coinsol], ) # EVE singleton_eve: Coin = next(coin_db.all_unspent_coins()) launcher_coin: Coin = singleton_top_layer.generate_launcher_coin( starting_coin, START_AMOUNT, ) launcher_id: bytes32 = launcher_coin.name() # This delegated puzzle just recreates the coin exactly delegated_puzzle: Program = Program.to(( 1, [[ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, singleton_eve.amount, ]], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) # Generate the lineage proof we will need from the launcher coin lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( launcher_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_eve.amount, inner_solution, ) singleton_eve_coinsol = CoinSolution( singleton_eve, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton_eve, delegated_puzzle, [singleton_eve_coinsol], ) # POST-EVE singleton: Coin = next(coin_db.all_unspent_coins()) # Same delegated_puzzle / inner_solution. We're just recreating ourself lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_eve_coinsol) # noqa # Same puzzle_reveal too full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton.amount, inner_solution, ) singleton_coinsol = CoinSolution( singleton, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton, delegated_puzzle, [singleton_coinsol], ) # CLAIM A P2_SINGLETON singleton_child: Coin = next(coin_db.all_unspent_coins()) p2_singleton_puz: Program = singleton_top_layer.pay_to_singleton_puzzle( launcher_id) p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash() ARBITRARY_AMOUNT: uint64 = 1379 coin_db.farm_coin(p2_singleton_ph, T1, ARBITRARY_AMOUNT) p2_singleton_coin: Coin = list( filter( lambda e: e.amount == ARBITRARY_AMOUNT, list(coin_db.all_unspent_coins()), ))[0] assertion, announcement, claim_coinsol = singleton_top_layer.claim_p2_singleton( p2_singleton_coin, adapted_puzzle_hash, launcher_id, ) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, singleton_eve.amount ], assertion, announcement, ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_eve.amount, inner_solution, ) singleton_claim_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle(coin_db, singleton_child, delegated_puzzle, [singleton_claim_coinsol, claim_coinsol]) # CLAIM A P2_SINGLETON_OR_DELAYED singleton_child: Coin = next(coin_db.all_unspent_coins()) DELAY_TIME: uint64 = 1 DELAY_PH: bytes32 = adapted_puzzle_hash p2_singleton_puz: Program = singleton_top_layer.pay_to_singleton_or_delay_puzzle( launcher_id, DELAY_TIME, DELAY_PH, ) p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash() ARBITRARY_AMOUNT: uint64 = 1379 coin_db.farm_coin(p2_singleton_ph, T1, ARBITRARY_AMOUNT) p2_singleton_coin: Coin = list( filter( lambda e: e.amount == ARBITRARY_AMOUNT, list(coin_db.all_unspent_coins()), ))[0] assertion, announcement, claim_coinsol = singleton_top_layer.claim_p2_singleton( p2_singleton_coin, adapted_puzzle_hash, launcher_id, ) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, singleton_eve.amount ], assertion, announcement, ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_eve.amount, inner_solution, ) delay_claim_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) # Fork it so we can try the other spend types fork_coin_db: CoinStore = copy.deepcopy(coin_db) fork_coin_db_2: CoinStore = copy.deepcopy(coin_db) make_and_spend_bundle(coin_db, singleton_child, delegated_puzzle, [delay_claim_coinsol, claim_coinsol]) # TRY TO SPEND AWAY TOO SOON (Negative Test) to_delay_ph_coinsol = singleton_top_layer.spend_to_delayed_puzzle( p2_singleton_coin, ARBITRARY_AMOUNT, launcher_id, DELAY_TIME, DELAY_PH, ) try: fork_coin_db.update_coin_store_for_spend_bundle( SpendBundle([to_delay_ph_coinsol], G2Element()), T1, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) except BadSpendBundleError as e: assert str( e ) == "condition validation failure Err.ASSERT_SECONDS_RELATIVE_FAILED" # SPEND TO DELAYED PUZZLE HASH fork_coin_db_2.update_coin_store_for_spend_bundle( SpendBundle([to_delay_ph_coinsol], G2Element()), CoinTimestamp(100, 10000005), DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # CREATE MULTIPLE ODD CHILDREN (Negative Test) singleton_child: Coin = next(coin_db.all_unspent_coins()) delegated_puzzle: Program = Program.to(( 1, [ [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 3], [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 7], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) multi_odd_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton_child, delegated_puzzle, [multi_odd_coinsol], exception=BadSpendBundleError, ex_msg="clvm validation failure Err.SEXP_ERROR", fail_msg="Too many odd children were allowed", ) # CREATE NO ODD CHILDREN (Negative Test) delegated_puzzle: Program = Program.to(( 1, [ [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 4], [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 10], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) no_odd_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton_child, delegated_puzzle, [no_odd_coinsol], exception=BadSpendBundleError, ex_msg="clvm validation failure Err.SEXP_ERROR", fail_msg="Need at least one odd child", ) # ATTEMPT TO CREATE AN EVEN SINGLETON (Negative test) fork_coin_db: CoinStore = copy.deepcopy(coin_db) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, singleton_child.puzzle_hash, 2, ], [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 1], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( delay_claim_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) singleton_even_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle( fork_coin_db, singleton_child, delegated_puzzle, [singleton_even_coinsol], ) # Now try a perfectly innocent spend evil_coin: Coin = next(fork_coin_db.all_unspent_coins()) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 1, ], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_even_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, 1, inner_solution, ) evil_coinsol = CoinSolution( evil_coin, puzzle_reveal, full_solution, ) make_and_spend_bundle( fork_coin_db, evil_coin, delegated_puzzle, [evil_coinsol], exception=BadSpendBundleError, ex_msg="condition validation failure Err.ASSERT_MY_COIN_ID_FAILED", fail_msg="This coin is even!", ) # MELTING # Remember, we're still spending singleton_child conditions = [ singleton_top_layer.MELT_CONDITION, [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, (singleton_child.amount - 1), ], ] delegated_puzzle: Program = p2_conditions.puzzle_for_conditions( conditions) inner_solution: Program = p2_delegated_puzzle_or_hidden_puzzle.solution_for_conditions( conditions) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( delay_claim_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) melt_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton_child, delegated_puzzle, [melt_coinsol], ) melted_coin = next(coin_db.all_unspent_coins()) assert melted_coin.puzzle_hash == adapted_puzzle_hash