Skip to content

Commit

Permalink
add test for a 1-block reorg in short_sync_backtrack()
Browse files Browse the repository at this point in the history
  • Loading branch information
arvidn committed Dec 3, 2024
1 parent f29edc1 commit a275a82
Showing 1 changed file with 138 additions and 11 deletions.
149 changes: 138 additions & 11 deletions chia/_tests/core/full_node/test_full_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
from chia.simulator.keyring import TempKeyring
from chia.simulator.setup_services import setup_full_node
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
from chia.simulator.wallet_tools import WalletTool
from chia.types.blockchain_format.classgroup import ClassgroupElement
from chia.types.blockchain_format.foliage import Foliage, FoliageTransactionBlock, TransactionsInfo
from chia.types.blockchain_format.program import Program
Expand Down Expand Up @@ -2299,9 +2300,18 @@ async def validate_coin_set(coin_store: CoinStore, blocks: list[FullBlock]) -> N
prev_hash = block.header_hash
rewards = block.get_included_reward_coins()
records = {rec.coin.name(): rec for rec in await coin_store.get_coins_added_at_height(block.height)}

# validate reward coins
for reward in rewards:
rec = records.pop(reward.name())
assert rec is not None
assert rec.confirmed_block_index == block.height
assert rec.coin == reward
assert rec.coinbase

if block.transactions_generator is None:
if len(records) > 0: # pragma: no cover
print(f"height: {block.height} rewards: {rewards} TX: No")
print(f"height: {block.height} unexpected coins in the DB: {records} TX: No")
print_coin_records(records)
assert records == {}
continue
Expand All @@ -2310,16 +2320,9 @@ async def validate_coin_set(coin_store: CoinStore, blocks: list[FullBlock]) -> N
# TODO: Support block references
assert False

# validate reward coins
for reward in rewards:
rec = records.pop(reward.name())
assert rec is not None
assert rec.confirmed_block_index == block.height
assert rec.coin == reward
assert rec.coinbase

flags = get_flags_for_height_and_constants(block.height, test_constants)
additions, removals = additions_and_removals(bytes(block.transactions_generator), [], flags, test_constants)

for add, hint in additions:
rec = records.pop(add.name())
assert rec is not None
Expand All @@ -2328,7 +2331,7 @@ async def validate_coin_set(coin_store: CoinStore, blocks: list[FullBlock]) -> N
assert not rec.coinbase

if len(records) > 0: # pragma: no cover
print(f"height: {block.height} rewards: {rewards} TX: Yes")
print(f"height: {block.height} unexpected coins in the DB: {records} TX: Yes")
print_coin_records(records)
assert records == {}

Expand All @@ -2340,7 +2343,7 @@ async def validate_coin_set(coin_store: CoinStore, blocks: list[FullBlock]) -> N
assert rec.coin == rem

if len(records) > 0: # pragma: no cover
print(f"height: {block.height} rewards: {rewards} TX: Yes")
print(f"height: {block.height} unexpected removals: {records} TX: Yes")
print_coin_records(records)
assert records == {}

Expand Down Expand Up @@ -2531,6 +2534,130 @@ def check_nodes_in_sync2():
await validate_coin_set(full_node_3.full_node._coin_store, blocks)


@pytest.mark.anyio
async def test_shallow_reorg_nodes(
three_nodes,
self_hostname: str,
bt: BlockTools,
):
full_node_1, full_node_2, _ = three_nodes

# node 1 has chan A, then we replace the top block and ensure
# node 2 follows along correctly

await connect_and_get_peer(full_node_1.full_node.server, full_node_2.full_node.server, self_hostname)

wallet_a = WalletTool(bt.constants)
WALLET_A_PUZZLE_HASHES = [wallet_a.get_new_puzzlehash() for _ in range(2)]
coinbase_puzzlehash = WALLET_A_PUZZLE_HASHES[0]
receiver_puzzlehash = WALLET_A_PUZZLE_HASHES[1]

chain = bt.get_consecutive_blocks(
10,
farmer_reward_puzzle_hash=coinbase_puzzlehash,
pool_reward_puzzle_hash=receiver_puzzlehash,
guarantee_transaction_block=True,
)
await add_blocks_in_batches(chain, full_node_1.full_node)

