From 893dace7255abb38aa914db030f4b3466f518565 Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Thu, 12 Sep 2024 14:46:22 +0300 Subject: [PATCH 01/10] refactor --- .../blockchain/blockchain_test_utils.py | 3 +- chia/_tests/blockchain/test_blockchain.py | 153 ++++++++++++++---- chia/_tests/core/full_node/test_full_node.py | 32 +--- chia/_tests/core/test_db_conversion.py | 6 +- chia/_tests/core/test_db_validation.py | 6 +- .../test_third_party_harvesters.py | 6 +- chia/_tests/util/full_sync.py | 9 +- chia/_tests/util/misc.py | 5 +- chia/_tests/wallet/sync/test_wallet_sync.py | 6 +- chia/consensus/blockchain.py | 79 ++------- chia/full_node/full_node.py | 39 ++--- chia/simulator/full_node_simulator.py | 9 +- tools/test_full_sync.py | 19 ++- 13 files changed, 214 insertions(+), 158 deletions(-) diff --git a/chia/_tests/blockchain/blockchain_test_utils.py b/chia/_tests/blockchain/blockchain_test_utils.py index 0c23df1cef9d..2464b53d7a21 100644 --- a/chia/_tests/blockchain/blockchain_test_utils.py +++ b/chia/_tests/blockchain/blockchain_test_utils.py @@ -93,7 +93,8 @@ async def _validate_and_add_block( raise AssertionError(f"Expected {expected_error} but got {Err(results.error)}") await check_block_store_invariant(blockchain) return None - + if fork_info is None: + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) if use_bls_cache: bls_cache = BLSCache(100) else: diff --git a/chia/_tests/blockchain/test_blockchain.py b/chia/_tests/blockchain/test_blockchain.py index be0a159a873a..210e7e1aeb72 100644 --- a/chia/_tests/blockchain/test_blockchain.py +++ b/chia/_tests/blockchain/test_blockchain.py @@ -27,6 +27,7 @@ from chia.consensus.blockchain import AddBlockResult, Blockchain from chia.consensus.coinbase import create_farmer_coin from chia.consensus.constants import ConsensusConstants +from chia.consensus.find_fork_point import lookup_fork_chain from chia.consensus.full_block_to_block_record import block_to_block_record from chia.consensus.get_block_generator import get_block_generator from chia.consensus.multiprocess_validation import PreValidationResult @@ -1856,12 +1857,13 @@ async def test_pre_validation( end_pv = time.time() times_pv.append(end_pv - start_pv) assert res is not None + fork_info = ForkInfo(-1, -1, empty_blockchain.constants.GENESIS_CHALLENGE) for n in range(end_i - i): assert res[n] is not None assert res[n].error is None block = blocks_to_validate[n] start_rb = time.time() - result, err, _ = await empty_blockchain.add_block(block, res[n], None, ssi) + result, err, _ = await empty_blockchain.add_block(block, res[n], None, ssi, fork_info=fork_info) end_rb = time.time() times_rb.append(end_rb - start_rb) assert err is None @@ -1955,7 +1957,11 @@ async def test_conditions( ) # Ignore errors from pre-validation, we are testing block_body_validation repl_preval_results = replace(pre_validation_results[0], error=None, required_iters=uint64(1)) - code, err, state_change = await b.add_block(blocks[-1], repl_preval_results, None, sub_slot_iters=ssi) + block = blocks[-1] + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + code, err, state_change = await b.add_block( + block, repl_preval_results, None, sub_slot_iters=ssi, fork_info=fork_info + ) assert code == AddBlockResult.NEW_PEAK assert err is None assert state_change is not None @@ -2070,7 +2076,11 @@ async def test_timelock_conditions( [blocks[-1]], {}, sub_slot_iters=ssi, difficulty=diff, prev_ses_block=None, validate_signatures=True ) assert pre_validation_results is not None - assert (await b.add_block(blocks[-1], pre_validation_results[0], None, sub_slot_iters=ssi))[0] == expected + block = blocks[-1] + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + assert (await b.add_block(block, pre_validation_results[0], None, sub_slot_iters=ssi, fork_info=fork_info))[ + 0 + ] == expected if expected == AddBlockResult.NEW_PEAK: # ensure coin was in fact spent @@ -2144,7 +2154,11 @@ async def test_aggsig_garbage( ) # Ignore errors from pre-validation, we are testing block_body_validation repl_preval_results = replace(pre_validation_results[0], error=None, required_iters=uint64(1)) - res, error, state_change = await b.add_block(blocks[-1], repl_preval_results, None, sub_slot_iters=ssi) + block = blocks[-1] + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + res, error, state_change = await b.add_block( + block, repl_preval_results, None, sub_slot_iters=ssi, fork_info=fork_info + ) assert res == AddBlockResult.NEW_PEAK assert error is None assert state_change is not None and state_change.fork_height == uint32(2) @@ -2261,7 +2275,11 @@ async def test_ephemeral_timelock( [blocks[-1]], {}, sub_slot_iters=ssi, difficulty=diff, prev_ses_block=None, validate_signatures=True ) assert pre_validation_results is not None - assert (await b.add_block(blocks[-1], pre_validation_results[0], None, sub_slot_iters=ssi))[0] == expected + block = blocks[-1] + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + assert (await b.add_block(block, pre_validation_results[0], None, sub_slot_iters=ssi, fork_info=fork_info))[ + 0 + ] == expected if expected == AddBlockResult.NEW_PEAK: # ensure coin1 was in fact spent @@ -2601,9 +2619,15 @@ async def test_cost_exceeds_max( ) ssi = b.constants.SUB_SLOT_ITERS_STARTING diff = b.constants.DIFFICULTY_STARTING + block = blocks[-1] + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) err = ( await b.add_block( - blocks[-1], PreValidationResult(None, uint64(1), npc_result, True, uint32(0)), None, sub_slot_iters=ssi + block, + PreValidationResult(None, uint64(1), npc_result, True, uint32(0)), + None, + sub_slot_iters=ssi, + fork_info=fork_info, ) )[1] assert err in [Err.BLOCK_COST_EXCEEDS_MAX] @@ -2670,8 +2694,13 @@ async def test_invalid_cost_in_block( constants=bt.constants, ) ssi = b.constants.SUB_SLOT_ITERS_STARTING + fork_info = ForkInfo(block_2.height - 1, block_2.height - 1, block_2.prev_header_hash) _, err, _ = await b.add_block( - block_2, PreValidationResult(None, uint64(1), npc_result, False, uint32(0)), None, sub_slot_iters=ssi + block_2, + PreValidationResult(None, uint64(1), npc_result, False, uint32(0)), + None, + sub_slot_iters=ssi, + fork_info=fork_info, ) assert err == Err.INVALID_BLOCK_COST @@ -2699,8 +2728,13 @@ async def test_invalid_cost_in_block( height=softfork_height, constants=bt.constants, ) + fork_info = ForkInfo(block_2.height - 1, block_2.height - 1, block_2.prev_header_hash) _, err, _ = await b.add_block( - block_2, PreValidationResult(None, uint64(1), npc_result, False, uint32(0)), None, sub_slot_iters=ssi + block_2, + PreValidationResult(None, uint64(1), npc_result, False, uint32(0)), + None, + sub_slot_iters=ssi, + fork_info=fork_info, ) assert err == Err.INVALID_BLOCK_COST @@ -2729,9 +2763,13 @@ async def test_invalid_cost_in_block( npc_result = get_name_puzzle_conditions( block_generator, max_cost, mempool_mode=False, height=softfork_height, constants=bt.constants ) - + fork_info = ForkInfo(block_2.height - 1, block_2.height - 1, block_2.prev_header_hash) result, err, _ = await b.add_block( - block_2, PreValidationResult(None, uint64(1), npc_result, False, uint32(0)), None, sub_slot_iters=ssi + block_2, + PreValidationResult(None, uint64(1), npc_result, False, uint32(0)), + None, + sub_slot_iters=ssi, + fork_info=fork_info, ) assert err == Err.INVALID_BLOCK_COST @@ -3006,20 +3044,31 @@ async def test_double_spent_in_reorg(self, empty_blockchain: Blockchain, bt: Blo blocks_reorg = bt.get_consecutive_blocks( 1, block_list_input=blocks_reorg, guarantee_transaction_block=True, transaction_data=tx_2 ) - - await _validate_and_add_block(b, blocks_reorg[-1], expected_error=Err.UNKNOWN_UNSPENT) + peak = b.get_peak() + assert peak is not None + fork_info = await get_fork_info(b, blocks_reorg[-1], peak) + await _validate_and_add_block(b, blocks_reorg[-1], expected_error=Err.UNKNOWN_UNSPENT, fork_info=fork_info) # Finally add the block to the fork (spending both in same bundle, this is ephemeral) agg = SpendBundle.aggregate([tx, tx_2]) blocks_reorg = bt.get_consecutive_blocks( 1, block_list_input=blocks_reorg[:-1], guarantee_transaction_block=True, transaction_data=agg ) - await _validate_and_add_block(b, blocks_reorg[-1], expected_result=AddBlockResult.ADDED_AS_ORPHAN) + + peak = b.get_peak() + assert peak is not None + fork_info = await get_fork_info(b, blocks_reorg[-1], peak) + await _validate_and_add_block( + b, blocks_reorg[-1], expected_result=AddBlockResult.ADDED_AS_ORPHAN, fork_info=fork_info + ) blocks_reorg = bt.get_consecutive_blocks( 1, block_list_input=blocks_reorg, guarantee_transaction_block=True, transaction_data=tx_2 ) - await _validate_and_add_block(b, blocks_reorg[-1], expected_error=Err.DOUBLE_SPEND_IN_FORK) + peak = b.get_peak() + assert peak is not None + fork_info = await get_fork_info(b, blocks_reorg[-1], peak) + await _validate_and_add_block(b, blocks_reorg[-1], expected_error=Err.DOUBLE_SPEND_IN_FORK, fork_info=fork_info) rewards_ph = wt.get_new_puzzlehash() blocks_reorg = bt.get_consecutive_blocks( @@ -3028,9 +3077,13 @@ async def test_double_spent_in_reorg(self, empty_blockchain: Blockchain, bt: Blo guarantee_transaction_block=True, farmer_reward_puzzle_hash=rewards_ph, ) + + peak = b.get_peak() + assert peak is not None + fork_info = await get_fork_info(b, blocks_reorg[-10], peak) for block in blocks_reorg[-10:]: await _validate_and_add_block_multi_result( - b, block, expected_result=[AddBlockResult.ADDED_AS_ORPHAN, AddBlockResult.NEW_PEAK] + b, block, expected_result=[AddBlockResult.ADDED_AS_ORPHAN, AddBlockResult.NEW_PEAK], fork_info=fork_info ) # ephemeral coin is spent @@ -3239,16 +3292,19 @@ async def test_get_tx_peak_reorg( assert maybe_header_hash(b.get_tx_peak()) == last_tx_block reorg_last_tx_block: Optional[bytes32] = None - + fork_block = blocks[9] + fork_info = ForkInfo(fork_block.height, fork_block.height, fork_block.header_hash) blocks_reorg_chain = bt.get_consecutive_blocks(7, blocks[:10], seed=b"2") assert blocks_reorg_chain[reorg_point].is_transaction_block() is False for reorg_block in blocks_reorg_chain: if reorg_block.height < 10: await _validate_and_add_block(b, reorg_block, expected_result=AddBlockResult.ALREADY_HAVE_BLOCK) elif reorg_block.height < reorg_point: - await _validate_and_add_block(b, reorg_block, expected_result=AddBlockResult.ADDED_AS_ORPHAN) + await _validate_and_add_block( + b, reorg_block, expected_result=AddBlockResult.ADDED_AS_ORPHAN, fork_info=fork_info + ) elif reorg_block.height >= reorg_point: - await _validate_and_add_block(b, reorg_block) + await _validate_and_add_block(b, reorg_block, fork_info=fork_info) if reorg_block.is_transaction_block(): reorg_last_tx_block = reorg_block.header_hash @@ -3298,7 +3354,10 @@ async def test_long_reorg( assert pre_validation_results[i].error is None if (block.height % 100) == 0: print(f"main chain: {block.height:4} weight: {block.weight}") - (result, err, _) = await b.add_block(block, pre_validation_results[i], None, sub_slot_iters=ssi) + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + (result, err, _) = await b.add_block( + block, pre_validation_results[i], None, sub_slot_iters=ssi, fork_info=fork_info + ) await check_block_store_invariant(b) assert err is None assert result == AddBlockResult.NEW_PEAK @@ -3323,7 +3382,7 @@ async def test_long_reorg( b.clean_block_records() first_peak = b.get_peak() - fork_info: Optional[ForkInfo] = None + fork_info_2: Optional[ForkInfo] = None for reorg_block in reorg_blocks: if (reorg_block.height % 100) == 0: peak = b.get_peak() @@ -3337,14 +3396,14 @@ async def test_long_reorg( if reorg_block.height < num_blocks_chain_2_start: await _validate_and_add_block(b, reorg_block, expected_result=AddBlockResult.ALREADY_HAVE_BLOCK) elif reorg_block.weight <= chain_1_weight: - if fork_info is None: - fork_info = ForkInfo(reorg_block.height - 1, reorg_block.height - 1, reorg_block.prev_header_hash) + if fork_info_2 is None: + fork_info_2 = ForkInfo(reorg_block.height - 1, reorg_block.height - 1, reorg_block.prev_header_hash) await _validate_and_add_block( - b, reorg_block, expected_result=AddBlockResult.ADDED_AS_ORPHAN, fork_info=fork_info + b, reorg_block, expected_result=AddBlockResult.ADDED_AS_ORPHAN, fork_info=fork_info_2 ) elif reorg_block.weight > chain_1_weight: await _validate_and_add_block( - b, reorg_block, expected_result=AddBlockResult.NEW_PEAK, fork_info=fork_info + b, reorg_block, expected_result=AddBlockResult.NEW_PEAK, fork_info=fork_info_2 ) # if these asserts fires, there was no reorg @@ -3844,12 +3903,16 @@ async def test_reorg_flip_flop(empty_blockchain: Blockchain, bt: BlockTools) -> preval: List[PreValidationResult] = await b.pre_validate_blocks_multiprocessing( [block1], {}, sub_slot_iters=ssi, difficulty=diff, prev_ses_block=None, validate_signatures=False ) - _, err, _ = await b.add_block(block1, preval[0], None, sub_slot_iters=ssi) + + fork_info = ForkInfo(block1.height - 1, block1.height - 1, block1.prev_header_hash) + _, err, _ = await b.add_block(block1, preval[0], None, sub_slot_iters=ssi, fork_info=fork_info) assert err is None preval = await b.pre_validate_blocks_multiprocessing( [block2], {}, sub_slot_iters=ssi, difficulty=diff, prev_ses_block=None, validate_signatures=False ) - _, err, _ = await b.add_block(block2, preval[0], None, sub_slot_iters=ssi) + + fork_info = ForkInfo(block2.height - 1, block2.height - 1, block2.prev_header_hash) + _, err, _ = await b.add_block(block2, preval[0], None, sub_slot_iters=ssi, fork_info=fork_info) assert err is None peak = b.get_peak() @@ -3880,7 +3943,8 @@ async def test_get_tx_peak(default_400_blocks: List[FullBlock], empty_blockchain last_tx_block_record = None for b, prevalidation_res in zip(test_blocks, res): assert bc.get_tx_peak() == last_tx_block_record - _, err, _ = await bc.add_block(b, prevalidation_res, None, sub_slot_iters=ssi) + fork_info = ForkInfo(b.height - 1, b.height - 1, b.prev_header_hash) + _, err, _ = await bc.add_block(b, prevalidation_res, None, sub_slot_iters=ssi, fork_info=fork_info) assert err is None if b.is_transaction_block(): @@ -4009,3 +4073,38 @@ async def test_lookup_block_generators( b.clean_block_records() with pytest.raises(AssertionError): await b.lookup_block_generators(blocks_1[600].prev_header_hash, {uint32(3)}) + + +async def get_fork_info(blockchain: Blockchain, block: FullBlock, peak: BlockRecord) -> ForkInfo: + fork_chain, fork_hash = await lookup_fork_chain( + blockchain, + (peak.height, peak.header_hash), + (block.height - 1, block.prev_header_hash), + blockchain.constants, + ) + # now we know how long the fork is, and can compute the fork + # height. + fork_height = block.height - len(fork_chain) - 1 + fork_info = ForkInfo(fork_height, fork_height, fork_hash) + + log.warning(f"slow path in block validation. Building coin set for fork ({fork_height}, {block.height})") + + # now run all the blocks of the fork to compute the additions + # and removals. They are recorded in the fork_info object + counter = 0 + start = time.monotonic() + for height in range(fork_info.fork_height + 1, block.height): + fork_block: Optional[FullBlock] = await blockchain.block_store.get_full_block(fork_chain[uint32(height)]) + assert fork_block is not None + assert fork_block.height - 1 == fork_info.peak_height + assert fork_block.height == 0 or fork_block.prev_header_hash == fork_info.peak_hash + await blockchain.run_single_block(fork_block, fork_info) + counter += 1 + end = time.monotonic() + log.info( + f"executed {counter} block generators in {end - start:2f} s. " + f"{len(fork_info.additions_since_fork)} additions, " + f"{len(fork_info.removals_since_fork)} removals" + ) + + return fork_info diff --git a/chia/_tests/core/full_node/test_full_node.py b/chia/_tests/core/full_node/test_full_node.py index 89bb5555c278..1358c84b40c1 100644 --- a/chia/_tests/core/full_node/test_full_node.py +++ b/chia/_tests/core/full_node/test_full_node.py @@ -422,8 +422,9 @@ async def check_transaction_confirmed(transaction) -> bool: diff = bt.constants.DIFFICULTY_STARTING reog_blocks = bt.get_consecutive_blocks(14) for r in range(0, len(reog_blocks), 3): + fork_info = ForkInfo(-1, -1, bt.constants.GENESIS_CHALLENGE) for reorg_block in reog_blocks[:r]: - await _validate_and_add_block_no_error(blockchain, reorg_block) + await _validate_and_add_block_no_error(blockchain, reorg_block, fork_info=fork_info) for i in range(1, height): for batch_size in range(1, height, 3): results = await blockchain.pre_validate_blocks_multiprocessing( @@ -440,8 +441,9 @@ async def check_transaction_confirmed(transaction) -> bool: assert result.error is None for r in range(0, len(all_blocks), 3): + fork_info = ForkInfo(-1, -1, bt.constants.GENESIS_CHALLENGE) for block in all_blocks[:r]: - await _validate_and_add_block_no_error(blockchain, block) + await _validate_and_add_block_no_error(blockchain, block, fork_info=fork_info) for i in range(1, height): for batch_size in range(1, height, 3): results = await blockchain.pre_validate_blocks_multiprocessing( @@ -984,9 +986,7 @@ async def test_new_transaction_and_mempool(self, wallet_nodes, self_hostname, se block_list_input=blocks[:-1], guarantee_transaction_block=True, ) - for block in blocks[-2:]: - await full_node_1.full_node.add_block(block, peer) - + await add_blocks_in_batches(blocks[-2:], full_node_1.full_node, blocks[-2].prev_header_hash) # Can now resubmit a transaction after the reorg status, err = await full_node_1.full_node.add_transaction( successful_bundle, successful_bundle.name(), peer, test=True @@ -2287,16 +2287,7 @@ async def test_long_reorg( # not in the cache. We need to explicitly prune the cache to get that # effect. node.full_node.blockchain.clean_block_records() - - fork_info: Optional[ForkInfo] = None - for b in reorg_blocks: - if (b.height % 128) == 0: - peak = node.full_node.blockchain.get_peak() - print(f"reorg chain: {b.height:4} " f"weight: {b.weight:7} " f"peak: {str(peak.header_hash)[:6]}") - if b.height > fork_point and fork_info is None: - fork_info = ForkInfo(fork_point, fork_point, reorg_blocks[fork_point].header_hash) - await node.full_node.add_block(b, fork_info=fork_info) - + await add_blocks_in_batches(reorg_blocks, node.full_node) # if these asserts fires, there was no reorg peak = node.full_node.blockchain.get_peak() assert peak.header_hash != chain_1_peak @@ -2311,7 +2302,6 @@ async def test_long_reorg( assert peak.height > chain_1_height else: assert peak.height < chain_1_height - # now reorg back to the original chain # this exercises the case where we have some of the blocks in the DB already node.full_node.blockchain.clean_block_records() @@ -2321,15 +2311,7 @@ async def test_long_reorg( blocks = default_10000_blocks[fork_point - 100 : 3200] else: blocks = default_10000_blocks[fork_point - 100 : 5500] - - fork_block = blocks[0] - fork_info = ForkInfo(fork_block.height - 1, fork_block.height - 1, fork_block.prev_header_hash) - for b in blocks: - if (b.height % 128) == 0: - peak = node.full_node.blockchain.get_peak() - print(f"original chain: {b.height:4} " f"weight: {b.weight:7} " f"peak: {str(peak.header_hash)[:6]}") - await node.full_node.add_block(b, fork_info=fork_info) - + await add_blocks_in_batches(blocks, node.full_node) # if these asserts fires, there was no reorg back to the original chain peak = node.full_node.blockchain.get_peak() assert peak.header_hash != chain_2_peak diff --git a/chia/_tests/core/test_db_conversion.py b/chia/_tests/core/test_db_conversion.py index 8761c3e7e0ab..1de1a8cbcc87 100644 --- a/chia/_tests/core/test_db_conversion.py +++ b/chia/_tests/core/test_db_conversion.py @@ -8,6 +8,7 @@ from chia._tests.util.temp_file import TempFile from chia.cmds.db_upgrade_func import convert_v1_to_v2 +from chia.consensus.block_body_validation import ForkInfo from chia.consensus.blockchain import Blockchain from chia.consensus.multiprocess_validation import PreValidationResult from chia.full_node.block_store import BlockStore @@ -75,7 +76,10 @@ async def test_blocks(default_1000_blocks, with_hints: bool): sub_slot_iters = block.finished_sub_slots[0].challenge_chain.new_sub_slot_iters # await _validate_and_add_block(bc, block) results = PreValidationResult(None, uint64(1), None, False, uint32(0)) - result, err, _ = await bc.add_block(block, results, None, sub_slot_iters=sub_slot_iters) + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + result, err, _ = await bc.add_block( + block, results, None, sub_slot_iters=sub_slot_iters, fork_info=fork_info + ) assert err is None # now, convert v1 in_file to v2 out_file diff --git a/chia/_tests/core/test_db_validation.py b/chia/_tests/core/test_db_validation.py index 034d6896a440..756e59d35f8d 100644 --- a/chia/_tests/core/test_db_validation.py +++ b/chia/_tests/core/test_db_validation.py @@ -10,6 +10,7 @@ from chia._tests.util.temp_file import TempFile from chia.cmds.db_validate_func import validate_v2 +from chia.consensus.block_body_validation import ForkInfo from chia.consensus.blockchain import Blockchain from chia.consensus.default_constants import DEFAULT_CONSTANTS from chia.consensus.multiprocess_validation import PreValidationResult @@ -145,7 +146,10 @@ async def make_db(db_file: Path, blocks: List[FullBlock]) -> None: if block.finished_sub_slots[0].challenge_chain.new_sub_slot_iters is not None: sub_slot_iters = block.finished_sub_slots[0].challenge_chain.new_sub_slot_iters results = PreValidationResult(None, uint64(1), None, False, uint32(0)) - result, err, _ = await bc.add_block(block, results, None, sub_slot_iters=sub_slot_iters) + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + result, err, _ = await bc.add_block( + block, results, None, sub_slot_iters=sub_slot_iters, fork_info=fork_info + ) assert err is None diff --git a/chia/_tests/farmer_harvester/test_third_party_harvesters.py b/chia/_tests/farmer_harvester/test_third_party_harvesters.py index 356fbdb453d3..0d1b95b8fd37 100644 --- a/chia/_tests/farmer_harvester/test_third_party_harvesters.py +++ b/chia/_tests/farmer_harvester/test_third_party_harvesters.py @@ -13,6 +13,7 @@ from pytest_mock import MockerFixture from chia._tests.util.time_out_assert import time_out_assert +from chia.consensus.block_body_validation import ForkInfo from chia.consensus.blockchain import AddBlockResult from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty from chia.consensus.multiprocess_validation import PreValidationResult @@ -444,7 +445,10 @@ async def add_test_blocks_into_full_node(blocks: List[FullBlock], full_node: Ful if block.height != 0 and len(block.finished_sub_slots) > 0: # pragma: no cover if block.finished_sub_slots[0].challenge_chain.new_sub_slot_iters is not None: ssi = block.finished_sub_slots[0].challenge_chain.new_sub_slot_iters - r, _, _ = await full_node.blockchain.add_block(blocks[i], pre_validation_results[i], None, sub_slot_iters=ssi) + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + r, _, _ = await full_node.blockchain.add_block( + blocks[i], pre_validation_results[i], None, sub_slot_iters=ssi, fork_info=fork_info + ) assert r == AddBlockResult.NEW_PEAK diff --git a/chia/_tests/util/full_sync.py b/chia/_tests/util/full_sync.py index 1ea532fbf7b4..5829c1a40a58 100644 --- a/chia/_tests/util/full_sync.py +++ b/chia/_tests/util/full_sync.py @@ -14,6 +14,7 @@ from chia._tests.util.constants import test_constants as TEST_CONSTANTS from chia.cmds.init_funcs import chia_init +from chia.consensus.block_body_validation import ForkInfo from chia.consensus.constants import replace_str_to_bytes from chia.consensus.default_constants import DEFAULT_CONSTANTS from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty @@ -206,8 +207,14 @@ async def run_sync_test( ssi, diff = get_next_sub_slot_iters_and_difficulty( full_node.constants, True, block_record, full_node.blockchain ) + fork_height = block_batch[0].height - 1 + header_hash = block_batch[0].prev_header_hash success, summary, _, _, _, _ = await full_node.add_block_batch( - block_batch, peer_info, None, current_ssi=ssi, current_difficulty=diff + block_batch, + peer_info, + ForkInfo(fork_height, fork_height, header_hash), + current_ssi=ssi, + current_difficulty=diff, ) end_height = block_batch[-1].height full_node.blockchain.clean_block_record(end_height - full_node.constants.BLOCKS_CACHE_SIZE) diff --git a/chia/_tests/util/misc.py b/chia/_tests/util/misc.py index 82728a025631..0b3c0457ba4c 100644 --- a/chia/_tests/util/misc.py +++ b/chia/_tests/util/misc.py @@ -55,6 +55,7 @@ import chia._tests from chia._tests import ether from chia._tests.core.data_layer.util import ChiaRoot +from chia.consensus.block_body_validation import ForkInfo from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty from chia.full_node.full_node import FullNode from chia.full_node.mempool import Mempool @@ -700,12 +701,14 @@ async def add_blocks_in_batches( if header_hash is None: diff = full_node.constants.DIFFICULTY_STARTING ssi = full_node.constants.SUB_SLOT_ITERS_STARTING + header_hash = full_node.constants.GENESIS_CHALLENGE else: block_record = await full_node.blockchain.get_block_record_from_db(header_hash) ssi, diff = get_next_sub_slot_iters_and_difficulty( full_node.constants, True, block_record, full_node.blockchain ) prev_ses_block = None + fork_info = ForkInfo(blocks[0].height - 1, blocks[0].height - 1, header_hash) for block_batch in to_batches(blocks, 64): b = block_batch.entries[0] if (b.height % 128) == 0: @@ -713,7 +716,7 @@ async def add_blocks_in_batches( success, _, ssi, diff, prev_ses_block, err = await full_node.add_block_batch( block_batch.entries, PeerInfo("0.0.0.0", 0), - None, + fork_info, current_ssi=ssi, current_difficulty=diff, prev_ses_block=prev_ses_block, diff --git a/chia/_tests/wallet/sync/test_wallet_sync.py b/chia/_tests/wallet/sync/test_wallet_sync.py index af12ae6ff6c4..324b0a5b41b0 100644 --- a/chia/_tests/wallet/sync/test_wallet_sync.py +++ b/chia/_tests/wallet/sync/test_wallet_sync.py @@ -18,6 +18,7 @@ from chia._tests.util.setup_nodes import OldSimulatorsAndWallets from chia._tests.util.time_out_assert import time_out_assert, time_out_assert_not_none from chia._tests.weight_proof.test_weight_proof import load_blocks_dont_validate +from chia.consensus.block_body_validation import ForkInfo from chia.consensus.block_record import BlockRecord from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward from chia.consensus.constants import ConsensusConstants @@ -358,10 +359,11 @@ async def test_long_sync_wallet( sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty( full_node.constants, True, block_record, full_node.blockchain ) + fork_height = blocks_reorg[-num_blocks - 10].height - 1 await full_node.add_block_batch( blocks_reorg[-num_blocks - 10 : -1], PeerInfo("0.0.0.0", 0), - None, + ForkInfo(fork_height, fork_height, blocks_reorg[-num_blocks - 10].prev_header_hash), current_ssi=sub_slot_iters, current_difficulty=difficulty, ) @@ -481,7 +483,7 @@ async def test_wallet_reorg_get_coinbase( await full_node.add_block_batch( blocks_reorg_2[-44:], PeerInfo("0.0.0.0", 0), - None, + ForkInfo(blocks_reorg_2[-45].height, blocks_reorg_2[-45].height, blocks_reorg_2[-45].header_hash), current_ssi=sub_slot_iters, current_difficulty=difficulty, ) diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 9f964445d74f..1cd3bdb7fe09 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -4,7 +4,6 @@ import dataclasses import enum import logging -import time import traceback from concurrent.futures import Executor from concurrent.futures.process import ProcessPoolExecutor @@ -294,7 +293,7 @@ async def add_block( pre_validation_result: PreValidationResult, bls_cache: Optional[BLSCache], sub_slot_iters: uint64, - fork_info: Optional[ForkInfo] = None, + fork_info: ForkInfo, prev_ses_block: Optional[BlockRecord] = None, ) -> Tuple[AddBlockResult, Optional[Err], Optional[StateChangeSummary]]: """ @@ -353,75 +352,19 @@ async def add_block( # maybe fork_info should be mandatory to pass in, but we have a lot of # tests that make sure the Blockchain object can handle any blocks, # including orphaned ones, without any fork context - if fork_info is None: - block_rec = await self.get_block_record_from_db(header_hash) - if block_rec is not None: - self.add_block_record(block_rec) - # this means we have already seen and validated this block. - return AddBlockResult.ALREADY_HAVE_BLOCK, None, None - elif extending_main_chain: - # this is the common and efficient case where we extend the main - # chain. The fork_info can be empty - prev_height = block.height - 1 - fork_info = ForkInfo(prev_height, prev_height, block.prev_header_hash) - else: - assert peak is not None - # the block is extending a fork, and we don't have any fork_info - # for it. This can potentially be quite expensive and we should - # try to avoid getting here - - # first, collect all the block hashes of the forked chain - # the block we're trying to add doesn't exist in the chain yet, - # so we need to start traversing from its prev_header_hash - fork_chain, fork_hash = await lookup_fork_chain( - self, - (peak.height, peak.header_hash), - (block.height - 1, block.prev_header_hash), - self.constants, - ) - # now we know how long the fork is, and can compute the fork - # height. - fork_height = block.height - len(fork_chain) - 1 - fork_info = ForkInfo(fork_height, fork_height, fork_hash) - - log.warning( - f"slow path in block validation. Building coin set for fork ({fork_height}, {block.height})" - ) - - # now run all the blocks of the fork to compute the additions - # and removals. They are recorded in the fork_info object - counter = 0 - start = time.monotonic() - for height in range(fork_info.fork_height + 1, block.height): - fork_block: Optional[FullBlock] = await self.block_store.get_full_block(fork_chain[uint32(height)]) - assert fork_block is not None - assert fork_block.height - 1 == fork_info.peak_height - assert fork_block.height == 0 or fork_block.prev_header_hash == fork_info.peak_hash - await self.run_single_block(fork_block, fork_info) - counter += 1 - end = time.monotonic() - log.info( - f"executed {counter} block generators in {end - start:2f} s. " - f"{len(fork_info.additions_since_fork)} additions, " - f"{len(fork_info.removals_since_fork)} removals" - ) - - else: - if extending_main_chain: - fork_info.reset(block.height - 1, block.prev_header_hash) - - block_rec = await self.get_block_record_from_db(header_hash) - if block_rec is not None: - # We have already validated the block, but if it's not part of the - # main chain, we still need to re-run it to update the additions and - # removals in fork_info. + block_rec = await self.get_block_record_from_db(header_hash) + if block_rec is not None: + self.add_block_record(block_rec) + # this means we have already seen and validated this block. + if fork_info is not None: await self.advance_fork_info(block, fork_info) fork_info.include_spends(npc_result, block, header_hash) - self.add_block_record(block_rec) - return AddBlockResult.ALREADY_HAVE_BLOCK, None, None + return AddBlockResult.ALREADY_HAVE_BLOCK, None, None - if fork_info.peak_hash != block.prev_header_hash: - await self.advance_fork_info(block, fork_info) + if extending_main_chain: + # this is the common and efficient case where we extend the main + # chain. The fork_info can be empty + fork_info.reset(block.height - 1, block.prev_header_hash) # if these prerequisites of the fork_info aren't met, the fork_info # object is invalid for this block. If the caller would have passed in diff --git a/chia/full_node/full_node.py b/chia/full_node/full_node.py index 01b04553ad56..f35da5f76c8e 100644 --- a/chia/full_node/full_node.py +++ b/chia/full_node/full_node.py @@ -591,6 +591,12 @@ async def short_sync_batch(self, peer: WSChiaConnection, start_height: uint32, t try: peer_info = peer.get_peer_logging() + if start_height > 0: + fork_hash = self.blockchain.height_to_hash(uint32(start_height - 1)) + else: + fork_hash = self.constants.GENESIS_CHALLENGE + assert fork_hash + fork_info = ForkInfo(start_height - 1, start_height - 1, fork_hash) for height in range(start_height, target_height, batch_size): end_height = min(target_height, height + batch_size) request = RequestBlocks(uint32(height), uint32(end_height), True) @@ -608,7 +614,7 @@ async def short_sync_batch(self, peer: WSChiaConnection, start_height: uint32, t self.constants, new_slot, prev_b, self.blockchain ) success, state_change_summary, ssi, diff, _, _ = await self.add_block_batch( - response.blocks, peer_info, None, ssi, diff + response.blocks, peer_info, fork_info, ssi, diff ) if not success: raise ValueError(f"Error short batch syncing, failed to validate blocks {height}-{end_height}") @@ -1156,21 +1162,12 @@ async def validate_block_batches( # for deep reorgs peak: Optional[BlockRecord] if fork_info is None: - peak = self.blockchain.get_peak() - extending_main_chain: bool = peak is None or ( - peak.header_hash == blocks[0].prev_header_hash or peak.header_hash == blocks[0].header_hash - ) - # if we're simply extending the main chain, it's important - # *not* to pass in a ForkInfo object, as it can potentially - # accrue a large state (with no value, since we can validate - # against the CoinStore) - if not extending_main_chain: - if fork_point_height == 0: - fork_info = ForkInfo(-1, -1, self.constants.GENESIS_CHALLENGE) - else: - fork_hash = self.blockchain.height_to_hash(uint32(fork_point_height - 1)) - assert fork_hash is not None - fork_info = ForkInfo(fork_point_height - 1, fork_point_height - 1, fork_hash) + if fork_point_height > 0: + fork_hash = self.blockchain.height_to_hash(uint32(fork_point_height - 1)) + assert fork_hash is not None + else: + fork_hash = self.constants.GENESIS_CHALLENGE + fork_info = ForkInfo(fork_point_height - 1, fork_point_height - 1, fork_hash) success, state_change_summary, ssi, diff, prev_ses_block, err = await self.add_block_batch( blocks, @@ -1278,7 +1275,7 @@ async def add_block_batch( self, all_blocks: List[FullBlock], peer_info: PeerInfo, - fork_info: Optional[ForkInfo], + fork_info: ForkInfo, current_ssi: uint64, current_difficulty: uint64, prev_ses_block: Optional[BlockRecord] = None, @@ -1304,16 +1301,10 @@ async def add_block_batch( if block_rec.sub_epoch_summary_included.new_difficulty is not None: current_difficulty = block_rec.sub_epoch_summary_included.new_difficulty - if fork_info is None: - continue # the below section updates the fork_info object, if # there is one. - - # TODO: it seems unnecessary to request overlapping block ranges - # when syncing if block.height <= fork_info.peak_height: continue - # we have already validated this block once, no need to do it again. # however, if this block is not part of the main chain, we need to # update the fork context with its additions and removals @@ -1759,7 +1750,6 @@ async def add_block( peer: Optional[WSChiaConnection] = None, bls_cache: Optional[BLSCache] = None, raise_on_disconnected: bool = False, - fork_info: Optional[ForkInfo] = None, ) -> Optional[Message]: """ Add a full block from a peer full node (or ourselves). @@ -1889,6 +1879,7 @@ async def add_block( pre_validation_results[0] if pre_validation_result is None else pre_validation_result ) assert result_to_validate.required_iters == pre_validation_results[0].required_iters + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) (added, error_code, state_change_summary) = await self.blockchain.add_block( block, result_to_validate, bls_cache, ssi, fork_info ) diff --git a/chia/simulator/full_node_simulator.py b/chia/simulator/full_node_simulator.py index a1e7dcfc0865..f75c1deedf0c 100644 --- a/chia/simulator/full_node_simulator.py +++ b/chia/simulator/full_node_simulator.py @@ -7,6 +7,7 @@ import anyio +from chia.consensus.block_body_validation import ForkInfo from chia.consensus.block_record import BlockRecord from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward from chia.consensus.blockchain import BlockchainMutexPriority @@ -181,11 +182,13 @@ async def farm_new_transaction_block( ) ) assert pre_validation_results is not None + fork_info = ForkInfo(-1, -1, self.full_node.constants.GENESIS_CHALLENGE) await self.full_node.blockchain.add_block( genesis, pre_validation_results[0], self.full_node._bls_cache, self.full_node.constants.SUB_SLOT_ITERS_STARTING, + fork_info, ) peak = self.full_node.blockchain.get_peak() @@ -243,11 +246,9 @@ async def farm_new_block(self, request: FarmNewBlockProtocol, force_wait_for_tim ) ) assert pre_validation_results is not None + fork_info = ForkInfo(-1, -1, self.full_node.constants.GENESIS_CHALLENGE) await self.full_node.blockchain.add_block( - genesis, - pre_validation_results[0], - self.full_node._bls_cache, - ssi, + genesis, pre_validation_results[0], self.full_node._bls_cache, ssi, fork_info ) peak = self.full_node.blockchain.get_peak() assert peak is not None diff --git a/tools/test_full_sync.py b/tools/test_full_sync.py index f944f702667b..4f5a5644a343 100755 --- a/tools/test_full_sync.py +++ b/tools/test_full_sync.py @@ -13,6 +13,7 @@ from chia._tests.util.full_sync import FakePeer, FakeServer, run_sync_test from chia.cmds.init_funcs import chia_init +from chia.consensus.block_body_validation import ForkInfo from chia.consensus.constants import replace_str_to_bytes from chia.consensus.default_constants import DEFAULT_CONSTANTS from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty @@ -158,8 +159,16 @@ async def run_sync_checkpoint( ssi, diff = get_next_sub_slot_iters_and_difficulty( full_node.constants, True, block_record, full_node.blockchain ) + + fork_height = block_batch[0].height - 1 + header_hash = block_batch[0].prev_header_hash + success, _, _, _, _, _ = await full_node.add_block_batch( - block_batch, peer_info, None, current_ssi=ssi, current_difficulty=diff + block_batch, + peer_info, + ForkInfo(fork_height, fork_height, header_hash), + current_ssi=ssi, + current_difficulty=diff, ) end_height = block_batch[-1].height full_node.blockchain.clean_block_record(end_height - full_node.constants.BLOCKS_CACHE_SIZE) @@ -176,8 +185,14 @@ async def run_sync_checkpoint( ssi, diff = get_next_sub_slot_iters_and_difficulty( full_node.constants, True, block_record, full_node.blockchain ) + fork_height = block_batch[0].height - 1 + fork_header_hash = block_batch[0].prev_header_hash success, _, _, _, _, _ = await full_node.add_block_batch( - block_batch, peer_info, None, current_ssi=ssi, current_difficulty=diff + block_batch, + peer_info, + ForkInfo(fork_height, fork_height, fork_header_hash), + current_ssi=ssi, + current_difficulty=diff, ) if not success: raise RuntimeError("failed to ingest block batch") From d0e78381fe3eeb26af0de2f097bf813085e54d07 Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Wed, 25 Sep 2024 12:47:53 +0300 Subject: [PATCH 02/10] minor fixes --- chia/_tests/blockchain/test_blockchain.py | 4 +--- chia/consensus/blockchain.py | 8 +------- chia/simulator/full_node_simulator.py | 5 ++--- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/chia/_tests/blockchain/test_blockchain.py b/chia/_tests/blockchain/test_blockchain.py index 210e7e1aeb72..acae6e5cc4f7 100644 --- a/chia/_tests/blockchain/test_blockchain.py +++ b/chia/_tests/blockchain/test_blockchain.py @@ -1857,12 +1857,12 @@ async def test_pre_validation( end_pv = time.time() times_pv.append(end_pv - start_pv) assert res is not None - fork_info = ForkInfo(-1, -1, empty_blockchain.constants.GENESIS_CHALLENGE) for n in range(end_i - i): assert res[n] is not None assert res[n].error is None block = blocks_to_validate[n] start_rb = time.time() + fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) result, err, _ = await empty_blockchain.add_block(block, res[n], None, ssi, fork_info=fork_info) end_rb = time.time() times_rb.append(end_rb - start_rb) @@ -4087,8 +4087,6 @@ async def get_fork_info(blockchain: Blockchain, block: FullBlock, peak: BlockRec fork_height = block.height - len(fork_chain) - 1 fork_info = ForkInfo(fork_height, fork_height, fork_hash) - log.warning(f"slow path in block validation. Building coin set for fork ({fork_height}, {block.height})") - # now run all the blocks of the fork to compute the additions # and removals. They are recorded in the fork_info object counter = 0 diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 1cd3bdb7fe09..d8ba850b61ad 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -348,17 +348,11 @@ async def add_block( assert required_iters is not None header_hash: bytes32 = block.header_hash - - # maybe fork_info should be mandatory to pass in, but we have a lot of - # tests that make sure the Blockchain object can handle any blocks, - # including orphaned ones, without any fork context block_rec = await self.get_block_record_from_db(header_hash) if block_rec is not None: self.add_block_record(block_rec) # this means we have already seen and validated this block. - if fork_info is not None: - await self.advance_fork_info(block, fork_info) - fork_info.include_spends(npc_result, block, header_hash) + assert fork_info is not None return AddBlockResult.ALREADY_HAVE_BLOCK, None, None if extending_main_chain: diff --git a/chia/simulator/full_node_simulator.py b/chia/simulator/full_node_simulator.py index f75c1deedf0c..a5918bc6f8f2 100644 --- a/chia/simulator/full_node_simulator.py +++ b/chia/simulator/full_node_simulator.py @@ -7,6 +7,7 @@ import anyio +from chia._tests.util.misc import add_blocks_in_batches from chia.consensus.block_body_validation import ForkInfo from chia.consensus.block_record import BlockRecord from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward @@ -303,9 +304,7 @@ async def reorg_from_index_to_new_index(self, request: ReorgProtocol): guarantee_transaction_block=True, seed=seed, ) - - for block in more_blocks: - await self.full_node.add_block(block) + await add_blocks_in_batches(more_blocks, self.full_node, more_blocks[0].prev_header_hash) async def farm_blocks_to_puzzlehash( self, From c4a4802fc0dcf01e6a05d9be8ff1252e9c957a9a Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Sun, 29 Sep 2024 15:06:52 +0300 Subject: [PATCH 03/10] fix_tests --- chia/_tests/blockchain/test_blockchain.py | 18 ++++++++++-------- chia/consensus/blockchain.py | 14 ++++++++------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/chia/_tests/blockchain/test_blockchain.py b/chia/_tests/blockchain/test_blockchain.py index acae6e5cc4f7..406f7c39804d 100644 --- a/chia/_tests/blockchain/test_blockchain.py +++ b/chia/_tests/blockchain/test_blockchain.py @@ -3354,7 +3354,9 @@ async def test_long_reorg( assert pre_validation_results[i].error is None if (block.height % 100) == 0: print(f"main chain: {block.height:4} weight: {block.weight}") - fork_info = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + + fork_info: Optional[ForkInfo] = ForkInfo(block.height - 1, block.height - 1, block.prev_header_hash) + assert fork_info is not None (result, err, _) = await b.add_block( block, pre_validation_results[i], None, sub_slot_iters=ssi, fork_info=fork_info ) @@ -3382,7 +3384,7 @@ async def test_long_reorg( b.clean_block_records() first_peak = b.get_peak() - fork_info_2: Optional[ForkInfo] = None + fork_info2 = None for reorg_block in reorg_blocks: if (reorg_block.height % 100) == 0: peak = b.get_peak() @@ -3396,14 +3398,14 @@ async def test_long_reorg( if reorg_block.height < num_blocks_chain_2_start: await _validate_and_add_block(b, reorg_block, expected_result=AddBlockResult.ALREADY_HAVE_BLOCK) elif reorg_block.weight <= chain_1_weight: - if fork_info_2 is None: - fork_info_2 = ForkInfo(reorg_block.height - 1, reorg_block.height - 1, reorg_block.prev_header_hash) + if fork_info2 is None: + fork_info2 = ForkInfo(reorg_block.height - 1, reorg_block.height - 1, reorg_block.prev_header_hash) await _validate_and_add_block( - b, reorg_block, expected_result=AddBlockResult.ADDED_AS_ORPHAN, fork_info=fork_info_2 + b, reorg_block, expected_result=AddBlockResult.ADDED_AS_ORPHAN, fork_info=fork_info2 ) elif reorg_block.weight > chain_1_weight: await _validate_and_add_block( - b, reorg_block, expected_result=AddBlockResult.NEW_PEAK, fork_info=fork_info_2 + b, reorg_block, expected_result=AddBlockResult.NEW_PEAK, fork_info=fork_info2 ) # if these asserts fires, there was no reorg @@ -3443,7 +3445,7 @@ async def test_long_reorg( # start the fork point a few blocks back, to test that the blockchain # can catch up fork_block = default_10000_blocks[num_blocks_chain_2_start - 200] - fork_info = ForkInfo(fork_block.height, fork_block.height, fork_block.header_hash) + fork_info3 = ForkInfo(fork_block.height, fork_block.height, fork_block.header_hash) await b.warmup(fork_block.height) for block in blocks: if (block.height % 128) == 0: @@ -3460,7 +3462,7 @@ async def test_long_reorg( expect = AddBlockResult.ADDED_AS_ORPHAN else: expect = AddBlockResult.NEW_PEAK - await _validate_and_add_block(b, block, fork_info=fork_info, expected_result=expect) + await _validate_and_add_block(b, block, fork_info=fork_info3, expected_result=expect) # if these asserts fires, there was no reorg back to the original chain peak = b.get_peak() diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index d8ba850b61ad..11318df9ff3d 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -348,17 +348,19 @@ async def add_block( assert required_iters is not None header_hash: bytes32 = block.header_hash + + if extending_main_chain: + fork_info.reset(block.height - 1, block.prev_header_hash) + block_rec = await self.get_block_record_from_db(header_hash) if block_rec is not None: + await self.advance_fork_info(block, fork_info) + fork_info.include_spends(npc_result, block, header_hash) self.add_block_record(block_rec) - # this means we have already seen and validated this block. - assert fork_info is not None return AddBlockResult.ALREADY_HAVE_BLOCK, None, None - if extending_main_chain: - # this is the common and efficient case where we extend the main - # chain. The fork_info can be empty - fork_info.reset(block.height - 1, block.prev_header_hash) + if fork_info.peak_hash != block.prev_header_hash: + await self.advance_fork_info(block, fork_info) # if these prerequisites of the fork_info aren't met, the fork_info # object is invalid for this block. If the caller would have passed in From 782bd01994e03bc58cab646e70677df40141ba5a Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Sun, 29 Sep 2024 20:18:03 +0300 Subject: [PATCH 04/10] add finish sync to add_blocks_in_batches --- chia/_tests/core/test_full_node_rpc.py | 4 ++-- chia/_tests/util/misc.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/chia/_tests/core/test_full_node_rpc.py b/chia/_tests/core/test_full_node_rpc.py index 1d0e247aa303..20e8fc150cf8 100644 --- a/chia/_tests/core/test_full_node_rpc.py +++ b/chia/_tests/core/test_full_node_rpc.py @@ -11,6 +11,7 @@ from chia._tests.blockchain.blockchain_test_utils import _validate_and_add_block from chia._tests.conftest import ConsensusMode from chia._tests.connection_utils import connect_and_get_peer +from chia._tests.util.misc import add_blocks_in_batches from chia._tests.util.rpc import validate_get_routes from chia._tests.util.time_out_assert import time_out_assert from chia.consensus.block_record import BlockRecord @@ -537,8 +538,7 @@ async def test_signage_points(two_nodes_sim_and_wallets_services, empty_blockcha # Perform a reorg blocks = bt.get_consecutive_blocks(12, seed=b"1234") - for block in blocks: - await full_node_api_1.full_node.add_block(block) + await add_blocks_in_batches(blocks, full_node_api_1.full_node) # Signage point is no longer in the blockchain res = await client.get_recent_signage_point_or_eos(sp.cc_vdf.output.get_hash(), None) diff --git a/chia/_tests/util/misc.py b/chia/_tests/util/misc.py index 0b3c0457ba4c..303fe228e977 100644 --- a/chia/_tests/util/misc.py +++ b/chia/_tests/util/misc.py @@ -723,3 +723,4 @@ async def add_blocks_in_batches( ) assert err is None assert success is True + await full_node._finish_sync() From f82fc1b1ef74087d5ffefe701547ddd8b60e176a Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Mon, 14 Oct 2024 17:01:04 +0300 Subject: [PATCH 05/10] add_blocks_in_batches updates wallets --- chia/_tests/process_junit.py | 4 +- chia/_tests/util/misc.py | 61 +++++++++------------------ chia/_tests/util/time_out_assert.py | 54 +++++++++++++++++++++++- chia/logs/__init__.py | 0 chia/simulator/full_node_simulator.py | 2 +- 5 files changed, 74 insertions(+), 47 deletions(-) create mode 100644 chia/logs/__init__.py diff --git a/chia/_tests/process_junit.py b/chia/_tests/process_junit.py index fb1388ba62a6..cf5e1d25c8b5 100644 --- a/chia/_tests/process_junit.py +++ b/chia/_tests/process_junit.py @@ -12,8 +12,8 @@ import click import lxml.etree -from chia._tests.util.misc import BenchmarkData, DataTypeProtocol, TestId -from chia._tests.util.time_out_assert import TimeOutAssertData +from chia._tests.util.misc import BenchmarkData, TestId +from chia._tests.util.time_out_assert import DataTypeProtocol, TimeOutAssertData supported_data_types: List[Type[DataTypeProtocol]] = [TimeOutAssertData, BenchmarkData] supported_data_types_by_tag: Dict[str, Type[DataTypeProtocol]] = {cls.tag: cls for cls in supported_data_types} diff --git a/chia/_tests/util/misc.py b/chia/_tests/util/misc.py index 303fe228e977..8e38f98ba2a8 100644 --- a/chia/_tests/util/misc.py +++ b/chia/_tests/util/misc.py @@ -15,8 +15,6 @@ from concurrent.futures import Future from dataclasses import dataclass, field from enum import Enum -from inspect import getframeinfo, stack -from pathlib import Path from statistics import mean from textwrap import dedent from time import thread_time @@ -29,7 +27,6 @@ ClassVar, Collection, Dict, - Iterable, Iterator, List, Optional, @@ -54,10 +51,12 @@ import chia import chia._tests from chia._tests import ether +from chia._tests.connection_utils import add_dummy_connection from chia._tests.core.data_layer.util import ChiaRoot +from chia._tests.util.time_out_assert import DataTypeProtocol, caller_file_and_line from chia.consensus.block_body_validation import ForkInfo from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty -from chia.full_node.full_node import FullNode +from chia.full_node.full_node import FullNode, PeakPostProcessingResult from chia.full_node.mempool import Mempool from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.condition_opcodes import ConditionOpcode @@ -617,27 +616,6 @@ def marshal(self) -> Dict[str, Any]: } -T = TypeVar("T") - - -@dataclasses.dataclass(frozen=True) -class DataTypeProtocol(Protocol): - tag: ClassVar[str] - - line: int - path: Path - label: str - duration: float - limit: float - - __match_args__: ClassVar[Tuple[str, ...]] = () - - @classmethod - def unmarshal(cls: Type[T], marshalled: Dict[str, Any]) -> T: ... - - def marshal(self) -> Dict[str, Any]: ... - - T_ComparableEnum = TypeVar("T_ComparableEnum", bound="ComparableEnum") @@ -679,20 +657,6 @@ def __ge__(self: T_ComparableEnum, other: T_ComparableEnum) -> object: return self.value.__ge__(other.value) -def caller_file_and_line(distance: int = 1, relative_to: Iterable[Path] = ()) -> Tuple[str, int]: - caller = getframeinfo(stack()[distance + 1][0]) - - caller_path = Path(caller.filename) - options: List[str] = [caller_path.as_posix()] - for path in relative_to: - try: - options.append(caller_path.relative_to(path).as_posix()) - except ValueError: - pass - - return min(options, key=len), caller.lineno - - async def add_blocks_in_batches( blocks: List[FullBlock], full_node: FullNode, @@ -701,19 +665,25 @@ async def add_blocks_in_batches( if header_hash is None: diff = full_node.constants.DIFFICULTY_STARTING ssi = full_node.constants.SUB_SLOT_ITERS_STARTING - header_hash = full_node.constants.GENESIS_CHALLENGE + fork_height = -1 + fork_info = ForkInfo(-1, fork_height, full_node.constants.GENESIS_CHALLENGE) else: block_record = await full_node.blockchain.get_block_record_from_db(header_hash) + assert block_record is not None ssi, diff = get_next_sub_slot_iters_and_difficulty( full_node.constants, True, block_record, full_node.blockchain ) + fork_height = block_record.height + fork_info = ForkInfo(block_record.height, fork_height, block_record.header_hash) + prev_ses_block = None - fork_info = ForkInfo(blocks[0].height - 1, blocks[0].height - 1, header_hash) + _, dummy_node_id = await add_dummy_connection(full_node.server, "127.0.0.1", 12315) + dummy_peer = full_node.server.all_connections[dummy_node_id] for block_batch in to_batches(blocks, 64): b = block_batch.entries[0] if (b.height % 128) == 0: print(f"main chain: {b.height:4} weight: {b.weight}") - success, _, ssi, diff, prev_ses_block, err = await full_node.add_block_batch( + success, state_change_summary, ssi, diff, prev_ses_block, err = await full_node.add_block_batch( block_batch.entries, PeerInfo("0.0.0.0", 0), fork_info, @@ -723,4 +693,11 @@ async def add_blocks_in_batches( ) assert err is None assert success is True + if state_change_summary is not None: + peak_fb: Optional[FullBlock] = await full_node.blockchain.get_full_peak() + assert peak_fb is not None + ppp_result: PeakPostProcessingResult = await full_node.peak_post_processing( + peak_fb, state_change_summary, dummy_peer + ) + await full_node.peak_post_processing_2(peak_fb, dummy_peer, state_change_summary, ppp_result) await full_node._finish_sync() diff --git a/chia/_tests/util/time_out_assert.py b/chia/_tests/util/time_out_assert.py index 644f6d9b97cc..b10a84636bd2 100644 --- a/chia/_tests/util/time_out_assert.py +++ b/chia/_tests/util/time_out_assert.py @@ -6,18 +6,54 @@ import logging import pathlib import time -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Tuple, cast, final +from inspect import getframeinfo, stack +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Dict, + Iterable, + List, + Protocol, + Tuple, + Type, + TypeVar, + cast, + final, +) import chia import chia._tests from chia._tests import ether -from chia._tests.util.misc import DataTypeProtocol, caller_file_and_line from chia.protocols.protocol_message_types import ProtocolMessageTypes from chia.util.timing import adjusted_timeout log = logging.getLogger(__name__) +T = TypeVar("T") + + +@dataclasses.dataclass(frozen=True) +class DataTypeProtocol(Protocol): + tag: ClassVar[str] + + line: int + path: Path + label: str + duration: float + limit: float + + __match_args__: ClassVar[Tuple[str, ...]] = () + + @classmethod + def unmarshal(cls: Type[T], marshalled: Dict[str, Any]) -> T: ... + + def marshal(self) -> Dict[str, Any]: ... + + @final @dataclasses.dataclass(frozen=True) class TimeOutAssertData: @@ -152,3 +188,17 @@ async def bool_f(): return True return bool_f + + +def caller_file_and_line(distance: int = 1, relative_to: Iterable[Path] = ()) -> Tuple[str, int]: + caller = getframeinfo(stack()[distance + 1][0]) + + caller_path = Path(caller.filename) + options: List[str] = [caller_path.as_posix()] + for path in relative_to: + try: + options.append(caller_path.relative_to(path).as_posix()) + except ValueError: + pass + + return min(options, key=len), caller.lineno diff --git a/chia/logs/__init__.py b/chia/logs/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/chia/simulator/full_node_simulator.py b/chia/simulator/full_node_simulator.py index aa08dff8da05..a034ac29a2d9 100644 --- a/chia/simulator/full_node_simulator.py +++ b/chia/simulator/full_node_simulator.py @@ -306,7 +306,7 @@ async def reorg_from_index_to_new_index(self, request: ReorgProtocol): guarantee_transaction_block=True, seed=seed, ) - await add_blocks_in_batches(more_blocks, self.full_node, more_blocks[0].prev_header_hash) + await add_blocks_in_batches(more_blocks, self.full_node, current_blocks[old_index].header_hash) async def farm_blocks_to_puzzlehash( self, From a7f8a2f917b8d3636dedff00b52e368fb261fc0a Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Wed, 16 Oct 2024 11:15:34 +0300 Subject: [PATCH 06/10] test fixes --- chia/_tests/blockchain/test_blockchain.py | 5 +++-- .../test_blockchain_transactions.py | 19 +++++++------------ .../full_node/stores/test_full_node_store.py | 17 +++++++++++------ chia/_tests/wallet/sync/test_wallet_sync.py | 6 ++---- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/chia/_tests/blockchain/test_blockchain.py b/chia/_tests/blockchain/test_blockchain.py index 87d7f817a504..254a071cc8d2 100644 --- a/chia/_tests/blockchain/test_blockchain.py +++ b/chia/_tests/blockchain/test_blockchain.py @@ -3603,9 +3603,10 @@ async def test_reorg_transaction(self, empty_blockchain: Blockchain, bt: BlockTo ) for block in blocks: await _validate_and_add_block(b, block) - + fork_block = blocks[11] + fork_info = ForkInfo(fork_block.height, fork_block.height, fork_block.header_hash) for block in blocks_fork: - await _validate_and_add_block_no_error(b, block) + await _validate_and_add_block_no_error(b, block, fork_info=fork_info) @pytest.mark.anyio async def test_get_header_blocks_in_range_tx_filter(self, empty_blockchain: Blockchain, bt: BlockTools) -> None: diff --git a/chia/_tests/blockchain/test_blockchain_transactions.py b/chia/_tests/blockchain/test_blockchain_transactions.py index 9f4f31f18408..7115274f006a 100644 --- a/chia/_tests/blockchain/test_blockchain_transactions.py +++ b/chia/_tests/blockchain/test_blockchain_transactions.py @@ -8,6 +8,7 @@ from chia._tests.blockchain.blockchain_test_utils import _validate_and_add_block from chia._tests.util.generator_tools_testing import run_and_get_removals_and_additions +from chia._tests.util.misc import add_blocks_in_batches from chia.full_node.full_node_api import FullNodeAPI from chia.protocols import wallet_protocol from chia.server.server import ChiaServer @@ -321,8 +322,7 @@ async def test_validate_blockchain_spend_reorg_coin( transaction_data=spend_bundle, guarantee_transaction_block=True, ) - - await full_node_api_1.full_node.add_block(new_blocks[-1]) + await add_blocks_in_batches([new_blocks[-1]], full_node_api_1.full_node, blocks[5].prev_header_hash) coin_2 = None for coin in run_and_get_removals_and_additions( @@ -346,7 +346,7 @@ async def test_validate_blockchain_spend_reorg_coin( transaction_data=spend_bundle, guarantee_transaction_block=True, ) - await full_node_api_1.full_node.add_block(new_blocks[-1]) + await add_blocks_in_batches([new_blocks[-1]], full_node_api_1.full_node, blocks[5].prev_header_hash) coin_3 = None for coin in run_and_get_removals_and_additions( @@ -370,8 +370,7 @@ async def test_validate_blockchain_spend_reorg_coin( transaction_data=spend_bundle, guarantee_transaction_block=True, ) - - await full_node_api_1.full_node.add_block(new_blocks[-1]) + await add_blocks_in_batches([new_blocks[-1]], full_node_api_1.full_node, blocks[5].prev_header_hash) @pytest.mark.anyio async def test_validate_blockchain_spend_reorg_cb_coin( @@ -383,9 +382,7 @@ async def test_validate_blockchain_spend_reorg_cb_coin( receiver_1_puzzlehash = WALLET_A_PUZZLE_HASHES[1] full_node_api_1, _, _, _, bt = two_nodes blocks = bt.get_consecutive_blocks(num_blocks, farmer_reward_puzzle_hash=coinbase_puzzlehash) - - for block in blocks: - await full_node_api_1.full_node.add_block(block) + await add_blocks_in_batches(blocks, full_node_api_1.full_node) # Spends a coinbase created in reorg new_blocks = bt.get_consecutive_blocks( @@ -396,8 +393,7 @@ async def test_validate_blockchain_spend_reorg_cb_coin( guarantee_transaction_block=True, ) - for block in new_blocks: - await full_node_api_1.full_node.add_block(block) + await add_blocks_in_batches(new_blocks, full_node_api_1.full_node, blocks[6].prev_header_hash) spend_block = new_blocks[-1] spend_coin = None @@ -415,8 +411,7 @@ async def test_validate_blockchain_spend_reorg_cb_coin( transaction_data=spend_bundle, guarantee_transaction_block=True, ) - - await full_node_api_1.full_node.add_block(new_blocks[-1]) + await add_blocks_in_batches([new_blocks[-1]], full_node_api_1.full_node, blocks[6].prev_header_hash) @pytest.mark.anyio async def test_validate_blockchain_spend_reorg_since_genesis( diff --git a/chia/_tests/core/full_node/stores/test_full_node_store.py b/chia/_tests/core/full_node/stores/test_full_node_store.py index 7e0412f2a662..8a77bb3abf23 100644 --- a/chia/_tests/core/full_node/stores/test_full_node_store.py +++ b/chia/_tests/core/full_node/stores/test_full_node_store.py @@ -9,6 +9,7 @@ from chia._tests.blockchain.blockchain_test_utils import _validate_and_add_block, _validate_and_add_block_no_error from chia._tests.util.blockchain import create_blockchain from chia._tests.util.blockchain_mock import BlockchainMock +from chia.consensus.block_body_validation import ForkInfo from chia.consensus.blockchain import AddBlockResult, Blockchain from chia.consensus.constants import ConsensusConstants from chia.consensus.default_constants import DEFAULT_CONSTANTS @@ -476,11 +477,14 @@ async def test_basic_store( normalized_to_identity_cc_ip=normalized_to_identity, normalized_to_identity_cc_sp=normalized_to_identity, ) + + fork_info = ForkInfo(-1, -1, blockchain.constants.GENESIS_CHALLENGE) for block in blocks_reorg: + peak = blockchain.get_peak() assert peak is not None - await _validate_and_add_block_no_error(blockchain, block) + await _validate_and_add_block_no_error(blockchain, block, fork_info=fork_info) peak_here = blockchain.get_peak() assert peak_here is not None @@ -558,7 +562,7 @@ async def test_basic_store( normalized_to_identity_cc_ip=normalized_to_identity, normalized_to_identity_cc_sp=normalized_to_identity, ) - await _validate_and_add_block(blockchain, blocks[-1]) + await _validate_and_add_block(blockchain, blocks[-1], fork_info=fork_info) peak_here = blockchain.get_peak() assert peak_here is not None if peak_here.header_hash == blocks[-1].header_hash: @@ -910,8 +914,9 @@ async def test_basic_store( normalized_to_identity_icc_eos=normalized_to_identity, ) + fork_info = ForkInfo(-1, -1, blockchain.constants.GENESIS_CHALLENGE) for block in blocks[:5]: - await _validate_and_add_block_no_error(blockchain, block) + await _validate_and_add_block_no_error(blockchain, block, fork_info=fork_info) sb = blockchain.block_record(block.header_hash) result = await blockchain.get_sp_and_ip_sub_slots(block.header_hash) assert result is not None @@ -940,7 +945,7 @@ async def test_basic_store( ) store.add_to_future_ip(new_ip) - await _validate_and_add_block_no_error(blockchain, prev_block) + await _validate_and_add_block_no_error(blockchain, prev_block, fork_info=fork_info) result = await blockchain.get_sp_and_ip_sub_slots(prev_block.header_hash) assert result is not None sp_sub_slot, ip_sub_slot = result @@ -982,13 +987,13 @@ async def test_basic_store( # Then do a reorg up to B2, removing all signage points after B2, but not before log.warning(f"Adding blocks up to {blocks[-1]}") for block in blocks: - await _validate_and_add_block_no_error(blockchain, block) + await _validate_and_add_block_no_error(blockchain, block, fork_info=fork_info) log.warning("Starting loop") while True: log.warning("Looping") blocks = custom_block_tools.get_consecutive_blocks(1, block_list_input=blocks, skip_slots=1) - await _validate_and_add_block_no_error(blockchain, blocks[-1]) + await _validate_and_add_block_no_error(blockchain, blocks[-1], fork_info=fork_info) peak = blockchain.get_peak() assert peak is not None result = await blockchain.get_sp_and_ip_sub_slots(peak.header_hash) diff --git a/chia/_tests/wallet/sync/test_wallet_sync.py b/chia/_tests/wallet/sync/test_wallet_sync.py index 30499609c8b9..fef814b995aa 100644 --- a/chia/_tests/wallet/sync/test_wallet_sync.py +++ b/chia/_tests/wallet/sync/test_wallet_sync.py @@ -403,8 +403,7 @@ async def test_wallet_reorg_sync( await wallet_server.start_client(PeerInfo(self_hostname, full_node_server.get_port()), None) # Insert 400 blocks - await full_node.add_block(default_400_blocks[0]) - await add_blocks_in_batches(default_400_blocks[1:], full_node) + await add_blocks_in_batches(default_400_blocks, full_node) # Farm few more with reward for _ in range(num_blocks - 1): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(phs[0])) @@ -426,8 +425,7 @@ async def test_wallet_reorg_sync( num_blocks = 30 blocks_reorg = bt.get_consecutive_blocks(num_blocks, block_list_input=default_400_blocks[:-5]) - for block in blocks_reorg[-30:]: - await full_node.add_block(block) + await add_blocks_in_batches(blocks_reorg[-30:], full_node, blocks_reorg[-30].prev_header_hash) for wallet_node, wallet_server in wallets: wallet = wallet_node.wallet_state_manager.main_wallet From 700bd4c54e2ea742ef3ab3101783d85b98070b33 Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Wed, 16 Oct 2024 11:15:59 +0300 Subject: [PATCH 07/10] fix condition in add_block --- chia/consensus/blockchain.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 82a609edf46d..0d335f26b3d1 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -340,12 +340,12 @@ async def add_block( if extending_main_chain: fork_info.reset(block.height - 1, block.prev_header_hash) - block_rec = await self.get_block_record_from_db(header_hash) - if block_rec is not None: + block_rec = await self.get_block_record_from_db(header_hash) + if block_rec is not None: + if extending_main_chain: await self.advance_fork_info(block, fork_info) fork_info.include_spends(pre_validation_result.conds, block, header_hash) - self.add_block_record(block_rec) - return AddBlockResult.ALREADY_HAVE_BLOCK, None, None + return AddBlockResult.ALREADY_HAVE_BLOCK, None, None if fork_info.peak_hash != block.prev_header_hash: await self.advance_fork_info(block, fork_info) From 85eb8adf55eb4c0d89a4bcae489fd269ecfcd4cb Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Wed, 16 Oct 2024 16:28:24 +0300 Subject: [PATCH 08/10] add known block to cache --- chia/consensus/blockchain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 0d335f26b3d1..4150eeadc016 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -345,6 +345,7 @@ async def add_block( if extending_main_chain: await self.advance_fork_info(block, fork_info) fork_info.include_spends(pre_validation_result.conds, block, header_hash) + self.add_block_record(block_rec) return AddBlockResult.ALREADY_HAVE_BLOCK, None, None if fork_info.peak_hash != block.prev_header_hash: From 83520609e2b816c65c980d28d3c49c2f83c47285 Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Wed, 16 Oct 2024 16:30:47 +0300 Subject: [PATCH 09/10] restore comment --- chia/consensus/blockchain.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 4150eeadc016..5bd738e0a0b5 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -342,6 +342,9 @@ async def add_block( block_rec = await self.get_block_record_from_db(header_hash) if block_rec is not None: + # We have already validated the block, but if it's not part of the + # main chain, we still need to re-run it to update the additions and + # removals in fork_info. if extending_main_chain: await self.advance_fork_info(block, fork_info) fork_info.include_spends(pre_validation_result.conds, block, header_hash) From 58ac803f633a34e7e3b630be8a1da4dcf195fa74 Mon Sep 17 00:00:00 2001 From: almogdepaz Date: Wed, 16 Oct 2024 16:36:02 +0300 Subject: [PATCH 10/10] remove init file from log dir --- chia/logs/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 chia/logs/__init__.py diff --git a/chia/logs/__init__.py b/chia/logs/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000