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
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()
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())}" )
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.")
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])
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.")
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
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.")
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()
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.")
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()
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
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