all_coins = []
for spend_block in chain:
for coin in spend_block.get_included_reward_coins():
if coin.puzzle_hash == coinbase_puzzlehash:
all_coins.append(coin)

def check_nodes_in_sync():
p1 = full_node_2.full_node.blockchain.get_peak()
p2 = full_node_1.full_node.blockchain.get_peak()
return p1 == p2

await time_out_assert(10, check_nodes_in_sync)
await validate_coin_set(full_node_1.full_node.blockchain.coin_store, chain)
await validate_coin_set(full_node_2.full_node.blockchain.coin_store, chain)

# we spend a coin in the next block
spend_bundle = wallet_a.generate_signed_transaction(uint64(1_000), receiver_puzzlehash, all_coins.pop())

# make a non transaction block with fewer iterations than a, which should
# replace it
chain_b = bt.get_consecutive_blocks(
1,
chain,
guarantee_transaction_block=False,
seed=b"{seed}",
)

chain_a = bt.get_consecutive_blocks(
1,
chain,
farmer_reward_puzzle_hash=coinbase_puzzlehash,
pool_reward_puzzle_hash=receiver_puzzlehash,
transaction_data=spend_bundle,
guarantee_transaction_block=True,
min_signage_point=chain_b[-1].reward_chain_block.signage_point_index,
)

print(f"chain A: {chain_a[-1].header_hash.hex()}")
print(f"chain B: {chain_b[-1].header_hash.hex()}")

assert chain_b[-1].total_iters < chain_a[-1].total_iters

await add_blocks_in_batches(chain_a[-1:], full_node_1.full_node, chain[-1].header_hash)

await time_out_assert(10, check_nodes_in_sync)
await validate_coin_set(full_node_1.full_node.blockchain.coin_store, chain_a)
await validate_coin_set(full_node_2.full_node.blockchain.coin_store, chain_a)

for coin in chain_b[-1].get_included_reward_coins():
if coin.puzzle_hash == coinbase_puzzlehash:
all_coins.append(coin)

await add_blocks_in_batches(chain_b[-1:], full_node_1.full_node, chain[-1].header_hash)

# make sure node 1 reorged onto chain B
assert full_node_1.full_node.blockchain.get_peak().header_hash == chain_b[-1].header_hash

await time_out_assert(10, check_nodes_in_sync)
await validate_coin_set(full_node_1.full_node.blockchain.coin_store, chain_b)
await validate_coin_set(full_node_2.full_node.blockchain.coin_store, chain_b)

# now continue building the chain on top of B
# since spend_bundle was supposed to have been reorged-out, we should be
# able to include it in another block, howerver, since we replaced a TX
# block with a non-TX block, it won't be available immediately at height 11

# add a TX block, this will make spend_bundle valid in the next block
chain = bt.get_consecutive_blocks(
1,
chain,
farmer_reward_puzzle_hash=coinbase_puzzlehash,
pool_reward_puzzle_hash=receiver_puzzlehash,
guarantee_transaction_block=True,
)
for coin in chain[-1].get_included_reward_coins():
if coin.puzzle_hash == coinbase_puzzlehash:
all_coins.append(coin)

for i in range(3):
chain = bt.get_consecutive_blocks(
1,
chain,
farmer_reward_puzzle_hash=coinbase_puzzlehash,
pool_reward_puzzle_hash=receiver_puzzlehash,
transaction_data=spend_bundle,
guarantee_transaction_block=True,
)
for coin in chain[-1].get_included_reward_coins():
if coin.puzzle_hash == coinbase_puzzlehash:
all_coins.append(coin)
spend_bundle = wallet_a.generate_signed_transaction(uint64(1_000), receiver_puzzlehash, all_coins.pop())

await add_blocks_in_batches(chain[-4:], full_node_1.full_node, chain[-5].header_hash)
await time_out_assert(10, check_nodes_in_sync)
await validate_coin_set(full_node_1.full_node.blockchain.coin_store, chain)
await validate_coin_set(full_node_2.full_node.blockchain.coin_store, chain)


@pytest.mark.anyio
@pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.HARD_FORK_2_0], reason="save time")
async def test_eviction_from_bls_cache(one_node_one_block: tuple[FullNodeSimulator, ChiaServer, BlockTools]) -> None:
Expand Down

0 comments on commit a275a82

Please sign in to comment.