Esempio n. 1
0
def test_enr_db_raises_DuplicateRecord(enr_db):
    private_key = PrivateKeyFactory().to_bytes()

    enr_a = ENRFactory(
        private_key=private_key,
        sequence_number=1,
        custom_kv_pairs={b"custom": b"enr-a"},
    )
    enr_b = ENRFactory(
        private_key=private_key,
        sequence_number=1,
        custom_kv_pairs={b"custom": b"enr-b"},
    )

    assert enr_a.node_id == enr_b.node_id
    assert enr_a.sequence_number == enr_b.sequence_number

    assert enr_a != enr_b

    # set it the first time.
    enr_db.set_enr(enr_a)

    with pytest.raises(DuplicateRecord):
        enr_db.set_enr(enr_b, raise_on_error=True)

    # without the flag it should silently ignore the error
    enr_db.set_enr(enr_b)
Esempio n. 2
0
def test_query_for_ipv4_endpoint(enr_db, constraint):
    # doesn't have either key
    enr_a = ENRFactory.minimal()
    # two have the correct keys
    enr_b = ENRFactory()
    enr_c = ENRFactory()

    # missing port
    enr_d_manager = ENRManagerFactory()
    enr_d_manager.update((IP_V4_ADDRESS_ENR_KEY, IPv4Factory().packed))

    enr_d = enr_d_manager.enr

    # missing ip address
    enr_e_manager = ENRManagerFactory()
    enr_e_manager.update((UDP_PORT_ENR_KEY, 30303))
    enr_e_manager.update((TCP_PORT_ENR_KEY, 30303))

    enr_e = enr_e_manager.enr

    enr_db.set_enr(enr_a)
    enr_db.set_enr(enr_b)
    enr_db.set_enr(enr_c)
    enr_db.set_enr(enr_d)
    enr_db.set_enr(enr_e)

    enr_results = tuple(enr_db.query(constraint))
    assert len(enr_results) == 2

    assert set(enr_results) == {enr_b, enr_c}
Esempio n. 3
0
def test_query_excludes_outdated_matching_records(enr_db):
    private_key_a = PrivateKeyFactory().to_bytes()

    enr_a_0 = ENRFactory(
        sequence_number=0,
        private_key=private_key_a,
        custom_kv_pairs={b"test": b"value-A"},
    )
    enr_a_7 = ENRFactory(sequence_number=7, private_key=private_key_a)

    private_key_b = PrivateKeyFactory().to_bytes()

    enr_b_1 = ENRFactory(
        sequence_number=1,
        private_key=private_key_b,
        custom_kv_pairs={b"test": b"value-A"},
    )

    enr_db.set_enr(enr_a_0)
    enr_db.set_enr(enr_a_7)

    enr_db.set_enr(enr_b_1)

    enr_results = tuple(enr_db.query(KeyExists(b"test")))
    assert len(enr_results) == 1

    enr = enr_results[0]
    assert enr == enr_b_1
Esempio n. 4
0
def test_wait_time_full_queue_and_table(
    topic_table, max_queue_size, target_ad_lifetime
):
    # fill one queue
    topic = TopicFactory()
    reg_time = 0
    oldest_queue_eol = reg_time + target_ad_lifetime
    while not topic_table.is_queue_full(topic):
        topic_table.register(topic, ENRFactory(), reg_time)
        reg_time += 1

    # fill the rest of the table
    oldest_table_eol = reg_time + target_ad_lifetime
    while not topic_table.is_full:
        topic_table.register(TopicFactory(), ENRFactory(), reg_time)
        reg_time += 1

    assert topic_table.get_wait_time(topic, 0) == oldest_queue_eol
    assert topic_table.get_wait_time(TopicFactory(), 0) == oldest_queue_eol

    # refill queue
    oldest_queue_eol = reg_time + target_ad_lifetime
    for _ in range(max_queue_size):
        topic_table.register(topic, ENRFactory(), reg_time)
        reg_time += 1

    assert topic_table.get_wait_time(topic, 0) == oldest_queue_eol
    assert topic_table.get_wait_time(TopicFactory(), 0) == oldest_table_eol
