コード例 #1
0
def fill_block(chain, from_, key, gas, data):
    if not isinstance(chain, MiningChain):
        pytest.skip(
            "Cannot fill block automatically unless using a MiningChain")
        return

    recipient = decode_hex('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c')
    amount = 100

    vm = chain.get_vm()
    assert vm.get_header().gas_used == 0

    while True:
        tx = new_transaction(chain.get_vm(),
                             from_,
                             recipient,
                             amount,
                             key,
                             gas=gas,
                             data=data)
        try:
            chain.apply_transaction(tx)
        except ValidationError as exc:
            if str(exc).startswith("Transaction exceeds gas limit"):
                break
            else:
                raise exc
        else:
            new_header = chain.get_vm().get_block().header
            assert new_header.gas_used > 0
            assert new_header.gas_used <= new_header.gas_limit

    assert chain.get_vm().get_block().header.gas_used > 0
コード例 #2
0
ファイル: test_london.py プロジェクト: marcgarreau/py-evm
def test_base_fee_evolution(london_plus_miner, funded_address,
                            funded_address_private_key, num_txns,
                            expected_base_fee):
    chain = london_plus_miner
    assert chain.header.gas_limit == FOUR_TXN_GAS_LIMIT

    vm = chain.get_vm()
    txns = [
        new_transaction(
            vm,
            funded_address,
            b'\x00' * 20,
            private_key=funded_address_private_key,
            gas=21000,
            nonce=nonce,
        ) for nonce in range(num_txns)
    ]
    block_import, _, _ = chain.mine_all(txns, gas_limit=FOUR_TXN_GAS_LIMIT)
    mined_header = block_import.imported_block.header
    assert mined_header.gas_limit == FOUR_TXN_GAS_LIMIT
    assert mined_header.gas_used == 21000 * num_txns
    assert mined_header.base_fee_per_gas == 10**9  # Initialize at 1 gwei

    block_import, _, _ = chain.mine_all([], gas_limit=FOUR_TXN_GAS_LIMIT)
    mined_header = block_import.imported_block.header
    assert mined_header.gas_limit == FOUR_TXN_GAS_LIMIT
    assert mined_header.gas_used == 0
    # Check that the base fee evolved correctly, depending on how much gas was used in the parent
    assert mined_header.base_fee_per_gas == expected_base_fee
コード例 #3
0
    def deploy_dos_contract(self, chain: MiningChain) -> None:
        # Instantiate the contract
        dos_contract = self.w3.eth.contract(
            abi=self.contract_interface['abi'],
            bytecode=self.contract_interface['bin'])

        # Build transaction to deploy the contract
        w3_tx1 = dos_contract.constructor().buildTransaction(W3_TX_DEFAULTS)

        tx = new_transaction(
            vm=chain.get_vm(),
            private_key=FUNDED_ADDRESS_PRIVATE_KEY,
            from_=FUNDED_ADDRESS,
            to=CREATE_CONTRACT_ADDRESS,
            amount=0,
            gas=FIRST_TX_GAS_LIMIT,
            data=decode_hex(w3_tx1['data']),
        )

        logging.debug(f'Applying Transaction {tx}')

        block, receipt, computation = chain.apply_transaction(tx)
        self.deployed_contract_address = computation.msg.storage_address

        computation.raise_if_error()

        # Interact with the deployed contract by calling the totalSupply() API ?????
        self.dos_contract = self.w3.eth.contract(
            address=Web3.toChecksumAddress(
                encode_hex(self.deployed_contract_address)),
            abi=self.contract_interface['abi'],
        )
コード例 #4
0
ファイル: test_vm.py プロジェクト: marcgarreau/py-evm
def test_import_block(chain, funded_address, funded_address_private_key):
    recipient = decode_hex('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c')
    amount = 100
    from_ = funded_address
    tx = new_transaction(chain.get_vm(), from_, recipient, amount,
                         funded_address_private_key)
    if isinstance(chain, MiningChain):
        # Can use the mining chain functionality to build transactions in-flight
        pending_header = chain.header
        new_block, _, computation = chain.apply_transaction(tx)
    else:
        # Have to manually build the block for the import_block test
        new_block, _, computations = chain.build_block_with_transactions([tx])
        computation = computations[0]

        # Generate the pending header to import the new block on
        pending_header = chain.create_header_from_parent(
            chain.get_canonical_head())

    assert not computation.is_error

    # import the built block
    validation_vm = chain.get_vm(pending_header)
    block, _ = validation_vm.import_block(new_block)
    assert block.transactions == (tx, )
