def test_farm_block_one_spendbundle(): REWARD = 10000 unspent_db = RAM_DB() chain_view = ChainView.for_genesis_hash(GENESIS_BLOCK, unspent_db) pos = ProofOfSpace(get_pool_public_key(), get_plot_public_key()) puzzle_hash = puzzle_hash_for_index(1) empty_spend_bundle = SpendBundle.aggregate([]) header, header_signature, body = farm_block(GENESIS_BLOCK, Signature.zero(), 1, pos, empty_spend_bundle, puzzle_hash, REWARD) coinbase_coin = body.coinbase_coin conditions = standard_conditions() spend_bundle = spend_coin(coin=coinbase_coin, conditions=conditions, index=1) header, header_signature, body = farm_block(GENESIS_BLOCK, Signature.zero(), 1, pos, spend_bundle, puzzle_hash, REWARD) removals = removals_for_body(body) assert len(removals) == 1 assert removals[0] == list(spend_bundle.coin_solutions)[0].coin.name() run = asyncio.get_event_loop().run_until_complete additions, removals = run( chain_view.accept_new_block(header, unspent_db, REWARD, 0)) assert len(additions) == 4 assert len(removals) == 1
def test_farm_two_blocks(): """ In this test, we farm two blocks: one empty block, then one block which spends the coinbase transaction from the empty block. """ REWARD = 10000 unspent_db = RAM_DB() chain_view = ChainView.for_genesis_hash(GENESIS_BLOCK, unspent_db) assert chain_view.genesis_hash == GENESIS_BLOCK assert chain_view.tip_hash == HeaderHash(GENESIS_BLOCK) assert chain_view.tip_index == 0 assert chain_view.unspent_db == unspent_db pos_1 = ProofOfSpace(get_pool_public_key(), get_plot_public_key()) puzzle_hash = puzzle_hash_for_index(1) empty_spend_bundle = SpendBundle.aggregate([]) header, header_signature, body = farm_block(GENESIS_BLOCK, Signature.zero(), 1, pos_1, empty_spend_bundle, puzzle_hash, REWARD) run = asyncio.get_event_loop().run_until_complete additions, removals = run( chain_view.accept_new_block(header, unspent_db, REWARD, 0)) assert len(additions) == 2 assert len(removals) == 0 # TODO: check additions assert additions[1].puzzle_hash == body.fees_coin.puzzle_hash assert additions[1].amount == 0 chain_view = run( chain_view.augment_chain_view(header, header_signature, unspent_db, unspent_db, REWARD, 0)) assert chain_view.genesis_hash == GENESIS_BLOCK assert chain_view.tip_hash == HeaderHash(header) assert chain_view.tip_index == 1 assert chain_view.unspent_db == unspent_db conditions = standard_conditions() spend_bundle_2 = spend_coin(coin=additions[0], conditions=conditions, index=1) assert validate_spend_bundle_signature(spend_bundle_2) pos_2 = ProofOfSpace(get_pool_public_key(1), get_plot_public_key()) header_2, header_signature_2, body_2 = farm_block(header, header_signature, 2, pos_2, spend_bundle_2, puzzle_hash, REWARD) print(header_2) print(header_signature_2) removals = removals_for_body(body_2) assert len(removals) == 1 assert removals[0] == list(spend_bundle_2.coin_solutions)[0].coin.name()
def make_client_server(): init_logging() run = asyncio.get_event_loop().run_until_complete path = pathlib.Path(tempfile.mkdtemp(), "port") server, aiter = run(start_unix_server_aiter(path)) rws_aiter = map_aiter(lambda rw: dict( reader=rw[0], writer=rw[1], server=server), aiter) initial_block_hash = bytes(([0] * 31) + [1]) ledger = ledger_api.LedgerAPI(initial_block_hash, RAM_DB()) server_task = asyncio.ensure_future(api_server(rws_aiter, ledger)) remote = run(proxy_for_unix_connection(path)) # make sure server_task isn't garbage collected remote.server_task = server_task return remote
def test_client_server(): init_logging() run = asyncio.get_event_loop().run_until_complete path = pathlib.Path(tempfile.mkdtemp(), "port") server, aiter = run(start_unix_server_aiter(path)) rws_aiter = map_aiter( lambda rw: dict(reader=rw[0], writer=rw[1], server=server), aiter) initial_block_hash = bytes(([0] * 31) + [1]) ledger = ledger_api.LedgerAPI(initial_block_hash, RAM_DB()) server_task = asyncio.ensure_future(api_server(rws_aiter, ledger)) run(client_test(path)) server_task.cancel()
def test_farm_block_empty(): REWARD = 10000 unspent_db = RAM_DB() chain_view = ChainView.for_genesis_hash(GENESIS_BLOCK, unspent_db) pos = ProofOfSpace(get_pool_public_key(), get_plot_public_key()) puzzle_hash = puzzle_hash_for_index(1) spend_bundle = SpendBundle.aggregate([]) header, header_signature, body = farm_block(GENESIS_BLOCK, Signature.zero(), 1, pos, spend_bundle, puzzle_hash, REWARD) removals = removals_for_body(body) assert len(removals) == 0 run = asyncio.get_event_loop().run_until_complete additions, removals = run( chain_view.accept_new_block(header, unspent_db, REWARD, 0)) assert len(additions) == 2 assert len(removals) == 0
def run_ledger_api(server, aiter): db = RAM_DB() INITIAL_BLOCK_HASH = bytes(([0] * 31) + [1]) ledger = ledger_api.LedgerAPI(INITIAL_BLOCK_HASH, db) rws_aiter = map_aiter(lambda rw: dict(reader=rw[0], writer=rw[1], server=server), aiter) return api_server(rws_aiter, ledger)
async def accept_new_block(chain_view: ChainView, header: Header, storage: Storage, coinbase_reward: int, timestamp: int): """ Checks the block against the existing ChainView object. Returns a list of additions (coins), and removals (coin names). Missing blobs must be resolvable by storage. If the given block is invalid, a ConsensusError is raised. """ try: newly_created_block_index = chain_view.tip_index + 1 # verify header extends current view if header.previous_hash != chain_view.tip_hash: raise ConsensusError(Err.DOES_NOT_EXTEND, header) # get body body = await header.body_hash.obj(storage) if body is None: raise ConsensusError(Err.MISSING_FROM_STORAGE, header.body_hash) # ensure block program generates solutions npc_list = name_puzzle_conditions_list(body.solution_program) # build removals list removals = tuple(_[0] for _ in npc_list) # build additions def additions_iter(body, npc_list): yield body.coinbase_coin yield body.fees_coin for coin_name, puzzle_hash, conditions_dict in npc_list: for _ in created_outputs_for_conditions_dict( conditions_dict, coin_name): yield _ additions = tuple(additions_iter(body, npc_list)) for coin in additions: if coin.amount >= MAX_COIN_AMOUNT: raise ConsensusError(Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, coin) # watch out for duplicate outputs addition_counter = collections.Counter(_.name() for _ in additions) for k, v in addition_counter.items(): if v > 1: raise ConsensusError(Err.DUPLICATE_OUTPUT, k) # watch out for double-spends removal_counter = collections.Counter(removals) for k, v in removal_counter.items(): if v > 1: raise ConsensusError(Err.DOUBLE_SPEND, k) # create a temporary overlay DB with the additions ram_storage = RAM_DB() for _ in additions: await ram_storage.add_preimage(bytes(_)) ram_db = RAMUnspentDB(additions, newly_created_block_index) overlay_storage = OverlayStorage(ram_storage, storage) unspent_db = OverlayUnspentDB(chain_view.unspent_db, ram_db) coin_futures = [ asyncio.ensure_future(_[0].obj(overlay_storage)) for _ in npc_list ] # build cpc_list from npc_list cpc_list = [] for coin_future, (coin_name, puzzle_hash, conditions_dict) in zip(coin_futures, npc_list): coin = await coin_future if coin is None: raise ConsensusError(Err.UNKNOWN_UNSPENT, coin_name) cpc_list.append((coin, puzzle_hash, conditions_dict)) # check that the revealed removal puzzles actually match the puzzle hash for coin, puzzle_hash, conditions_dict in cpc_list: if puzzle_hash != coin.puzzle_hash: raise ConsensusError(Err.WRONG_PUZZLE_HASH, coin) # build coin_to_unspent dictionary futures = [] for coin, puzzle_hash, conditions_dict in cpc_list: futures.append( asyncio.ensure_future( unspent_db.unspent_for_coin_name(coin.name()))) coin_to_unspent = {} for (coin, puzzle_hash, conditions_dict), future in zip(cpc_list, futures): coin_to_unspent[coin.name()] = await future # check removals against UnspentDB for coin, puzzle_hash, conditions_dict in cpc_list: if coin in additions: # it's an ephemeral coin, created and destroyed in the same block continue coin_name = coin.name() unspent = coin_to_unspent[coin_name] if (unspent is None or unspent.confirmed_block_index == 0 or unspent.confirmed_block_index > chain_view.tip_index): raise ConsensusError(Err.UNKNOWN_UNSPENT, coin_name) if (0 < unspent.spent_block_index <= chain_view.tip_index): raise ConsensusError(Err.DOUBLE_SPEND, coin_name) # check fees fees = 0 for coin, puzzle_hash, conditions_dict in cpc_list: fees -= coin.amount for coin in additions: fees += coin.amount if fees != coinbase_reward: raise ConsensusError(Err.BAD_COINBASE_REWARD, body.coinbase_coin) # check solution for each CoinSolution pair # this is where CHECKLOCKTIME etc. are verified context = dict( block_index=newly_created_block_index, removals=set(removals), coin_to_unspent=coin_to_unspent, creation_time=timestamp, ) hash_key_pairs = [] for coin, puzzle_hash, conditions_dict in cpc_list: check_conditions_dict(coin, conditions_dict, context) hash_key_pairs.extend( hash_key_pairs_for_conditions_dict(conditions_dict)) # verify aggregated signature if not body.aggregated_signature.validate(hash_key_pairs): raise ConsensusError(Err.BAD_AGGREGATE_SIGNATURE, body) return additions, removals except ConsensusError: raise except Exception as ex: breakpoint() raise ConsensusError(Err.UNKNOWN, ex)