Esempio n. 5
0
def test_registration_single_queue(topic_table, max_queue_size):
    topic = TopicFactory()
    enr = ENRFactory()
    other_enr = ENRFactory()

    topic_table.get_enrs_for_topic(topic) == ()
    topic_table.register(topic, enr, 0)
    assert topic_table.get_enrs_for_topic(topic) == (enr,)
    topic_table.register(topic, other_enr, 0)
    assert topic_table.get_enrs_for_topic(topic) == (other_enr, enr)

    with pytest.raises(ValueError):
        topic_table.register(topic, enr, 0)

    while not topic_table.is_queue_full(topic):
        topic_table.register(topic, ENRFactory(), 0)

    with pytest.raises(ValueError):
        topic_table.register(topic, ENRFactory(), 0)

    enrs_before = topic_table.get_enrs_for_topic(topic)
    new_enr = ENRFactory()
    topic_table.register(topic, new_enr, topic_table.get_wait_time(topic, 0))
    enrs_after = topic_table.get_enrs_for_topic(topic)
    assert enrs_after == (new_enr,) + enrs_before[:-1]
Esempio n. 6
0
def test_record_query_with_multi_key_constraint(conn):
    record_a = Record.from_enr(
        ENRFactory(custom_kv_pairs={b"test-a": b"value-A"}, ))
    record_b = Record.from_enr(
        ENRFactory(custom_kv_pairs={
            b"test-a": b"value-A",
            b"test-b": b"value-B"
        }, ))
    record_c = Record.from_enr(
        ENRFactory(custom_kv_pairs={b"test-b": b"value-B"}, ))
    record_d = Record.from_enr(ENRFactory())
    record_e = Record.from_enr(
        ENRFactory(custom_kv_pairs={
            b"test-a": b"value-A",
            b"test-b": b"value-B"
        }, ))

    insert_record(conn, record_a)
    insert_record(conn, record_b)
    insert_record(conn, record_c)
    insert_record(conn, record_d)
    insert_record(conn, record_e)

    matched_records = tuple(
        query_records(conn, required_keys=(b"test-a", b"test-b")))
    assert len(matched_records) == 2
    assert set(matched_records) == {record_b, record_e}
Esempio n. 7
0
def test_record_query_with_no_constraints(conn):
    record_a = Record.from_enr(ENRFactory())
    record_b = Record.from_enr(ENRFactory())

    insert_record(conn, record_a)
    insert_record(conn, record_b)

    all_records = tuple(query_records(conn))
    assert set(all_records) == {record_a, record_b}
Esempio n. 8
0
def test_wait_time_full_table(topic_table, target_ad_lifetime):
    # fill one queue
    reg_time = 0
    oldest_table_eol = reg_time + target_ad_lifetime
    while not topic_table.is_full:
        assert topic_table.get_wait_time(TopicFactory(), 0) == 0
        topic_table.register(TopicFactory(), ENRFactory(), reg_time)
        reg_time += 1

    assert topic_table.get_wait_time(TopicFactory(), 0) == oldest_table_eol
    topic_table.register(TopicFactory(), ENRFactory(), reg_time)
    assert topic_table.get_wait_time(TopicFactory(), 0) == oldest_table_eol + 1
Esempio n. 9
0
def test_registration_two_queues(topic_table, max_queue_size):
    topic1 = TopicFactory()
    topic2 = TopicFactory()
    enr = ENRFactory()

    topic_table.register(topic1, enr, 0)
    while not topic_table.is_queue_full(topic1):
        topic_table.register(topic1, ENRFactory(), 0)

    topic_table.register(topic2, enr, 1)
    while not topic_table.is_queue_full(topic2):
        topic_table.register(topic2, ENRFactory(), 1)

    with pytest.raises(ValueError):
        topic_table.register(topic1, ENRFactory(), 1)
    with pytest.raises(ValueError):
        topic_table.register(topic2, ENRFactory(), 1)
    with pytest.raises(ValueError):
        topic_table.register(topic2, ENRFactory(), topic_table.get_wait_time(topic1, 0))

    enrs_topic1_before = topic_table.get_enrs_for_topic(topic1)
    enrs_topic2_before = topic_table.get_enrs_for_topic(topic2)
    new_enr_topic1 = ENRFactory()
    new_enr_topic2 = ENRFactory()

    topic_table.register(topic1, new_enr_topic1, topic_table.get_wait_time(topic1, 0))
    topic_table.register(topic2, new_enr_topic2, topic_table.get_wait_time(topic2, 0))

    enrs_topic1_after = topic_table.get_enrs_for_topic(topic1)
    enrs_topic2_after = topic_table.get_enrs_for_topic(topic2)
    assert enrs_topic1_after == (new_enr_topic1,) + enrs_topic1_before[:-1]
    assert enrs_topic2_after == (new_enr_topic2,) + enrs_topic2_before[:-1]
