Example #1
0
async def setup_wallet_node(port,
                            introducer_port=None,
                            key_seed=b"setup_wallet_node",
                            dic={}):
    config = load_config(root_path, "config.yaml", "wallet")
    if "starting_height" in dic:
        config["starting_height"] = dic["starting_height"]

    keychain = Keychain(key_seed.hex(), True)
    keychain.add_private_key_seed(key_seed)
    private_key = keychain.get_all_private_keys()[0][0]
    test_constants_copy = test_constants.copy()
    for k in dic.keys():
        test_constants_copy[k] = dic[k]
    db_path = root_path / f"test-wallet-db-{port}.db"
    if db_path.exists():
        db_path.unlink()
    config["database_path"] = str(db_path)

    net_config = load_config(root_path, "config.yaml")
    ping_interval = net_config.get("ping_interval")
    network_id = net_config.get("network_id")

    wallet = await WalletNode.create(
        config,
        private_key,
        root_path,
        override_constants=test_constants_copy,
        name="wallet1",
    )
    assert ping_interval is not None
    assert network_id is not None
    server = ChiaServer(
        port,
        wallet,
        NodeType.WALLET,
        ping_interval,
        network_id,
        root_path,
        config,
        "wallet-server",
    )
    wallet.set_server(server)

    yield (wallet, server)

    server.close_all()
    await wallet.wallet_state_manager.clear_all_stores()
    await wallet.wallet_state_manager.close_all_stores()
    wallet.wallet_state_manager.unlink_db()
    await server.await_closed()
