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 _run_generator( constants_dict: bytes, unfinished_block_bytes: bytes, block_generator_bytes: bytes, ) -> Tuple[Optional[Err], Optional[bytes]]: """ Runs the CLVM generator from bytes inputs. This is meant to be called under a ProcessPoolExecutor, in order to validate the heavy parts of a block (clvm program) in a different process. """ try: constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) unfinished_block: UnfinishedBlock = UnfinishedBlock.from_bytes( unfinished_block_bytes) assert unfinished_block.transactions_info is not None block_generator: BlockGenerator = BlockGenerator.from_bytes( block_generator_bytes) assert block_generator.program == unfinished_block.transactions_generator npc_result: NPCResult = get_name_puzzle_conditions( block_generator, min(constants.MAX_BLOCK_COST_CLVM, unfinished_block.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, mempool_mode=False, ) if npc_result.error is not None: return Err(npc_result.error), None except ValidationError as e: return e.code, None except Exception: return Err.UNKNOWN, None return None, bytes(npc_result)
def run_and_get_removals_and_additions( block: FullBlock, max_cost: int, cost_per_byte: int, rust_checker: bool, safe_mode=False) -> Tuple[List[bytes32], List[Coin]]: removals: List[bytes32] = [] additions: List[Coin] = [] assert len(block.transactions_generator_ref_list) == 0 if not block.is_transaction_block(): return [], [] if block.transactions_generator is not None: npc_result = get_name_puzzle_conditions( BlockGenerator(block.transactions_generator, []), max_cost, cost_per_byte=cost_per_byte, safe_mode=safe_mode, rust_checker=rust_checker, ) # build removals list for npc in npc_result.npc_list: removals.append(npc.coin_name) additions.extend(additions_for_npc(npc_result.npc_list)) rewards = block.get_included_reward_coins() additions.extend(rewards) return removals, additions
def create_multiple_ref_generator(args: MultipleCompressorArg, spend_bundle: SpendBundle) -> BlockGenerator: """ Decompress a transaction by referencing bytes from multiple input generator references """ compressed_cse_list = compressed_coin_spend_entry_list(spend_bundle) program = TEST_MULTIPLE.curry( DECOMPRESS_PUZZLE, DECOMPRESS_CSE_WITH_PREFIX, args.arg[0].start, args.arg[0].end - args.split_offset, args.arg[1].end - args.split_offset, args.arg[1].end, compressed_cse_list, ) # TODO aqk: Improve ergonomics of CompressorArg -> GeneratorArg conversion generator_list = [ args.arg[0].generator, args.arg[1].generator, ] generator_heights = [ FAKE_BLOCK_HEIGHT1, FAKE_BLOCK_HEIGHT2, ] return BlockGenerator(program, generator_list, generator_heights)
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 run_full_block(block: FullBlock, constants: ConsensusConstants) -> List[CAT]: generator_args = ref_list_to_args(block.transactions_generator_ref_list) if block.transactions_generator is None or block.transactions_info is None: raise RuntimeError("transactions_generator of FullBlock is null") block_generator = BlockGenerator(block.transactions_generator, generator_args, []) return run_generator( block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), block.height )
def simple_solution_generator(bundle: SpendBundle) -> BlockGenerator: """ Simply quotes the solutions we know. """ cse_list = spend_bundle_to_coin_solution_entry_list(bundle) block_program = SerializedProgram.from_bytes(SExp.to((binutils.assemble("#q"), cse_list)).as_bin()) generator = BlockGenerator(block_program, []) return generator
async def test_strict_mode(self, rust_checker: bool): wallet_tool = bt.get_pool_wallet_tool() ph = wallet_tool.get_new_puzzlehash() num_blocks = 3 blocks = bt.get_consecutive_blocks(num_blocks, [], guarantee_transaction_block=True, pool_reward_puzzle_hash=ph, farmer_reward_puzzle_hash=ph) coinbase = None for coin in blocks[2].get_included_reward_coins(): if coin.puzzle_hash == ph: coinbase = coin break assert coinbase is not None spend_bundle = wallet_tool.generate_signed_transaction( coinbase.amount, BURN_PUZZLE_HASH, coinbase, ) assert spend_bundle is not None pk = bytes.fromhex( "88bc9360319e7c54ab42e19e974288a2d7a817976f7633f4b43f36ce72074e59c4ab8ddac362202f3e366f0aebbb6280" ) puzzle = p2_delegated_puzzle_or_hidden_puzzle.puzzle_for_pk(pk) disassembly = binutils.disassemble(puzzle) program = SerializedProgram.from_bytes( binutils.assemble( f"(q ((0x3d2331635a58c0d49912bc1427d7db51afe3f20a7b4bcaffa17ee250dcbcbfaa {disassembly} 300" f" (() (q . ((65 '00000000000000000000000000000000' 0x0cbba106e000))) ()))))" ).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 coin_name = npc_result.npc_list[0].coin_name error, puzzle, solution = get_puzzle_and_solution_for_coin( generator, coin_name, test_constants.MAX_BLOCK_COST_CLVM) assert error is None
def simple_solution_generator(bundle: SpendBundle) -> BlockGenerator: """ Simply quotes the solutions we know. """ cse_list = spend_bundle_to_serialized_coin_solution_entry_list(bundle) block_program = b"\xff" block_program += SExp.to(binutils.assemble("#q")).as_bin() block_program += b"\xff" + cse_list + b"\x80" return BlockGenerator(SerializedProgram.from_bytes(block_program), [])
def run_generator_with_args( generator_program_hex: str, generator_args: List[SerializedProgram], constants: ConsensusConstants, cost: uint64, height: uint32, ) -> List[CAT]: if not generator_program_hex: return [] generator_program = SerializedProgram.fromhex(generator_program_hex) block_generator = BlockGenerator(generator_program, generator_args, []) return run_generator(block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, cost), height)
def get_puzzle_and_solution_for_coin(generator: BlockGenerator, coin_name: bytes): try: block_program = generator.program if not generator.generator_args: block_program_args = NIL else: block_program_args = create_generator_args(generator.generator_refs()) cost, result = GENERATOR_FOR_SINGLE_COIN_MOD.run_with_cost(block_program, block_program_args, coin_name) puzzle = result.first() solution = result.rest().first() return None, puzzle, solution except Exception as e: return e, None, None
async def test_tx_generator_speed(self): LARGE_BLOCK_COIN_CONSUMED_COUNT = 687 generator_bytes = large_block_generator(LARGE_BLOCK_COIN_CONSUMED_COUNT) program = SerializedProgram.from_bytes(generator_bytes) start_time = time.time() generator = BlockGenerator(program, []) npc_result = get_name_puzzle_conditions(generator, False) end_time = time.time() duration = end_time - start_time assert npc_result.error is None assert len(npc_result.npc_list) == LARGE_BLOCK_COIN_CONSUMED_COUNT log.info(f"Time spent: {duration}") assert duration < 3
def create_block_generator( generator: SerializedProgram, block_heights_list: List[uint32], generator_block_cache: GeneratorBlockCacheInterface ) -> Optional[BlockGenerator]: """`create_block_generator` will returns None if it fails to look up any referenced block""" generator_arg_list: List[GeneratorArg] = [] for i in block_heights_list: previous_generator = generator_block_cache.get_generator_for_block_height( i) if previous_generator is None: log.error( f"Failed to look up generator for block {i}. Ref List: {block_heights_list}" ) return None generator_arg_list.append(GeneratorArg(i, previous_generator)) return BlockGenerator(generator, generator_arg_list)
def create_compressed_generator( original_generator: CompressorArg, compressed_cse_list: List[List[Union[List[uint64], List[Union[bytes, None, Program]]]]], ) -> BlockGenerator: """ Bind the generator block program template to a particular reference block, template bytes offsets, and SpendBundle. """ start = original_generator.start end = original_generator.end program = DECOMPRESS_BLOCK.curry(DECOMPRESS_PUZZLE, DECOMPRESS_CSE_WITH_PREFIX, Program.to(start), Program.to(end), compressed_cse_list) generator_arg = GeneratorArg(original_generator.block_height, original_generator.generator) return BlockGenerator(program, [generator_arg])
async def test_tx_generator_speed(self, rust_checker: bool): LARGE_BLOCK_COIN_CONSUMED_COUNT = 687 generator_bytes = large_block_generator( LARGE_BLOCK_COIN_CONSUMED_COUNT) program = SerializedProgram.from_bytes(generator_bytes) start_time = time.time() generator = BlockGenerator(program, []) 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, ) end_time = time.time() duration = end_time - start_time assert npc_result.error is None assert len(npc_result.npc_list) == LARGE_BLOCK_COIN_CONSUMED_COUNT log.info(f"Time spent: {duration}") assert duration < 1
def batch_pre_validate_blocks( constants_dict: Dict, blocks_pickled: Dict[bytes, bytes], full_blocks_pickled: Optional[List[bytes]], header_blocks_pickled: Optional[List[bytes]], prev_transaction_generators: List[Optional[bytes]], npc_results: Dict[uint32, bytes], check_filter: bool, expected_difficulty: List[uint64], expected_sub_slot_iters: List[uint64], ) -> List[bytes]: blocks = {} for k, v in blocks_pickled.items(): blocks[k] = BlockRecord.from_bytes(v) results: List[PreValidationResult] = [] constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) if full_blocks_pickled is not None and header_blocks_pickled is not None: assert ValueError("Only one should be passed here") if full_blocks_pickled is not None: for i in range(len(full_blocks_pickled)): try: block: FullBlock = FullBlock.from_bytes(full_blocks_pickled[i]) tx_additions: List[Coin] = [] removals: List[bytes32] = [] npc_result: Optional[NPCResult] = None if block.height in npc_results: npc_result = NPCResult.from_bytes( npc_results[block.height]) assert npc_result is not None if npc_result.npc_list is not None: removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) else: removals, tx_additions = [], [] if block.transactions_generator is not None and npc_result is None: prev_generator_bytes = prev_transaction_generators[i] assert prev_generator_bytes is not None assert block.transactions_info is not None block_generator: BlockGenerator = BlockGenerator.from_bytes( prev_generator_bytes) assert block_generator.program == block.transactions_generator npc_result = get_name_puzzle_conditions( block_generator, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), True) removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) header_block = get_block_header(block, tx_additions, removals) required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int: Optional[uint16] = None if error is not None: error_int = uint16(error.code.value) results.append( PreValidationResult(error_int, required_iters, npc_result)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None)) elif header_blocks_pickled is not None: for i in range(len(header_blocks_pickled)): try: header_block = HeaderBlock.from_bytes(header_blocks_pickled[i]) required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int = None if error is not None: error_int = uint16(error.code.value) results.append( PreValidationResult(error_int, required_iters, None)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None)) return [bytes(r) for r in results]
def block_generator() -> BlockGenerator: generator_args = [ GeneratorArg(uint32(0), to_sp(FIRST_GENERATOR)), GeneratorArg(uint32(1), to_sp(SECOND_GENERATOR)) ] return BlockGenerator(to_sp(COMPILED_GENERATOR_CODE), generator_args)
async def test_basic_coin_store(self, rust_checker: bool): wallet_a = WALLET_A reward_ph = wallet_a.get_new_puzzlehash() for cache_size in [0]: # Generate some coins blocks = bt.get_consecutive_blocks( 10, [], farmer_reward_puzzle_hash=reward_ph, pool_reward_puzzle_hash=reward_ph, ) coins_to_spend: List[Coin] = [] for block in blocks: if block.is_transaction_block(): for coin in block.get_included_reward_coins(): if coin.puzzle_hash == reward_ph: coins_to_spend.append(coin) spend_bundle = wallet_a.generate_signed_transaction( uint64(1000), wallet_a.get_new_puzzlehash(), coins_to_spend[0]) db_path = Path("fndb_test.db") if db_path.exists(): db_path.unlink() connection = await aiosqlite.connect(db_path) db_wrapper = DBWrapper(connection) coin_store = await CoinStore.create(db_wrapper, cache_size=uint32(cache_size)) blocks = bt.get_consecutive_blocks( 10, blocks, farmer_reward_puzzle_hash=reward_ph, pool_reward_puzzle_hash=reward_ph, transaction_data=spend_bundle, ) # Adding blocks to the coin store should_be_included_prev: Set[Coin] = set() should_be_included: Set[Coin] = set() for block in blocks: farmer_coin, pool_coin = get_future_reward_coins(block) should_be_included.add(farmer_coin) should_be_included.add(pool_coin) if block.is_transaction_block(): if block.transactions_generator is not None: block_gen: BlockGenerator = BlockGenerator( block.transactions_generator, []) npc_result = get_name_puzzle_conditions( block_gen, bt.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=bt.constants.COST_PER_BYTE, safe_mode=False, rust_checker=rust_checker, ) tx_removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) else: tx_removals, tx_additions = [], [] assert block.get_included_reward_coins( ) == should_be_included_prev await coin_store.new_block(block, tx_additions, tx_removals) if block.height != 0: with pytest.raises(Exception): await coin_store.new_block(block, tx_additions, tx_removals) for expected_coin in should_be_included_prev: # Check that the coinbase rewards are added record = await coin_store.get_coin_record( expected_coin.name()) assert record is not None assert not record.spent assert record.coin == expected_coin for coin_name in tx_removals: # Check that the removed coins are set to spent record = await coin_store.get_coin_record(coin_name) assert record.spent for coin in tx_additions: # Check that the added coins are added record = await coin_store.get_coin_record(coin.name()) assert not record.spent assert coin == record.coin should_be_included_prev = should_be_included.copy() should_be_included = set() await connection.close() Path("fndb_test.db").unlink()
async def test_basic_coin_store(self, cache_size: uint32, db_version): wallet_a = WALLET_A reward_ph = wallet_a.get_new_puzzlehash() # Generate some coins blocks = bt.get_consecutive_blocks( 10, [], farmer_reward_puzzle_hash=reward_ph, pool_reward_puzzle_hash=reward_ph, ) coins_to_spend: List[Coin] = [] for block in blocks: if block.is_transaction_block(): for coin in block.get_included_reward_coins(): if coin.puzzle_hash == reward_ph: coins_to_spend.append(coin) spend_bundle = wallet_a.generate_signed_transaction( uint64(1000), wallet_a.get_new_puzzlehash(), coins_to_spend[0]) async with DBConnection(db_version) as db_wrapper: coin_store = await CoinStore.create(db_wrapper, cache_size=cache_size) blocks = bt.get_consecutive_blocks( 10, blocks, farmer_reward_puzzle_hash=reward_ph, pool_reward_puzzle_hash=reward_ph, transaction_data=spend_bundle, ) # Adding blocks to the coin store should_be_included_prev: Set[Coin] = set() should_be_included: Set[Coin] = set() for block in blocks: farmer_coin, pool_coin = get_future_reward_coins(block) should_be_included.add(farmer_coin) should_be_included.add(pool_coin) if block.is_transaction_block(): if block.transactions_generator is not None: block_gen: BlockGenerator = BlockGenerator( block.transactions_generator, []) npc_result = get_name_puzzle_conditions( block_gen, bt.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=bt.constants.COST_PER_BYTE, mempool_mode=False, ) tx_removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) else: tx_removals, tx_additions = [], [] assert block.get_included_reward_coins( ) == should_be_included_prev if block.is_transaction_block(): assert block.foliage_transaction_block is not None await coin_store.new_block( block.height, block.foliage_transaction_block.timestamp, block.get_included_reward_coins(), tx_additions, tx_removals, ) if block.height != 0: with pytest.raises(Exception): await coin_store.new_block( block.height, block.foliage_transaction_block.timestamp, block.get_included_reward_coins(), tx_additions, tx_removals, ) for expected_coin in should_be_included_prev: # Check that the coinbase rewards are added record = await coin_store.get_coin_record( expected_coin.name()) assert record is not None assert not record.spent assert record.coin == expected_coin for coin_name in tx_removals: # Check that the removed coins are set to spent record = await coin_store.get_coin_record(coin_name) assert record.spent for coin in tx_additions: # Check that the added coins are added record = await coin_store.get_coin_record(coin.name()) assert not record.spent assert coin == record.coin should_be_included_prev = should_be_included.copy() should_be_included = set()
async def get_block_generator( self, block: Union[FullBlock, UnfinishedBlock], additional_blocks=None) -> Optional[BlockGenerator]: if additional_blocks is None: additional_blocks = {} ref_list = block.transactions_generator_ref_list if block.transactions_generator is None: assert len(ref_list) == 0 return None if len(ref_list) == 0: return BlockGenerator(block.transactions_generator, [], []) result: List[SerializedProgram] = [] previous_block_hash = block.prev_header_hash if (self.try_block_record(previous_block_hash) and self.height_to_hash( self.block_record(previous_block_hash).height) == previous_block_hash): # We are not in a reorg, no need to look up alternate header hashes # (we can get them from height_to_hash) for ref_height in block.transactions_generator_ref_list: header_hash = self.height_to_hash(ref_height) # if ref_height is invalid, this block should have failed with # FUTURE_GENERATOR_REFS before getting here assert header_hash is not None ref_block = await self.block_store.get_full_block(header_hash) assert ref_block is not None if ref_block.transactions_generator is None: raise ValueError(Err.GENERATOR_REF_HAS_NO_GENERATOR) result.append(ref_block.transactions_generator) else: # First tries to find the blocks in additional_blocks reorg_chain: Dict[uint32, FullBlock] = {} curr: Union[FullBlock, UnfinishedBlock] = block additional_height_dict = {} while curr.prev_header_hash in additional_blocks: prev: FullBlock = additional_blocks[curr.prev_header_hash] additional_height_dict[prev.height] = prev if isinstance(curr, FullBlock): assert curr.height == prev.height + 1 reorg_chain[prev.height] = prev curr = prev peak: Optional[BlockRecord] = self.get_peak() if self.contains_block(curr.prev_header_hash) and peak is not None: # Then we look up blocks up to fork point one at a time, backtracking previous_block_hash = curr.prev_header_hash prev_block_record = await self.block_store.get_block_record( previous_block_hash) prev_block = await self.block_store.get_full_block( previous_block_hash) assert prev_block is not None assert prev_block_record is not None fork = find_fork_point_in_chain(self, peak, prev_block_record) curr_2: Optional[FullBlock] = prev_block assert curr_2 is not None and isinstance(curr_2, FullBlock) reorg_chain[curr_2.height] = curr_2 while curr_2.height > fork and curr_2.height > 0: curr_2 = await self.block_store.get_full_block( curr_2.prev_header_hash) assert curr_2 is not None reorg_chain[curr_2.height] = curr_2 for ref_height in block.transactions_generator_ref_list: if ref_height in reorg_chain: ref_block = reorg_chain[ref_height] assert ref_block is not None if ref_block.transactions_generator is None: raise ValueError(Err.GENERATOR_REF_HAS_NO_GENERATOR) result.append(ref_block.transactions_generator) else: if ref_height in additional_height_dict: ref_block = additional_height_dict[ref_height] else: header_hash = self.height_to_hash(ref_height) # TODO: address hint error and remove ignore # error: Argument 1 to "get_full_block" of "Blockchain" has incompatible type # "Optional[bytes32]"; expected "bytes32" [arg-type] ref_block = await self.get_full_block( header_hash) # type: ignore[arg-type] assert ref_block is not None if ref_block.transactions_generator is None: raise ValueError(Err.GENERATOR_REF_HAS_NO_GENERATOR) result.append(ref_block.transactions_generator) assert len(result) == len(ref_list) return BlockGenerator(block.transactions_generator, result, [])
async def get_block_generator( self, block: Union[FullBlock, UnfinishedBlock], additional_blocks=None ) -> Optional[BlockGenerator]: if additional_blocks is None: additional_blocks = {} ref_list = block.transactions_generator_ref_list if block.transactions_generator is None: assert len(ref_list) == 0 return None if len(ref_list) == 0: return BlockGenerator(block.transactions_generator, []) result: List[GeneratorArg] = [] previous_block_hash = block.prev_header_hash if ( self.try_block_record(previous_block_hash) and self.height_to_hash(self.block_record(previous_block_hash).height) == previous_block_hash ): # We are not in a reorg, no need to look up alternate header hashes (we can get them from height_to_hash) for ref_height in block.transactions_generator_ref_list: header_hash = self.height_to_hash(ref_height) ref_block = await self.get_full_block(header_hash) assert ref_block is not None if ref_block.transactions_generator is None: raise ValueError(Err.GENERATOR_REF_HAS_NO_GENERATOR) result.append(GeneratorArg(ref_block.height, ref_block.transactions_generator)) else: # First tries to find the blocks in additional_blocks reorg_chain: Dict[uint32, FullBlock] = {} curr: Union[FullBlock, UnfinishedBlock] = block additional_height_dict = {} while curr.prev_header_hash in additional_blocks: prev: FullBlock = additional_blocks[curr.prev_header_hash] additional_height_dict[prev.height] = prev if isinstance(curr, FullBlock): assert curr.height == prev.height + 1 reorg_chain[prev.height] = prev curr = prev peak: Optional[BlockRecord] = self.get_peak() if self.contains_block(curr.prev_header_hash) and peak is not None: # Then we look up blocks up to fork point one at a time, backtracking previous_block_hash = curr.prev_header_hash prev_block_record = await self.block_store.get_block_record(previous_block_hash) prev_block = await self.block_store.get_full_block(previous_block_hash) assert prev_block is not None assert prev_block_record is not None fork = find_fork_point_in_chain(self, peak, prev_block_record) curr_2: Optional[FullBlock] = prev_block assert curr_2 is not None and isinstance(curr_2, FullBlock) reorg_chain[curr_2.height] = curr_2 while curr_2.height > fork and curr_2.height > 0: curr_2 = await self.block_store.get_full_block(curr_2.prev_header_hash) assert curr_2 is not None reorg_chain[curr_2.height] = curr_2 for ref_height in block.transactions_generator_ref_list: if ref_height in reorg_chain: ref_block = reorg_chain[ref_height] assert ref_block is not None if ref_block.transactions_generator is None: raise ValueError(Err.GENERATOR_REF_HAS_NO_GENERATOR) result.append(GeneratorArg(ref_block.height, ref_block.transactions_generator)) else: if ref_height in additional_height_dict: ref_block = additional_height_dict[ref_height] else: header_hash = self.height_to_hash(ref_height) ref_block = await self.get_full_block(header_hash) assert ref_block is not None if ref_block.transactions_generator is None: raise ValueError(Err.GENERATOR_REF_HAS_NO_GENERATOR) result.append(GeneratorArg(ref_block.height, ref_block.transactions_generator)) assert len(result) == len(ref_list) return BlockGenerator(block.transactions_generator, result)
def block_generator() -> BlockGenerator: generator_list = [to_sp(FIRST_GENERATOR), to_sp(SECOND_GENERATOR)] generator_heights = [uint32(0), uint32(1)] return BlockGenerator(to_sp(COMPILED_GENERATOR_CODE), generator_list, generator_heights)
def batch_pre_validate_blocks( constants_dict: Dict, blocks_pickled: Dict[bytes, bytes], full_blocks_pickled: Optional[List[bytes]], header_blocks_pickled: Optional[List[bytes]], prev_transaction_generators: List[Optional[bytes]], npc_results: Dict[uint32, bytes], check_filter: bool, expected_difficulty: List[uint64], expected_sub_slot_iters: List[uint64], validate_signatures: bool, ) -> List[bytes]: blocks: Dict[bytes, BlockRecord] = {} for k, v in blocks_pickled.items(): blocks[k] = BlockRecord.from_bytes(v) results: List[PreValidationResult] = [] constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) if full_blocks_pickled is not None and header_blocks_pickled is not None: assert ValueError("Only one should be passed here") # In this case, we are validating full blocks, not headers if full_blocks_pickled is not None: for i in range(len(full_blocks_pickled)): try: block: FullBlock = FullBlock.from_bytes(full_blocks_pickled[i]) tx_additions: List[Coin] = [] removals: List[bytes32] = [] npc_result: Optional[NPCResult] = None if block.height in npc_results: npc_result = NPCResult.from_bytes( npc_results[block.height]) assert npc_result is not None if npc_result.npc_list is not None: removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) else: removals, tx_additions = [], [] if block.transactions_generator is not None and npc_result is None: prev_generator_bytes = prev_transaction_generators[i] assert prev_generator_bytes is not None assert block.transactions_info is not None block_generator: BlockGenerator = BlockGenerator.from_bytes( prev_generator_bytes) assert block_generator.program == block.transactions_generator npc_result = get_name_puzzle_conditions( block_generator, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, mempool_mode=False, height=block.height, ) removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) if npc_result is not None and npc_result.error is not None: results.append( PreValidationResult(uint16(npc_result.error), None, npc_result, False)) continue header_block = get_block_header(block, tx_additions, removals) # TODO: address hint error and remove ignore # error: Argument 1 to "BlockCache" has incompatible type "Dict[bytes, BlockRecord]"; expected # "Dict[bytes32, BlockRecord]" [arg-type] required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), # type: ignore[arg-type] header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int: Optional[uint16] = None if error is not None: error_int = uint16(error.code.value) successfully_validated_signatures = False # If we failed CLVM, no need to validate signature, the block is already invalid if error_int is None: # If this is False, it means either we don't have a signature (not a tx block) or we have an invalid # signature (which also puts in an error) or we didn't validate the signature because we want to # validate it later. receive_block will attempt to validate the signature later. if validate_signatures: if npc_result is not None and block.transactions_info is not None: pairs_pks, pairs_msgs = pkm_pairs( npc_result.npc_list, constants.AGG_SIG_ME_ADDITIONAL_DATA) pks_objects: List[G1Element] = [ G1Element.from_bytes(pk) for pk in pairs_pks ] if not AugSchemeMPL.aggregate_verify( pks_objects, pairs_msgs, block. transactions_info.aggregated_signature): error_int = uint16( Err.BAD_AGGREGATE_SIGNATURE.value) else: successfully_validated_signatures = True results.append( PreValidationResult(error_int, required_iters, npc_result, successfully_validated_signatures)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None, False)) # In this case, we are validating header blocks elif header_blocks_pickled is not None: for i in range(len(header_blocks_pickled)): try: header_block = HeaderBlock.from_bytes(header_blocks_pickled[i]) # TODO: address hint error and remove ignore # error: Argument 1 to "BlockCache" has incompatible type "Dict[bytes, BlockRecord]"; expected # "Dict[bytes32, BlockRecord]" [arg-type] required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), # type: ignore[arg-type] header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int = None if error is not None: error_int = uint16(error.code.value) results.append( PreValidationResult(error_int, required_iters, None, False)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None, False)) return [bytes(r) for r in results]