Esempio n. 10
0
def test_query_by_key_existence(enr_db):
    enr_a = ENRFactory(custom_kv_pairs={b"test": b"value-A"})
    enr_b = ENRFactory(custom_kv_pairs={b"test": b"value-B"})
    enr_c = ENRFactory()

    enr_db.set_enr(enr_a)
    enr_db.set_enr(enr_b)
    enr_db.set_enr(enr_c)

    enr_results = set(enr_db.query(KeyExists(b"test")))
    assert len(enr_results) == 2

    assert enr_a in enr_results
    assert enr_b in enr_results
    assert enr_c not in enr_results
Esempio n. 11
0
async def test_client_request_response_stream_find_nodes_catches_invalid_response_total(
    alice, bob, alice_client, bob_client
):
    enrs = tuple(ENRFactory() for _ in range(10))

    async with trio.open_nursery() as nursery:
        async with bob_client.dispatcher.subscribe(FindNodeMessage) as subscription:

            async def _respond():
                request = await subscription.receive()
                message = AnyOutboundMessage(
                    FoundNodesMessage(request.message.request_id, 0, enrs),
                    alice.endpoint,
                    alice.node_id,
                )
                await bob_client.dispatcher.send_message(message)

            nursery.start_soon(_respond)

            with pytest.raises(
                ValidationError, match="Invalid `total` counter in response: total=0"
            ):
                async with alice_client.stream_find_nodes(
                    bob.node_id, bob.endpoint, distances=[256]
                ) as resp_aiter:
                    tuple([resp async for resp in resp_aiter])

            nursery.cancel_scope.cancel()
Esempio n. 12
0
def test_query_with_order_by_closest(enr_db):
    all_enrs = tuple(ENRFactory() for _ in range(4))

    target, *enrs = all_enrs
    target_node_id_as_int = int.from_bytes(target.node_id, "big")

    def distance_fn(enr):
        node_id_as_int = int.from_bytes(enr.node_id, "big")
        return target_node_id_as_int ^ node_id_as_int

    enrs_by_proximity = tuple(sorted(enrs, key=distance_fn))

    for enr in enrs:
        enr_db.set_enr(enr)

    enrs_closest_to_target = tuple(enr_db.query(ClosestTo(target.node_id)))
    assert enrs_closest_to_target == enrs_by_proximity

    enrs_closest_to_target_with_ip = tuple(
        enr_db.query(ClosestTo(target.node_id), KeyExists(b"ip")))
    assert enrs_closest_to_target_with_ip == enrs_by_proximity

    enrs_closest_to_target_with_ipv4_endpoint = tuple(
        enr_db.query(ClosestTo(target.node_id), has_tcp_ipv4_endpoint))
    assert enrs_closest_to_target_with_ipv4_endpoint == enrs_by_proximity
Esempio n. 13
0
async def test_network_stream_find_nodes_api_validates_response_distances(
        alice, bob, bob_client, alice_network, response_enr):
    if response_enr == "own":
        enr_for_response = bob.enr
    elif response_enr == "wrong":
        for _ in range(200):
            enr_for_response = ENRFactory()
            if compute_log_distance(enr_for_response.node_id,
                                    bob.node_id) == 256:
                break
        else:
            raise Exception("failed")
    else:
        raise Exception(f"unsupported param: {response_enr}")

    async with bob.events.find_nodes_received.subscribe() as subscription:
        async with trio.open_nursery() as nursery:

            async def _respond():
                request = await subscription.receive()
                await bob_client.send_found_nodes(
                    alice.node_id,
                    alice.endpoint,
                    enrs=(enr_for_response, ),
                    request_id=request.request_id,
                )

            nursery.start_soon(_respond)
            with trio.fail_after(REQUEST_RESPONSE_TIMEOUT):
                with pytest.raises(ValidationError,
                                   match="Invalid response: distance="):
                    async with alice_network.stream_find_nodes(
                            bob.node_id, bob.endpoint,
                            distances=[255]) as resp_aiter:
                        tuple([resp async for resp in resp_aiter])
