예제 #1
0
    def validate_additions(
        self,
        coins: List[Tuple[bytes32, List[Coin]]],
        proofs: Optional[List[Tuple[bytes32, bytes, Optional[bytes]]]],
        root,
    ):
        if proofs is None:
            # Verify root
            additions_merkle_set = MerkleSet()

            # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
            for puzzle_hash, coins_l in coins:
                additions_merkle_set.add_already_hashed(puzzle_hash)
                additions_merkle_set.add_already_hashed(
                    hash_coin_list(coins_l))

            additions_root = additions_merkle_set.get_root()
            if root != additions_root:
                return False
        else:
            for i in range(len(coins)):
                assert coins[i][0] == proofs[i][0]
                coin_list_1: List[Coin] = coins[i][1]
                puzzle_hash_proof: bytes32 = proofs[i][1]
                coin_list_proof: Optional[bytes32] = proofs[i][2]
                if len(coin_list_1) == 0:
                    # Verify exclusion proof for puzzle hash
                    not_included = confirm_not_included_already_hashed(
                        root,
                        coins[i][0],
                        puzzle_hash_proof,
                    )
                    if not_included is False:
                        return False
                else:
                    try:
                        # Verify inclusion proof for coin list
                        included = confirm_included_already_hashed(
                            root,
                            hash_coin_list(coin_list_1),
                            coin_list_proof,
                        )
                        if included is False:
                            return False
                    except AssertionError:
                        return False
                    try:
                        # Verify inclusion proof for puzzle hash
                        included = confirm_included_already_hashed(
                            root,
                            coins[i][0],
                            puzzle_hash_proof,
                        )
                        if included is False:
                            return False
                    except AssertionError:
                        return False

        return True
예제 #2
0
    def validate_removals(self, coins, proofs, root):
        if proofs is None:
            # If there are no proofs, it means all removals were returned in the response.
            # we must find the ones relevant to our wallets.

            # Verify removals root
            removals_merkle_set = MerkleSet()
            for name_coin in coins:
                # TODO review all verification
                name, coin = name_coin
                if coin is not None:
                    removals_merkle_set.add_already_hashed(coin.name())
            removals_root = removals_merkle_set.get_root()
            if root != removals_root:
                return False
        else:
            # This means the full node has responded only with the relevant removals
            # for our wallet. Each merkle proof must be verified.
            if len(coins) != len(proofs):
                return False
            for i in range(len(coins)):
                # Coins are in the same order as proofs
                if coins[i][0] != proofs[i][0]:
                    return False
                coin = coins[i][1]
                if coin is None:
                    # Verifies merkle proof of exclusion
                    not_included = confirm_not_included_already_hashed(
                        root,
                        coins[i][0],
                        proofs[i][1],
                    )
                    if not_included is False:
                        return False
                else:
                    # Verifies merkle proof of inclusion of coin name
                    if coins[i][0] != coin.name():
                        return False
                    included = confirm_included_already_hashed(
                        root,
                        coin.name(),
                        proofs[i][1],
                    )
                    if included is False:
                        return False
        return True
예제 #3
0
    async def test_basics(self):
        wallet_tool = WalletTool()

        num_blocks = 10
        blocks = bt.get_consecutive_blocks(
            test_constants,
            num_blocks,
            [],
            10,
            reward_puzzlehash=wallet_tool.get_new_puzzlehash(),
        )

        merkle_set = MerkleSet()
        merkle_set_reverse = MerkleSet()

        for block in reversed(blocks):
            merkle_set_reverse.add_already_hashed(
                block.header.data.coinbase.name())

        for block in blocks:
            merkle_set.add_already_hashed(block.header.data.coinbase.name())

        for block in blocks:
            result, proof = merkle_set.is_included_already_hashed(
                block.header.data.coinbase.name())
            assert result is True
            result_fee, proof_fee = merkle_set.is_included_already_hashed(
                block.header.data.fees_coin.name())
            assert result_fee is False
            validate_proof = confirm_included_already_hashed(
                merkle_set.get_root(), block.header.data.coinbase.name(),
                proof)
            validate_proof_fee = confirm_included_already_hashed(
                merkle_set.get_root(), block.header.data.fees_coin.name(),
                proof_fee)
            assert validate_proof is True
            assert validate_proof_fee is False

        # Test if order of adding items change the outcome
        assert merkle_set.get_root() == merkle_set_reverse.get_root()
예제 #4
0
    def _validate_merkle_root(
        self,
        block: FullBlock,
        tx_additions: List[Coin] = None,
        tx_removals: List[bytes32] = None,
    ) -> Optional[Err]:
        additions = []
        removals = []
        if tx_additions:
            additions.extend(tx_additions)
        if tx_removals:
            removals.extend(tx_removals)

        removal_merkle_set = MerkleSet()
        addition_merkle_set = MerkleSet()

        # Create removal Merkle set
        for coin_name in removals:
            removal_merkle_set.add_already_hashed(coin_name)

        # Create addition Merkle set
        puzzlehash_coins_map: Dict[bytes32, List[Coin]] = {}

        for coin in additions + [block.get_coinbase(), block.get_fees_coin()]:
            if coin.puzzle_hash in puzzlehash_coins_map:
                puzzlehash_coins_map[coin.puzzle_hash].append(coin)
            else:
                puzzlehash_coins_map[coin.puzzle_hash] = [coin]

        # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
        for puzzle, coins in puzzlehash_coins_map.items():
            addition_merkle_set.add_already_hashed(puzzle)
            addition_merkle_set.add_already_hashed(hash_coin_list(coins))

        additions_root = addition_merkle_set.get_root()
        removals_root = removal_merkle_set.get_root()

        if block.header.data.additions_root != additions_root:
            return Err.BAD_ADDITION_ROOT
        if block.header.data.removals_root != removals_root:
            return Err.BAD_REMOVAL_ROOT

        return None
