async def test_reorg_from_genesis(self): blocks = bt.get_consecutive_blocks(test_constants, 20, [], 9, b"0") store = FullNodeStore("fndb_test") await store._clear_database() b: Blockchain = Blockchain(store, test_constants) await b.initialize() for block in blocks: await b.receive_block(block) assert b.get_current_tips()[0].height == 20 # Reorg from genesis blocks_reorg_chain = bt.get_consecutive_blocks(test_constants, 21, [blocks[0]], 9, b"1") for reorg_block in blocks_reorg_chain: result = await b.receive_block(reorg_block) if reorg_block.height == 0: assert result == ReceiveBlockResult.ALREADY_HAVE_BLOCK elif reorg_block.height < 19: assert result == ReceiveBlockResult.ADDED_AS_ORPHAN else: assert result == ReceiveBlockResult.ADDED_TO_HEAD assert b.get_current_tips()[0].height == 21 # Reorg back to original branch blocks_reorg_chain_2 = bt.get_consecutive_blocks( test_constants, 3, blocks, 9, b"3") await b.receive_block(blocks_reorg_chain_2[20] ) == ReceiveBlockResult.ADDED_AS_ORPHAN assert (await b.receive_block(blocks_reorg_chain_2[21] )) == ReceiveBlockResult.ADDED_TO_HEAD assert (await b.receive_block(blocks_reorg_chain_2[22] )) == ReceiveBlockResult.ADDED_TO_HEAD
async def test1(self): store = FullNodeStore("fndb_test") await store._clear_database() blocks = bt.get_consecutive_blocks(test_constants, 10, [], 10) b: Blockchain = Blockchain(test_constants) await store.add_block(blocks[0]) await b.initialize({}) for i in range(1, 9): assert (await b.receive_block(blocks[i] )) == ReceiveBlockResult.ADDED_TO_HEAD await store.add_block(blocks[i]) full_node_1 = FullNode(store, b) server_1 = ChiaServer(21234, full_node_1, NodeType.FULL_NODE) _ = await server_1.start_server("127.0.0.1", None) full_node_1._set_server(server_1) full_node_2 = FullNode(store, b) server_2 = ChiaServer(21235, full_node_2, NodeType.FULL_NODE) full_node_2._set_server(server_2) await server_2.start_client(PeerInfo("127.0.0.1", uint16(21234)), None) await asyncio.sleep(2) # Allow connections to get made num_unfinished_blocks = 1000 start_unf = time.time() for i in range(num_unfinished_blocks): msg = Message("unfinished_block", peer_protocol.UnfinishedBlock(blocks[9])) server_1.push_message( OutboundMessage(NodeType.FULL_NODE, msg, Delivery.BROADCAST)) # Send the whole block ast the end so we can detect when the node is done block_msg = Message("block", peer_protocol.Block(blocks[9])) server_1.push_message( OutboundMessage(NodeType.FULL_NODE, block_msg, Delivery.BROADCAST)) while time.time() - start_unf < 300: if max([h.height for h in b.get_current_tips()]) == 9: print( f"Time taken to process {num_unfinished_blocks} is {time.time() - start_unf}" ) server_1.close_all() server_2.close_all() await server_1.await_closed() await server_2.await_closed() return await asyncio.sleep(0.1) server_1.close_all() server_2.close_all() await server_1.await_closed() await server_2.await_closed() raise Exception("Took too long to process blocks")
async def test_get_header_hashes(self): blocks = bt.get_consecutive_blocks(test_constants, 5, [], 9, b"0") store = FullNodeStore("fndb_test") await store._clear_database() b: Blockchain = Blockchain(store, test_constants) await b.initialize() for block in blocks: await b.receive_block(block) header_hashes = b.get_header_hashes(blocks[-1].header_hash) assert len(header_hashes) == 6 print(header_hashes) print([block.header_hash for block in blocks]) assert header_hashes == [block.header_hash for block in blocks]
async def initial_blockchain(self): """ Provides a list of 10 valid blocks, as well as a blockchain with 9 blocks added to it. """ store = FullNodeStore("fndb_test") await store._clear_database() blocks = bt.get_consecutive_blocks(test_constants, 10, [], 10) b: Blockchain = Blockchain(store, test_constants) await b.initialize() for i in range(1, 9): assert (await b.receive_block(blocks[i] )) == ReceiveBlockResult.ADDED_TO_HEAD return (blocks, b)
async def test_basic_blockchain(self): store = FullNodeStore("fndb_test") await store._clear_database() bc1: Blockchain = Blockchain(store) await bc1.initialize() assert len(bc1.get_current_tips()) == 1 genesis_block = bc1.get_current_tips()[0] assert genesis_block.height == 0 assert genesis_block.challenge assert (bc1.get_header_blocks_by_height( [uint32(0)], genesis_block.header_hash))[0] == genesis_block assert (await bc1.get_next_difficulty( genesis_block.header_hash)) == genesis_block.challenge.total_weight assert await bc1.get_next_ips(genesis_block.header_hash) > 0
async def test2(self): num_blocks = 100 store = FullNodeStore("fndb_test") await store._clear_database() blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10) b: Blockchain = Blockchain(test_constants) await store.add_block(blocks[0]) await b.initialize({}) full_node_1 = FullNode(store, b) server_1 = ChiaServer(21236, full_node_1, NodeType.FULL_NODE) _ = await server_1.start_server("127.0.0.1", None) full_node_1._set_server(server_1) full_node_2 = FullNode(store, b) server_2 = ChiaServer(21237, full_node_2, NodeType.FULL_NODE) full_node_2._set_server(server_2) await server_2.start_client(PeerInfo("127.0.0.1", uint16(21236)), None) await asyncio.sleep(2) # Allow connections to get made start_unf = time.time() for i in range(1, num_blocks): msg = Message("block", peer_protocol.Block(blocks[i])) server_1.push_message( OutboundMessage(NodeType.FULL_NODE, msg, Delivery.BROADCAST)) while time.time() - start_unf < 300: if max([h.height for h in b.get_current_tips()]) == num_blocks - 1: print( f"Time taken to process {num_blocks} is {time.time() - start_unf}" ) server_1.close_all() server_2.close_all() await server_1.await_closed() await server_2.await_closed() return await asyncio.sleep(0.1) server_1.close_all() server_2.close_all() await server_1.await_closed() await server_2.await_closed() raise Exception("Took too long to process blocks")
async def load_header_blocks_from_store( store: FullNodeStore, ) -> Dict[str, HeaderBlock]: seen_blocks: Dict[str, HeaderBlock] = {} tips: List[HeaderBlock] = [] async for full_block in store.get_blocks(): if not tips or full_block.weight > tips[0].weight: tips = [full_block.header_block] seen_blocks[full_block.header_hash] = full_block.header_block header_blocks = {} if len(tips) > 0: curr: HeaderBlock = tips[0] reverse_blocks: List[HeaderBlock] = [curr] while curr.height > 0: curr = seen_blocks[curr.prev_header_hash] reverse_blocks.append(curr) for block in reversed(reverse_blocks): header_blocks[block.header_hash] = block return header_blocks
async def test_basic_reorg(self): blocks = bt.get_consecutive_blocks(test_constants, 100, [], 9) store = FullNodeStore("fndb_test") await store._clear_database() b: Blockchain = Blockchain(store, test_constants) await b.initialize() for block in blocks: await b.receive_block(block) assert b.get_current_tips()[0].height == 100 blocks_reorg_chain = bt.get_consecutive_blocks(test_constants, 30, blocks[:90], 9, b"1") for reorg_block in blocks_reorg_chain: result = await b.receive_block(reorg_block) if reorg_block.height < 90: assert result == ReceiveBlockResult.ALREADY_HAVE_BLOCK elif reorg_block.height < 99: assert result == ReceiveBlockResult.ADDED_AS_ORPHAN elif reorg_block.height >= 100: assert result == ReceiveBlockResult.ADDED_TO_HEAD assert b.get_current_tips()[0].height == 119
async def test_difficulty_change(self): num_blocks = 30 # Make it 5x faster than target time blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 2) store = FullNodeStore("fndb_test") await store._clear_database() b: Blockchain = Blockchain(store, test_constants) await b.initialize() for i in range(1, num_blocks): assert (await b.receive_block(blocks[i] )) == ReceiveBlockResult.ADDED_TO_HEAD diff_25 = await b.get_next_difficulty(blocks[24].header_hash) diff_26 = await b.get_next_difficulty(blocks[25].header_hash) diff_27 = await b.get_next_difficulty(blocks[26].header_hash) assert diff_26 == diff_25 assert diff_27 > diff_26 assert (diff_27 / diff_26) <= test_constants["DIFFICULTY_FACTOR"] assert (await b.get_next_ips(blocks[1].header_hash )) == constants["VDF_IPS_STARTING"] assert (await b.get_next_ips(blocks[24].header_hash )) == (await b.get_next_ips(blocks[23].header_hash)) assert (await b.get_next_ips(blocks[25].header_hash )) == (await b.get_next_ips(blocks[24].header_hash)) assert (await b.get_next_ips(blocks[26].header_hash )) > (await b.get_next_ips( blocks[25].header_hash)) assert (await b.get_next_ips(blocks[27].header_hash )) == (await b.get_next_ips(blocks[26].header_hash))
async def test_lca(self): blocks = bt.get_consecutive_blocks(test_constants, 5, [], 9, b"0") store = FullNodeStore("fndb_test") await store._clear_database() b: Blockchain = Blockchain(store, test_constants) await b.initialize() for block in blocks: await b.receive_block(block) assert b.lca_block == blocks[3] block_5_2 = bt.get_consecutive_blocks(test_constants, 1, blocks[:5], 9, b"1")[5] block_5_3 = bt.get_consecutive_blocks(test_constants, 1, blocks[:5], 9, b"2")[5] await b.receive_block(block_5_2) assert b.lca_block == blocks[4] await b.receive_block(block_5_3) assert b.lca_block == blocks[4] reorg = bt.get_consecutive_blocks(test_constants, 6, [], 9, b"3") for block in reorg: await b.receive_block(block) assert b.lca_block == blocks[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 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) assert not db.seen_block(h_hash_1) assert db.seen_block(h_hash_1) db.clear_seen_blocks() assert not db.seen_block(h_hash_1)
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 = FullNodeStore(f"fndb_{db_id}") blockchain = Blockchain(store) log.info("Initializing blockchain from disk") await blockchain.initialize() 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", "") 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) wait_for_ui, ui_close_cb = None, 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 def signal_received(): if ui_close_cb: ui_close_cb() master_close_cb() asyncio.get_running_loop().add_signal_handler(signal.SIGINT, signal_received) asyncio.get_running_loop().add_signal_handler(signal.SIGTERM, signal_received) if "-u" in sys.argv: # Starts the UI if -u is provided index = sys.argv.index("-u") ui_ssh_port = int(sys.argv[index + 1]) from src.ui.prompt_ui import start_ssh_server wait_for_ui, ui_close_cb = await start_ssh_server( store, blockchain, server, port, ui_ssh_port, full_node.config["ssh_filename"], 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() # Awaits for all ui instances to close if wait_for_ui is not None: await wait_for_ui() await asyncio.get_running_loop().shutdown_asyncgens()