Esempio n. 14
0
async def test_network_stream_find_nodes(alice, bob, alice_network,
                                         bob_client):
    enrs = tuple(ENRFactory() for _ in range(FOUND_NODES_MAX_PAYLOAD_SIZE + 1))
    distances = set(
        [compute_log_distance(enr.node_id, bob.node_id) for enr in enrs])

    async with trio.open_nursery() as nursery:
        async with bob.events.find_nodes_received.subscribe() as subscription:

            async def _send_response():
                find_nodes = await subscription.receive()
                await bob_client.send_found_nodes(
                    alice.node_id,
                    alice.endpoint,
                    enrs=enrs,
                    request_id=find_nodes.message.request_id,
                )

            nursery.start_soon(_send_response)

            with trio.fail_after(2):
                async with alice_network.stream_find_nodes(
                        bob.node_id, bob.endpoint,
                        distances=distances) as resp_aiter:
                    actual_enrs = tuple([resp async for resp in resp_aiter])
            assert actual_enrs == enrs

            nursery.cancel_scope.cancel()
Esempio n. 15
0
async def test_bootstrap_nodes():
    private_key = PrivateKeyFactory().to_bytes()
    bootnode1 = ENRFactory(private_key=private_key)
    bootnode2 = ENRFactory()
    discovery = MockDiscoveryService([Node(bootnode1), Node(bootnode2)])

    assert discovery.enr_db.get_enr(bootnode1.node_id) == bootnode1
    assert discovery.enr_db.get_enr(bootnode2.node_id) == bootnode2
    assert [node.enr for node in discovery.bootstrap_nodes] == [bootnode1, bootnode2]

    # If our DB gets updated with a newer ENR of one of our bootnodes, the @bootstrap_nodes
    # property will reflect that.
    new_bootnode1 = ENRFactory(
        private_key=private_key, sequence_number=bootnode1.sequence_number + 1)
    discovery.enr_db.set_enr(new_bootnode1)
    assert [node.enr for node in discovery.bootstrap_nodes] == [new_bootnode1, bootnode2]
Esempio n. 16
0
async def test_network_find_nodes_api(alice, bob):
    distances = {0}

    async with alice.network() as alice_network:
        async with bob.network() as bob_network:
            for _ in range(200):
                enr = ENRFactory()
                bob.enr_db.set_enr(enr)
                bob_network.routing_table.update(enr.node_id)
                distances.add(compute_log_distance(enr.node_id, bob.node_id))
                if distances.issuperset({0, 256, 255}):
                    break
            else:
                raise Exception("failed")

            with trio.fail_after(2):
                enrs = await alice_network.find_nodes(bob.node_id, 0, 255, 256)

    assert any(enr.node_id == bob.node_id for enr in enrs)
    response_distances = {
        compute_log_distance(enr.node_id, bob.node_id)
        for enr in enrs
        if enr.node_id != bob.node_id
    }
    assert response_distances == {256, 255}
Esempio n. 17
0
def test_enr_manager_handles_existing_enr_in_database(enr_db):
    private_key = PrivateKeyFactory()
    enr = ENRFactory(private_key=private_key.to_bytes(), sequence_number=10)
    enr_db.set_enr(enr)

    enr_manager = ENRManager(private_key, enr_db)
    assert enr_manager.enr == enr
Esempio n. 18
0
async def test_network_responds_to_find_node_requests(alice, bob):
    distances = {0}

    async with alice.network() as alice_network:
        async with bob.network() as bob_network:
            for _ in range(200):
                enr = ENRFactory()
                bob.enr_db.set_enr(enr)
                bob_network.routing_table.update(enr.node_id)
                distances.add(compute_log_distance(enr.node_id, bob.node_id))
                if distances.issuperset({0, 256, 255}):
                    break
            else:
                raise Exception("failed")

            with trio.fail_after(2):
                responses = await alice_network.client.find_nodes(
                    bob.endpoint, bob.node_id, distances=(0, 255, 256),
                )

    assert all(
        isinstance(response.message, FoundNodesMessage) for response in responses
    )
    response_enrs = tuple(
        enr for response in responses for enr in response.message.enrs
    )
    response_distances = {
        compute_log_distance(enr.node_id, bob.node_id)
        if enr.node_id != bob.node_id
        else 0
        for enr in response_enrs
    }
    assert response_distances.issuperset({0, 255, 256})
