async def test_invalid_pos_hash(self, initial_blockchain):
        blocks, b = initial_blockchain

        bad_pos_proof = bytearray([i for i in blocks[9].proof_of_space.proof])
        bad_pos_proof[0] = uint8((bad_pos_proof[0] + 1) % 256)
        bad_pos = ProofOfSpace(
            blocks[9].proof_of_space.challenge_hash,
            blocks[9].proof_of_space.pool_public_key,
            blocks[9].proof_of_space.plot_public_key,
            blocks[9].proof_of_space.size,
            bytes(bad_pos_proof),
        )
        new_header_data = HeaderData(
            blocks[9].header.data.height,
            blocks[9].header.data.prev_header_hash,
            blocks[9].header.data.timestamp,
            blocks[9].header.data.filter_hash,
            bad_pos.get_hash(),
            blocks[9].header.data.weight,
            blocks[9].header.data.total_iters,
            blocks[9].header.data.additions_root,
            blocks[9].header.data.removals_root,
            blocks[9].header.data.farmer_rewards_puzzle_hash,
            blocks[9].header.data.total_transaction_fees,
            blocks[9].header.data.pool_target,
            blocks[9].header.data.aggregated_signature,
            blocks[9].header.data.cost,
            blocks[9].header.data.extension_data,
            blocks[9].header.data.generator_hash,
        )

        # Proof of space has invalid
        block_bad = FullBlock(
            blocks[9].proof_of_space,
            blocks[9].proof_of_time,
            Header(
                new_header_data,
                bt.get_plot_signature(
                    new_header_data, blocks[9].proof_of_space.plot_public_key
                ),
            ),
            blocks[9].transactions_generator,
            blocks[9].transactions_filter,
        )
        result, removed, error_code = await b.receive_block(block_bad)
        assert result == ReceiveBlockResult.INVALID_BLOCK
        assert error_code == Err.INVALID_POSPACE_HASH