Example #2
0
class BlockTools:
    """
    Tools to generate blocks for testing.
    """
    def __init__(
        self,
        root_path: Path = TEST_ROOT_PATH,
        real_plots: bool = False,
    ):
        create_default_chia_config(root_path)
        initialize_ssl(root_path)
        self.root_path = root_path
        self.n_wesolowski = uint8(0)
        self.real_plots = real_plots

        if not real_plots:
            # No real plots supplied, so we will use the small test plots
            self.use_any_pos = True
            self.plot_config: Dict = {"plots": {}}
            # Can't go much lower than 19, since plots start having no solutions
            k: uint8 = uint8(19)
            # Uses many plots for testing, in order to guarantee proofs of space at every height
            num_plots = 40
            # Use the empty string as the seed for the private key

            self.keychain = Keychain("testing", True)
            self.keychain.delete_all_keys()
            self.keychain.add_private_key_seed(b"block_tools")
            pool_sk: PrivateKey = self.keychain.get_all_private_keys(
            )[0][0].get_private_key()
            pool_pk: PublicKey = pool_sk.get_public_key()

            plot_sks: List[PrivateKey] = [
                PrivateKey.from_seed(pn.to_bytes(4, "big"))
                for pn in range(num_plots)
            ]
            plot_pks: List[PublicKey] = [
                sk.get_public_key() for sk in plot_sks
            ]

            plot_seeds: List[bytes32] = [
                ProofOfSpace.calculate_plot_seed(pool_pk, plot_pk)
                for plot_pk in plot_pks
            ]
            plot_dir = get_plot_dir(root_path)
            mkdir(plot_dir)
            filenames: List[str] = [
                f"genesis-plots-{k}{std_hash(int.to_bytes(i, 4, 'big')).hex()}.dat"
                for i in range(num_plots)
            ]
            done_filenames = set()
            temp_dir = plot_dir / "plot.tmp"
            mkdir(temp_dir)
            try:
                for pn, filename in enumerate(filenames):
                    if not (plot_dir / filename).exists():
                        plotter = DiskPlotter()
                        plotter.create_plot_disk(
                            str(plot_dir),
                            str(plot_dir),
                            str(plot_dir),
                            filename,
                            k,
                            b"genesis",
                            plot_seeds[pn],
                        )
                        done_filenames.add(filename)
                    self.plot_config["plots"][str(plot_dir / filename)] = {
                        "pool_pk": bytes(pool_pk).hex(),
                        "sk": bytes(plot_sks[pn]).hex(),
                        "pool_sk": bytes(pool_sk).hex(),
                    }
                save_config(self.root_path, "plots.yaml", self.plot_config)

            except KeyboardInterrupt:
                for filename in filenames:
                    if (filename not in done_filenames
                            and (plot_dir / filename).exists()):
                        (plot_dir / filename).unlink()
                sys.exit(1)
        else:
            try:
                plot_config = load_config(DEFAULT_ROOT_PATH, "plots.yaml")
                normal_config = load_config(DEFAULT_ROOT_PATH, "config.yaml")
            except FileNotFoundError:
                raise RuntimeError(
                    "Plots not generated. Run chia-create-plots")
            self.keychain = Keychain(testing=False)
            private_keys: List[PrivateKey] = [
                k.get_private_key()
                for (k, _) in self.keychain.get_all_private_keys()
            ]
            pool_pubkeys: List[PublicKey] = [
                sk.get_public_key() for sk in private_keys
            ]
            if len(private_keys) == 0:
                raise RuntimeError(
                    "Keys not generated. Run `chia generate keys`")

            self.prover_dict, _, _ = load_plots(normal_config["harvester"],
                                                plot_config, pool_pubkeys,
                                                DEFAULT_ROOT_PATH)

            new_plot_config: Dict = {"plots": {}}
            for key, value in plot_config["plots"].items():
                for sk in private_keys:
                    if (bytes(sk.get_public_key()).hex() == value["pool_pk"]
                            and key in self.prover_dict):
                        new_plot_config["plots"][key] = value
                        new_plot_config["plots"][key]["pool_sk"] = bytes(
                            sk).hex()

            self.plot_config = new_plot_config
            self.use_any_pos = False
            a = self.plot_config["plots"]
            print(f"Using {len(a)} reals plots to initialize block_tools")

        private_key = self.keychain.get_all_private_keys()[0][0]
        self.fee_target = create_puzzlehash_for_pk(
            BLSPublicKey(bytes(private_key.public_child(1).get_public_key())))

    def get_harvester_signature(self, header_data: HeaderData,
                                plot_pk: PublicKey):
        for value_dict in self.plot_config["plots"].values():
            if (PrivateKey.from_bytes(bytes.fromhex(
                    value_dict["sk"])).get_public_key() == plot_pk):
                return PrivateKey.from_bytes(bytes.fromhex(
                    value_dict["sk"])).sign_prepend(header_data.get_hash())

    def get_consecutive_blocks(
            self,
            input_constants: Dict,
            num_blocks: int,
            block_list: List[FullBlock] = [],
            seconds_per_block=None,
            seed: bytes = b"",
            reward_puzzlehash: bytes32 = None,
            transaction_data_at_height: Dict[int, Tuple[Program,
                                                        BLSSignature]] = None,
            fees: uint64 = uint64(0),
    ) -> List[FullBlock]:
        if transaction_data_at_height is None:
            transaction_data_at_height = {}
        test_constants: Dict[str, Any] = constants.copy()
        for key, value in input_constants.items():
            test_constants[key] = value
        if seconds_per_block is None:
            seconds_per_block = test_constants["BLOCK_TIME_TARGET"]

        if len(block_list) == 0:
            if "GENESIS_BLOCK" in test_constants:
                block_list.append(
                    FullBlock.from_bytes(test_constants["GENESIS_BLOCK"]))
            else:
                block_list.append(
                    self.create_genesis_block(test_constants, std_hash(seed),
                                              seed))
            prev_difficulty = test_constants["DIFFICULTY_STARTING"]
            curr_difficulty = prev_difficulty
            curr_min_iters = test_constants["MIN_ITERS_STARTING"]
        elif len(block_list) < (test_constants["DIFFICULTY_EPOCH"] +
                                test_constants["DIFFICULTY_DELAY"]):
            # First epoch (+delay), so just get first difficulty
            prev_difficulty = block_list[0].weight
            curr_difficulty = block_list[0].weight
            assert test_constants["DIFFICULTY_STARTING"] == prev_difficulty
            curr_min_iters = test_constants["MIN_ITERS_STARTING"]
        else:
            curr_difficulty = block_list[-1].weight - block_list[-2].weight
            prev_difficulty = (
                block_list[-1 - test_constants["DIFFICULTY_EPOCH"]].weight -
                block_list[-2 - test_constants["DIFFICULTY_EPOCH"]].weight)
            assert block_list[-1].proof_of_time is not None
            curr_min_iters = calculate_min_iters_from_iterations(
                block_list[-1].proof_of_space,
                curr_difficulty,
                block_list[-1].proof_of_time.number_of_iterations,
            )

        starting_height = block_list[-1].height + 1
        timestamp = block_list[-1].header.data.timestamp
        for next_height in range(starting_height,
                                 starting_height + num_blocks):
            if (next_height > test_constants["DIFFICULTY_EPOCH"]
                    and next_height % test_constants["DIFFICULTY_EPOCH"]
                    == test_constants["DIFFICULTY_DELAY"]):
                # Calculates new difficulty
                height1 = uint64(next_height -
                                 (test_constants["DIFFICULTY_EPOCH"] +
                                  test_constants["DIFFICULTY_DELAY"]) - 1)
                height2 = uint64(next_height -
                                 (test_constants["DIFFICULTY_EPOCH"]) - 1)
                height3 = uint64(next_height -
                                 (test_constants["DIFFICULTY_DELAY"]) - 1)
                if height1 >= 0:
                    block1 = block_list[height1]
                    iters1 = block1.header.data.total_iters
                    timestamp1 = block1.header.data.timestamp
                else:
                    block1 = block_list[0]
                    timestamp1 = (block1.header.data.timestamp -
                                  test_constants["BLOCK_TIME_TARGET"])
                    iters1 = uint64(0)
                timestamp2 = block_list[height2].header.data.timestamp
                timestamp3 = block_list[height3].header.data.timestamp

                block3 = block_list[height3]
                iters3 = block3.header.data.total_iters
                term1 = (test_constants["DIFFICULTY_DELAY"] * prev_difficulty *
                         (timestamp3 - timestamp2) *
                         test_constants["BLOCK_TIME_TARGET"])

                term2 = ((test_constants["DIFFICULTY_WARP_FACTOR"] - 1) *
                         (test_constants["DIFFICULTY_EPOCH"] -
                          test_constants["DIFFICULTY_DELAY"]) *
                         curr_difficulty * (timestamp2 - timestamp1) *
                         test_constants["BLOCK_TIME_TARGET"])

                # Round down after the division
                new_difficulty_precise: uint64 = uint64(
                    (term1 + term2) //
                    (test_constants["DIFFICULTY_WARP_FACTOR"] *
                     (timestamp3 - timestamp2) * (timestamp2 - timestamp1)))
                new_difficulty = uint64(
                    truncate_to_significant_bits(
                        new_difficulty_precise,
                        test_constants["SIGNIFICANT_BITS"]))
                max_diff = uint64(
                    truncate_to_significant_bits(
                        test_constants["DIFFICULTY_FACTOR"] * curr_difficulty,
                        test_constants["SIGNIFICANT_BITS"],
                    ))
                min_diff = uint64(
                    truncate_to_significant_bits(
                        curr_difficulty // test_constants["DIFFICULTY_FACTOR"],
                        test_constants["SIGNIFICANT_BITS"],
                    ))
                if new_difficulty >= curr_difficulty:
                    new_difficulty = min(
                        new_difficulty,
                        max_diff,
                    )
                else:
                    new_difficulty = max([uint64(1), new_difficulty, min_diff])

                min_iters_precise = uint64(
                    (iters3 - iters1) //
                    (test_constants["DIFFICULTY_EPOCH"] *
                     test_constants["MIN_ITERS_PROPORTION"]))
                curr_min_iters = uint64(
                    truncate_to_significant_bits(
                        min_iters_precise, test_constants["SIGNIFICANT_BITS"]))
                prev_difficulty = curr_difficulty
                curr_difficulty = new_difficulty
            time_taken = seconds_per_block
            timestamp += time_taken

            transactions: Optional[Program] = None
            aggsig: Optional[BLSSignature] = None
            if next_height in transaction_data_at_height:
                transactions, aggsig = transaction_data_at_height[next_height]

            update_difficulty = (next_height %
                                 test_constants["DIFFICULTY_EPOCH"] ==
                                 test_constants["DIFFICULTY_DELAY"])
            block_list.append(
                self.create_next_block(
                    test_constants,
                    block_list[-1],
                    timestamp,
                    update_difficulty,
                    curr_difficulty,
                    curr_min_iters,
                    seed,
                    reward_puzzlehash,
                    transactions,
                    aggsig,
                    fees,
                ))
        return block_list

    def create_genesis_block(
        self,
        input_constants: Dict,
        challenge_hash=bytes([0] * 32),
        seed: bytes = b"",
        reward_puzzlehash: Optional[bytes32] = None,
    ) -> FullBlock:
        """
        Creates the genesis block with the specified details.
        """
        test_constants: Dict[str, Any] = constants.copy()
        for key, value in input_constants.items():
            test_constants[key] = value

        return self._create_block(
            test_constants,
            challenge_hash,
            uint32(0),
            bytes([0] * 32),
            uint64(0),
            uint128(0),
            uint64(int(time.time())),
            uint64(test_constants["DIFFICULTY_STARTING"]),
            uint64(test_constants["MIN_ITERS_STARTING"]),
            seed,
            True,
            reward_puzzlehash,
        )

    def create_next_block(
            self,
            input_constants: Dict,
            prev_block: FullBlock,
            timestamp: uint64,
            update_difficulty: bool,
            difficulty: uint64,
            min_iters: uint64,
            seed: bytes = b"",
            reward_puzzlehash: bytes32 = None,
            transactions: Program = None,
            aggsig: BLSSignature = None,
            fees: uint64 = uint64(0),
    ) -> FullBlock:
        """
        Creates the next block with the specified details.
        """
        test_constants: Dict[str, Any] = constants.copy()
        for key, value in input_constants.items():
            test_constants[key] = value
        assert prev_block.proof_of_time is not None
        if update_difficulty:
            challenge = Challenge(
                prev_block.proof_of_space.challenge_hash,
                std_hash(prev_block.proof_of_space.get_hash() +
                         prev_block.proof_of_time.output.get_hash()),
                difficulty,
            )
        else:
            challenge = Challenge(
                prev_block.proof_of_space.challenge_hash,
                std_hash(prev_block.proof_of_space.get_hash() +
                         prev_block.proof_of_time.output.get_hash()),
                None,
            )

        return self._create_block(
            test_constants,
            challenge.get_hash(),
            uint32(prev_block.height + 1),
            prev_block.header_hash,
            prev_block.header.data.total_iters,
            prev_block.weight,
            timestamp,
            uint64(difficulty),
            min_iters,
            seed,
            False,
            reward_puzzlehash,
            transactions,
            aggsig,
            fees,
        )

    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"]))
                try:
                    if self.real_plots:
                        prover = self.prover_dict[plots[i][0]]
                    else:
                        prover = DiskProver(plots[i][0])
                except (ValueError, KeyError) as e:
                    continue
                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