Esempio n. 19
0
async def test_v51_rpc_sendFindNodes_web3(bob_node_id_param_w3, bob,
                                          bob_network, w3):
    distances = set()

    for _ in range(10):
        enr = ENRFactory()
        distances.add(compute_log_distance(bob.node_id, enr.node_id))
        bob.enr_db.set_enr(enr)

    async with bob_network.client.dispatcher.subscribe(
            FindNodeMessage) as subscription:
        first_response = await trio.to_thread.run_sync(
            w3.discv5.send_find_nodes, bob_node_id_param_w3, 0)
        with trio.fail_after(2):
            first_receipt = await subscription.receive()

        assert encode_hex(
            first_receipt.message.request_id) == first_response.value
        assert first_receipt.message.distances == (0, )

        # request with multiple distances
        second_response = await trio.to_thread.run_sync(
            w3.discv5.send_find_nodes, bob_node_id_param_w3, tuple(distances))

        with trio.fail_after(2):
            second_receipt = await subscription.receive()
        assert encode_hex(
            second_receipt.message.request_id) == second_response.value
        assert second_receipt.message.distances == tuple(distances)
Esempio n. 20
0
async def test_v51_rpc_sendFindNodes(make_request, bob_node_id_param, bob,
                                     bob_network):
    distances = set()

    for _ in range(10):
        enr = ENRFactory()
        distances.add(compute_log_distance(bob.node_id, enr.node_id))
        bob.enr_db.set_enr(enr)

    async with bob_network.client.dispatcher.subscribe(
            FindNodeMessage) as subscription:
        single_response = await make_request("discv5_sendFindNodes",
                                             [bob_node_id_param, 0])
        with trio.fail_after(2):
            first_receipt = await subscription.receive()

        assert encode_hex(first_receipt.message.request_id) == single_response
        assert first_receipt.message.distances == (0, )

        # request with multiple distances
        multiple_response = await make_request(
            "discv5_sendFindNodes",
            [bob_node_id_param, tuple(distances)],
        )

        with trio.fail_after(2):
            second_receipt = await subscription.receive()
        assert encode_hex(
            second_receipt.message.request_id) == multiple_response
        assert second_receipt.message.distances == tuple(distances)
Esempio n. 21
0
def test_found_nodes_message_encoding_round_trip(num_enr_records):
    enrs = tuple(ENRFactory() for _ in range(num_enr_records))
    encoded_enrs = tuple(rlp.encode(enr) for enr in enrs)
    payload = FoundNodesPayload(num_enr_records, encoded_enrs)
    message = FoundNodesMessage(payload)
    encoded = message.to_wire_bytes()
    result = decode_message(encoded)
    assert result.payload == message.payload
Esempio n. 22
0
async def test_generate_eth_cap_enr_field():
    base_db = AtomicDB()
    ChainDB(base_db).persist_header(ROPSTEN_GENESIS_HEADER)

    enr_field = await generate_eth_cap_enr_field(ROPSTEN_VM_CONFIGURATION, AsyncHeaderDB(base_db))

    enr = ENRFactory(custom_kv_pairs={enr_field[0]: enr_field[1]})
    assert extract_forkid(enr) == ForkID(hash=to_bytes(hexstr='0x30c7ddbc'), next=10)
Esempio n. 23
0
def enr(private_key, endpoint):
    return ENRFactory(
        private_key=private_key,
        custom_kv_pairs={
            b"ip": endpoint.ip_address,
            b"udp": endpoint.port
        },
    )
Esempio n. 24
0
def test_wait_time_full_queue(topic_table, max_total_size, target_ad_lifetime):
    topic = TopicFactory()
    different_topic = TopicFactory()

    reg_time = 0
    oldest_queue_eol = reg_time + target_ad_lifetime
    while not topic_table.is_queue_full(topic):
        assert topic_table.get_wait_time(topic, 0) == 0
        assert topic_table.get_wait_time(different_topic, 0) == 0
        topic_table.register(topic, ENRFactory(), reg_time)
        reg_time += 1

    assert topic_table.get_wait_time(topic, 0) == oldest_queue_eol
    assert topic_table.get_wait_time(different_topic, 0) == 0
    topic_table.register(topic, ENRFactory(), reg_time)
    assert topic_table.get_wait_time(topic, 0) == oldest_queue_eol + 1
    assert topic_table.get_wait_time(different_topic, 0) == 0