async def validate_unfinished_block_header(
    constants: ConsensusConstants,
    headers: Dict[bytes32, Header],
    height_to_hash: Dict[uint32, bytes32],
    block_header: Header,
    proof_of_space: ProofOfSpace,
    prev_header_block: Optional[HeaderBlock],
    pre_validated: bool = False,
    pos_quality_string: bytes32 = None,
) -> Tuple[Optional[Err], Optional[uint64]]:
    """
    Block validation algorithm. Returns the number of VDF iterations that this block's
    proof of time must have, if the candidate block is fully valid (except for proof of
    time). The same as validate_block, but without proof of time and challenge validation.
    If the block is invalid, an error code is returned.

    Does NOT validate transactions and fees.
    """
    if not pre_validated:
        # 1. The hash of the proof of space must match header_data.proof_of_space_hash
        if proof_of_space.get_hash() != block_header.data.proof_of_space_hash:
            return (Err.INVALID_POSPACE_HASH, None)

        # 2. The coinbase signature must be valid, according the the pool public key
        # TODO: change numbers

        # 3. Check harvester signature of header data is valid based on harvester key
        validates = blspy.AugSchemeMPL.verify(
            proof_of_space.plot_public_key,
            block_header.data.get_hash(),
            block_header.plot_signature,
        )
        if not validates:
            return (Err.INVALID_PLOT_SIGNATURE, None)

    # 4. If not genesis, the previous block must exist
    if prev_header_block is not None and block_header.prev_header_hash not in headers:
        return (Err.DOES_NOT_EXTEND, None)

    # 5. If not genesis, the timestamp must be >= the average timestamp of last 11 blocks
    # and less than 2 hours in the future (if block height < 11, average all previous blocks).
    # Average is the sum, int diveded by the number of timestamps
    if prev_header_block is not None:
        last_timestamps: List[uint64] = []
        curr = prev_header_block.header
        while len(last_timestamps) < constants.NUMBER_OF_TIMESTAMPS:
            last_timestamps.append(curr.data.timestamp)
            fetched = headers.get(curr.prev_header_hash, None)
            if not fetched:
                break
            curr = fetched
        if len(last_timestamps) != constants.NUMBER_OF_TIMESTAMPS:
            # For blocks 1 to 10, average timestamps of all previous blocks
            assert curr.height == 0
        prev_time: uint64 = uint64(
            int(sum(last_timestamps) // len(last_timestamps)))
        if block_header.data.timestamp < prev_time:
            return (Err.TIMESTAMP_TOO_FAR_IN_PAST, None)
        if block_header.data.timestamp > time.time(
        ) + constants.MAX_FUTURE_TIME:
            return (Err.TIMESTAMP_TOO_FAR_IN_FUTURE, None)

    # 7. Extension data must be valid, if any is present

    # Compute challenge of parent
    challenge_hash: bytes32
    if prev_header_block is not None:
        challenge: Challenge = prev_header_block.challenge
        challenge_hash = challenge.get_hash()
        # 8. Check challenge hash of prev is the same as in pos
        if challenge_hash != proof_of_space.challenge_hash:
            return (Err.INVALID_POSPACE_CHALLENGE, None)

    # 10. The proof of space must be valid on the challenge
    if pos_quality_string is None:
        pos_quality_string = proof_of_space.verify_and_get_quality_string(
            constants.NUMBER_ZERO_BITS_CHALLENGE_SIG)
        if not pos_quality_string:
            return (Err.INVALID_POSPACE, None)

    if prev_header_block is not None:
        # 11. If not genesis, the height on the previous block must be one less than on this block
        if block_header.height != prev_header_block.height + 1:
            return (Err.INVALID_HEIGHT, None)
    else:
        # 12. If genesis, the height must be 0
        if block_header.height != 0:
            return (Err.INVALID_HEIGHT, None)

    # 13. The pool max height must be valid
    if (block_header.data.pool_target.max_height != 0 and
            block_header.data.pool_target.max_height < block_header.height):
        return (Err.INVALID_POOL_TARGET, None)

    difficulty: uint64
    if prev_header_block is not None:
        difficulty = get_next_difficulty(constants, headers, height_to_hash,
                                         prev_header_block.header)
        min_iters = get_next_min_iters(constants, headers, height_to_hash,
                                       prev_header_block)
    else:
        difficulty = uint64(constants.DIFFICULTY_STARTING)
        min_iters = uint64(constants.MIN_ITERS_STARTING)

    number_of_iters: uint64 = calculate_iterations_quality(
        pos_quality_string,
        proof_of_space.size,
        difficulty,
        min_iters,
    )

    assert count_significant_bits(difficulty) <= constants.SIGNIFICANT_BITS
    assert count_significant_bits(min_iters) <= constants.SIGNIFICANT_BITS

    if prev_header_block is not None:
        # 17. If not genesis, the total weight must be the parent weight + difficulty
        if block_header.weight != prev_header_block.weight + difficulty:
            return (Err.INVALID_WEIGHT, None)

        # 18. If not genesis, the total iters must be parent iters + number_iters
        if (block_header.data.total_iters !=
                prev_header_block.header.data.total_iters + number_of_iters):
            return (Err.INVALID_TOTAL_ITERS, None)
    else:
        # 19. If genesis, the total weight must be starting difficulty
        if block_header.weight != difficulty:
            return (Err.INVALID_WEIGHT, None)

        # 20. If genesis, the total iters must be number iters
        if block_header.data.total_iters != number_of_iters:
            return (Err.INVALID_TOTAL_ITERS, None)

    return (None, number_of_iters)
Example #3
0
async def validate_unfinished_block_header(
    constants: Dict,
    headers: Dict[bytes32, Header],
    height_to_hash: Dict[uint32, bytes32],
    block_header: Header,
    proof_of_space: ProofOfSpace,
    prev_header_block: Optional[HeaderBlock],
    pre_validated: bool = False,
    pos_quality_string: bytes32 = None,
) -> Tuple[Optional[Err], Optional[uint64]]:
    """
    Block validation algorithm. Returns the number of VDF iterations that this block's
    proof of time must have, if the candidate block is fully valid (except for proof of
    time). The same as validate_block, but without proof of time and challenge validation.
    If the block is invalid, an error code is returned.

    Does NOT validate transactions and fees.
    """
    if not pre_validated:
        # 1. The hash of the proof of space must match header_data.proof_of_space_hash
        if proof_of_space.get_hash() != block_header.data.proof_of_space_hash:
            return (Err.INVALID_POSPACE_HASH, None)

        # 2. The coinbase signature must be valid, according the the pool public key
        pair = block_header.data.coinbase_signature.PkMessagePair(
            proof_of_space.pool_pubkey,
            block_header.data.coinbase.name(),
        )

        if not block_header.data.coinbase_signature.validate([pair]):
            return (Err.INVALID_COINBASE_SIGNATURE, None)

        # 3. Check harvester signature of header data is valid based on harvester key
        if not block_header.harvester_signature.verify(
            [blspy.Util.hash256(block_header.data.get_hash())],
            [proof_of_space.plot_pubkey],
        ):
            return (Err.INVALID_HARVESTER_SIGNATURE, None)

    # 4. If not genesis, the previous block must exist
    if prev_header_block is not None and block_header.prev_header_hash not in headers:
        return (Err.DOES_NOT_EXTEND, None)

    # 5. If not genesis, the timestamp must be >= the average timestamp of last 11 blocks
    # and less than 2 hours in the future (if block height < 11, average all previous blocks).
    # Average is the sum, int diveded by the number of timestamps
    if prev_header_block is not None:
        last_timestamps: List[uint64] = []
        curr = prev_header_block.header
        while len(last_timestamps) < constants["NUMBER_OF_TIMESTAMPS"]:
            last_timestamps.append(curr.data.timestamp)
            fetched = headers.get(curr.prev_header_hash, None)
            if not fetched:
                break
            curr = fetched
        if len(last_timestamps) != constants["NUMBER_OF_TIMESTAMPS"]:
            # For blocks 1 to 10, average timestamps of all previous blocks
            assert curr.height == 0
        prev_time: uint64 = uint64(
            int(sum(last_timestamps) // len(last_timestamps)))
        if block_header.data.timestamp < prev_time:
            return (Err.TIMESTAMP_TOO_FAR_IN_PAST, None)
        if block_header.data.timestamp > time.time(
        ) + constants["MAX_FUTURE_TIME"]:
            return (Err.TIMESTAMP_TOO_FAR_IN_FUTURE, None)

    # 7. Extension data must be valid, if any is present

    # Compute challenge of parent
    challenge_hash: bytes32
    if prev_header_block is not None:
        challenge: Challenge = prev_header_block.challenge
        challenge_hash = challenge.get_hash()
        # 8. Check challenge hash of prev is the same as in pos
        if challenge_hash != proof_of_space.challenge_hash:
            return (Err.INVALID_POSPACE_CHALLENGE, None)

    # 10. The proof of space must be valid on the challenge
    if pos_quality_string is None:
        pos_quality_string = proof_of_space.verify_and_get_quality_string()
        if not pos_quality_string:
            return (Err.INVALID_POSPACE, None)

    if prev_header_block is not None:
        # 11. If not genesis, the height on the previous block must be one less than on this block
        if block_header.height != prev_header_block.height + 1:
            return (Err.INVALID_HEIGHT, None)
    else:
        # 12. If genesis, the height must be 0
        if block_header.height != 0:
            return (Err.INVALID_HEIGHT, None)

    # 13. The coinbase reward must match the block schedule
    coinbase_reward = calculate_block_reward(block_header.height)
    if coinbase_reward != block_header.data.coinbase.amount:
        return (Err.INVALID_COINBASE_AMOUNT, None)

    # 13b. The coinbase parent id must be the height
    if block_header.data.coinbase.parent_coin_info != block_header.height.to_bytes(
            32, "big"):
        return (Err.INVALID_COINBASE_PARENT, None)

    # 13c. The fees coin parent id must be hash(hash(height))
    if block_header.data.fees_coin.parent_coin_info != std_hash(
            std_hash(uint32(block_header.height))):
        return (Err.INVALID_FEES_COIN_PARENT, None)

    difficulty: uint64
    if prev_header_block is not None:
        difficulty = get_next_difficulty(constants, headers, height_to_hash,
                                         prev_header_block.header)
        min_iters = get_next_min_iters(constants, headers, height_to_hash,
                                       prev_header_block)
    else:
        difficulty = uint64(constants["DIFFICULTY_STARTING"])
        min_iters = uint64(constants["MIN_ITERS_STARTING"])

    number_of_iters: uint64 = calculate_iterations_quality(
        pos_quality_string,
        proof_of_space.size,
        difficulty,
        min_iters,
    )

    assert count_significant_bits(difficulty) <= constants["SIGNIFICANT_BITS"]
    assert count_significant_bits(min_iters) <= constants["SIGNIFICANT_BITS"]

    if prev_header_block is not None:
        # 17. If not genesis, the total weight must be the parent weight + difficulty
        if block_header.weight != prev_header_block.weight + difficulty:
            return (Err.INVALID_WEIGHT, None)

        # 18. If not genesis, the total iters must be parent iters + number_iters
        if (block_header.data.total_iters !=
                prev_header_block.header.data.total_iters + number_of_iters):
            return (Err.INVALID_TOTAL_ITERS, None)
    else:
        # 19. If genesis, the total weight must be starting difficulty
        if block_header.weight != difficulty:
            return (Err.INVALID_WEIGHT, None)

        # 20. If genesis, the total iters must be number iters
        if block_header.data.total_iters != number_of_iters:
            return (Err.INVALID_TOTAL_ITERS, None)

    return (None, number_of_iters)