コード例 #5
0
ファイル: erc20_interact.py プロジェクト: youngqqcn/py-evm
    def _erc_transfer_from(self,
                           addr1: str,
                           addr2: str,
                           chain: MiningChain,
                           nonce: int = None) -> None:

        w3_tx = self.simple_token.functions.transferFrom(
            addr1, addr2, TRANSER_FROM_AMOUNT).buildTransaction(W3_TX_DEFAULTS)

        tx = new_transaction(
            vm=chain.get_vm(),
            private_key=SECOND_ADDRESS_PRIVATE_KEY,
            from_=SECOND_ADDRESS,
            to=self.deployed_contract_address,
            amount=0,
            gas=SECOND_TX_GAS_LIMIT,
            data=decode_hex(w3_tx['data']),
            nonce=nonce,
        )

        def callback(receipt, computation) -> None:
            computation.raise_if_error()
            assert computation.output == b'\0' * 31 + b'\x01', computation.output

        return tx, callback
コード例 #6
0
ファイル: erc20_interact.py プロジェクト: youngqqcn/py-evm
    def _deploy_simple_token(self,
                             chain: MiningChain,
                             nonce: int = None) -> None:
        # Instantiate the contract
        SimpleToken = self.w3.eth.contract(
            abi=self.contract_interface['abi'],
            bytecode=self.contract_interface['bin'])
        # Build transaction to deploy the contract
        w3_tx = SimpleToken.constructor().buildTransaction(W3_TX_DEFAULTS)
        tx = new_transaction(
            vm=chain.get_vm(),
            private_key=FUNDED_ADDRESS_PRIVATE_KEY,
            from_=FUNDED_ADDRESS,
            to=CREATE_CONTRACT_ADDRESS,
            amount=0,
            gas=FIRST_TX_GAS_LIMIT,
            data=decode_hex(w3_tx['data']),
            nonce=nonce,
        )

        def callback(receipt, computation) -> None:
            computation.raise_if_error()

            # Keep track of deployed contract address
            self.deployed_contract_address = computation.msg.storage_address

            # Keep track of simple_token object
            self.simple_token = self.w3.eth.contract(
                address=Web3.toChecksumAddress(
                    encode_hex(self.deployed_contract_address)),
                abi=self.contract_interface['abi'],
            )

        return tx, callback
コード例 #7
0
ファイル: test_clique_consensus.py プロジェクト: veox/py-evm
def test_import_block(paragon_chain, clique):

    vm = paragon_chain.get_vm()
    tx = new_transaction(vm, ALICE, BOB, 10, ALICE_PK)
    assert vm.state.get_balance(ALICE) == ALICE_INITIAL_BALANCE
    assert vm.state.get_balance(BOB) == BOB_INITIAL_BALANCE
    assert vm.state.get_balance(vm.get_block().header.coinbase) == 0

    signed_header = sign_block_header(
        vm.get_block().header.copy(
            extra_data=VANITY_LENGTH * b'0' + SIGNATURE_LENGTH * b'0',
            state_root=
            b'\x99\xaa\xf5CF^\x95_\xce~\xe4)\x00\xb1zr\x1dr\xd6\x00N^\xa6\xdc\xc41\x90~\xb7te\x00',  # noqa: E501
            transaction_root=
            b'\xd1\t\xc4\x150\x9f\xb0\xb4H{\xfd$?Q\x16\x90\xac\xb2L[f\x98\xdd\xc6*\xf7\n\x84f\xafg\xb3',  # noqa: E501
            nonce=NONCE_DROP,
            gas_used=21000,
            difficulty=2,
            receipt_root=
            b'\x05k#\xfb\xbaH\x06\x96\xb6_\xe5\xa5\x9b\x8f!H\xa1)\x91\x03\xc4\xf5}\xf89#:\xf2\xcfL\xa2\xd2'  # noqa: E501
        ),
        ALICE_PK)

    block = vm.get_block_class()(header=signed_header, transactions=[tx])

    paragon_chain.import_block(block)

    # Alice new balance is old balance - 10 + 21000 tx fee (she's the signer)
    assert paragon_chain.get_vm().state.get_balance(ALICE) == 20999990
    assert paragon_chain.get_vm().state.get_balance(BOB) == 21000010
    # Nothing goes to the coinbase in Clique
    assert paragon_chain.get_vm().state.get_balance(
        vm.get_block().header.coinbase) == 0