예제 #5
0
def validate_block_merkle_roots(
    block_additions_root: bytes32,
    block_removals_root: bytes32,
    tx_additions: List[Coin] = None,
    tx_removals: List[bytes32] = None,
) -> Optional[Err]:
    if tx_removals is None:
        tx_removals = []
    if tx_additions is None:
        tx_additions = []
    removal_merkle_set = MerkleSet()
    addition_merkle_set = MerkleSet()

    # Create removal Merkle set
    for coin_name in tx_removals:
        removal_merkle_set.add_already_hashed(coin_name)

    # Create addition Merkle set
    puzzlehash_coins_map: Dict[bytes32, List[Coin]] = {}

    for coin in tx_additions:
        if coin.puzzle_hash in puzzlehash_coins_map:
            puzzlehash_coins_map[coin.puzzle_hash].append(coin)
        else:
            puzzlehash_coins_map[coin.puzzle_hash] = [coin]

    # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
    for puzzle, coins in puzzlehash_coins_map.items():
        addition_merkle_set.add_already_hashed(puzzle)
        addition_merkle_set.add_already_hashed(hash_coin_list(coins))

    additions_root = addition_merkle_set.get_root()
    removals_root = removal_merkle_set.get_root()

    if block_additions_root != additions_root:
        return Err.BAD_ADDITION_ROOT
    if block_removals_root != removals_root:
        return Err.BAD_REMOVAL_ROOT

    return None
예제 #6
0
    def _create_block(
            self,
            test_constants: ConsensusConstants,
            challenge_hash: bytes32,
            height: uint32,
            prev_header_hash: bytes32,
            prev_iters: uint64,
            prev_weight: uint128,
            timestamp: uint64,
            difficulty: int,
            min_iters: int,
            seed: bytes,
            genesis: bool = False,
            reward_puzzlehash: bytes32 = None,
            transactions: Program = None,
            aggsig: G2Element = None,
            fees: uint64 = uint64(0),
    ) -> FullBlock:
        """
        Creates a block with the specified details. Uses the stored plots to create a proof of space,
        and also evaluates the VDF for the proof of time.
        """
        selected_plot_info = None
        selected_proof_index = 0
        selected_quality: Optional[bytes] = None
        best_quality = 0
        plots = [
            pinfo for _, pinfo in sorted(list(self.plots.items()),
                                         key=lambda x: str(x[0]))
        ]
        if self.use_any_pos:
            random.seed(seed)
            for i in range(len(plots) * 3):
                # Allow passing in seed, to create reorgs and different chains
                seeded_pn = random.randint(0, len(plots) - 1)
                plot_info = plots[seeded_pn]
                plot_id = plot_info.prover.get_id()
                ccp = ProofOfSpace.can_create_proof(
                    plot_id,
                    challenge_hash,
                    test_constants.NUMBER_ZERO_BITS_CHALLENGE_SIG,
                )
                if not ccp:
                    continue
                qualities = plot_info.prover.get_qualities_for_challenge(
                    challenge_hash)
                if len(qualities) > 0:
                    selected_plot_info = plot_info
                    selected_quality = qualities[0]
                    break
        else:
            for i in range(len(plots)):
                plot_info = plots[i]
                j = 0
                plot_id = plot_info.prover.get_id()
                ccp = ProofOfSpace.can_create_proof(
                    plot_id,
                    challenge_hash,
                    test_constants.NUMBER_ZERO_BITS_CHALLENGE_SIG,
                )
                if not ccp:
                    continue
                qualities = plot_info.prover.get_qualities_for_challenge(
                    challenge_hash)
                for quality in qualities:
                    qual_int = int.from_bytes(quality, "big", signed=False)
                    if qual_int > best_quality:
                        best_quality = qual_int
                        selected_quality = quality
                        selected_plot_info = plot_info
                        selected_proof_index = j
                    j += 1

        assert selected_plot_info is not None
        if selected_quality is None:
            raise RuntimeError("No proofs for this challenge")

        proof_xs: bytes = selected_plot_info.prover.get_full_proof(
            challenge_hash, selected_proof_index)

        plot_pk = ProofOfSpace.generate_plot_public_key(
            selected_plot_info.local_sk.get_g1(),
            selected_plot_info.farmer_public_key,
        )
        proof_of_space: ProofOfSpace = ProofOfSpace(
            challenge_hash,
            selected_plot_info.pool_public_key,
            plot_pk,
            selected_plot_info.prover.get_size(),
            proof_xs,
        )

        number_iters: uint64 = pot_iterations.calculate_iterations(
            proof_of_space,
            difficulty,
            min_iters,
            test_constants.NUMBER_ZERO_BITS_CHALLENGE_SIG,
        )
        if self.real_plots:
            print(f"Performing {number_iters} VDF iterations")

        int_size = (test_constants.DISCRIMINANT_SIZE_BITS + 16) >> 4

        result = prove(challenge_hash, test_constants.DISCRIMINANT_SIZE_BITS,
                       number_iters)

        output = ClassgroupElement(
            int512(int.from_bytes(
                result[0:int_size],
                "big",
                signed=True,
            )),
            int512(
                int.from_bytes(
                    result[int_size:2 * int_size],
                    "big",
                    signed=True,
                )),
        )
        proof_bytes = result[2 * int_size:4 * int_size]

        proof_of_time = ProofOfTime(
            challenge_hash,
            number_iters,
            output,
            uint8(0),
            proof_bytes,
        )

        # Use the extension data to create different blocks based on header hash
        extension_data: bytes32 = bytes32(
            [random.randint(0, 255) for _ in range(32)])
        cost = uint64(0)

        fee_reward = uint64(block_rewards.calculate_base_fee(height) + fees)

        std_hash(std_hash(height))

        # Create filter
        byte_array_tx: List[bytes32] = []
        tx_additions: List[Coin] = []
        tx_removals: List[bytes32] = []
        if transactions:
            error, npc_list, _ = get_name_puzzle_conditions(transactions)
            additions: List[Coin] = additions_for_npc(npc_list)
            for coin in additions:
                tx_additions.append(coin)
                byte_array_tx.append(bytearray(coin.puzzle_hash))
            for npc in npc_list:
                tx_removals.append(npc.coin_name)
                byte_array_tx.append(bytearray(npc.coin_name))
        farmer_ph = self.farmer_ph
        pool_ph = self.pool_ph
        if reward_puzzlehash is not None:
            farmer_ph = reward_puzzlehash
            pool_ph = reward_puzzlehash

        byte_array_tx.append(bytearray(farmer_ph))
        byte_array_tx.append(bytearray(pool_ph))
        bip158: PyBIP158 = PyBIP158(byte_array_tx)
        encoded = bytes(bip158.GetEncoded())

        removal_merkle_set = MerkleSet()
        addition_merkle_set = MerkleSet()

        # Create removal Merkle set
        for coin_name in tx_removals:
            removal_merkle_set.add_already_hashed(coin_name)

        # Create addition Merkle set
        puzzlehash_coin_map: Dict[bytes32, List[Coin]] = {}
        cb_reward = calculate_block_reward(height)
        cb_coin = create_coinbase_coin(height, pool_ph, cb_reward)
        fees_coin = create_fees_coin(height, farmer_ph, fee_reward)
        for coin in tx_additions + [cb_coin, fees_coin]:
            if coin.puzzle_hash in puzzlehash_coin_map:
                puzzlehash_coin_map[coin.puzzle_hash].append(coin)
            else:
                puzzlehash_coin_map[coin.puzzle_hash] = [coin]

        # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
        for puzzle, coins in puzzlehash_coin_map.items():
            addition_merkle_set.add_already_hashed(puzzle)
            addition_merkle_set.add_already_hashed(hash_coin_list(coins))

        additions_root = addition_merkle_set.get_root()
        removal_root = removal_merkle_set.get_root()

        generator_hash = (transactions.get_tree_hash()
                          if transactions is not None else bytes32([0] * 32))
        filter_hash = std_hash(encoded)

        pool_target = PoolTarget(pool_ph, uint32(height))
        pool_target_signature = self.get_pool_key_signature(
            pool_target, proof_of_space.pool_public_key)
        assert pool_target_signature is not None
        final_aggsig: G2Element = pool_target_signature
        if aggsig is not None:
            final_aggsig = AugSchemeMPL.aggregate([final_aggsig, aggsig])

        header_data: HeaderData = HeaderData(
            height,
            prev_header_hash,
            timestamp,
            filter_hash,
            proof_of_space.get_hash(),
            uint128(prev_weight + difficulty),
            uint64(prev_iters + number_iters),
            additions_root,
            removal_root,
            farmer_ph,
            fee_reward,
            pool_target,
            final_aggsig,
            cost,
            extension_data,
            generator_hash,
        )

        header_hash_sig: G2Element = self.get_plot_signature(
            header_data, plot_pk)

        header: Header = Header(header_data, header_hash_sig)

        full_block: FullBlock = FullBlock(proof_of_space, proof_of_time,
                                          header, transactions, encoded)

        return full_block
