Ejemplo n.º 1
0
 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
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 6
0
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
    )
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
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), [])
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
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])
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
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]
Ejemplo n.º 17
0
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)
Ejemplo n.º 18
0
    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()
Ejemplo n.º 19
0
    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()
Ejemplo n.º 20
0
    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, [])
Ejemplo n.º 21
0
    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)
Ejemplo n.º 22
0
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)
Ejemplo n.º 23
0
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]