コード例 #8
0
def tx(chain, funded_address, funded_address_private_key):
    recipient = decode_hex('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c')
    amount = 100
    vm = chain.get_vm()
    from_ = funded_address
    return new_transaction(vm, from_, recipient, amount,
                           funded_address_private_key)
コード例 #9
0
ファイル: test_london.py プロジェクト: marcgarreau/py-evm
def test_state_does_not_revert_on_reserved_0xEF_byte_for_create_transaction_pre_london(
        pre_london_miner, funded_address, funded_address_private_key, data):
    chain = pre_london_miner
    vm = chain.get_vm()
    initial_balance = vm.state.get_balance(funded_address)

    # test the parametrized negative cases
    create_contract_txn_0xef_byte = new_transaction(
        vm=vm,
        from_=funded_address,
        to=Address(b''),
        amount=0,
        private_key=funded_address_private_key,
        gas=60000,
        nonce=0,
        data=data,
    )

    block_import, _, computations = chain.mine_all(
        [create_contract_txn_0xef_byte], gas_limit=99904)

    computation = computations[0]
    mined_header = block_import.imported_block.header
    txn = block_import.imported_block.transactions[0]
    end_balance = computation.state.get_balance(funded_address)

    assert computation.is_success
    assert computation.state.get_nonce(funded_address) == 1
    # txn consumes gas and fees
    assert end_balance == initial_balance - (txn.gas_price *
                                             mined_header.gas_used)
コード例 #10
0
    def next_transaction(self, chain: MiningChain) -> None:

        if self.config.to_address is None:
            to_address = generate_random_address()
        else:
            to_address = self.config.to_address

        tx = new_transaction(
            vm=chain.get_vm(),
            private_key=FUNDED_ADDRESS_PRIVATE_KEY,
            from_=FUNDED_ADDRESS,
            to=to_address,
            amount=100,
            data=b'',
            nonce=self._next_nonce,
        )
        logging.debug(f'Built Transaction {tx}')

        self._next_nonce = tx.nonce + 1

        def callback(receipt, computation) -> None:
            logging.debug(f'Receipt {receipt}')
            logging.debug(f'Computation {computation}')

        return tx, callback
コード例 #11
0
 def mk_estimation_txn(chain, from_, from_key, data):
     vm = chain.get_vm()
     tx_params = dict(from_=from_,
                      to=ADDR_1010,
                      amount=200,
                      private_key=from_key,
                      gas=chain.header.gas_limit,
                      data=data)
     return new_transaction(vm, **tx_params)
コード例 #12
0
def test_missing_state_root(chain_without_block_validation, funded_address):
    valid_vm = chain_without_block_validation.get_vm()
    tx = new_transaction(valid_vm, from_=funded_address, to=ADDRESS)

    head = chain_without_block_validation.get_canonical_head()
    header_with_bad_state_root = head.copy(state_root=b'X' * 32)
    busted_vm = chain_without_block_validation.get_vm(header_with_bad_state_root)

    # notice that the state root is missing by the raised MissingAccountTrieNode
    with pytest.raises(MissingAccountTrieNode):
        busted_vm.state.apply_transaction(tx)
コード例 #13
0
def tx2(chain, funded_address, funded_address_private_key):
    recipient = b'\x88' * 20
    amount = 100
    vm = chain.get_vm()
    from_ = funded_address
    return new_transaction(vm,
                           from_,
                           recipient,
                           amount,
                           funded_address_private_key,
                           nonce=1)
コード例 #14
0
def test_get_transaction_result(chain, simple_contract_address, signature,
                                gas_price, expected):

    function_selector = function_signature_to_4byte_selector(signature)
    call_txn = new_transaction(
        chain.get_vm(),
        b'\xff' * 20,
        simple_contract_address,
        gas_price=gas_price,
        data=function_selector,
    )
    result_bytes = chain.get_transaction_result(call_txn,
                                                chain.get_canonical_head())
    assert result_bytes == expected
コード例 #15
0
def test_get_transaction_result_revert(vm, chain_from_vm,
                                       simple_contract_address, signature,
                                       expected):

    chain = chain_from_vm(vm)
    function_selector = function_signature_to_4byte_selector(signature)
    call_txn = new_transaction(
        chain.get_vm(),
        b'\xff' * 20,
        simple_contract_address,
        data=function_selector,
    )
    with pytest.raises(expected):
        chain.get_transaction_result(call_txn, chain.get_canonical_head())