예제 #7
0
def create_foliage(
    constants: ConsensusConstants,
    reward_block_unfinished: RewardChainBlockUnfinished,
    spend_bundle: Optional[SpendBundle],
    additions: List[Coin],
    removals: List[Coin],
    prev_block: Optional[BlockRecord],
    blocks: BlockchainInterface,
    total_iters_sp: uint128,
    timestamp: uint64,
    farmer_reward_puzzlehash: bytes32,
    pool_target: PoolTarget,
    get_plot_signature: Callable[[bytes32, G1Element], G2Element],
    get_pool_signature: Callable[[PoolTarget, Optional[G1Element]],
                                 Optional[G2Element]],
    seed: bytes32 = b"",
) -> Tuple[Foliage, Optional[FoliageTransactionBlock],
           Optional[TransactionsInfo], Optional[SerializedProgram]]:
    """
    Creates a foliage for a given reward chain block. This may or may not be a tx block. In the case of a tx block,
    the return values are not None. This is called at the signage point, so some of this information may be
    tweaked at the infusion point.

    Args:
        constants: consensus constants being used for this chain
        reward_block_unfinished: the reward block to look at, potentially at the signage point
        spend_bundle: the spend bundle including all transactions
        prev_block: the previous block at the signage point
        blocks: dict from header hash to blocks, of all ancestor blocks
        total_iters_sp: total iters at the signage point
        timestamp: timestamp to put into the foliage block
        farmer_reward_puzzlehash: where to pay out farming reward
        pool_target: where to pay out pool reward
        get_plot_signature: retrieve the signature corresponding to the plot public key
        get_pool_signature: retrieve the signature corresponding to the pool public key
        seed: seed to randomize block

    """

    if prev_block is not None:
        res = get_prev_transaction_block(prev_block, blocks, total_iters_sp)
        is_transaction_block: bool = res[0]
        prev_transaction_block: Optional[BlockRecord] = res[1]
    else:
        # Genesis is a transaction block
        prev_transaction_block = None
        is_transaction_block = True

    random.seed(seed)
    # Use the extension data to create different blocks based on header hash
    extension_data: bytes32 = random.randint(0, 100000000).to_bytes(32, "big")
    if prev_block is None:
        height: uint32 = uint32(0)
    else:
        height = uint32(prev_block.height + 1)

    # Create filter
    byte_array_tx: List[bytes32] = []
    tx_additions: List[Coin] = []
    tx_removals: List[bytes32] = []

    pool_target_signature: Optional[G2Element] = get_pool_signature(
        pool_target, reward_block_unfinished.proof_of_space.pool_public_key)

    foliage_data = FoliageBlockData(
        reward_block_unfinished.get_hash(),
        pool_target,
        pool_target_signature,
        farmer_reward_puzzlehash,
        extension_data,
    )

    foliage_block_data_signature: G2Element = get_plot_signature(
        foliage_data.get_hash(),
        reward_block_unfinished.proof_of_space.plot_public_key,
    )

    prev_block_hash: bytes32 = constants.GENESIS_CHALLENGE
    if height != 0:
        assert prev_block is not None
        prev_block_hash = prev_block.header_hash

    solution_program: Optional[SerializedProgram] = None
    if is_transaction_block:
        spend_bundle_fees: int = 0
        aggregate_sig: G2Element = G2Element.infinity()
        cost = uint64(0)

        if spend_bundle is not None:
            solution_program = best_solution_program(spend_bundle)
            aggregate_sig = spend_bundle.aggregated_signature

        # Calculate the cost of transactions
        if solution_program is not None:
            result: CostResult = calculate_cost_of_program(
                solution_program, constants.CLVM_COST_RATIO_CONSTANT)
            cost = result.cost
            removal_amount = 0
            addition_amount = 0
            for coin in removals:
                removal_amount += coin.amount
            for coin in additions:
                addition_amount += coin.amount
            spend_bundle_fees = removal_amount - addition_amount
        else:
            spend_bundle_fees = 0

        # TODO: prev generators root
        reward_claims_incorporated = []
        if height > 0:
            assert prev_transaction_block is not None
            assert prev_block is not None
            curr: BlockRecord = prev_block
            while not curr.is_transaction_block:
                curr = blocks.block_record(curr.prev_hash)

            assert curr.fees is not None
            pool_coin = create_pool_coin(
                curr.height,
                curr.pool_puzzle_hash,
                calculate_pool_reward(curr.height),
            )

            farmer_coin = create_farmer_coin(
                curr.height,
                curr.farmer_puzzle_hash,
                uint64(calculate_base_farmer_reward(curr.height) + curr.fees),
            )
            assert curr.header_hash == prev_transaction_block.header_hash
            reward_claims_incorporated += [pool_coin, farmer_coin]

            if curr.height > 0:
                curr = blocks.block_record(curr.prev_hash)
                # Prev block is not genesis
                while not curr.is_transaction_block:
                    pool_coin = create_pool_coin(
                        curr.height,
                        curr.pool_puzzle_hash,
                        calculate_pool_reward(curr.height),
                    )
                    farmer_coin = create_farmer_coin(
                        curr.height,
                        curr.farmer_puzzle_hash,
                        calculate_base_farmer_reward(curr.height),
                    )
                    reward_claims_incorporated += [pool_coin, farmer_coin]
                    curr = blocks.block_record(curr.prev_hash)
        additions.extend(reward_claims_incorporated.copy())
        for coin in additions:
            tx_additions.append(coin)
            byte_array_tx.append(bytearray(coin.puzzle_hash))
        for coin in removals:
            tx_removals.append(coin.name())
            byte_array_tx.append(bytearray(coin.name()))

        bip158: PyBIP158 = PyBIP158(byte_array_tx)
        encoded = bytes(bip158.GetEncoded())

        removal_merkle_set = MerkleSet()
        addition_merkle_set = MerkleSet()

        # Create removal Merkle set
        for coin_name in tx_removals:
            removal_merkle_set.add_already_hashed(coin_name)

        # Create addition Merkle set
        puzzlehash_coin_map: Dict[bytes32, List[Coin]] = {}

        for coin in tx_additions:
            if coin.puzzle_hash in puzzlehash_coin_map:
                puzzlehash_coin_map[coin.puzzle_hash].append(coin)
            else:
                puzzlehash_coin_map[coin.puzzle_hash] = [coin]

        # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
        for puzzle, coins in puzzlehash_coin_map.items():
            addition_merkle_set.add_already_hashed(puzzle)
            addition_merkle_set.add_already_hashed(hash_coin_list(coins))

        additions_root = addition_merkle_set.get_root()
        removals_root = removal_merkle_set.get_root()

        generator_hash = solution_program.get_tree_hash(
        ) if solution_program is not None else bytes32([0] * 32)
        filter_hash: bytes32 = std_hash(encoded)

        transactions_info: Optional[TransactionsInfo] = TransactionsInfo(
            bytes([0] * 32),
            generator_hash,
            aggregate_sig,
            uint64(spend_bundle_fees),
            cost,
            reward_claims_incorporated,
        )
        if prev_transaction_block is None:
            prev_transaction_block_hash: bytes32 = constants.GENESIS_CHALLENGE
        else:
            prev_transaction_block_hash = prev_transaction_block.header_hash

        assert transactions_info is not None
        foliage_transaction_block: Optional[
            FoliageTransactionBlock] = FoliageTransactionBlock(
                prev_transaction_block_hash,
                timestamp,
                filter_hash,
                additions_root,
                removals_root,
                transactions_info.get_hash(),
            )
        assert foliage_transaction_block is not None

        foliage_transaction_block_hash: Optional[
            bytes32] = foliage_transaction_block.get_hash()
        foliage_transaction_block_signature: Optional[
            G2Element] = get_plot_signature(
                foliage_transaction_block_hash,
                reward_block_unfinished.proof_of_space.plot_public_key)
        assert foliage_transaction_block_signature is not None
    else:
        foliage_transaction_block_hash = None
        foliage_transaction_block_signature = None
        foliage_transaction_block = None
        transactions_info = None
    assert (foliage_transaction_block_hash is
            None) == (foliage_transaction_block_signature is None)

    foliage = Foliage(
        prev_block_hash,
        reward_block_unfinished.get_hash(),
        foliage_data,
        foliage_block_data_signature,
        foliage_transaction_block_hash,
        foliage_transaction_block_signature,
    )

    return foliage, foliage_transaction_block, transactions_info, solution_program
