예제 #1
0
    def get_consecutive_blocks(
            self,
            test_constants: ConsensusConstants,
            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,
                                                        G2Element]] = None,
            fees: uint64 = uint64(0),
    ) -> List[FullBlock]:
        if transaction_data_at_height is None:
            transaction_data_at_height = {}
        if seconds_per_block is None:
            seconds_per_block = test_constants.BLOCK_TIME_TARGET

        if len(block_list) == 0:
            block_list.append(
                FullBlock.from_bytes(test_constants.GENESIS_BLOCK))
            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,
                test_constants.NUMBER_ZERO_BITS_CHALLENGE_SIG,
            )

        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 = uint64(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[G2Element] = 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
예제 #2
0
    async def test_basic_store(self):
        assert sqlite3.threadsafety == 1
        blocks = bt.get_consecutive_blocks(test_constants, 9, [], 9, b"0")
        blocks_alt = bt.get_consecutive_blocks(test_constants, 3, [], 9, b"1")
        db_filename = Path("blockchain_test.db")
        db_filename_2 = Path("blockchain_test_2.db")
        db_filename_3 = Path("blockchain_test_3.db")

        if db_filename.exists():
            db_filename.unlink()
        if db_filename_2.exists():
            db_filename_2.unlink()
        if db_filename_3.exists():
            db_filename_3.unlink()

        connection = await aiosqlite.connect(db_filename)
        connection_2 = await aiosqlite.connect(db_filename_2)
        connection_3 = await aiosqlite.connect(db_filename_3)

        db = await FullNodeStore.create(connection)
        db_2 = await FullNodeStore.create(connection_2)
        try:
            await db._clear_database()

            genesis = FullBlock.from_bytes(test_constants["GENESIS_BLOCK"])

            # Save/get block
            for block in blocks:
                await db.add_block(block)
                assert block == await db.get_block(block.header_hash)

            await db.add_block(blocks_alt[2])
            assert len(await db.get_blocks_at([1, 2])) == 3

            # Get headers (added alt block also, so +1)
            assert len(await db.get_headers()) == len(blocks) + 1

            # Save/get sync
            for sync_mode in (False, True):
                db.set_sync_mode(sync_mode)
                assert sync_mode == db.get_sync_mode()

            # clear sync info
            await db.clear_sync_info()

            # add/get potential tip, get potential tips num
            db.add_potential_tip(blocks[6])
            assert blocks[6] == db.get_potential_tip(blocks[6].header_hash)

            # Add potential block
            await db.add_potential_block(genesis)
            assert genesis == await db.get_potential_block(uint32(0))

            # Add/get candidate block
            assert db.get_candidate_block(0) is None
            partial = (
                blocks[5].transactions_generator,
                blocks[5].transactions_filter,
                blocks[5].header.data,
                blocks[5].proof_of_space,
            )
            db.add_candidate_block(blocks[5].header_hash, *partial)
            assert db.get_candidate_block(blocks[5].header_hash) == partial
            db.clear_candidate_blocks_below(uint32(8))
            assert db.get_candidate_block(blocks[5].header_hash) is None

            # Proof of time heights
            chall_iters = (bytes32(bytes([1] * 32)), uint32(32532535))
            chall_iters_2 = (bytes32(bytes([3] * 32)), uint32(12522535))
            assert db.get_proof_of_time_heights(chall_iters) is None
            db.add_proof_of_time_heights(chall_iters, 5)
            db.add_proof_of_time_heights(chall_iters_2, 7)
            db.clear_proof_of_time_heights_below(6)
            assert db.get_proof_of_time_heights(chall_iters) is None
            assert db.get_proof_of_time_heights(chall_iters_2) is not None

            # Add/get unfinished block
            i = 1
            for block in blocks:
                key = (block.header_hash, uint64(1000))

                # Different database should have different data
                db_2.add_unfinished_block(key, block)

                assert db.get_unfinished_block(key) is None
                db.add_unfinished_block(key, block)
                assert db.get_unfinished_block(key) == block
                assert len(db.get_unfinished_blocks()) == i
                i += 1
            db.clear_unfinished_blocks_below(uint32(5))
            assert len(db.get_unfinished_blocks()) == 5

            # Set/get unf block leader
            assert db.get_unfinished_block_leader() == (0, (1 << 64) - 1)
            db.set_unfinished_block_leader(key)
            assert db.get_unfinished_block_leader() == key

            assert db.get_disconnected_block(
                blocks[0].prev_header_hash) is None
            # Disconnected blocks
            for block in blocks:
                db.add_disconnected_block(block)
                db.get_disconnected_block(block.prev_header_hash) == block

            db.clear_disconnected_blocks_below(uint32(5))
            assert db.get_disconnected_block(
                blocks[4].prev_header_hash) is None

            h_hash_1 = bytes32(token_bytes(32))
            assert not db.seen_unfinished_block(h_hash_1)
            assert db.seen_unfinished_block(h_hash_1)
            db.clear_seen_unfinished_blocks()
            assert not db.seen_unfinished_block(h_hash_1)

        except Exception:
            await connection.close()
            await connection_2.close()
            await connection_3.close()
            db_filename.unlink()
            db_filename_2.unlink()
            raise

        # Different database should have different data
        db_3 = await FullNodeStore.create(connection_3)
        assert db_3.get_unfinished_block_leader() == (0, (1 << 64) - 1)

        await connection.close()
        await connection_2.close()
        await connection_3.close()
        db_filename.unlink()
        db_filename_2.unlink()
        db_filename_3.unlink()
예제 #3
0
    async def _sync(self):
        """
        Wallet has fallen far behind (or is starting up for the first time), and must be synced
        up to the LCA of the blockchain.
        """
        # 1. Get all header hashes
        self.header_hashes = []
        self.header_hashes_error = False
        self.proof_hashes = []
        self.potential_header_hashes = {}
        genesis = FullBlock.from_bytes(self.constants["GENESIS_BLOCK"])
        genesis_challenge = genesis.proof_of_space.challenge_hash
        request_header_hashes = wallet_protocol.RequestAllHeaderHashesAfter(
            uint32(0), genesis_challenge)
        yield OutboundMessage(
            NodeType.FULL_NODE,
            Message("request_all_header_hashes_after", request_header_hashes),
            Delivery.RESPOND,
        )
        timeout = 100
        sleep_interval = 10
        sleep_interval_short = 1
        start_wait = time.time()
        while time.time() - start_wait < timeout:
            if self._shut_down:
                return
            if self.header_hashes_error:
                raise ValueError(
                    f"Received error from full node while fetching hashes from {request_header_hashes}."
                )
            if len(self.header_hashes) > 0:
                break
            await asyncio.sleep(0.5)
        if len(self.header_hashes) == 0:
            raise TimeoutError("Took too long to fetch header hashes.")

        # 2. Find fork point
        fork_point_height: uint32 = self.wallet_state_manager.find_fork_point_alternate_chain(
            self.header_hashes)
        fork_point_hash: bytes32 = self.header_hashes[fork_point_height]

        # Sync a little behind, in case there is a short reorg
        tip_height = (len(self.header_hashes) - 5 if
                      len(self.header_hashes) > 5 else len(self.header_hashes))
        self.log.info(
            f"Fork point: {fork_point_hash} at height {fork_point_height}. Will sync up to {tip_height}"
        )
        for height in range(0, tip_height + 1):
            self.potential_blocks_received[uint32(height)] = asyncio.Event()

        header_validate_start_height: uint32
        if self.config["starting_height"] == 0:
            header_validate_start_height = fork_point_height
        else:
            # Request all proof hashes
            request_proof_hashes = wallet_protocol.RequestAllProofHashes()
            yield OutboundMessage(
                NodeType.FULL_NODE,
                Message("request_all_proof_hashes", request_proof_hashes),
                Delivery.RESPOND,
            )
            start_wait = time.time()
            while time.time() - start_wait < timeout:
                if self._shut_down:
                    return
                if len(self.proof_hashes) > 0:
                    break
                await asyncio.sleep(0.5)
            if len(self.proof_hashes) == 0:
                raise TimeoutError("Took too long to fetch proof hashes.")
            if len(self.proof_hashes) < tip_height:
                raise ValueError("Not enough proof hashes fetched.")

            # Creates map from height to difficulty
            heights: List[uint32] = []
            difficulty_weights: List[uint64] = []
            difficulty: uint64
            for i in range(tip_height):
                if self.proof_hashes[i][1] is not None:
                    difficulty = self.proof_hashes[i][1]
                if i > (fork_point_height +
                        1) and i % 2 == 1:  # Only add odd heights
                    heights.append(uint32(i))
                    difficulty_weights.append(difficulty)

            # Randomly sample based on difficulty
            query_heights_odd = sorted(
                list(
                    set(
                        random.choices(heights,
                                       difficulty_weights,
                                       k=min(100, len(heights))))))
            query_heights: List[uint32] = []

            for odd_height in query_heights_odd:
                query_heights += [uint32(odd_height - 1), odd_height]

            # Send requests for these heights
            # Verify these proofs
            last_request_time = float(0)
            highest_height_requested = uint32(0)
            request_made = False

            for height_index in range(len(query_heights)):
                total_time_slept = 0
                while True:
                    if self._shut_down:
                        return
                    if total_time_slept > timeout:
                        raise TimeoutError("Took too long to fetch blocks")

                    # Request batches that we don't have yet
                    for batch_start_index in range(
                            height_index,
                            min(
                                height_index + self.config["num_sync_batches"],
                                len(query_heights),
                            ),
                    ):
                        blocks_missing = not self.potential_blocks_received[
                            uint32(query_heights[batch_start_index])].is_set()
                        if ((time.time() - last_request_time > sleep_interval
                             and blocks_missing)
                                or (query_heights[batch_start_index]) >
                                highest_height_requested):
                            self.log.info(
                                f"Requesting sync header {query_heights[batch_start_index]}"
                            )
                            if (query_heights[batch_start_index] >
                                    highest_height_requested):
                                highest_height_requested = uint32(
                                    query_heights[batch_start_index])
                            request_made = True
                            request_header = wallet_protocol.RequestHeader(
                                uint32(query_heights[batch_start_index]),
                                self.header_hashes[
                                    query_heights[batch_start_index]],
                            )
                            yield OutboundMessage(
                                NodeType.FULL_NODE,
                                Message("request_header", request_header),
                                Delivery.RANDOM,
                            )
                    if request_made:
                        last_request_time = time.time()
                        request_made = False
                    try:
                        aw = self.potential_blocks_received[uint32(
                            query_heights[height_index])].wait()
                        await asyncio.wait_for(aw, timeout=sleep_interval)
                        break
                    except concurrent.futures.TimeoutError:
                        total_time_slept += sleep_interval
                        self.log.info("Did not receive desired headers")

            self.log.info(
                f"Finished downloading sample of headers at heights: {query_heights}, validating."
            )
            # Validates the downloaded proofs
            assert self.wallet_state_manager.validate_select_proofs(
                self.proof_hashes,
                query_heights_odd,
                self.cached_blocks,
                self.potential_header_hashes,
            )
            self.log.info("All proofs validated successfuly.")

            # Add blockrecords one at a time, to catch up to starting height
            weight = self.wallet_state_manager.block_records[
                fork_point_hash].weight
            header_validate_start_height = min(
                max(fork_point_height, self.config["starting_height"] - 1),
                tip_height + 1,
            )
            if fork_point_height == 0:
                difficulty = self.constants["DIFFICULTY_STARTING"]
            else:
                fork_point_parent_hash = self.wallet_state_manager.block_records[
                    fork_point_hash].prev_header_hash
                fork_point_parent_weight = self.wallet_state_manager.block_records[
                    fork_point_parent_hash]
                difficulty = uint64(weight - fork_point_parent_weight)
            for height in range(fork_point_height + 1,
                                header_validate_start_height):
                _, difficulty_change, total_iters = self.proof_hashes[height]
                weight += difficulty
                block_record = BlockRecord(
                    self.header_hashes[height],
                    self.header_hashes[height - 1],
                    uint32(height),
                    weight,
                    [],
                    [],
                    total_iters,
                    None,
                )
                res = await self.wallet_state_manager.receive_block(
                    block_record, None)
                assert (res == ReceiveBlockResult.ADDED_TO_HEAD
                        or res == ReceiveBlockResult.ADDED_AS_ORPHAN)
            self.log.info(
                f"Fast sync successful up to height {header_validate_start_height - 1}"
            )

        # Download headers in batches, and verify them as they come in. We download a few batches ahead,
        # in case there are delays. TODO(mariano): optimize sync by pipelining
        last_request_time = float(0)
        highest_height_requested = uint32(0)
        request_made = False

        for height_checkpoint in range(header_validate_start_height + 1,
                                       tip_height + 1):
            total_time_slept = 0
            while True:
                if self._shut_down:
                    return
                if total_time_slept > timeout:
                    raise TimeoutError("Took too long to fetch blocks")

                # Request batches that we don't have yet
                for batch_start in range(
                        height_checkpoint,
                        min(
                            height_checkpoint +
                            self.config["num_sync_batches"],
                            tip_height + 1,
                        ),
                ):
                    batch_end = min(batch_start + 1, tip_height + 1)
                    blocks_missing = any([
                        not (self.potential_blocks_received[uint32(h)]
                             ).is_set() for h in range(batch_start, batch_end)
                    ])
                    if (time.time() - last_request_time > sleep_interval
                            and blocks_missing
                        ) or (batch_end - 1) > highest_height_requested:
                        self.log.info(f"Requesting sync header {batch_start}")
                        if batch_end - 1 > highest_height_requested:
                            highest_height_requested = uint32(batch_end - 1)
                        request_made = True
                        request_header = wallet_protocol.RequestHeader(
                            uint32(batch_start),
                            self.header_hashes[batch_start],
                        )
                        yield OutboundMessage(
                            NodeType.FULL_NODE,
                            Message("request_header", request_header),
                            Delivery.RANDOM,
                        )
                if request_made:
                    last_request_time = time.time()
                    request_made = False

                awaitables = [
                    self.potential_blocks_received[uint32(
                        height_checkpoint)].wait()
                ]
                future = asyncio.gather(*awaitables, return_exceptions=True)
                try:
                    await asyncio.wait_for(future, timeout=sleep_interval)
                except concurrent.futures.TimeoutError:
                    try:
                        await future
                    except asyncio.CancelledError:
                        pass
                    total_time_slept += sleep_interval
                    self.log.info("Did not receive desired headers")
                    continue

                # Succesfully downloaded header. Now confirm it's added to chain.
                hh = self.potential_header_hashes[height_checkpoint]
                if hh in self.wallet_state_manager.block_records:
                    # Successfully added the block to chain
                    break
                else:
                    # Not added to chain yet. Try again soon.
                    await asyncio.sleep(sleep_interval_short)
                    total_time_slept += sleep_interval_short
                    if hh in self.wallet_state_manager.block_records:
                        break
                    else:
                        self.log.warning(
                            "Received header, but it has not been added to chain. Retrying."
                        )
                        _, hb, tfilter = self.cached_blocks[hh]
                        respond_header_msg = wallet_protocol.RespondHeader(
                            hb, tfilter)
                        async for msg in self.respond_header(
                                respond_header_msg):
                            yield msg

        self.log.info(
            f"Finished sync process up to height {max(self.wallet_state_manager.height_to_hash.keys())}"
        )
예제 #4
0
async def main():
    config = load_config_cli("config.yaml", "full_node")
    setproctitle("chia_full_node")
    initialize_logging("FullNode %(name)-23s", config["logging"])

    log = logging.getLogger(__name__)
    server_closed = False

    # Create the store (DB) and full node instance
    store = await FullNodeStore.create(f"blockchain_{config['database_id']}.db"
                                       )

    genesis: FullBlock = FullBlock.from_bytes(constants["GENESIS_BLOCK"])
    await store.add_block(genesis)

    log.info("Initializing blockchain from disk")
    small_header_blocks: Dict[
        str, SmallHeaderBlock] = await load_header_blocks_from_store(store)
    blockchain = await Blockchain.create(small_header_blocks)

    full_node = FullNode(store, blockchain, config)

    if config["enable_upnp"]:
        log.info(f"Attempting to enable UPnP (open up port {config['port']})")
        try:
            upnp = miniupnpc.UPnP()
            upnp.discoverdelay = 5
            upnp.discover()
            upnp.selectigd()
            upnp.addportmapping(config["port"], "TCP", upnp.lanaddr,
                                config["port"], "chia", "")
            log.info(f"Port {config['port']} opened with UPnP.")
        except Exception as e:
            log.warning(f"UPnP failed: {e}")

    # Starts the full node server (which full nodes can connect to)
    server = ChiaServer(config["port"], full_node, NodeType.FULL_NODE)
    full_node._set_server(server)
    _ = await server.start_server(config["host"], full_node._on_connect)
    rpc_cleanup = None

    def master_close_cb():
        nonlocal server_closed
        if not server_closed:
            # Called by the UI, when node is closed, or when a signal is sent
            log.info("Closing all connections, and server...")
            full_node._shutdown()
            server.close_all()
            server_closed = True

    if config["start_rpc_server"]:
        # Starts the RPC server if -r is provided
        rpc_cleanup = await start_rpc_server(full_node, master_close_cb,
                                             config["rpc_port"])

    asyncio.get_running_loop().add_signal_handler(signal.SIGINT,
                                                  master_close_cb)
    asyncio.get_running_loop().add_signal_handler(signal.SIGTERM,
                                                  master_close_cb)

    full_node._start_bg_tasks()

    log.info("Waiting to connect to some peers...")
    await asyncio.sleep(3)
    log.info(
        f"Connected to {len(server.global_connections.get_connections())} peers."
    )

    if config["connect_to_farmer"] and not server_closed:
        peer_info = PeerInfo(
            full_node.config["farmer_peer"]["host"],
            full_node.config["farmer_peer"]["port"],
        )
        _ = await server.start_client(peer_info, None)

    if config["connect_to_timelord"] and not server_closed:
        peer_info = PeerInfo(
            full_node.config["timelord_peer"]["host"],
            full_node.config["timelord_peer"]["port"],
        )
        _ = await server.start_client(peer_info, None)

    # Awaits for server and all connections to close
    await server.await_closed()
    log.info("Closed all node servers.")

    # Waits for the rpc server to close
    if rpc_cleanup is not None:
        await rpc_cleanup()
    log.info("Closed RPC server.")

    await store.close()
    log.info("Closed store.")

    await asyncio.get_running_loop().shutdown_asyncgens()
    log.info("Node fully closed.")
예제 #5
0
 async def get_blocks(self) -> AsyncGenerator[FullBlock, None]:
     async with self.db.execute("SELECT * FROM blocks") as cursor:
         async for row in cursor:
             yield FullBlock.from_bytes(row[2])
예제 #6
0
async def main():
    root_path = DEFAULT_ROOT_PATH
    net_config = load_config(root_path, "config.yaml")
    config = load_config_cli(root_path, "config.yaml", "full_node")
    setproctitle("chia_full_node")
    initialize_logging("FullNode %(name)-23s", config["logging"], root_path)

    log = logging.getLogger(__name__)
    server_closed = False

    db_path = path_from_root(DEFAULT_ROOT_PATH,
                             config["simulator_database_path"])
    mkdir(db_path.parent)
    connection = await aiosqlite.connect(db_path)

    # Create the store (DB) and full node instance
    store = await FullNodeStore.create(connection)
    await store._clear_database()

    genesis: FullBlock = FullBlock.from_bytes(test_constants["GENESIS_BLOCK"])
    await store.add_block(genesis)
    unspent_store = await CoinStore.create(connection)

    log.info("Initializing blockchain from disk")
    blockchain = await Blockchain.create(unspent_store, store, test_constants)

    mempool_manager = MempoolManager(unspent_store, test_constants)
    await mempool_manager.new_tips(await blockchain.get_full_tips())

    full_node = FullNodeSimulator(
        store,
        blockchain,
        config,
        mempool_manager,
        unspent_store,
        override_constants=test_constants,
    )

    ping_interval = net_config.get("ping_interval")
    network_id = net_config.get("network_id")

    # Starts the full node server (which full nodes can connect to)
    assert ping_interval is not None
    assert network_id is not None
    server = ChiaServer(
        config["port"],
        full_node,
        NodeType.FULL_NODE,
        ping_interval,
        network_id,
        DEFAULT_ROOT_PATH,
        config,
    )
    full_node._set_server(server)
    _ = await server.start_server(full_node._on_connect)
    rpc_cleanup = None

    def master_close_cb():
        nonlocal server_closed
        if not server_closed:
            # Called by the UI, when node is closed, or when a signal is sent
            log.info("Closing all connections, and server...")
            full_node._shutdown()
            server.close_all()
            server_closed = True

    if config["start_rpc_server"]:
        # Starts the RPC server
        rpc_cleanup = await start_rpc_server(full_node, master_close_cb,
                                             config["rpc_port"])

    try:
        asyncio.get_running_loop().add_signal_handler(signal.SIGINT,
                                                      master_close_cb)
        asyncio.get_running_loop().add_signal_handler(signal.SIGTERM,
                                                      master_close_cb)
    except NotImplementedError:
        log.info("signal handlers unsupported")

    log.info("Waiting to connect to some peers...")
    await asyncio.sleep(3)
    log.info(
        f"Connected to {len(server.global_connections.get_connections())} peers."
    )

    # Awaits for server and all connections to close
    await server.await_closed()
    log.info("Closed all node servers.")

    # Waits for the rpc server to close
    if rpc_cleanup is not None:
        await rpc_cleanup()
    log.info("Closed RPC server.")

    await store.close()
    log.info("Closed store.")

    await unspent_store.close()
    log.info("Closed unspent store.")

    await asyncio.get_running_loop().shutdown_asyncgens()
    log.info("Node fully closed.")
예제 #7
0
    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
예제 #8
0
async def main():
    # Create the store (DB) and full node instance
    db_id = 0
    if "-id" in sys.argv:
        db_id = int(sys.argv[sys.argv.index("-id") + 1])
    store = await FullNodeStore.create(f"blockchain_{db_id}.db")

    genesis: FullBlock = FullBlock.from_bytes(constants["GENESIS_BLOCK"])
    await store.add_block(genesis)

    log.info("Initializing blockchain from disk")
    header_blocks: Dict[
        str, HeaderBlock] = await load_header_blocks_from_store(store)
    blockchain = await Blockchain.create(header_blocks)

    full_node = FullNode(store, blockchain)
    # Starts the full node server (which full nodes can connect to)
    host, port = parse_host_port(full_node)

    if full_node.config["enable_upnp"]:
        log.info(f"Attempting to enable UPnP (open up port {port})")
        try:
            upnp = miniupnpc.UPnP()
            upnp.discoverdelay = 5
            upnp.discover()
            upnp.selectigd()
            upnp.addportmapping(port, "TCP", upnp.lanaddr, port, "chia", "")
            log.info(f"Port {port} opened with UPnP.")
        except Exception as e:
            log.warning(f"UPnP failed: {e}")

    server = ChiaServer(port, full_node, NodeType.FULL_NODE)
    full_node._set_server(server)
    _ = await server.start_server(host, full_node._on_connect)
    rpc_cleanup = None

    def master_close_cb():
        global server_closed
        if not server_closed:
            # Called by the UI, when node is closed, or when a signal is sent
            log.info("Closing all connections, and server...")
            full_node._shutdown()
            server.close_all()
            server_closed = True

    if "-r" in sys.argv:
        # Starts the RPC server if -r is provided
        index = sys.argv.index("-r")
        rpc_port = int(sys.argv[index + 1])
        rpc_cleanup = await start_rpc_server(full_node, master_close_cb,
                                             rpc_port)

    asyncio.get_running_loop().add_signal_handler(signal.SIGINT,
                                                  master_close_cb)
    asyncio.get_running_loop().add_signal_handler(signal.SIGTERM,
                                                  master_close_cb)

    connect_to_farmer = "-f" in sys.argv
    connect_to_timelord = "-t" in sys.argv

    full_node._start_bg_tasks()

    log.info("Waiting to connect to some peers...")
    await asyncio.sleep(3)
    log.info(
        f"Connected to {len(server.global_connections.get_connections())} peers."
    )

    if connect_to_farmer and not server_closed:
        peer_info = PeerInfo(
            full_node.config["farmer_peer"]["host"],
            full_node.config["farmer_peer"]["port"],
        )
        _ = await server.start_client(peer_info, None)

    if connect_to_timelord and not server_closed:
        peer_info = PeerInfo(
            full_node.config["timelord_peer"]["host"],
            full_node.config["timelord_peer"]["port"],
        )
        _ = await server.start_client(peer_info, None)

    # Awaits for server and all connections to close
    await server.await_closed()

    # Waits for the rpc server to close
    if rpc_cleanup is not None:
        await rpc_cleanup()

    await store.close()
    await asyncio.get_running_loop().shutdown_asyncgens()
    log.info("Node fully closed.")
예제 #9
0
    async def test_block_store(self):
        assert sqlite3.threadsafety == 1
        blocks = bt.get_consecutive_blocks(test_constants, 9, [], 9, b"0")
        blocks_alt = bt.get_consecutive_blocks(test_constants, 3, [], 9, b"1")
        db_filename = Path("blockchain_test.db")
        db_filename_2 = Path("blockchain_test_2.db")
        db_filename_3 = Path("blockchain_test_3.db")

        if db_filename.exists():
            db_filename.unlink()
        if db_filename_2.exists():
            db_filename_2.unlink()
        if db_filename_3.exists():
            db_filename_3.unlink()

        connection = await aiosqlite.connect(db_filename)
        connection_2 = await aiosqlite.connect(db_filename_2)
        connection_3 = await aiosqlite.connect(db_filename_3)

        db = await BlockStore.create(connection)
        db_2 = await BlockStore.create(connection_2)
        db_3 = await BlockStore.create(connection_3)
        try:
            genesis = FullBlock.from_bytes(test_constants["GENESIS_BLOCK"])

            # Save/get block
            for block in blocks:
                await db.add_block(block)
                assert block == await db.get_block(block.header_hash)

            await db.add_block(blocks_alt[2])
            assert len(await db.get_blocks_at([1, 2])) == 3

            # Get headers (added alt block also, so +1)
            assert len(await db.get_headers()) == len(blocks) + 1

            # Test LCA
            assert (await db.get_lca()) is None
            await db.set_lca(blocks[-3].header_hash)
            assert (await db.get_lca()) == blocks[-3].header
            await db.set_tips([blocks[-2].header_hash, blocks[-1].header_hash])
            assert (await
                    db.get_tips()) == [blocks[-2].header, blocks[-1].header]

            coin_store: CoinStore = await CoinStore.create(connection_3)
            b: Blockchain = await Blockchain.create(coin_store, db_3,
                                                    test_constants)

            assert b.lca_block == genesis.header
            assert b.tips == [genesis.header]
            assert await db_3.get_lca() == genesis.header
            assert await db_3.get_tips() == [genesis.header]

            for block in blocks:
                await b.receive_block(block)

            assert b.lca_block == blocks[-3].header
            assert set(b.tips) == set(
                [blocks[-3].header, blocks[-2].header, blocks[-1].header])
            left = sorted(b.tips, key=lambda t: t.height)
            right = sorted((await db_3.get_tips()), key=lambda t: t.height)
            assert left == right

        except Exception:
            await connection.close()
            await connection_2.close()
            await connection_3.close()
            db_filename.unlink()
            db_filename_2.unlink()
            db_filename_3.unlink()
            b.shut_down()
            raise

        await connection.close()
        await connection_2.close()
        await connection_3.close()
        db_filename.unlink()
        db_filename_2.unlink()
        db_filename_3.unlink()
        b.shut_down()
예제 #10
0
async def async_main():
    root_path = DEFAULT_ROOT_PATH
    config = load_config_cli(root_path, "config.yaml", "full_node")
    net_config = load_config(root_path, "config.yaml")
    setproctitle("chia_full_node")
    initialize_logging("FullNode %(name)-23s", config["logging"], root_path)

    log = logging.getLogger(__name__)
    server_closed = False

    db_path = path_from_root(root_path, config["database_path"])
    mkdir(db_path.parent)

    # Create the store (DB) and full node instance
    connection = await aiosqlite.connect(db_path)
    store = await FullNodeStore.create(connection)

    genesis: FullBlock = FullBlock.from_bytes(constants["GENESIS_BLOCK"])
    await store.add_block(genesis)
    unspent_store = await CoinStore.create(connection)

    log.info("Initializing blockchain from disk")
    blockchain = await Blockchain.create(unspent_store, store)
    log.info("Blockchain initialized")

    mempool_manager = MempoolManager(unspent_store)
    await mempool_manager.new_tips(await blockchain.get_full_tips())

    full_node = FullNode(store, blockchain, config, mempool_manager, unspent_store)

    if config["enable_upnp"]:
        log.info(f"Attempting to enable UPnP (open up port {config['port']})")
        try:
            upnp = miniupnpc.UPnP()
            upnp.discoverdelay = 5
            upnp.discover()
            upnp.selectigd()
            upnp.addportmapping(
                config["port"], "TCP", upnp.lanaddr, config["port"], "chia", ""
            )
            log.info(f"Port {config['port']} opened with UPnP.")
        except Exception:
            log.exception(f"UPnP failed")

    # Starts the full node server (which full nodes can connect to)
    ping_interval = net_config.get("ping_interval")
    network_id = net_config.get("network_id")
    assert ping_interval is not None
    assert network_id is not None
    server = ChiaServer(
        config["port"],
        full_node,
        NodeType.FULL_NODE,
        ping_interval,
        network_id,
        DEFAULT_ROOT_PATH,
        config,
    )
    full_node._set_server(server)
    _ = await server.start_server(full_node._on_connect)
    rpc_cleanup = None

    def master_close_cb():
        nonlocal server_closed
        if not server_closed:
            # Called by the UI, when node is closed, or when a signal is sent
            log.info("Closing all connections, and server...")
            full_node._shutdown()
            server.close_all()
            server_closed = True

    if config["start_rpc_server"]:
        # Starts the RPC server
        rpc_cleanup = await start_rpc_server(
            full_node, master_close_cb, config["rpc_port"]
        )

    try:
        asyncio.get_running_loop().add_signal_handler(signal.SIGINT, master_close_cb)
        asyncio.get_running_loop().add_signal_handler(signal.SIGTERM, master_close_cb)
    except NotImplementedError:
        log.info("signal handlers unsupported")

    full_node._start_bg_tasks()

    # Awaits for server and all connections to close
    await server.await_closed()
    log.info("Closed all node servers.")

    # Waits for the rpc server to close
    if rpc_cleanup is not None:
        await rpc_cleanup()
    log.info("Closed RPC server.")

    await connection.close()
    log.info("Closed db connection.")

    await asyncio.get_running_loop().shutdown_asyncgens()
    log.info("Node fully closed.")
예제 #11
0
async def setup_full_node_simulator(db_name,
                                    port,
                                    introducer_port=None,
                                    dic={}):
    # SETUP
    test_constants_copy = test_constants.copy()
    for k in dic.keys():
        test_constants_copy[k] = dic[k]

    db_path = Path(db_name)
    connection = await aiosqlite.connect(db_path)
    store_1 = await FullNodeStore.create(connection)
    await store_1._clear_database()
    unspent_store_1 = await CoinStore.create(connection)
    await unspent_store_1._clear_database()
    mempool_1 = MempoolManager(unspent_store_1, test_constants_copy)

    b_1: Blockchain = await Blockchain.create(unspent_store_1, store_1,
                                              test_constants_copy)
    await mempool_1.new_tips(await b_1.get_full_tips())

    await store_1.add_block(
        FullBlock.from_bytes(test_constants_copy["GENESIS_BLOCK"]))

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

    config = load_config(root_path, "config.yaml", "full_node")

    if introducer_port is not None:
        config["introducer_peer"]["host"] = "127.0.0.1"
        config["introducer_peer"]["port"] = introducer_port
    full_node_1 = FullNodeSimulator(
        store_1,
        b_1,
        config,
        mempool_1,
        unspent_store_1,
        f"full_node_{port}",
        test_constants_copy,
    )
    assert ping_interval is not None
    assert network_id is not None
    server_1 = ChiaServer(
        port,
        full_node_1,
        NodeType.FULL_NODE,
        ping_interval,
        network_id,
        "full-node-simulator-server",
    )
    _ = await server_1.start_server(full_node_1._on_connect)
    full_node_1._set_server(server_1)

    yield (full_node_1, server_1)

    # TEARDOWN
    full_node_1._shutdown()
    server_1.close_all()
    await server_1.await_closed()
    await connection.close()
    Path(db_name).unlink()
예제 #12
0
    async def test_basic_database(self):
        blocks = bt.get_consecutive_blocks(test_constants, 9, [], 9, b"0")

        db = FullNodeStore("fndb_test")
        await db._clear_database()
        genesis = FullBlock.from_bytes(constants["GENESIS_BLOCK"])

        # Save/get block
        for block in blocks:
            await db.add_block(block)
            assert block == await db.get_block(block.header_hash)

        # Save/get sync
        for sync_mode in (False, True):
            await db.set_sync_mode(sync_mode)
            assert sync_mode == await db.get_sync_mode()

        # clear sync info
        await db.clear_sync_info()

        # add/get potential tip, get potential tips num
        await db.add_potential_tip(blocks[6])
        assert blocks[6] == await db.get_potential_tip(blocks[6].header_hash)

        # add/get potential trunk
        header = genesis.header_block
        db.add_potential_header(header)
        assert db.get_potential_header(genesis.height) == header

        # Add potential block
        await db.add_potential_block(genesis)
        assert genesis == await db.get_potential_block(uint32(0))

        # Add/get candidate block
        assert await db.get_candidate_block(0) is None
        partial = (
            blocks[5].body,
            blocks[5].header_block.header.data,
            blocks[5].header_block.proof_of_space,
        )
        await db.add_candidate_block(blocks[5].header_hash, *partial)
        assert await db.get_candidate_block(blocks[5].header_hash) == partial
        await db.clear_candidate_blocks_below(uint32(8))
        assert await db.get_candidate_block(blocks[5].header_hash) is None

        # Add/get unfinished block
        i = 1
        for block in blocks:
            key = (block.header_hash, uint64(1000))
            assert await db.get_unfinished_block(key) is None
            await db.add_unfinished_block(key, block)
            assert await db.get_unfinished_block(key) == block
            assert len(await db.get_unfinished_blocks()) == i
            i += 1
        await db.clear_unfinished_blocks_below(uint32(5))
        assert len(await db.get_unfinished_blocks()) == 5

        # Set/get unf block leader
        assert db.get_unfinished_block_leader() == (0, (1 << 64) - 1)
        db.set_unfinished_block_leader(key)
        assert db.get_unfinished_block_leader() == key

        assert await db.get_disconnected_block(blocks[0].prev_header_hash
                                               ) is None
        # Disconnected blocks
        for block in blocks:
            await db.add_disconnected_block(block)
            await db.get_disconnected_block(block.prev_header_hash) == block

        await db.clear_disconnected_blocks_below(uint32(5))
        assert await db.get_disconnected_block(blocks[4].prev_header_hash
                                               ) is None
예제 #13
0
    def get_consecutive_blocks(
        self,
        input_constants: Dict,
        num_blocks: int,
        block_list: List[FullBlock] = [],
        seconds_per_block=constants["BLOCK_TIME_TARGET"],
        seed: bytes = b"",
    ) -> List[FullBlock]:
        test_constants: Dict[str, Any] = constants.copy()
        for key, value in input_constants.items():
            test_constants[key] = value

        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,
                                              sha256(seed).digest(), seed))
            prev_difficulty = test_constants["DIFFICULTY_STARTING"]
            curr_difficulty = prev_difficulty
            curr_ips = test_constants["VDF_IPS_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_ips = test_constants["VDF_IPS_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].header_block.proof_of_time
            curr_ips = calculate_ips_from_iterations(
                block_list[-1].header_block.proof_of_space,
                curr_difficulty,
                block_list[-1].header_block.proof_of_time.number_of_iterations,
                test_constants["MIN_BLOCK_TIME"],
            )

        starting_height = block_list[-1].height + 1
        timestamp = block_list[-1].header_block.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]
                    assert block1.header_block.challenge
                    iters1 = block1.header_block.challenge.total_iters
                    timestamp1 = block1.header_block.header.data.timestamp
                else:
                    block1 = block_list[0]
                    assert block1.header_block.challenge
                    timestamp1 = (block1.header_block.header.data.timestamp -
                                  test_constants["BLOCK_TIME_TARGET"])
                    iters1 = block1.header_block.challenge.total_iters
                timestamp2 = block_list[
                    height2].header_block.header.data.timestamp
                timestamp3 = block_list[
                    height3].header_block.header.data.timestamp

                block3 = block_list[height3]
                assert block3.header_block.challenge
                iters3 = block3.header_block.challenge.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: uint64 = uint64(
                    (term1 + term2) //
                    (test_constants["DIFFICULTY_WARP_FACTOR"] *
                     (timestamp3 - timestamp2) * (timestamp2 - timestamp1)))

                if new_difficulty >= curr_difficulty:
                    new_difficulty = min(
                        new_difficulty,
                        uint64(test_constants["DIFFICULTY_FACTOR"] *
                               curr_difficulty),
                    )
                else:
                    new_difficulty = max([
                        uint64(1),
                        new_difficulty,
                        uint64(curr_difficulty //
                               test_constants["DIFFICULTY_FACTOR"]),
                    ])

                new_ips = uint64(
                    (iters3 - iters1) // (timestamp3 - timestamp1))
                if new_ips >= curr_ips:
                    curr_ips = min(
                        new_ips,
                        uint64(test_constants["IPS_FACTOR"] * new_ips))
                else:
                    curr_ips = max([
                        uint64(1),
                        new_ips,
                        uint64(curr_ips // test_constants["IPS_FACTOR"]),
                    ])

                prev_difficulty = curr_difficulty
                curr_difficulty = new_difficulty
            time_taken = seconds_per_block
            timestamp += time_taken
            block_list.append(
                self.create_next_block(
                    test_constants,
                    block_list[-1],
                    timestamp,
                    curr_difficulty,
                    curr_ips,
                    seed,
                ))
        return block_list
    async def create(
        key_config: Dict,
        config: Dict,
        db_path: Path,
        constants: Dict,
        name: str = None,
    ):
        self = WalletStateManager()
        self.config = config
        self.constants = constants

        if name:
            self.log = logging.getLogger(name)
        else:
            self.log = logging.getLogger(__name__)
        self.lock = asyncio.Lock()

        self.db_connection = await aiosqlite.connect(db_path)
        self.wallet_store = await WalletStore.create(self.db_connection)
        self.tx_store = await WalletTransactionStore.create(self.db_connection)
        self.puzzle_store = await WalletPuzzleStore.create(self.db_connection)
        self.user_store = await WalletUserStore.create(self.db_connection)
        self.lca = None
        self.sync_mode = False
        self.height_to_hash = {}
        self.block_records = await self.wallet_store.get_lca_path()
        genesis = FullBlock.from_bytes(self.constants["GENESIS_BLOCK"])
        self.genesis = genesis
        self.state_changed_callback = None
        self.pending_tx_callback = None
        self.difficulty_resets_prev = {}
        self.db_path = db_path

        main_wallet_info = await self.user_store.get_wallet_by_id(1)
        assert main_wallet_info is not None

        self.main_wallet = await Wallet.create(config, key_config, self,
                                               main_wallet_info)

        self.wallets = {}
        main_wallet = await Wallet.create(config, key_config, self,
                                          main_wallet_info)
        self.wallets[main_wallet_info.id] = main_wallet

        for wallet_info in await self.get_all_wallets():
            self.log.info(f"wallet_info {wallet_info}")
            if wallet_info.type == WalletType.STANDARD_WALLET:
                if wallet_info.id == 1:
                    continue
                wallet = await Wallet.create(config, key_config, self,
                                             main_wallet_info)
                self.wallets[wallet_info.id] = wallet
            elif wallet_info.type == WalletType.RATE_LIMITED:
                wallet = await RLWallet.create(
                    config,
                    key_config,
                    self,
                    wallet_info,
                    self.main_wallet,
                )
                self.wallets[wallet_info.id] = wallet

        async with self.puzzle_store.lock:
            await self.create_more_puzzle_hashes(from_zero=True)

        if len(self.block_records) > 0:
            # Initializes the state based on the DB block records
            # Header hash with the highest weight
            self.lca = max((item[1].weight, item[0])
                           for item in self.block_records.items())[1]
            for key, value in self.block_records.items():
                self.height_to_hash[value.height] = value.header_hash

            # Checks genesis block is the same in config, as in DB
            assert self.block_records[genesis.header_hash].height == 0
            assert self.block_records[
                genesis.header_hash].weight == genesis.weight
        else:
            # Loads the genesis block if there are no blocks
            genesis_challenge = Challenge(
                genesis.proof_of_space.challenge_hash,
                std_hash(genesis.proof_of_space.get_hash() +
                         genesis.proof_of_time.output.get_hash()),
                None,
            )
            genesis_hb = HeaderBlock(
                genesis.proof_of_space,
                genesis.proof_of_time,
                genesis_challenge,
                genesis.header,
            )
            await self.receive_block(
                BlockRecord(
                    genesis.header_hash,
                    genesis.prev_header_hash,
                    uint32(0),
                    genesis.weight,
                    [],
                    [],
                    genesis_hb.header.data.total_iters,
                    genesis_challenge.get_hash(),
                ),
                genesis_hb,
            )

        return self