コード例 #16
0
    def sstore_uint64_revert(self, chain: MiningChain) -> None:
        w3_tx4 = self.dos_contract.functions.storageEntropyRevert(
        ).buildTransaction(W3_TX_DEFAULTS)

        tx4 = new_transaction(
            vm=chain.get_vm(),
            private_key=FUNDED_ADDRESS_PRIVATE_KEY,
            from_=FUNDED_ADDRESS,
            to=self.deployed_contract_address,
            amount=0,
            gas=FORTH_TX_GAS_LIMIT,
            data=decode_hex(w3_tx4['data']),
        )

        block, receipt, computation = chain.apply_transaction(tx4)
コード例 #17
0
    def create_empty_contract_revert(self, chain: MiningChain) -> None:
        w3_tx5 = self.dos_contract.functions.createEmptyContractRevert(
        ).buildTransaction(W3_TX_DEFAULTS)

        tx5 = new_transaction(
            vm=chain.get_vm(),
            private_key=FUNDED_ADDRESS_PRIVATE_KEY,
            from_=FUNDED_ADDRESS,
            to=self.deployed_contract_address,
            amount=0,
            gas=FIFTH_TX_GAS_LIMIT,
            data=decode_hex(w3_tx5['data']),
        )

        block, receipt, computation = chain.apply_transaction(tx5)
コード例 #18
0
def test_estimate_gas(
    chain_without_block_validation_from_vm,
    data,
    gas_estimator,
    to,
    on_pending,
    vm_cls,
    expected,
    funded_address,
    funded_address_private_key,
    should_sign_tx,
):
    chain = chain_without_block_validation_from_vm(vm_cls)
    if gas_estimator:
        chain.gas_estimator = gas_estimator
    vm = chain.get_vm()
    amount = 100
    from_ = funded_address

    tx_params = dict(vm=vm, from_=from_, to=to, amount=amount, data=data)

    # either make a signed or unsigned transaction
    if should_sign_tx:
        tx = new_transaction(private_key=funded_address_private_key,
                             **tx_params)
    else:
        tx = new_transaction(**tx_params)

    if on_pending:
        # estimate on *pending* block
        pending_header = chain.create_header_from_parent(
            chain.get_canonical_head())
        assert chain.estimate_gas(tx, pending_header) == expected
    else:
        # estimates on top of *latest* block
        assert chain.estimate_gas(tx) == expected
コード例 #19
0
def mine_blocks_with_receipts(chain, num_blocks, num_tx_per_block,
                              funded_address, funded_address_private_key):

    for _ in range(num_blocks):
        block_receipts = []
        for _ in range(num_tx_per_block):
            tx = new_transaction(
                chain.get_vm(),
                from_=funded_address,
                to=force_bytes_to_address(b'\x10\x10'),
                private_key=funded_address_private_key,
            )
            new_block, tx_receipt, computation = chain.apply_transaction(tx)
            block_receipts.append(tx_receipt)
            computation.raise_if_error()

        yield chain.mine_block(), block_receipts
コード例 #20
0
def test_chain_builder_mine_block_with_transactions(
        mining_chain, funded_address, funded_address_private_key):
    tx = new_transaction(
        mining_chain.get_vm(),
        from_=funded_address,
        to=ZERO_ADDRESS,
        private_key=funded_address_private_key,
    )

    chain = build(
        mining_chain,
        mine_block(transactions=[tx]),
    )

    block = chain.get_canonical_block_by_number(1)
    assert len(block.transactions) == 1
    assert block.transactions[0] == tx
コード例 #21
0
def test_chaindb_get_receipt_by_index(
        chain,
        funded_address,
        funded_address_private_key):
    NUMBER_BLOCKS_IN_CHAIN = 5
    TRANSACTIONS_IN_BLOCK = 10
    REQUIRED_BLOCK_NUMBER = 2
    REQUIRED_RECEIPT_INDEX = 3

    for block_number in range(NUMBER_BLOCKS_IN_CHAIN):
        for tx_index in range(TRANSACTIONS_IN_BLOCK):
            tx = new_transaction(
                chain.get_vm(),
                from_=funded_address,
                to=force_bytes_to_address(b'\x10\x10'),
                private_key=funded_address_private_key,
            )
            new_block, tx_receipt, computation = chain.apply_transaction(tx)
            computation.raise_if_error()

            if (block_number + 1) == REQUIRED_BLOCK_NUMBER and tx_index == REQUIRED_RECEIPT_INDEX:
                actual_receipt = tx_receipt

        chain.mine_block()

    # Check that the receipt retrieved is indeed the actual one
    chaindb_retrieved_receipt = chain.chaindb.get_receipt_by_index(
        REQUIRED_BLOCK_NUMBER,
        REQUIRED_RECEIPT_INDEX,
    )
    assert chaindb_retrieved_receipt == actual_receipt

    # Raise error if block number is not found
    with pytest.raises(ReceiptNotFound):
        chain.chaindb.get_receipt_by_index(
            NUMBER_BLOCKS_IN_CHAIN + 1,
            REQUIRED_RECEIPT_INDEX,
        )

    # Raise error if receipt index is out of range
    with pytest.raises(ReceiptNotFound):
        chain.chaindb.get_receipt_by_index(
            NUMBER_BLOCKS_IN_CHAIN,
            TRANSACTIONS_IN_BLOCK + 1,
        )
