|
| 1 | +import pytest |
| 2 | + |
| 3 | +from eth import constants |
| 4 | +from eth.chains.base import MiningChain |
| 5 | +from eth.chains.tester import MAINNET_VMS |
| 6 | +from eth.db.backends.memory import MemoryDB |
| 7 | + |
| 8 | + |
| 9 | +VM_CLASSES = tuple( |
| 10 | + VMClass |
| 11 | + for _, VMClass |
| 12 | + in MAINNET_VMS.items() |
| 13 | +) |
| 14 | + |
| 15 | + |
| 16 | +@pytest.fixture(params=VM_CLASSES) |
| 17 | +def chain(request, genesis_state): |
| 18 | + VMClass = request.param.configure(validate_seal=lambda block: None) |
| 19 | + |
| 20 | + class ChainForTest(MiningChain): |
| 21 | + vm_configuration = ((0, VMClass),) |
| 22 | + network_id = 1337 |
| 23 | + |
| 24 | + genesis_params = { |
| 25 | + 'block_number': constants.GENESIS_BLOCK_NUMBER, |
| 26 | + 'difficulty': constants.GENESIS_DIFFICULTY, |
| 27 | + 'gas_limit': constants.GENESIS_GAS_LIMIT, |
| 28 | + } |
| 29 | + chain = ChainForTest.from_genesis(MemoryDB(), genesis_params, genesis_state) |
| 30 | + return chain |
| 31 | + |
| 32 | + |
| 33 | +ZERO_ADDRESS = b'\x00' * 20 |
| 34 | + |
| 35 | + |
| 36 | +def test_import_block_with_reorg(chain, funded_address_private_key): |
| 37 | + # mine 3 "common blocks" |
| 38 | + block_0 = chain.get_canonical_block_by_number(0) |
| 39 | + assert block_0.number == 0 |
| 40 | + block_1 = chain.mine_block() |
| 41 | + assert block_1.number == 1 |
| 42 | + block_2 = chain.mine_block() |
| 43 | + assert block_2.number == 2 |
| 44 | + block_3 = chain.mine_block() |
| 45 | + assert block_3.number == 3 |
| 46 | + |
| 47 | + # make a duplicate chain with no shared state |
| 48 | + fork_db = MemoryDB(chain.chaindb.db.kv_store.copy()) |
| 49 | + fork_chain = type(chain)(fork_db, chain.header) |
| 50 | + |
| 51 | + # sanity check to verify that the two chains are the same. |
| 52 | + assert chain.header == fork_chain.header |
| 53 | + |
| 54 | + assert chain.header.block_number == 4 |
| 55 | + assert fork_chain.header.block_number == 4 |
| 56 | + |
| 57 | + assert fork_chain.get_canonical_head() == chain.get_canonical_head() |
| 58 | + |
| 59 | + assert fork_chain.get_canonical_block_by_number(0) == block_0 |
| 60 | + assert fork_chain.get_canonical_block_by_number(1) == block_1 |
| 61 | + assert fork_chain.get_canonical_block_by_number(2) == block_2 |
| 62 | + assert fork_chain.get_canonical_block_by_number(3) == block_3 |
| 63 | + |
| 64 | + # now cause the fork chain to diverge from the main chain |
| 65 | + tx = fork_chain.create_unsigned_transaction( |
| 66 | + nonce=0, |
| 67 | + gas_price=1, |
| 68 | + gas=21000, |
| 69 | + to=ZERO_ADDRESS, |
| 70 | + value=0, |
| 71 | + data=b'', |
| 72 | + ) |
| 73 | + fork_chain.apply_transaction(tx.as_signed_transaction(funded_address_private_key)) |
| 74 | + |
| 75 | + # Mine 3 blocks, ensuring that the difficulty of the main chain remains |
| 76 | + # equal or greater than the fork chain |
| 77 | + block_4 = chain.mine_block() |
| 78 | + f_block_4 = fork_chain.mine_block() |
| 79 | + assert f_block_4 != block_4 |
| 80 | + assert block_4.number == 4 |
| 81 | + assert f_block_4.number == 4 |
| 82 | + assert f_block_4.header.difficulty <= block_4.header.difficulty |
| 83 | + |
| 84 | + f_block_5, block_5 = fork_chain.mine_block(), chain.mine_block() |
| 85 | + assert f_block_5 != block_5 |
| 86 | + assert block_5.number == 5 |
| 87 | + assert f_block_5.number == 5 |
| 88 | + assert f_block_5.header.difficulty <= block_5.header.difficulty |
| 89 | + |
| 90 | + f_block_6, block_6 = fork_chain.mine_block(), chain.mine_block() |
| 91 | + assert f_block_6 != block_6 |
| 92 | + assert block_6.number == 6 |
| 93 | + assert f_block_6.number == 6 |
| 94 | + assert f_block_6.header.difficulty <= block_6.header.difficulty |
| 95 | + |
| 96 | + # now mine the 7th block which will outpace the main chain difficulty. |
| 97 | + f_block_7 = fork_chain.mine_block() |
| 98 | + |
| 99 | + pre_fork_chain_head = chain.header |
| 100 | + |
| 101 | + # now we proceed to import the blocks from the fork chain into the main |
| 102 | + # chain. Blocks 4, 5, and 6 should import resulting in no re-organization. |
| 103 | + for block in (f_block_4, f_block_5, f_block_6): |
| 104 | + _, new_canonical_blocks, old_canonical_blocks = chain.import_block(block) |
| 105 | + assert not new_canonical_blocks |
| 106 | + assert not old_canonical_blocks |
| 107 | + assert chain.header == pre_fork_chain_head |
| 108 | + |
| 109 | + # now we import block 7 from the fork chain. This should cause a re-org. |
| 110 | + _, new_canonical_blocks, old_canonical_blocks = chain.import_block(f_block_7) |
| 111 | + assert new_canonical_blocks == (f_block_4, f_block_5, f_block_6, f_block_7) |
| 112 | + assert old_canonical_blocks == (block_4, block_5, block_6) |
| 113 | + |
| 114 | + assert chain.get_canonical_head() == f_block_7.header |
0 commit comments