def run_test(puzzle_hash, solution, payments): run = asyncio.get_event_loop().run_until_complete remote = make_client_server() coin = farm_spendable_coin(remote, puzzle_hash) spend_bundle = build_spend_bundle(coin, solution) # push it r = run(remote.push_tx(tx=spend_bundle)) assert r["response"].startswith("accepted") print(r) # confirm it farm_spendable_coin(remote) # get unspents r = run(remote.all_unspents()) print("unspents = %s" % r.get("unspents")) unspents = r["unspents"] # ensure all outputs are there for puzzle_hash, amount in payments: expected_coin = Coin(coin.name(), puzzle_hash, amount) name = expected_coin.name() assert name in unspents unspent = run(remote.unspent_for_coin_name(coin_name=name)) assert unspent.confirmed_block_index == 2 assert unspent.spent_block_index == 0
async def process_blocks(wallet, ledger_api, last_known_header, current_header_hash): r = await ledger_api.hash_preimage(hash=current_header_hash) header = Header.from_bytes(r) body = Body.from_bytes(await ledger_api.hash_preimage(hash=header.body_hash)) if header.previous_hash != last_known_header: await process_blocks(wallet, ledger_api, last_known_header, header.previous_hash) print(f'processing block {HeaderHash(header)}') additions = list(additions_for_body(body)) removals = removals_for_body(body) removals = [ Coin.from_bytes(await ledger_api.hash_preimage(hash=x)) for x in removals ] wallet.notify(additions, removals) clawback_coins = [coin for coin in additions if wallet.is_in_escrow(coin)] if len(clawback_coins) != 0: print(f'WARNING! Coins from this wallet have been moved to escrow!\n' f'Attempting to send a clawback for these coins:') for coin in clawback_coins: print(f'Coin ID: {coin.name()}, Amount: {coin.amount}') transaction = wallet.generate_clawback_transaction(clawback_coins) r = await ledger_api.push_tx(tx=transaction) if type(r) is RemoteError: print('Clawback failed') else: print('Clawback transaction submitted')
def commit_and_notify(remote, wallets, reward_recipient): run = asyncio.get_event_loop().run_until_complete coinbase_puzzle_hash = reward_recipient.get_new_puzzlehash() fees_puzzle_hash = reward_recipient.get_new_puzzlehash() r = run( remote.next_block(coinbase_puzzle_hash=coinbase_puzzle_hash, fees_puzzle_hash=fees_puzzle_hash)) body = r.get("body") additions = list(additions_for_body(body)) removals = removals_for_body(body) removals = [ Coin.from_bytes(run(remote.hash_preimage(hash=x))) for x in removals ] tip = run(remote.get_tip()) index = int(tip["tip_index"]) for wallet in wallets: if isinstance(wallet, RLWallet): spend_bundle = wallet.notify(additions, removals, index) else: spend_bundle = wallet.notify(additions, removals) if spend_bundle is not None: for bun in spend_bundle: _ = run(remote.push_tx(tx=bun))
def generate_unsigned_transaction(self, amount, newpuzzlehash): if self.temp_balance < amount: return None # TODO: Should we throw a proper error here, or just return None? utxos = self.select_coins(amount) spends = [] output_id = None spend_value = sum([coin.amount for coin in utxos]) change = spend_value - amount for coin in utxos: puzzle_hash = coin.puzzle_hash pubkey, secretkey = self.get_keys(puzzle_hash) puzzle = self.puzzle_for_pk(pubkey.serialize()) if output_id is None: primaries = [{'puzzlehash': newpuzzlehash, 'amount': amount}] if change > 0: changepuzzlehash = self.get_new_puzzlehash() primaries.append({ 'puzzlehash': changepuzzlehash, 'amount': change }) # add change coin into temp_utxo set self.temp_utxos.add(Coin(coin, changepuzzlehash, change)) solution = make_solution(primaries=primaries) output_id = sha256(coin.name() + newpuzzlehash) else: solution = make_solution(consumed=[coin.name()]) spends.append((puzzle, CoinSolution(coin, solution))) self.temp_balance -= amount return spends
def farm_new_block(previous_header: HeaderHash, previous_signature: Signature, block_index: int, proof_of_space: ProofOfSpace, spend_bundle: SpendBundle, coinbase_coin: Coin, coinbase_signature: BLSSignature, fees_puzzle_hash: ProgramHash, timestamp: uint64): """ Steps: - collect up a consistent set of removals and solutions - run solutions to get the additions - select a timestamp = max(now, minimum_legal_timestamp) - create blank extension data - collect up coinbase coin with coinbase signature (if solo mining, we get these locally) - return Header, Body """ program_cost = 0 assert validate_spend_bundle_signature(spend_bundle) solution_program = best_solution_program(spend_bundle) extension_data = std_hash(b'') block_index_hash = block_index.to_bytes(32, "big") fees_coin = Coin(block_index_hash, fees_puzzle_hash, spend_bundle.fees()) body = Body(coinbase_signature, coinbase_coin, fees_coin, solution_program, program_cost, spend_bundle.aggregated_signature) header = Header(previous_header, previous_signature, timestamp, proof_of_space, body, extension_data) return header, body
async def do_spend_coin(wallet, storage, input): """ UI to spend a coin. """ coins = [] while True: coin_str = input("Enter hex id of coin to spend> ") if len(coin_str) == 0: break coin_name = bytes.fromhex(coin_str) preimage = await storage.hash_preimage(hash=coin_name) if preimage is None: print(f"can't find coin id {coin_name.hex()}") continue coin = Coin.from_bytes(preimage) coin_puzzle_hash_hex = coin.puzzle_hash.hex() print(f"coin puzzle hash is {coin_puzzle_hash_hex}") coins.append(coin) if len(coins) == 0: return dest_address = "14c56fdefb47e2208de54b6c609a907c522348c96e8cfb41c7a8c75f44835dd9" print(f"sending 1 coin to {dest_address}, rest fees") # create an unfinalized SpendBundle pst = spend_coin(wallet, coins, dest_address) pst_encoded = bytes(pst) print(pst_encoded.hex()) # keep requesting signatures until finalized sigs = [] while True: sig_str = input("Enter a signature> ") try: sig = BLSSignature.from_bytes(bytes.fromhex(sig_str)) except Exception as ex: print("failed: %s" % ex) continue sigs.append(sig) sigs = list(set(sigs)) spend_bundle, summary_list = finalize_pst(wallet, pst, sigs) if spend_bundle: break for summary in summary_list: print( "coin %s has %d of %d sigs" % (summary[0].name(), len(summary[2]), summary[3]) ) print("spend bundle = %s" % bytes(spend_bundle).hex()) # optionally send to ledger sim r = input(f"Send to ledger sim? (y/n)> ") if r.lower().startswith("y"): r = await storage.ledger_sim().push_tx(tx=spend_bundle) return spend_bundle
async def update_ledger(wallet, ledger_api, most_recent_header): if most_recent_header is None: r = await ledger_api.get_all_blocks() else: r = await ledger_api.get_recent_blocks(most_recent_header=most_recent_header) update_list = BodyList.from_bytes(r) for body in update_list: additions = list(additions_for_body(body)) print(additions) removals = removals_for_body(body) removals = [Coin.from_bytes(await ledger_api.hash_preimage(hash=x)) for x in removals] wallet.notify(additions, removals)
async def new_block(wallet, ledger_api): coinbase_puzzle_hash = wallet.get_new_puzzlehash() fees_puzzle_hash = wallet.get_new_puzzlehash() r = await ledger_api.next_block(coinbase_puzzle_hash=coinbase_puzzle_hash, fees_puzzle_hash=fees_puzzle_hash) body = r["body"] # breakpoint() most_recent_header = r['header'] # breakpoint() additions = list(additions_for_body(body)) removals = removals_for_body(body) removals = [Coin.from_bytes(await ledger_api.hash_preimage(hash=x)) for x in removals] wallet.notify(additions, removals) return most_recent_header
async def all_coins_and_unspents(storage): """ Query the ledger sim instance for all coins and unspents. """ coins = [] unspents = [] coin_name_unspent_pairs = [_ async for _ in storage.all_unspents()] for coin_name, unspent in coin_name_unspent_pairs: preimage = await storage.hash_preimage(hash=coin_name) coin = Coin.from_bytes(preimage) unspents.append(unspent) coins.append(coin) return coins, unspents
async def restore(ledger_api, wallet, header_hash): recovery_string = input( 'Enter the recovery string of the wallet to be restored: ') recovery_dict = recovery_string_to_dict(recovery_string) root_public_key_serialized = recovery_dict['root_public_key'].serialize() recovery_pubkey = recovery_dict['root_public_key'].public_child( 0).get_public_key().serialize() unspent_coins = await get_unspent_coins(ledger_api, header_hash) recoverable_coins = [] print('scanning', end='') for coin in unspent_coins: if wallet.can_generate_puzzle_hash_with_root_public_key( coin.puzzle_hash, root_public_key_serialized, recovery_dict['stake_factor'], recovery_dict['escrow_duration']): recoverable_coins.append(coin) print('*', end='', flush=True) else: print('.', end='', flush=True) recoverable_amount = sum([coin.amount for coin in recoverable_coins]) print( f'\nFound {len(recoverable_coins)} coins totaling {recoverable_amount}' ) stake_amount = round(recoverable_amount * (recovery_dict['stake_factor'] - 1)) if wallet.current_balance < stake_amount: print( f'Insufficient funds to stake the recovery process. {stake_amount} needed.' ) return for coin in recoverable_coins: print(f'Coin ID: {coin.name()}, Amount: {coin.amount}') pubkey = wallet.find_pubkey_for_hash(coin.puzzle_hash, root_public_key_serialized, recovery_dict['stake_factor'], recovery_dict['escrow_duration']) signed_transaction, destination_puzzlehash, amount = \ wallet.generate_signed_recovery_to_escrow_transaction(coin, recovery_pubkey, pubkey, recovery_dict['stake_factor'], recovery_dict['escrow_duration']) child = Coin(coin.name(), destination_puzzlehash, amount) r = await ledger_api.push_tx(tx=signed_transaction) if type(r) is RemoteError: print(f'Failed to recover {coin.name()}') else: print(f'Recovery transaction submitted for Coin ID: {coin.name()}') wallet.escrow_coins[recovery_string].add(child)
def commit_and_notify(remote, wallets, reward_recipient): run = asyncio.get_event_loop().run_until_complete coinbase_puzzle_hash = reward_recipient.get_new_puzzlehash() fees_puzzle_hash = reward_recipient.get_new_puzzlehash() r = run(remote.next_block(coinbase_puzzle_hash=coinbase_puzzle_hash, fees_puzzle_hash=fees_puzzle_hash)) body = r.get("body") additions = list(additions_for_body(body)) removals = [Coin.from_bytes(run(remote.hash_preimage(hash=x))) for x in removals_for_body(body)] for wallet in wallets: wallet.notify(additions, removals) return additions, removals
async def process_blocks(wallet, ledger_api, last_known_header, current_header_hash): r = await ledger_api.hash_preimage(hash=current_header_hash) header = Header.from_bytes(r) body = Body.from_bytes(await ledger_api.hash_preimage(hash=header.body_hash)) if header.previous_hash != last_known_header: await process_blocks(wallet, ledger_api, last_known_header, header.previous_hash) print(f'processing block {HeaderHash(header)}') additions = list(additions_for_body(body)) removals = removals_for_body(body) removals = [ Coin.from_bytes(await ledger_api.hash_preimage(hash=x)) for x in removals ] wallet.notify(additions, removals)
async def update_ledger(wallet, ledger_api, most_recent_header): if most_recent_header is None: r = await ledger_api.get_all_blocks() else: r = await ledger_api.get_recent_blocks(most_recent_header=most_recent_header) update_list = BodyList.from_bytes(r) tip = await ledger_api.get_tip() index = int(tip["tip_index"]) for body in update_list: additions = list(additions_for_body(body)) removals = removals_for_body(body) removals = [Coin.from_bytes(await ledger_api.hash_preimage(hash=x)) for x in removals] spend_bundle_list = wallet.notify(additions, removals, index) if spend_bundle_list is not None: for spend_bundle in spend_bundle_list: _ = await ledger_api.push_tx(tx=spend_bundle) return most_recent_header
async def get_unspent_coins(ledger_api, header_hash): r = await ledger_api.get_tip() if r['genesis_hash'] == header_hash: return set() r = await ledger_api.hash_preimage(hash=header_hash) header = Header.from_bytes(r) unspent_coins = await get_unspent_coins(ledger_api, header.previous_hash) body = Body.from_bytes(await ledger_api.hash_preimage(hash=header.body_hash)) additions = list(additions_for_body(body)) unspent_coins.update(additions) removals = removals_for_body(body) removals = [ Coin.from_bytes(await ledger_api.hash_preimage(hash=x)) for x in removals ] unspent_coins.difference_update(removals) return unspent_coins
def test_recovery_with_block_time(): remote = make_client_server() run = asyncio.get_event_loop().run_until_complete wallet_a = RecoverableWallet(Decimal('1.1'), 1, DurationType.BLOCKS) wallet_b = RecoverableWallet(Decimal('1.1'), 1, DurationType.BLOCKS) farmer = RecoverableWallet(Decimal('1.1'), 1, DurationType.BLOCKS) wallets = [wallet_a, wallet_b, farmer] commit_and_notify(remote, wallets, wallet_a) commit_and_notify(remote, wallets, wallet_b) assert wallet_a.current_balance == 1000000000 assert wallet_b.current_balance == 1000000000 recovery_string = wallet_a.get_backup_string() recovery_dict = recovery_string_to_dict(recovery_string) root_public_key_serialized = bytes(recovery_dict['root_public_key']) recovery_pubkey = bytes(recovery_dict['root_public_key'].public_child(0)) for coin in wallet_a.my_utxos.copy(): pubkey = wallet_b.find_pubkey_for_hash(coin.puzzle_hash, root_public_key_serialized, recovery_dict['stake_factor'], recovery_dict['escrow_duration'], recovery_dict['duration_type']) signed_transaction, destination_puzzlehash, amount = \ wallet_b.generate_signed_recovery_to_escrow_transaction(coin, recovery_pubkey, pubkey, recovery_dict['stake_factor'], recovery_dict['escrow_duration'], recovery_dict['duration_type']) child = Coin(coin.name(), destination_puzzlehash, amount) r = run(remote.push_tx(tx=signed_transaction)) assert(type(r) is not RemoteError) wallet_b.escrow_coins[recovery_string].add(child) commit_and_notify(remote, wallets, farmer) assert wallet_a.current_balance == 0 assert wallet_b.current_balance == 900000000 # check too soon recovery transaction for recovery_string, coin_set in wallet_b.escrow_coins.items(): recovery_dict = recovery_string_to_dict(recovery_string) root_public_key = recovery_dict['root_public_key'] secret_key = recovery_dict['secret_key'] escrow_duration = recovery_dict['escrow_duration'] duration_type = recovery_dict['duration_type'] signed_transaction = wallet_b.generate_recovery_transaction(coin_set, root_public_key, secret_key, escrow_duration, duration_type) r = run(remote.push_tx(tx=signed_transaction)) assert type(r) is RemoteError # pass time commit_and_notify(remote, wallets, farmer) for recovery_string, coin_set in wallet_b.escrow_coins.items(): recovery_dict = recovery_string_to_dict(recovery_string) root_public_key = recovery_dict['root_public_key'] secret_key = recovery_dict['secret_key'] escrow_duration = recovery_dict['escrow_duration'] duration_type = recovery_dict['duration_type'] signed_transaction = wallet_b.generate_recovery_transaction(coin_set, root_public_key, secret_key, escrow_duration, duration_type) r = run(remote.push_tx(tx=signed_transaction)) assert type(r) is not RemoteError commit_and_notify(remote, wallets, farmer) assert wallet_a.current_balance == 0 assert wallet_b.current_balance == 2000000000
def create_coinbase_coin(block_index: int, puzzle_hash: ProgramHash, reward: uint64): block_index_as_hash = block_index.to_bytes(32, "big") return Coin(block_index_as_hash, puzzle_hash, reward)
def signature_for_coinbase(coin: Coin, pool_private_key: blspy.PrivateKey): message_hash = coin.name() return BLSSignature( pool_private_key.sign_prepend_prehashed(message_hash).serialize())