コード例 #22
0
ファイル: test_vm.py プロジェクト: marcgarreau/py-evm
def test_apply_transaction(chain, funded_address, funded_address_private_key,
                           funded_address_initial_balance):
    vm = chain.get_vm()
    recipient = decode_hex('0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c')
    amount = 100
    from_ = funded_address
    tx = new_transaction(vm, from_, recipient, amount,
                         funded_address_private_key)
    receipt, computation = vm.apply_transaction(vm.get_header(), tx)
    new_header = vm.add_receipt_to_header(vm.get_header(), receipt)

    assert not computation.is_error
    tx_gas = tx.gas_price * constants.GAS_TX
    state = vm.state
    assert state.get_balance(from_) == (funded_address_initial_balance -
                                        amount - tx_gas)
    assert state.get_balance(recipient) == amount

    assert new_header.gas_used == constants.GAS_TX
コード例 #23
0
    def _erc_approve(self, addr2: str, chain: MiningChain) -> None:
        w3_tx = self.simple_token.functions.approve(
            addr2, TRANSFER_AMOUNT).buildTransaction(W3_TX_DEFAULTS)

        tx = new_transaction(
            vm=chain.get_vm(),
            private_key=FUNDED_ADDRESS_PRIVATE_KEY,
            from_=FUNDED_ADDRESS,
            to=self.deployed_contract_address,
            amount=0,
            gas=SECOND_TX_GAS_LIMIT,
            data=decode_hex(w3_tx['data']),
        )

        block, receipt, computation = chain.apply_transaction(tx)

        computation.raise_if_error()

        assert to_int(computation.output) == 1
コード例 #24
0
    def apply_transaction(self, chain: MiningChain) -> None:

        if self.config.to_address is None:
            to_address = generate_random_address()
        else:
            to_address = self.config.to_address

        tx = new_transaction(vm=chain.get_vm(),
                             private_key=FUNDED_ADDRESS_PRIVATE_KEY,
                             from_=FUNDED_ADDRESS,
                             to=to_address,
                             amount=100,
                             data=b'')

        logging.debug(f'Applying Transaction {tx}')

        block, receipt, computation = chain.apply_transaction(tx)

        logging.debug(f'Block {block}')
        logging.debug(f'Receipt {receipt}')
        logging.debug(f'Computation {computation}')