Esempio n. 25
0
def remote_enr(remote_private_key, remote_endpoint):
    return ENRFactory(
        private_key=remote_private_key,
        custom_kv_pairs={
            b"ip": remote_endpoint.ip_address,
            b"udp": remote_endpoint.port,
        },
    )
Esempio n. 26
0
async def filled_routing_table(routing_table, enr_db):
    # add entries until the first bucket is full
    while len(routing_table.get_nodes_at_log_distance(
            255)) < routing_table.bucket_size:
        enr = ENRFactory()
        routing_table.update(enr.node_id)
        enr_db.set_enr(enr)
    return routing_table
Esempio n. 27
0
async def test_v51_rpc_sendFoundNodes_invalid_params(make_request,
                                                     invalid_node_id, bob):
    enrs = set()
    for _ in range(10):
        enr = ENRFactory()
        bob.enr_db.set_enr(enr)
        enrs.add(repr(enr))
    single_enr = next(iter(enrs))

    # bad node_id
    with pytest.raises(Exception, match="'error':"):
        await make_request("discv5_sendFoundNodes",
                           [invalid_node_id, tuple(enrs), 0])
    with pytest.raises(Exception, match="'error':"):
        await make_request("discv5_sendFoundNodes",
                           [invalid_node_id, tuple(enrs), [0]])

    # invalid enr
    with pytest.raises(Exception, match="'error':"):
        await make_request("discv5_sendFoundNodes",
                           [invalid_node_id, (single_enr[4:], ), 0])

    # invalid distances
    with pytest.raises(Exception, match="'error':"):
        await make_request(
            "discv5_sendFoundNodes",
            [bob.node_id.hex(), tuple(enrs), -1])
    with pytest.raises(Exception, match="'error':"):
        await make_request(
            "discv5_sendFoundNodes",
            [bob.node_id.hex(), tuple(enrs), 257])
    with pytest.raises(Exception, match="'error':"):
        await make_request(
            "discv5_sendFoundNodes",
            [bob.node_id.hex(), tuple(enrs), 1.2])
    with pytest.raises(Exception, match="'error':"):
        await make_request(
            "discv5_sendFoundNodes",
            [bob.node_id.hex(), tuple(enrs), []])
    with pytest.raises(Exception, match="'error':"):
        await make_request(
            "discv5_sendFoundNodes",
            [bob.node_id.hex(), tuple(enrs), "xyz"])
    with pytest.raises(Exception, match="'error':"):
        await make_request(
            "discv5_sendFoundNodes",
            [bob.node_id.hex(), tuple(enrs), [1, "2"]])

    # wrong params count
    with pytest.raises(Exception, match="'error':"):
        await make_request("discv5_sendFoundNodes", [])
    with pytest.raises(Exception, match="'error':"):
        await make_request("discv5_sendFoundNodes",
                           [bob.node_id.hex(), tuple(enrs)])
    with pytest.raises(Exception, match="'error':"):
        await make_request(
            "discv5_sendFoundNodes",
            [bob.node_id.hex(), tuple(enrs), 0, "extra"])
Esempio n. 28
0
def new_enr_manager():
    enr_db = QueryableENRDB(sqlite3.connect(":memory:"))
    private_key = PrivateKeyFactory()
    base_enr = ENRFactory(
        private_key=private_key.to_bytes(),
        sequence_number=secrets.randbelow(100) + 1,
    )
    enr_db.set_enr(base_enr)
    return ENRManager(private_key, enr_db)
Esempio n. 29
0
def test_get_and_set_enr(enr_db):
    private_key = PrivateKeyFactory().to_bytes()
    db = enr_db
    enr = ENRFactory(private_key=private_key)

    with pytest.raises(KeyError):
        db.get_enr(enr.node_id)

    db.set_enr(enr)
    assert db.get_enr(enr.node_id) == enr
Esempio n. 30
0
def test_record_deletion(conn):
    record = Record.from_enr(ENRFactory())
    insert_record(conn, record)
    assert get_record(conn, record.node_id) == record

    row_count = delete_record(conn, record.node_id)
    assert row_count == 1

    with pytest.raises(RecordNotFound):
        get_record(conn, record.node_id)