예제 #8
0
    async def respond_additions(self,
                                response: wallet_protocol.RespondAdditions):
        """
        The full node has responded with the additions for a block. We will use this
        to try to finish the block, and add it to the state.
        """
        if self._shut_down:
            return
        if response.header_hash not in self.cached_blocks:
            self.log.warning("Do not have header for additions")
            return
        block_record, header_block, transaction_filter = self.cached_blocks[
            response.header_hash]
        assert response.height == block_record.height

        additions: List[Coin]
        if response.proofs is None:
            # If there are no proofs, it means all additions were returned in the response.
            # we must find the ones relevant to our wallets.
            all_coins: List[Coin] = []
            for puzzle_hash, coin_list_0 in response.coins:
                all_coins += coin_list_0
            additions = await self.wallet_state_manager.get_relevant_additions(
                all_coins)
            # Verify root
            additions_merkle_set = MerkleSet()

            # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
            for puzzle_hash, coins in response.coins:
                additions_merkle_set.add_already_hashed(puzzle_hash)
                additions_merkle_set.add_already_hashed(hash_coin_list(coins))

            additions_root = additions_merkle_set.get_root()
            if header_block.header.data.additions_root != additions_root:
                return
        else:
            # This means the full node has responded only with the relevant additions
            # for our wallet. Each merkle proof must be verified.
            additions = []
            assert len(response.coins) == len(response.proofs)
            for i in range(len(response.coins)):
                assert response.coins[i][0] == response.proofs[i][0]
                coin_list_1: List[Coin] = response.coins[i][1]
                puzzle_hash_proof: bytes32 = response.proofs[i][1]
                coin_list_proof: Optional[bytes32] = response.proofs[i][2]
                if len(coin_list_1) == 0:
                    # Verify exclusion proof for puzzle hash
                    assert confirm_not_included_already_hashed(
                        header_block.header.data.additions_root,
                        response.coins[i][0],
                        puzzle_hash_proof,
                    )
                else:
                    # Verify inclusion proof for puzzle hash
                    assert confirm_included_already_hashed(
                        header_block.header.data.additions_root,
                        response.coins[i][0],
                        puzzle_hash_proof,
                    )
                    # Verify inclusion proof for coin list
                    assert confirm_included_already_hashed(
                        header_block.header.data.additions_root,
                        hash_coin_list(coin_list_1),
                        coin_list_proof,
                    )
                    for coin in coin_list_1:
                        assert coin.puzzle_hash == response.coins[i][0]
                    additions += coin_list_1
        new_br = BlockRecord(
            block_record.header_hash,
            block_record.prev_header_hash,
            block_record.height,
            block_record.weight,
            additions,
            None,
            block_record.total_iters,
            header_block.challenge.get_hash(),
        )
        self.cached_blocks[response.header_hash] = (
            new_br,
            header_block,
            transaction_filter,
        )

        if transaction_filter is None:
            raise RuntimeError("Got additions for block with no transactions.")

        (
            _,
            removals,
        ) = await self.wallet_state_manager.get_filter_additions_removals(
            new_br, transaction_filter)
        request_all_removals = False
        for coin in additions:
            puzzle_store = self.wallet_state_manager.puzzle_store
            record_info: Optional[
                DerivationRecord] = await puzzle_store.get_derivation_record_for_puzzle_hash(
                    coin.puzzle_hash.hex())
            if (record_info is not None
                    and record_info.wallet_type == WalletType.COLOURED_COIN):
                request_all_removals = True
                break

        if len(removals) > 0 or request_all_removals:
            if request_all_removals:
                request_r = wallet_protocol.RequestRemovals(
                    header_block.height, header_block.header_hash, None)
            else:
                request_r = wallet_protocol.RequestRemovals(
                    header_block.height, header_block.header_hash, removals)
            yield OutboundMessage(
                NodeType.FULL_NODE,
                Message("request_removals", request_r),
                Delivery.RESPOND,
            )
        else:
            # We have collected all three things: header, additions, and removals (since there are no
            # relevant removals for us). Can proceed. Otherwise, we wait for the removals to arrive.
            new_br = BlockRecord(
                new_br.header_hash,
                new_br.prev_header_hash,
                new_br.height,
                new_br.weight,
                new_br.additions,
                [],
                new_br.total_iters,
                new_br.new_challenge_hash,
            )
            respond_header_msg: Optional[
                wallet_protocol.RespondHeader] = await self._block_finished(
                    new_br, header_block, transaction_filter)
            if respond_header_msg is not None:
                async for msg in self.respond_header(respond_header_msg):
                    yield msg
