def test_headerdb_persist_disconnected_headers(headerdb, genesis_header): headerdb.persist_header(genesis_header) headers = mk_header_chain(genesis_header, length=10) score_at_pseudo_genesis = 154618822656 # This is the score that we would reach at the tip if we persist the entire chain. # But we test to reach it by building on top of a trusted score. expected_score_at_tip = 188978561024 pseudo_genesis = headers[7] # Persist the checkpoint header with a trusted score headerdb.persist_checkpoint_header(pseudo_genesis, score_at_pseudo_genesis) assert_headers_eq(headerdb.get_canonical_head(), pseudo_genesis) headers_from_pseudo_genesis = ( headers[8], headers[9], ) headerdb.persist_header_chain(headers_from_pseudo_genesis, pseudo_genesis.parent_hash) head = headerdb.get_canonical_head() assert_headers_eq(head, headers[-1]) assert headerdb.get_score(head.hash) == expected_score_at_tip
def test_headerdb_persist_header_returns_new_canonical_chain(headerdb, genesis_header): gen_result, _ = headerdb.persist_header(genesis_header) assert gen_result == (genesis_header,) chain_a = mk_header_chain(genesis_header, 3) chain_b = mk_header_chain(genesis_header, 2) chain_c = mk_header_chain(genesis_header, 5) for header in chain_a: res, _ = headerdb.persist_header(header) assert res == (header,) for header in chain_b: res, _ = headerdb.persist_header(header) assert res == tuple() for idx, header in enumerate(chain_c, 1): res, _ = headerdb.persist_header(header) if idx <= 3: # prior to passing up `chain_a` each import should not return new # canonical headers. assert res == tuple() elif idx == 4: # at the point where `chain_c` passes `chain_a` we should get the # headers from `chain_c` up through current. assert res == chain_c[:idx] assert_headers_eq(res[-1], header) else: # after `chain_c` has become canonical we should just get each new # header back. assert res == (header,)
def test_different_cases_of_patching_gaps(headerdb, genesis_header, steps): headerdb.persist_header(genesis_header) new_chain_length = 10 chain_a = mk_header_chain(genesis_header, length=new_chain_length) chain_b = mk_header_chain(genesis_header, length=new_chain_length) def _get_chain(id): if chain_id == 'a': return chain_a elif chain_id == 'b': return chain_b else: raise Exception(f"Invalid chain id: {chain_id}") assert headerdb.get_header_chain_gaps() == GENESIS_CHAIN_GAPS for step_index, step in enumerate( steps): # noqa: B007 # step_index only present for debugging step_action, step_data = step if step_action is StepAction.PERSIST_CHECKPOINT: pseudo_genesis = chain_a[step_data - 1] pseudo_genesis_score = get_score(genesis_header, chain_a[0:step_data]) headerdb.persist_checkpoint_header(pseudo_genesis, pseudo_genesis_score) elif step_action is StepAction.PERSIST_HEADERS: chain_id, selector_fn = step_data headers = selector_fn(_get_chain(chain_id)) headerdb.persist_header_chain(headers) elif step_action is StepAction.VERIFY_GAPS: gaps = headerdb.get_header_chain_gaps() assert gaps == step_data all_gap_numbers = _all_gap_numbers( gaps, highest_block_number=new_chain_length + 1) for missing_block_number in all_gap_numbers: with pytest.raises(HeaderNotFound): headerdb.get_canonical_block_header_by_number( missing_block_number) elif step_action is StepAction.VERIFY_PERSIST_RAISES: chain_id, error, selector_fn = step_data headers = selector_fn(_get_chain(chain_id)) with pytest.raises(error): headerdb.persist_header_chain(headers) elif step_action is StepAction.VERIFY_CANONICAL_HEAD: # save actual and expected for easy reading on a failed test actual_canonical = headerdb.get_canonical_head() expected_canonical = chain_a[step_data - 1] assert_headers_eq(actual_canonical, expected_canonical) elif step_action is StepAction.VERIFY_CANONICAL_HEADERS: chain_id, selector_fn = step_data for header in selector_fn(_get_chain(chain_id)): assert headerdb.get_canonical_block_header_by_number( header.block_number) == header else: raise Exception("Unknown step action") _validate_gap_invariants(headerdb.get_header_chain_gaps()) _validate_consecutive_canonical_links(headerdb, new_chain_length + 1)
def test_headerdb_get_canonical_head_with_header_chain(headerdb, genesis_header): headerdb.persist_header(genesis_header) headers = mk_header_chain(genesis_header, length=10) headerdb.persist_header_chain(headers) head = headerdb.get_canonical_head() assert_headers_eq(head, headers[-1])
def test_headerdb_get_canonical_head_with_header_chain(headerdb, genesis_header): headerdb.persist_header(genesis_header) headers = mk_header_chain(genesis_header, length=10) headerdb.persist_header_chain(headers) head = headerdb.get_canonical_head() assert headerdb.get_score(head.hash) == 188978561024 assert_headers_eq(head, headers[-1])
def test_header_chain_initialization_header_already_persisted( base_db, genesis_header): headerdb = HeaderDB(base_db) headerdb.persist_header(genesis_header) # sanity check that the header is persisted assert_headers_eq(headerdb.get_canonical_head(), genesis_header) header_chain = HeaderChain.from_genesis_header(base_db, genesis_header) head = header_chain.get_canonical_head() assert_headers_eq(head, genesis_header)
def test_headerdb_canonical_header_retrieval_by_number(headerdb, genesis_header): headerdb.persist_header(genesis_header) headers = mk_header_chain(genesis_header, length=10) headerdb.persist_header_chain(headers) # can we get the genesis header by hash actual = headerdb.get_canonical_block_header_by_number(genesis_header.block_number) assert_headers_eq(actual, genesis_header) for header in headers: actual = headerdb.get_canonical_block_header_by_number(header.block_number) assert_headers_eq(actual, header)
def test_headerdb_get_canonical_head_with_header_chain_iterator( headerdb, genesis_header, chain_length): headerdb.persist_header(genesis_header) headers = mk_header_chain(genesis_header, length=chain_length) headerdb.persist_header_chain(header for header in headers) head = headerdb.get_canonical_head() if chain_length == 0: assert_headers_eq(head, genesis_header) else: assert_headers_eq(head, headers[-1])
def test_headerdb_persist_disconnected_headers(headerdb, genesis_header): headerdb.persist_header(genesis_header) headers = mk_header_chain(genesis_header, length=10) # This is the score that we would reach at the tip if we persist the entire chain. # But we test to reach it by building on top of a trusted score. expected_score_at_tip = get_score(genesis_header, headers) pseudo_genesis = headers[7] pseudo_genesis_score = get_score(genesis_header, headers[0:8]) assert headerdb.get_header_chain_gaps() == GENESIS_CHAIN_GAPS # Persist the checkpoint header with a trusted score headerdb.persist_checkpoint_header(pseudo_genesis, pseudo_genesis_score) assert headerdb.get_header_chain_gaps() == (((1, 7),), 9) assert_headers_eq(headerdb.get_canonical_head(), pseudo_genesis) headers_from_pseudo_genesis = (headers[8], headers[9],) headerdb.persist_header_chain(headers_from_pseudo_genesis, pseudo_genesis.parent_hash) assert headerdb.get_header_chain_gaps() == (((1, 7),), 11) head = headerdb.get_canonical_head() assert_headers_eq(head, headers[-1]) assert headerdb.get_score(head.hash) == expected_score_at_tip header_8 = headerdb.get_block_header_by_hash(headers[8].hash) assert_headers_eq(header_8, headers[8]) with pytest.raises(HeaderNotFound): headerdb.get_block_header_by_hash(headers[2].hash)
def assert_is_canonical_chain(headerdb, headers): if not headers: return # verify that the HEAD is correctly set. head = headerdb.get_canonical_head() assert_headers_eq(head, headers[-1]) # verify that each header is set as the canonical block. for header in headers: canonical_hash = headerdb.get_canonical_block_hash(header.block_number) assert canonical_hash == header.hash # verify difficulties are correctly set. base_header = headerdb.get_block_header_by_hash(headers[0].parent_hash) difficulties = tuple(h.difficulty for h in headers) scores = tuple(accumulate(operator.add, difficulties, base_header.difficulty)) for header, expected_score in zip(headers, scores[1:]): actual_score = headerdb.get_score(header.hash) assert actual_score == expected_score
def test_chaindb_get_block_header_by_hash(chaindb, block, header): block = block.copy(header=set_empty_root(chaindb, block.header)) header = set_empty_root(chaindb, header) chaindb.persist_block(block) block_header = chaindb.get_block_header_by_hash(block.hash) assert_headers_eq(block_header, header)
def test_header_chain_initialization_from_genesis_header( base_db, genesis_header): header_chain = HeaderChain.from_genesis_header(base_db, genesis_header) head = header_chain.get_canonical_head() assert_headers_eq(head, genesis_header)
def test_blockchain_fixtures(fixture_data, fixture): try: chain = new_chain_from_fixture(fixture) except ValueError as e: raise AssertionError(f"could not load chain for {fixture_data}") from e genesis_fields = genesis_fields_from_fixture(fixture) genesis_block = chain.get_canonical_block_by_number(0) genesis_header = genesis_block.header # Validate the genesis header RLP against the generated header if 'genesisRLP' in fixture: # Super hacky, but better than nothing: extract the header, then re-decode it fixture_decoded_block = rlp.decode(fixture['genesisRLP']) fixture_encoded_header = rlp.encode(fixture_decoded_block[0]) fixture_header = rlp.decode(fixture_encoded_header, sedes=HeaderSedes) # Error message with pretty output if header doesn't match assert_headers_eq(fixture_header, genesis_header) # Last gut check that transactions & receipts are valid, too assert rlp.encode(genesis_block) == fixture['genesisRLP'] assert_imported_genesis_header_unchanged(genesis_fields, genesis_header) # 1 - mine the genesis block # 2 - loop over blocks: # - apply transactions # - mine block # 3 - diff resulting state with expected state # 4 - check that all previous blocks were valid for block_fixture in fixture['blocks']: should_be_good_block = 'expectException' not in block_fixture if 'rlp_error' in block_fixture: assert not should_be_good_block continue if should_be_good_block: (original_block, executed_block, block_rlp) = apply_fixture_block_to_chain( block_fixture, chain, perform_validation=False # we manually validate below ) assert_mined_block_unchanged(original_block, executed_block) chain.validate_block(original_block) else: try: apply_fixture_block_to_chain(block_fixture, chain) except EXPECTED_BAD_BLOCK_EXCEPTIONS: # failure is expected on this bad block pass else: raise AssertionError( "Block should have caused a validation error") latest_block_hash = chain.get_canonical_block_by_number( chain.get_block().number - 1).hash if latest_block_hash != fixture['lastblockhash']: verify_state(fixture['postState'], chain.get_vm().state)