コード例 #25
0
def test_block_gap_tracking(chain, funded_address, funded_address_private_key,
                            has_uncle, has_transaction, can_fetch_block):

    # Mine three common blocks
    common_chain = api.build(
        chain,
        api.mine_blocks(3),
    )

    assert common_chain.get_canonical_head().block_number == 3
    assert common_chain.chaindb.get_chain_gaps() == ((), 4)

    tx = new_transaction(
        common_chain.get_vm(),
        from_=funded_address,
        to=ZERO_ADDRESS,
        private_key=funded_address_private_key,
    )
    uncle = api.build(common_chain,
                      api.mine_block()).get_canonical_block_header_by_number(4)
    uncles = [uncle] if has_uncle else []
    transactions = [tx] if has_transaction else []

    # Split and have the main chain mine four blocks, the uncle chain two blocks
    main_chain, uncle_chain = api.build(
        common_chain,
        api.chain_split(
            (
                # We have four different scenarios for our replaced blocks:
                #   1. Replaced by a trivial block without uncles or transactions
                #   2. Replaced by a block with transactions
                #   3. Replaced by a block with uncles
                #   4. 2 and 3 combined
                api.mine_block(uncles=uncles, transactions=transactions),
                api.mine_block(),
                api.mine_block(),
                api.mine_block(),
            ),
            # This will be the uncle chain
            (
                api.mine_block(extra_data=b'fork-it'),
                api.mine_block(),
            ),
        ),
    )

    main_head = main_chain.get_canonical_head()
    assert main_head.block_number == 7
    assert uncle_chain.get_canonical_head().block_number == 5

    assert main_chain.chaindb.get_chain_gaps() == ((), 8)
    assert uncle_chain.chaindb.get_chain_gaps() == ((), 6)

    main_header_6 = main_chain.chaindb.get_canonical_block_header_by_number(6)
    main_header_6_score = main_chain.chaindb.get_score(main_header_6.hash)

    gap_chain = api.copy(common_chain)
    assert gap_chain.get_canonical_head() == common_chain.get_canonical_head()

    gap_chain.chaindb.persist_checkpoint_header(main_header_6,
                                                main_header_6_score)
    # We created a gap in the chain of headers
    assert gap_chain.chaindb.get_header_chain_gaps() == (((4, 5), ), 7)
    # ...but not in the chain of blocks (yet!)
    assert gap_chain.chaindb.get_chain_gaps() == ((), 4)
    block_7 = main_chain.get_canonical_block_by_number(7)
    block_7_receipts = block_7.get_receipts(main_chain.chaindb)
    # Persist block 7 on top of the checkpoint
    gap_chain.chaindb.persist_unexecuted_block(block_7, block_7_receipts)
    assert gap_chain.chaindb.get_header_chain_gaps() == (((4, 5), ), 8)
    # Now we have a gap in the chain of blocks, too
    assert gap_chain.chaindb.get_chain_gaps() == (((4, 6), ), 8)

    # Overwriting header 3 doesn't cause us to re-open a block gap
    gap_chain.chaindb.persist_header_chain(
        [main_chain.chaindb.get_canonical_block_header_by_number(3)])
    assert gap_chain.chaindb.get_chain_gaps() == (((4, 6), ), 8)

    # Now get the uncle block
    uncle_block = uncle_chain.get_canonical_block_by_number(4)
    uncle_block_receipts = uncle_block.get_receipts(uncle_chain.chaindb)

    # Put the uncle block in the gap
    gap_chain.chaindb.persist_unexecuted_block(uncle_block,
                                               uncle_block_receipts)
    assert gap_chain.chaindb.get_header_chain_gaps() == (((5, 5), ), 8)
    assert gap_chain.chaindb.get_chain_gaps() == (((5, 6), ), 8)

    # Trying to save another uncle errors as its header isn't the parent of the checkpoint
    second_uncle = uncle_chain.get_canonical_block_by_number(5)
    second_uncle_receipts = second_uncle.get_receipts(uncle_chain.chaindb)
    with pytest.raises(CheckpointsMustBeCanonical):
        gap_chain.chaindb.persist_unexecuted_block(second_uncle,
                                                   second_uncle_receipts)

    # Now close the gap in the header chain with the actual correct headers
    actual_headers = [
        main_chain.chaindb.get_canonical_block_header_by_number(block_number)
        for block_number in range(4, 7)
    ]
    gap_chain.chaindb.persist_header_chain(actual_headers)
    # No more gaps in the header chain
    assert gap_chain.chaindb.get_header_chain_gaps() == ((), 8)
    # We detected the de-canonicalized uncle and re-opened the block gap
    assert gap_chain.chaindb.get_chain_gaps() == (((4, 6), ), 8)

    if can_fetch_block:
        # We can fetch the block even if the gap tracking reports it as missing if the block is
        # a "trivial" block, meaning one that doesn't have transactions nor uncles and hence
        # can be loaded by just the header alone.
        block_4 = gap_chain.get_canonical_block_by_number(4)
        assert block_4 == main_chain.get_canonical_block_by_number(4)
    else:
        # The uncle block was implicitly de-canonicalized with its header,
        # hence we can not fetch it any longer.
        with pytest.raises(BlockNotFound):
            gap_chain.get_canonical_block_by_number(4)
        # Add the missing block and assert the gap shrinks
        assert gap_chain.chaindb.get_chain_gaps() == (((4, 6), ), 8)
        block_4 = main_chain.get_canonical_block_by_number(4)
        block_4_receipts = block_4.get_receipts(main_chain.chaindb)
        gap_chain.chaindb.persist_unexecuted_block(block_4, block_4_receipts)
        assert gap_chain.chaindb.get_chain_gaps() == (((5, 6), ), 8)