예제 #9
0
    async def respond_removals(self,
                               response: wallet_protocol.RespondRemovals):
        """
        The full node has responded with the removals for a block. We will use this
        to try to finish the block, and add it to the state.
        """
        if self._shut_down:
            return
        if (response.header_hash not in self.cached_blocks or
                self.cached_blocks[response.header_hash][0].additions is None):
            self.log.warning(
                "Do not have header for removals, or do not have additions")
            return

        block_record, header_block, transaction_filter = self.cached_blocks[
            response.header_hash]
        assert response.height == block_record.height

        all_coins: List[Coin] = []
        for coin_name, coin in response.coins:
            if coin is not None:
                all_coins.append(coin)

        if response.proofs is None:
            # If there are no proofs, it means all removals were returned in the response.
            # we must find the ones relevant to our wallets.

            # Verify removals root
            removals_merkle_set = MerkleSet()
            for coin in all_coins:
                if coin is not None:
                    removals_merkle_set.add_already_hashed(coin.name())
            removals_root = removals_merkle_set.get_root()
            assert header_block.header.data.removals_root == removals_root

        else:
            # This means the full node has responded only with the relevant removals
            # for our wallet. Each merkle proof must be verified.
            assert len(response.coins) == len(response.proofs)
            for i in range(len(response.coins)):
                # Coins are in the same order as proofs
                assert response.coins[i][0] == response.proofs[i][0]
                coin = response.coins[i][1]
                if coin is None:
                    # Verifies merkle proof of exclusion
                    assert confirm_not_included_already_hashed(
                        header_block.header.data.removals_root,
                        response.coins[i][0],
                        response.proofs[i][1],
                    )
                else:
                    # Verifies merkle proof of inclusion of coin name
                    assert response.coins[i][0] == coin.name()
                    assert confirm_included_already_hashed(
                        header_block.header.data.removals_root,
                        coin.name(),
                        response.proofs[i][1],
                    )

        new_br = BlockRecord(
            block_record.header_hash,
            block_record.prev_header_hash,
            block_record.height,
            block_record.weight,
            block_record.additions,
            all_coins,
            block_record.total_iters,
            header_block.challenge.get_hash(),
        )

        self.cached_blocks[response.header_hash] = (
            new_br,
            header_block,
            transaction_filter,
        )

        # We have collected all three things: header, additions, and removals. Can proceed.
        respond_header_msg: Optional[
            wallet_protocol.RespondHeader] = await self._block_finished(
                new_br, header_block, transaction_filter)
        if respond_header_msg is not None:
            async for msg in self.respond_header(respond_header_msg):
                yield msg
예제 #10
0
    def _create_block(
        self,
        test_constants: Dict,
        challenge_hash: bytes32,
        height: uint32,
        prev_header_hash: bytes32,
        prev_iters: uint64,
        prev_weight: uint128,
        timestamp: uint64,
        difficulty: uint64,
        min_iters: uint64,
        seed: bytes,
        genesis: bool = False,
        reward_puzzlehash: bytes32 = None,
        transactions: Program = None,
        aggsig: BLSSignature = None,
        fees: uint64 = uint64(0),
    ) -> FullBlock:
        """
        Creates a block with the specified details. Uses the stored plots to create a proof of space,
        and also evaluates the VDF for the proof of time.
        """
        selected_prover = None
        selected_plot_sk = None
        selected_pool_sk = None
        selected_proof_index = 0
        plots = list(self.plot_config["plots"].items())
        selected_quality: Optional[bytes] = None
        best_quality = 0
        if self.use_any_pos:
            for i in range(len(plots) * 3):
                # Allow passing in seed, to create reorgs and different chains
                random.seed(seed + i.to_bytes(4, "big"))
                seeded_pn = random.randint(0, len(plots) - 1)
                pool_sk = PrivateKey.from_bytes(
                    bytes.fromhex(plots[seeded_pn][1]["pool_sk"])
                )
                plot_sk = PrivateKey.from_bytes(
                    bytes.fromhex(plots[seeded_pn][1]["sk"])
                )
                prover = DiskProver(plots[seeded_pn][0])
                qualities = prover.get_qualities_for_challenge(challenge_hash)
                if len(qualities) > 0:
                    if self.use_any_pos:
                        selected_quality = qualities[0]
                        selected_prover = prover
                        selected_pool_sk = pool_sk
                        selected_plot_sk = plot_sk
                        break
        else:
            for i in range(len(plots)):
                pool_sk = PrivateKey.from_bytes(bytes.fromhex(plots[i][1]["pool_sk"]))
                plot_sk = PrivateKey.from_bytes(bytes.fromhex(plots[i][1]["sk"]))
                prover = DiskProver(plots[i][0])
                qualities = prover.get_qualities_for_challenge(challenge_hash)
                j = 0
                for quality in qualities:
                    qual_int = int.from_bytes(quality, "big", signed=False)
                    if qual_int > best_quality:
                        best_quality = qual_int
                        selected_quality = quality
                        selected_prover = prover
                        selected_pool_sk = pool_sk
                        selected_plot_sk = plot_sk
                        selected_proof_index = j
                    j += 1

        assert selected_prover
        assert selected_pool_sk
        assert selected_plot_sk
        pool_pk = selected_pool_sk.get_public_key()
        plot_pk = selected_plot_sk.get_public_key()
        if selected_quality is None:
            raise RuntimeError("No proofs for this challenge")

        proof_xs: bytes = selected_prover.get_full_proof(
            challenge_hash, selected_proof_index
        )
        proof_of_space: ProofOfSpace = ProofOfSpace(
            challenge_hash, pool_pk, plot_pk, selected_prover.get_size(), proof_xs
        )
        number_iters: uint64 = pot_iterations.calculate_iterations(
            proof_of_space, difficulty, min_iters
        )
        # print("Doing iters", number_iters)
        int_size = (test_constants["DISCRIMINANT_SIZE_BITS"] + 16) >> 4

        result = prove(
            challenge_hash, test_constants["DISCRIMINANT_SIZE_BITS"], number_iters
        )

        output = ClassgroupElement(
            int512(int.from_bytes(result[0:int_size], "big", signed=True,)),
            int512(
                int.from_bytes(result[int_size : 2 * int_size], "big", signed=True,)
            ),
        )
        proof_bytes = result[2 * int_size : 4 * int_size]

        proof_of_time = ProofOfTime(
            challenge_hash, number_iters, output, self.n_wesolowski, proof_bytes,
        )

        if not reward_puzzlehash:
            reward_puzzlehash = self.fee_target

        # Use the extension data to create different blocks based on header hash
        extension_data: bytes32 = bytes32([random.randint(0, 255) for _ in range(32)])
        cost = uint64(0)

        coinbase_reward = block_rewards.calculate_block_reward(height)
        fee_reward = uint64(block_rewards.calculate_base_fee(height) + fees)

        coinbase_coin, coinbase_signature = create_coinbase_coin_and_signature(
            height, reward_puzzlehash, coinbase_reward, selected_pool_sk
        )

        parent_coin_name = std_hash(std_hash(height))
        fees_coin = Coin(parent_coin_name, reward_puzzlehash, uint64(fee_reward))

        # Create filter
        byte_array_tx: List[bytes32] = []
        tx_additions: List[Coin] = []
        tx_removals: List[bytes32] = []
        encoded = None
        if transactions:
            error, npc_list, _ = get_name_puzzle_conditions(transactions)
            additions: List[Coin] = additions_for_npc(npc_list)
            for coin in additions:
                tx_additions.append(coin)
                byte_array_tx.append(bytearray(coin.puzzle_hash))
            for npc in npc_list:
                tx_removals.append(npc.coin_name)
                byte_array_tx.append(bytearray(npc.coin_name))

            bip158: PyBIP158 = PyBIP158(byte_array_tx)
            encoded = bytes(bip158.GetEncoded())

        removal_merkle_set = MerkleSet()
        addition_merkle_set = MerkleSet()

        # Create removal Merkle set
        for coin_name in tx_removals:
            removal_merkle_set.add_already_hashed(coin_name)

        # Create addition Merkle set
        puzzlehash_coin_map: Dict[bytes32, List[Coin]] = {}
        for coin in tx_additions:
            if coin.puzzle_hash in puzzlehash_coin_map:
                puzzlehash_coin_map[coin.puzzle_hash].append(coin)
            else:
                puzzlehash_coin_map[coin.puzzle_hash] = [coin]

        # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
        for puzzle, coins in puzzlehash_coin_map.items():
            addition_merkle_set.add_already_hashed(puzzle)
            addition_merkle_set.add_already_hashed(hash_coin_list(coins))

        additions_root = addition_merkle_set.get_root()
        removal_root = removal_merkle_set.get_root()

        generator_hash = (
            transactions.get_tree_hash()
            if transactions is not None
            else bytes32([0] * 32)
        )
        filter_hash = std_hash(encoded) if encoded is not None else bytes32([0] * 32)

        header_data: HeaderData = HeaderData(
            height,
            prev_header_hash,
            timestamp,
            filter_hash,
            proof_of_space.get_hash(),
            uint128(prev_weight + difficulty),
            uint64(prev_iters + number_iters),
            additions_root,
            removal_root,
            coinbase_coin,
            coinbase_signature,
            fees_coin,
            aggsig,
            cost,
            extension_data,
            generator_hash,
        )

        header_hash_sig: PrependSignature = selected_plot_sk.sign_prepend(
            header_data.get_hash()
        )

        header: Header = Header(header_data, header_hash_sig)

        full_block: FullBlock = FullBlock(
            proof_of_space, proof_of_time, header, transactions, encoded
        )

        return full_block
예제 #11
0
    async def test_request_removals(self, two_nodes, wallet_blocks):
        full_node_1, full_node_2, server_1, server_2 = two_nodes
        wallet_a, wallet_receiver, blocks = wallet_blocks

        await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
        blocks_list = await get_block_path(full_node_1)
        blocks_new = bt.get_consecutive_blocks(
            test_constants, 5, seed=b"test_request_removals"
        )

        # Request removals for nonexisting block fails
        msgs = [
            _
            async for _ in full_node_1.request_removals(
                wallet_protocol.RequestRemovals(
                    blocks_new[-1].height, blocks_new[-1].header_hash, None
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RejectRemovalsRequest)

        # Request removals for orphaned block fails
        for block in blocks_new:
            async for _ in full_node_1.respond_block(fnp.RespondBlock(block)):
                pass
        msgs = [
            _
            async for _ in full_node_1.request_removals(
                wallet_protocol.RequestRemovals(
                    blocks_new[-1].height, blocks_new[-1].header_hash, None
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RejectRemovalsRequest)

        # If there are no transactions, empty proof and coins
        blocks_new = bt.get_consecutive_blocks(
            test_constants, 10, block_list=blocks_list,
        )
        for block in blocks_new:
            [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))]
        msgs = [
            _
            async for _ in full_node_1.request_removals(
                wallet_protocol.RequestRemovals(
                    blocks_new[-4].height, blocks_new[-4].header_hash, None
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals)
        assert len(msgs[0].message.data.coins) == 0
        assert msgs[0].message.data.proofs is None

        # Add a block with transactions
        spend_bundles = []
        for i in range(5):
            spend_bundles.append(
                wallet_a.generate_signed_transaction(
                    100,
                    wallet_a.get_new_puzzlehash(),
                    blocks_new[i - 8].get_coinbase(),
                )
            )
        height_with_transactions = len(blocks_new) + 1
        agg = SpendBundle.aggregate(spend_bundles)
        dic_h = {
            height_with_transactions: (
                best_solution_program(agg),
                agg.aggregated_signature,
            )
        }
        blocks_new = bt.get_consecutive_blocks(
            test_constants, 5, block_list=blocks_new, transaction_data_at_height=dic_h
        )
        for block in blocks_new:
            [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))]

        # If no coins requested, respond all coins and NO proof
        msgs = [
            _
            async for _ in full_node_1.request_removals(
                wallet_protocol.RequestRemovals(
                    blocks_new[height_with_transactions].height,
                    blocks_new[height_with_transactions].header_hash,
                    None,
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals)
        assert len(msgs[0].message.data.coins) == 5
        assert msgs[0].message.data.proofs is None

        removals_merkle_set = MerkleSet()
        for sb in spend_bundles:
            for coin in sb.removals():
                if coin is not None:
                    removals_merkle_set.add_already_hashed(coin.name())

        # Ask for one coin and check PoI
        coin_list = [spend_bundles[0].removals()[0].name()]
        msgs = [
            _
            async for _ in full_node_1.request_removals(
                wallet_protocol.RequestRemovals(
                    blocks_new[height_with_transactions].height,
                    blocks_new[height_with_transactions].header_hash,
                    coin_list,
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals)
        assert len(msgs[0].message.data.coins) == 1
        assert msgs[0].message.data.proofs is not None
        assert len(msgs[0].message.data.proofs) == 1
        assert confirm_included_already_hashed(
            blocks_new[height_with_transactions].header.data.removals_root,
            coin_list[0],
            msgs[0].message.data.proofs[0][1],
        )

        # Ask for one coin and check PoE
        coin_list = [token_bytes(32)]
        msgs = [
            _
            async for _ in full_node_1.request_removals(
                wallet_protocol.RequestRemovals(
                    blocks_new[height_with_transactions].height,
                    blocks_new[height_with_transactions].header_hash,
                    coin_list,
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals)
        assert len(msgs[0].message.data.coins) == 1
        assert msgs[0].message.data.coins[0][1] is None
        assert msgs[0].message.data.proofs is not None
        assert len(msgs[0].message.data.proofs) == 1
        assert confirm_not_included_already_hashed(
            blocks_new[height_with_transactions].header.data.removals_root,
            coin_list[0],
            msgs[0].message.data.proofs[0][1],
        )

        # Ask for two coins
        coin_list = [spend_bundles[0].removals()[0].name(), token_bytes(32)]
        msgs = [
            _
            async for _ in full_node_1.request_removals(
                wallet_protocol.RequestRemovals(
                    blocks_new[height_with_transactions].height,
                    blocks_new[height_with_transactions].header_hash,
                    coin_list,
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondRemovals)
        assert len(msgs[0].message.data.coins) == 2
        assert msgs[0].message.data.coins[0][1] is not None
        assert msgs[0].message.data.coins[1][1] is None
        assert msgs[0].message.data.proofs is not None
        assert len(msgs[0].message.data.proofs) == 2
        assert confirm_included_already_hashed(
            blocks_new[height_with_transactions].header.data.removals_root,
            coin_list[0],
            msgs[0].message.data.proofs[0][1],
        )
        assert confirm_not_included_already_hashed(
            blocks_new[height_with_transactions].header.data.removals_root,
            coin_list[1],
            msgs[0].message.data.proofs[1][1],
        )
예제 #12
0
    async def test_request_additions(self, two_nodes, wallet_blocks):
        full_node_1, full_node_2, server_1, server_2 = two_nodes
        wallet_a, wallet_receiver, blocks = wallet_blocks

        await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None)
        blocks_list = await get_block_path(full_node_1)
        blocks_new = bt.get_consecutive_blocks(
            test_constants, 5, seed=b"test_request_additions"
        )

        # Request additinos for nonexisting block fails
        msgs = [
            _
            async for _ in full_node_1.request_additions(
                wallet_protocol.RequestAdditions(
                    blocks_new[-1].height, blocks_new[-1].header_hash, None
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RejectAdditionsRequest)

        # Request additions for orphaned block fails
        for block in blocks_new:
            async for _ in full_node_1.respond_block(fnp.RespondBlock(block)):
                pass
        msgs = [
            _
            async for _ in full_node_1.request_additions(
                wallet_protocol.RequestAdditions(
                    blocks_new[-1].height, blocks_new[-1].header_hash, None
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RejectAdditionsRequest)

        # If there are no transactions, only cb and fees additions
        blocks_new = bt.get_consecutive_blocks(
            test_constants, 10, block_list=blocks_list,
        )
        for block in blocks_new:
            [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))]
        msgs = [
            _
            async for _ in full_node_1.request_additions(
                wallet_protocol.RequestAdditions(
                    blocks_new[-4].height, blocks_new[-4].header_hash, None
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions)
        assert len(msgs[0].message.data.coins) == 2
        assert msgs[0].message.data.proofs is None

        # Add a block with transactions
        spend_bundles = []
        puzzle_hashes = [wallet_a.get_new_puzzlehash(), wallet_a.get_new_puzzlehash()]
        for i in range(5):
            spend_bundles.append(
                wallet_a.generate_signed_transaction(
                    100, puzzle_hashes[i % 2], blocks_new[i - 8].get_coinbase(),
                )
            )
        height_with_transactions = len(blocks_new) + 1
        agg = SpendBundle.aggregate(spend_bundles)
        dic_h = {
            height_with_transactions: (
                best_solution_program(agg),
                agg.aggregated_signature,
            )
        }
        blocks_new = bt.get_consecutive_blocks(
            test_constants, 5, block_list=blocks_new, transaction_data_at_height=dic_h
        )
        for block in blocks_new:
            [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))]

        # If no puzzle hashes requested, respond all coins and NO proof
        msgs = [
            _
            async for _ in full_node_1.request_additions(
                wallet_protocol.RequestAdditions(
                    blocks_new[height_with_transactions].height,
                    blocks_new[height_with_transactions].header_hash,
                    None,
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions)
        # One puzzle hash with change and fee (x3) = 9, minus two repeated ph = 7 + coinbase and fees = 9
        assert len(msgs[0].message.data.coins) == 9
        assert msgs[0].message.data.proofs is None

        additions_merkle_set = MerkleSet()
        for sb in spend_bundles:
            for coin in sb.additions():
                if coin is not None:
                    additions_merkle_set.add_already_hashed(coin.name())

        # Ask for one coin and check both PoI
        ph_list = [puzzle_hashes[0]]
        msgs = [
            _
            async for _ in full_node_1.request_additions(
                wallet_protocol.RequestAdditions(
                    blocks_new[height_with_transactions].height,
                    blocks_new[height_with_transactions].header_hash,
                    ph_list,
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions)
        assert len(msgs[0].message.data.coins) == 1
        assert len(msgs[0].message.data.coins[0][1]) == 3
        assert msgs[0].message.data.proofs is not None
        assert len(msgs[0].message.data.proofs) == 1
        assert confirm_included_already_hashed(
            blocks_new[height_with_transactions].header.data.additions_root,
            ph_list[0],
            msgs[0].message.data.proofs[0][1],
        )
        coin_list_for_ph = [
            coin
            for coin in blocks_new[height_with_transactions].additions()
            if coin.puzzle_hash == ph_list[0]
        ]
        assert confirm_included_already_hashed(
            blocks_new[height_with_transactions].header.data.additions_root,
            hash_coin_list(coin_list_for_ph),
            msgs[0].message.data.proofs[0][2],
        )

        # Ask for one ph and check PoE
        ph_list = [token_bytes(32)]
        msgs = [
            _
            async for _ in full_node_1.request_additions(
                wallet_protocol.RequestAdditions(
                    blocks_new[height_with_transactions].height,
                    blocks_new[height_with_transactions].header_hash,
                    ph_list,
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions)
        assert len(msgs[0].message.data.coins) == 1
        assert len(msgs[0].message.data.coins[0][1]) == 0
        assert msgs[0].message.data.proofs is not None
        assert len(msgs[0].message.data.proofs) == 1
        assert confirm_not_included_already_hashed(
            blocks_new[height_with_transactions].header.data.additions_root,
            ph_list[0],
            msgs[0].message.data.proofs[0][1],
        )
        assert msgs[0].message.data.proofs[0][2] is None

        # Ask for two puzzle_hashes
        ph_list = [puzzle_hashes[0], token_bytes(32)]
        msgs = [
            _
            async for _ in full_node_1.request_additions(
                wallet_protocol.RequestAdditions(
                    blocks_new[height_with_transactions].height,
                    blocks_new[height_with_transactions].header_hash,
                    ph_list,
                )
            )
        ]
        assert len(msgs) == 1
        assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions)
        assert len(msgs[0].message.data.coins) == 2
        assert len(msgs[0].message.data.coins[0][1]) == 3
        assert msgs[0].message.data.proofs is not None
        assert len(msgs[0].message.data.proofs) == 2
        assert confirm_included_already_hashed(
            blocks_new[height_with_transactions].header.data.additions_root,
            ph_list[0],
            msgs[0].message.data.proofs[0][1],
        )
        assert confirm_included_already_hashed(
            blocks_new[height_with_transactions].header.data.additions_root,
            hash_coin_list(coin_list_for_ph),
            msgs[0].message.data.proofs[0][2],
        )
        assert confirm_not_included_already_hashed(
            blocks_new[height_with_transactions].header.data.additions_root,
            ph_list[1],
            msgs[0].message.data.proofs[1][1],
        )
        assert msgs[0].message.data.proofs[1][2] is None