diff --git a/test/common/unit/test_appointment.py b/test/common/unit/test_appointment.py index ed0df51..4a5162d 100644 --- a/test/common/unit/test_appointment.py +++ b/test/common/unit/test_appointment.py @@ -31,17 +31,11 @@ def test_init_appointment(appointment_data): # The appointment has no checks whatsoever, since the inspector is the one taking care or that, and the only one # creating appointments. appointment = Appointment( - appointment_data["locator"], - appointment_data["start_time"], - appointment_data["end_time"], - appointment_data["to_self_delay"], - appointment_data["encrypted_blob"], + appointment_data["locator"], appointment_data["to_self_delay"], appointment_data["encrypted_blob"] ) assert ( appointment_data["locator"] == appointment.locator - and appointment_data["start_time"] == appointment.start_time - and appointment_data["end_time"] == appointment.end_time and appointment_data["to_self_delay"] == appointment.to_self_delay and appointment_data["encrypted_blob"] == appointment.encrypted_blob ) @@ -49,19 +43,13 @@ def test_init_appointment(appointment_data): def test_to_dict(appointment_data): appointment = Appointment( - appointment_data["locator"], - appointment_data["start_time"], - appointment_data["end_time"], - appointment_data["to_self_delay"], - appointment_data["encrypted_blob"], + appointment_data["locator"], appointment_data["to_self_delay"], appointment_data["encrypted_blob"] ) dict_appointment = appointment.to_dict() assert ( appointment_data["locator"] == dict_appointment["locator"] - and appointment_data["start_time"] == dict_appointment["start_time"] - and appointment_data["end_time"] == dict_appointment["end_time"] and appointment_data["to_self_delay"] == dict_appointment["to_self_delay"] and appointment_data["encrypted_blob"] == dict_appointment["encrypted_blob"] ) @@ -94,13 +82,9 @@ def test_serialize(appointment_data): assert isinstance(serialized_appointment, bytes) locator = serialized_appointment[:16] - start_time = serialized_appointment[16:20] - end_time = serialized_appointment[20:24] - to_self_delay = serialized_appointment[24:28] - encrypted_blob = serialized_appointment[28:] + to_self_delay = serialized_appointment[16:20] + encrypted_blob = serialized_appointment[20:] assert binascii.hexlify(locator).decode() == appointment.locator - assert struct.unpack(">I", start_time)[0] == appointment.start_time - assert struct.unpack(">I", end_time)[0] == appointment.end_time assert struct.unpack(">I", to_self_delay)[0] == appointment.to_self_delay assert binascii.hexlify(encrypted_blob).decode() == appointment.encrypted_blob diff --git a/test/teos/unit/conftest.py b/test/teos/unit/conftest.py index f8f8174..f49bbb3 100644 --- a/test/teos/unit/conftest.py +++ b/test/teos/unit/conftest.py @@ -12,15 +12,14 @@ from bitcoind_mock.transaction import create_dummy_transaction from teos import DEFAULT_CONF from teos.carrier import Carrier -from teos.tools import bitcoin_cli from teos.users_dbm import UsersDBM from teos.gatekeeper import Gatekeeper from teos.responder import TransactionTracker from teos.block_processor import BlockProcessor from teos.appointments_dbm import AppointmentsDBM +from teos.extended_appointment import ExtendedAppointment from common.tools import compute_locator -from common.appointment import Appointment from common.constants import LOCATOR_LEN_HEX from common.config_loader import ConfigLoader from common.cryptographer import Cryptographer @@ -81,8 +80,14 @@ def block_processor(): @pytest.fixture(scope="module") -def gatekeeper(user_db_manager): - return Gatekeeper(user_db_manager, get_config().get("DEFAULT_SLOTS")) +def gatekeeper(user_db_manager, block_processor): + return Gatekeeper( + user_db_manager, + block_processor, + get_config().get("DEFAULT_SLOTS"), + get_config().get("DEFAULT_SUBSCRIPTION_DURATION"), + get_config().get("EXPIRY_DELTA"), + ) def generate_keypair(): @@ -98,11 +103,21 @@ def get_random_value_hex(nbytes): return prv_hex.zfill(2 * nbytes) -def generate_block(): +def generate_block_w_delay(): requests.post(url="http://{}:{}/generate".format(BTC_RPC_HOST, BTC_RPC_PORT), timeout=5) sleep(0.5) +def generate_blocks_w_delay(n): + for _ in range(n): + generate_block() + sleep(0.2) + + +def generate_block(): + requests.post(url="http://{}:{}/generate".format(BTC_RPC_HOST, BTC_RPC_PORT), timeout=5) + + def generate_blocks(n): for _ in range(n): generate_block() @@ -113,38 +128,23 @@ def fork(block_hash): requests.post(fork_endpoint, json={"parent": block_hash}) -def generate_dummy_appointment(real_height=True, start_time_offset=5, end_time_offset=30): - if real_height: - current_height = bitcoin_cli(bitcoind_connect_params).getblockcount() - - else: - current_height = 10 - +def generate_dummy_appointment(): dispute_tx = create_dummy_transaction() dispute_txid = dispute_tx.tx_id.hex() penalty_tx = create_dummy_transaction(dispute_txid) - dummy_appointment_data = { - "tx": penalty_tx.hex(), - "tx_id": dispute_txid, - "start_time": current_height + start_time_offset, - "end_time": current_height + end_time_offset, - "to_self_delay": 20, - } - locator = compute_locator(dispute_txid) - + dummy_appointment_data = {"tx": penalty_tx.hex(), "tx_id": dispute_txid, "to_self_delay": 20} encrypted_blob = Cryptographer.encrypt(dummy_appointment_data.get("tx"), dummy_appointment_data.get("tx_id")) appointment_data = { "locator": locator, - "start_time": dummy_appointment_data.get("start_time"), - "end_time": dummy_appointment_data.get("end_time"), "to_self_delay": dummy_appointment_data.get("to_self_delay"), "encrypted_blob": encrypted_blob, + "user_id": get_random_value_hex(16), } - return Appointment.from_dict(appointment_data), dispute_tx.hex() + return ExtendedAppointment.from_dict(appointment_data), dispute_tx.hex() def generate_dummy_tracker(): @@ -158,7 +158,7 @@ def generate_dummy_tracker(): dispute_txid=dispute_txid, penalty_txid=penalty_txid, penalty_rawtx=penalty_rawtx, - appointment_end=100, + user_id=get_random_value_hex(16), ) return TransactionTracker.from_dict(tracker_data) diff --git a/test/teos/unit/test_api.py b/test/teos/unit/test_api.py index dfe6b0f..b2dd68f 100644 --- a/test/teos/unit/test_api.py +++ b/test/teos/unit/test_api.py @@ -6,6 +6,7 @@ from teos.api import API import teos.errors as errors from teos.watcher import Watcher from teos.inspector import Inspector +from teos.gatekeeper import UserInfo from teos.appointments_dbm import AppointmentsDBM from teos.responder import Responder, TransactionTracker @@ -58,10 +59,10 @@ def get_all_db_manager(): def api(db_manager, carrier, block_processor, gatekeeper, run_bitcoind): sk, pk = generate_keypair() - responder = Responder(db_manager, carrier, block_processor) - watcher = Watcher(db_manager, block_processor, responder, sk.to_der(), MAX_APPOINTMENTS, config.get("EXPIRY_DELTA")) + responder = Responder(db_manager, gatekeeper, carrier, block_processor) + watcher = Watcher(db_manager, gatekeeper, block_processor, responder, sk.to_der(), MAX_APPOINTMENTS) inspector = Inspector(block_processor, config.get("MIN_TO_SELF_DELAY")) - api = API(config.get("API_HOST"), config.get("API_PORT"), inspector, watcher, gatekeeper) + api = API(config.get("API_HOST"), config.get("API_PORT"), inspector, watcher) return api @@ -141,8 +142,8 @@ def test_register_json_no_inner_dict(client): def test_add_appointment(api, client, appointment): - # Simulate the user registration - api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": 1} + # Simulate the user registration (end time does not matter here) + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=1, subscription_expiry=0) # Properly formatted appointment appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) @@ -154,8 +155,8 @@ def test_add_appointment(api, client, appointment): def test_add_appointment_no_json(api, client, appointment): - # Simulate the user registration - api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": 1} + # Simulate the user registration (end time does not matter here) + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=1, subscription_expiry=0) # Properly formatted appointment r = client.post(add_appointment_endpoint, data="random_message") @@ -163,8 +164,8 @@ def test_add_appointment_no_json(api, client, appointment): def test_add_appointment_json_no_inner_dict(api, client, appointment): - # Simulate the user registration - api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": 1} + # Simulate the user registration (end time does not matter here) + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=1, subscription_expiry=0) # Properly formatted appointment r = client.post(add_appointment_endpoint, json="random_message") @@ -172,8 +173,8 @@ def test_add_appointment_json_no_inner_dict(api, client, appointment): def test_add_appointment_wrong(api, client, appointment): - # Simulate the user registration - api.gatekeeper.registered_users[compressed_client_pk] = 1 + # Simulate the user registration (end time does not matter here) + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=1, subscription_expiry=0) # Incorrect appointment appointment.to_self_delay = 0 @@ -199,8 +200,8 @@ def test_add_appointment_not_registered(api, client, appointment): def test_add_appointment_registered_no_free_slots(api, client, appointment): - # Empty the user slots - api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": 0} + # Empty the user slots (end time does not matter here) + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=0, subscription_expiry=0) # Properly formatted appointment appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) @@ -212,8 +213,8 @@ def test_add_appointment_registered_no_free_slots(api, client, appointment): def test_add_appointment_registered_not_enough_free_slots(api, client, appointment): - # Give some slots to the user - api.gatekeeper.registered_users[compressed_client_pk] = 1 + # Give some slots to the user (end time does not matter here) + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=1, subscription_expiry=0) # Properly formatted appointment appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) @@ -232,8 +233,8 @@ def test_add_appointment_multiple_times_same_user(api, client, appointment, n=MU # Multiple appointments with the same locator should be valid and counted as updates appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) - # Simulate registering enough slots - api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": n} + # Simulate registering enough slots (end time does not matter here) + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=n, subscription_expiry=0) for _ in range(n): r = add_appointment( client, {"appointment": appointment.to_dict(), "signature": appointment_signature}, compressed_client_pk @@ -254,7 +255,7 @@ def test_add_appointment_multiple_times_different_users(api, client, appointment # Add one slot per public key for pair in user_keys: tmp_compressed_pk = hexlify(pair[1].format(compressed=True)).decode("utf-8") - api.gatekeeper.registered_users[tmp_compressed_pk] = {"available_slots": 2} + api.watcher.gatekeeper.registered_users[tmp_compressed_pk] = UserInfo(available_slots=2, subscription_expiry=0) # Send the appointments for compressed_pk, signature in zip(compressed_pks, signatures): @@ -268,10 +269,10 @@ def test_add_appointment_multiple_times_different_users(api, client, appointment def test_add_appointment_update_same_size(api, client, appointment): # Update an appointment by one of the same size and check that no additional slots are filled - api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": 1} + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=1, subscription_expiry=0) appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) - # # Since we will replace the appointment, we won't added to appointments + # Since we will replace the appointment, we won't added to appointments r = add_appointment( client, {"appointment": appointment.to_dict(), "signature": appointment_signature}, compressed_client_pk ) @@ -289,7 +290,7 @@ def test_add_appointment_update_same_size(api, client, appointment): def test_add_appointment_update_bigger(api, client, appointment): # Update an appointment by one bigger, and check additional slots are filled - api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": 2} + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=2, subscription_expiry=0) appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) r = add_appointment( @@ -317,8 +318,7 @@ def test_add_appointment_update_bigger(api, client, appointment): def test_add_appointment_update_smaller(api, client, appointment): # Update an appointment by one bigger, and check slots are freed - api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": 2} - + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=2, subscription_expiry=0) # This should take 2 slots appointment.encrypted_blob = TWO_SLOTS_BLOTS appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) @@ -338,7 +338,7 @@ def test_add_appointment_update_smaller(api, client, appointment): def test_add_too_many_appointment(api, client): # Give slots to the user - api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": 200} + api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=200, subscription_expiry=0) free_appointment_slots = MAX_APPOINTMENTS - len(api.watcher.appointments) @@ -406,6 +406,8 @@ def test_request_appointment_in_watcher(api, client, appointment): assert r.json.get("status") == "being_watched" # Check the the sent appointment matches the received one + appointment_dict = appointment.to_dict() + appointment_dict.pop("user_id") assert r.json.get("locator") == appointment.locator assert appointment.to_dict() == r.json.get("appointment") @@ -417,7 +419,7 @@ def test_request_appointment_in_responder(api, client, appointment): "dispute_txid": get_random_value_hex(32), "penalty_txid": get_random_value_hex(32), "penalty_rawtx": get_random_value_hex(250), - "appointment_end": appointment.end_time, + "user_id": get_random_value_hex(16), } tx_tracker = TransactionTracker.from_dict(tracker_data) @@ -442,10 +444,9 @@ def test_request_appointment_in_responder(api, client, appointment): assert tx_tracker.dispute_txid == r.json.get("appointment").get("dispute_txid") assert tx_tracker.penalty_txid == r.json.get("appointment").get("penalty_txid") assert tx_tracker.penalty_rawtx == r.json.get("appointment").get("penalty_rawtx") - assert tx_tracker.appointment_end == r.json.get("appointment").get("appointment_end") -def test_get_all_appointments_watcher(api, client, get_all_db_manager, appointment): +def test_get_all_appointments_watcher(api, client, get_all_db_manager): # Let's reset the dbs so we can test this clean api.watcher.db_manager = get_all_db_manager api.watcher.responder.db_manager = get_all_db_manager @@ -459,6 +460,7 @@ def test_get_all_appointments_watcher(api, client, get_all_db_manager, appointme non_triggered_appointments = {} for _ in range(10): uuid = get_random_value_hex(16) + appointment, _ = generate_dummy_appointment() appointment.locator = get_random_value_hex(16) non_triggered_appointments[uuid] = appointment.to_dict() api.watcher.db_manager.store_watcher_appointment(uuid, appointment.to_dict()) @@ -466,6 +468,7 @@ def test_get_all_appointments_watcher(api, client, get_all_db_manager, appointme triggered_appointments = {} for _ in range(10): uuid = get_random_value_hex(16) + appointment, _ = generate_dummy_appointment() appointment.locator = get_random_value_hex(16) triggered_appointments[uuid] = appointment.to_dict() api.watcher.db_manager.store_watcher_appointment(uuid, appointment.to_dict()) @@ -501,7 +504,7 @@ def test_get_all_appointments_responder(api, client, get_all_db_manager): "dispute_txid": get_random_value_hex(32), "penalty_txid": get_random_value_hex(32), "penalty_rawtx": get_random_value_hex(250), - "appointment_end": 20, + "user_id": get_random_value_hex(16), } tracker = TransactionTracker.from_dict(tracker_data) tx_trackers[uuid] = tracker.to_dict() diff --git a/test/teos/unit/test_appointments_dbm.py b/test/teos/unit/test_appointments_dbm.py index 48928f6..7c29f55 100644 --- a/test/teos/unit/test_appointments_dbm.py +++ b/test/teos/unit/test_appointments_dbm.py @@ -19,7 +19,7 @@ from test.teos.unit.conftest import get_random_value_hex, generate_dummy_appoint @pytest.fixture(scope="module") def watcher_appointments(): - return {uuid4().hex: generate_dummy_appointment(real_height=False)[0] for _ in range(10)} + return {uuid4().hex: generate_dummy_appointment()[0] for _ in range(10)} @pytest.fixture(scope="module") @@ -215,7 +215,7 @@ def test_store_load_triggered_appointment(db_manager): assert db_watcher_appointments == db_watcher_appointments_with_triggered # Create an appointment flagged as triggered - triggered_appointment, _ = generate_dummy_appointment(real_height=False) + triggered_appointment, _ = generate_dummy_appointment() uuid = uuid4().hex assert db_manager.store_watcher_appointment(uuid, triggered_appointment.to_dict()) is True db_manager.create_triggered_appointment_flag(uuid) diff --git a/test/teos/unit/test_builder.py b/test/teos/unit/test_builder.py index d921e36..5b3ec58 100644 --- a/test/teos/unit/test_builder.py +++ b/test/teos/unit/test_builder.py @@ -4,6 +4,7 @@ from queue import Queue from teos.builder import Builder from teos.watcher import Watcher +from teos.tools import bitcoin_cli from teos.responder import Responder from test.teos.unit.conftest import ( @@ -11,7 +12,6 @@ from test.teos.unit.conftest import ( generate_dummy_appointment, generate_dummy_tracker, generate_block, - bitcoin_cli, get_config, bitcoind_connect_params, generate_keypair, @@ -25,7 +25,7 @@ def test_build_appointments(): # Create some appointment data for i in range(10): - appointment, _ = generate_dummy_appointment(real_height=False) + appointment, _ = generate_dummy_appointment() uuid = uuid4().hex appointments_data[uuid] = appointment.to_dict() @@ -33,7 +33,7 @@ def test_build_appointments(): # Add some additional appointments that share the same locator to test all the builder's cases if i % 2 == 0: locator = appointment.locator - appointment, _ = generate_dummy_appointment(real_height=False) + appointment, _ = generate_dummy_appointment() uuid = uuid4().hex appointment.locator = locator @@ -46,7 +46,7 @@ def test_build_appointments(): for uuid, appointment in appointments.items(): assert uuid in appointments_data.keys() assert appointments_data[uuid].get("locator") == appointment.get("locator") - assert appointments_data[uuid].get("end_time") == appointment.get("end_time") + assert appointments_data[uuid].get("user_id") == appointment.get("user_id") assert len(appointments_data[uuid].get("encrypted_blob")) == appointment.get("size") assert uuid in locator_uuid_map[appointment.get("locator")] @@ -76,7 +76,7 @@ def test_build_trackers(): assert tracker.get("penalty_txid") == trackers_data[uuid].get("penalty_txid") assert tracker.get("locator") == trackers_data[uuid].get("locator") - assert tracker.get("appointment_end") == trackers_data[uuid].get("appointment_end") + assert tracker.get("user_id") == trackers_data[uuid].get("user_id") assert uuid in tx_tracker_map[tracker.get("penalty_txid")] @@ -95,14 +95,14 @@ def test_populate_block_queue(): assert len(blocks) == 0 -def test_update_states_empty_list(db_manager, carrier, block_processor): +def test_update_states_empty_list(db_manager, gatekeeper, carrier, block_processor): w = Watcher( db_manager=db_manager, + gatekeeper=gatekeeper, block_processor=block_processor, - responder=Responder(db_manager, carrier, block_processor), + responder=Responder(db_manager, gatekeeper, carrier, block_processor), sk_der=generate_keypair()[0].to_der(), max_appointments=config.get("MAX_APPOINTMENTS"), - expiry_delta=config.get("EXPIRY_DELTA"), ) missed_blocks_watcher = [] @@ -116,14 +116,14 @@ def test_update_states_empty_list(db_manager, carrier, block_processor): Builder.update_states(w, missed_blocks_responder, missed_blocks_watcher) -def test_update_states_responder_misses_more(run_bitcoind, db_manager, carrier, block_processor): +def test_update_states_responder_misses_more(run_bitcoind, db_manager, gatekeeper, carrier, block_processor): w = Watcher( db_manager=db_manager, + gatekeeper=gatekeeper, block_processor=block_processor, - responder=Responder(db_manager, carrier, block_processor), + responder=Responder(db_manager, gatekeeper, carrier, block_processor), sk_der=generate_keypair()[0].to_der(), max_appointments=config.get("MAX_APPOINTMENTS"), - expiry_delta=config.get("EXPIRY_DELTA"), ) blocks = [] @@ -140,15 +140,15 @@ def test_update_states_responder_misses_more(run_bitcoind, db_manager, carrier, assert w.responder.last_known_block == blocks[-1] -def test_update_states_watcher_misses_more(db_manager, carrier, block_processor): +def test_update_states_watcher_misses_more(db_manager, gatekeeper, carrier, block_processor): # Same as before, but data is now in the Responder w = Watcher( db_manager=db_manager, + gatekeeper=gatekeeper, block_processor=block_processor, - responder=Responder(db_manager, carrier, block_processor), + responder=Responder(db_manager, gatekeeper, carrier, block_processor), sk_der=generate_keypair()[0].to_der(), max_appointments=config.get("MAX_APPOINTMENTS"), - expiry_delta=config.get("EXPIRY_DELTA"), ) blocks = [] diff --git a/test/teos/unit/test_cleaner.py b/test/teos/unit/test_cleaner.py index ad2e263..163dde0 100644 --- a/test/teos/unit/test_cleaner.py +++ b/test/teos/unit/test_cleaner.py @@ -23,7 +23,7 @@ def set_up_appointments(db_manager, total_appointments): uuid = uuid4().hex locator = get_random_value_hex(LOCATOR_LEN_BYTES) - appointment = Appointment(locator, None, None, None, None) + appointment = Appointment(locator, None, None) appointments[uuid] = {"locator": appointment.locator} locator_uuid_map[locator] = [uuid] @@ -156,7 +156,8 @@ def test_flag_triggered_appointments(db_manager): assert set(triggered_appointments).issubset(db_appointments) -def test_delete_completed_trackers_db_match(db_manager): +def test_delete_trackers_db_match(db_manager): + # Completed and expired trackers are deleted using the same method. The only difference is the logging message height = 0 for _ in range(ITERATIONS): @@ -165,12 +166,12 @@ def test_delete_completed_trackers_db_match(db_manager): completed_trackers = {tracker: 6 for tracker in selected_trackers} - Cleaner.delete_completed_trackers(completed_trackers, height, trackers, tx_tracker_map, db_manager) + Cleaner.delete_trackers(completed_trackers, height, trackers, tx_tracker_map, db_manager) assert not set(completed_trackers).issubset(trackers.keys()) -def test_delete_completed_trackers_no_db_match(db_manager): +def test_delete_trackers_no_db_match(db_manager): height = 0 for _ in range(ITERATIONS): @@ -203,5 +204,5 @@ def test_delete_completed_trackers_no_db_match(db_manager): completed_trackers = {tracker: 6 for tracker in selected_trackers} # We should be able to delete the correct ones and not fail in the others - Cleaner.delete_completed_trackers(completed_trackers, height, trackers, tx_tracker_map, db_manager) + Cleaner.delete_trackers(completed_trackers, height, trackers, tx_tracker_map, db_manager) assert not set(completed_trackers).issubset(trackers.keys()) diff --git a/test/teos/unit/test_gatekeeper.py b/test/teos/unit/test_gatekeeper.py index bfb916f..9675593 100644 --- a/test/teos/unit/test_gatekeeper.py +++ b/test/teos/unit/test_gatekeeper.py @@ -1,8 +1,9 @@ import pytest -from teos.gatekeeper import IdentificationFailure, NotEnoughSlots +from teos.gatekeeper import AuthenticationFailure, NotEnoughSlots from common.cryptographer import Cryptographer +from common.exceptions import InvalidParameter from test.teos.unit.conftest import get_random_value_hex, generate_keypair, get_config @@ -10,7 +11,7 @@ from test.teos.unit.conftest import get_random_value_hex, generate_keypair, get_ config = get_config() -def test_init(gatekeeper): +def test_init(gatekeeper, run_bitcoind): assert isinstance(gatekeeper.default_slots, int) and gatekeeper.default_slots == config.get("DEFAULT_SLOTS") assert isinstance(gatekeeper.registered_users, dict) and len(gatekeeper.registered_users) == 0 @@ -20,14 +21,12 @@ def test_add_update_user(gatekeeper): user_pk = "02" + get_random_value_hex(32) for _ in range(10): - current_slots = gatekeeper.registered_users.get(user_pk) - current_slots = current_slots.get("available_slots") if current_slots is not None else 0 + user = gatekeeper.registered_users.get(user_pk) + current_slots = user.available_slots if user is not None else 0 gatekeeper.add_update_user(user_pk) - assert gatekeeper.registered_users.get(user_pk).get("available_slots") == current_slots + config.get( - "DEFAULT_SLOTS" - ) + assert gatekeeper.registered_users.get(user_pk).available_slots == current_slots + config.get("DEFAULT_SLOTS") # The same can be checked for multiple users for _ in range(10): @@ -35,14 +34,14 @@ def test_add_update_user(gatekeeper): user_pk = "03" + get_random_value_hex(32) gatekeeper.add_update_user(user_pk) - assert gatekeeper.registered_users.get(user_pk).get("available_slots") == config.get("DEFAULT_SLOTS") + assert gatekeeper.registered_users.get(user_pk).available_slots == config.get("DEFAULT_SLOTS") def test_add_update_user_wrong_pk(gatekeeper): # Passing a wrong pk defaults to the errors in check_user_pk. We can try with one. wrong_pk = get_random_value_hex(32) - with pytest.raises(ValueError): + with pytest.raises(InvalidParameter): gatekeeper.add_update_user(wrong_pk) @@ -50,7 +49,7 @@ def test_add_update_user_wrong_pk_prefix(gatekeeper): # Prefixes must be 02 or 03, anything else should fail wrong_pk = "04" + get_random_value_hex(32) - with pytest.raises(ValueError): + with pytest.raises(InvalidParameter): gatekeeper.add_update_user(wrong_pk) @@ -66,7 +65,7 @@ def test_identify_user(gatekeeper): message = "Hey, it's me" signature = Cryptographer.sign(message.encode(), sk) - assert gatekeeper.identify_user(message.encode(), signature) == compressed_pk + assert gatekeeper.authenticate_user(message.encode(), signature) == compressed_pk def test_identify_user_non_registered(gatekeeper): @@ -76,8 +75,8 @@ def test_identify_user_non_registered(gatekeeper): message = "Hey, it's me" signature = Cryptographer.sign(message.encode(), sk) - with pytest.raises(IdentificationFailure): - gatekeeper.identify_user(message.encode(), signature) + with pytest.raises(AuthenticationFailure): + gatekeeper.authenticate_user(message.encode(), signature) def test_identify_user_invalid_signature(gatekeeper): @@ -85,8 +84,8 @@ def test_identify_user_invalid_signature(gatekeeper): message = "Hey, it's me" signature = get_random_value_hex(72) - with pytest.raises(IdentificationFailure): - gatekeeper.identify_user(message.encode(), signature) + with pytest.raises(AuthenticationFailure): + gatekeeper.authenticate_user(message.encode(), signature) def test_identify_user_wrong(gatekeeper): @@ -97,41 +96,16 @@ def test_identify_user_wrong(gatekeeper): signature = Cryptographer.sign(message.encode(), sk) # Non-byte message and str sig - with pytest.raises(IdentificationFailure): - gatekeeper.identify_user(message, signature) + with pytest.raises(AuthenticationFailure): + gatekeeper.authenticate_user(message, signature) # byte message and non-str sig - with pytest.raises(IdentificationFailure): - gatekeeper.identify_user(message.encode(), signature.encode()) + with pytest.raises(AuthenticationFailure): + gatekeeper.authenticate_user(message.encode(), signature.encode()) # non-byte message and non-str sig - with pytest.raises(IdentificationFailure): - gatekeeper.identify_user(message, signature.encode()) + with pytest.raises(AuthenticationFailure): + gatekeeper.authenticate_user(message, signature.encode()) -def test_fill_slots(gatekeeper): - # Free slots will decrease the slot count of a user as long as he has enough slots, otherwise raise NotEnoughSlots - user_pk = "02" + get_random_value_hex(32) - gatekeeper.add_update_user(user_pk) - - gatekeeper.fill_slots(user_pk, config.get("DEFAULT_SLOTS") - 1) - assert gatekeeper.registered_users.get(user_pk).get("available_slots") == 1 - - with pytest.raises(NotEnoughSlots): - gatekeeper.fill_slots(user_pk, 2) - - # NotEnoughSlots is also raised if the user does not exist - with pytest.raises(NotEnoughSlots): - gatekeeper.fill_slots(get_random_value_hex(33), 2) - - -def test_free_slots(gatekeeper): - # Free slots simply adds slots to the user as long as it exists. - user_pk = "03" + get_random_value_hex(32) - gatekeeper.add_update_user(user_pk) - gatekeeper.free_slots(user_pk, 42) - - assert gatekeeper.registered_users.get(user_pk).get("available_slots") == config.get("DEFAULT_SLOTS") + 42 - - # Just making sure it does not crash for non-registered user - assert gatekeeper.free_slots(get_random_value_hex(33), 10) is None +# FIXME: MISSING TESTS diff --git a/test/teos/unit/test_inspector.py b/test/teos/unit/test_inspector.py index c7a2421..69b0558 100644 --- a/test/teos/unit/test_inspector.py +++ b/test/teos/unit/test_inspector.py @@ -4,8 +4,8 @@ from binascii import unhexlify import teos.errors as errors from teos.block_processor import BlockProcessor from teos.inspector import Inspector, InspectionFailed +from teos.extended_appointment import ExtendedAppointment -from common.appointment import Appointment from common.constants import LOCATOR_LEN_BYTES, LOCATOR_LEN_HEX from test.teos.unit.conftest import get_random_value_hex, bitcoind_connect_params, get_config @@ -95,101 +95,6 @@ def test_check_locator(): raise e -def test_check_start_time(): - # Time is defined in block height - current_time = 100 - - # Right format and right value (start time in the future) - start_time = 101 - assert inspector.check_start_time(start_time, current_time) is None - - # Start time too small (either same block or block in the past) - start_times = [100, 99, 98, -1] - for start_time in start_times: - with pytest.raises(InspectionFailed): - try: - inspector.check_start_time(start_time, current_time) - - except InspectionFailed as e: - assert e.erno == errors.APPOINTMENT_FIELD_TOO_SMALL - raise e - - # Empty field - start_time = None - with pytest.raises(InspectionFailed): - try: - inspector.check_start_time(start_time, current_time) - - except InspectionFailed as e: - assert e.erno == errors.APPOINTMENT_EMPTY_FIELD - raise e - - # Wrong data type - start_times = WRONG_TYPES - for start_time in start_times: - with pytest.raises(InspectionFailed): - try: - inspector.check_start_time(start_time, current_time) - - except InspectionFailed as e: - assert e.erno == errors.APPOINTMENT_WRONG_FIELD_TYPE - raise e - - -def test_check_end_time(): - # Time is defined in block height - current_time = 100 - start_time = 120 - - # Right format and right value (start time before end and end in the future) - end_time = 121 - assert inspector.check_end_time(end_time, start_time, current_time) is None - - # End time too small (start time after end time) - end_times = [120, 119, 118, -1] - for end_time in end_times: - with pytest.raises(InspectionFailed): - try: - inspector.check_end_time(end_time, start_time, current_time) - - except InspectionFailed as e: - assert e.erno == errors.APPOINTMENT_FIELD_TOO_SMALL - raise e - - # End time too small (either same height as current block or in the past) - current_time = 130 - end_times = [130, 129, 128, -1] - for end_time in end_times: - with pytest.raises(InspectionFailed): - try: - inspector.check_end_time(end_time, start_time, current_time) - - except InspectionFailed as e: - assert e.erno == errors.APPOINTMENT_FIELD_TOO_SMALL - raise e - - # Empty field - end_time = None - with pytest.raises(InspectionFailed): - try: - inspector.check_end_time(end_time, start_time, current_time) - - except InspectionFailed as e: - assert e.erno == errors.APPOINTMENT_EMPTY_FIELD - raise e - - # Wrong data type - end_times = WRONG_TYPES - for end_time in end_times: - with pytest.raises(InspectionFailed): - try: - inspector.check_end_time(end_time, start_time, current_time) - - except InspectionFailed as e: - assert e.erno == errors.APPOINTMENT_WRONG_FIELD_TYPE - raise e - - def test_check_to_self_delay(): # Right value, right format to_self_delays = [MIN_TO_SELF_DELAY, MIN_TO_SELF_DELAY + 1, MIN_TO_SELF_DELAY + 1000] @@ -234,10 +139,6 @@ def test_check_blob(): encrypted_blob = get_random_value_hex(120) assert inspector.check_blob(encrypted_blob) is None - # # Wrong content - # # FIXME: There is not proper defined format for this yet. It should be restricted by size at least, and check it - # # is multiple of the block size defined by the encryption function. - # Wrong type encrypted_blobs = WRONG_TYPES_NO_STR for encrypted_blob in encrypted_blobs: @@ -279,21 +180,13 @@ def test_inspect(run_bitcoind): to_self_delay = MIN_TO_SELF_DELAY encrypted_blob = get_random_value_hex(64) - appointment_data = { - "locator": locator, - "start_time": start_time, - "end_time": end_time, - "to_self_delay": to_self_delay, - "encrypted_blob": encrypted_blob, - } + appointment_data = {"locator": locator, "to_self_delay": to_self_delay, "encrypted_blob": encrypted_blob} appointment = inspector.inspect(appointment_data) assert ( - type(appointment) == Appointment + type(appointment) == ExtendedAppointment and appointment.locator == locator - and appointment.start_time == start_time - and appointment.end_time == end_time and appointment.to_self_delay == to_self_delay and appointment.encrypted_blob == encrypted_blob ) diff --git a/test/teos/unit/test_responder.py b/test/teos/unit/test_responder.py index 7c4d53d..83dd4e6 100644 --- a/test/teos/unit/test_responder.py +++ b/test/teos/unit/test_responder.py @@ -9,23 +9,31 @@ from threading import Thread from teos.carrier import Carrier from teos.tools import bitcoin_cli from teos.chain_monitor import ChainMonitor +from teos.block_processor import BlockProcessor +from teos.gatekeeper import Gatekeeper, UserInfo from teos.appointments_dbm import AppointmentsDBM -from teos.responder import Responder, TransactionTracker +from teos.responder import Responder, TransactionTracker, CONFIRMATIONS_BEFORE_RETRY from common.constants import LOCATOR_LEN_HEX from bitcoind_mock.transaction import create_dummy_transaction, create_tx_from_hex from test.teos.unit.conftest import ( generate_block, generate_blocks, + generate_block_w_delay, + generate_blocks_w_delay, get_random_value_hex, bitcoind_connect_params, bitcoind_feed_params, + get_config, ) +config = get_config() + + @pytest.fixture(scope="module") -def responder(db_manager, carrier, block_processor): - responder = Responder(db_manager, carrier, block_processor) +def responder(db_manager, gatekeeper, carrier, block_processor): + responder = Responder(db_manager, gatekeeper, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() @@ -66,28 +74,26 @@ def create_dummy_tracker_data(random_txid=False, penalty_rawtx=None): if random_txid is True: penalty_txid = get_random_value_hex(32) - appointment_end = bitcoin_cli(bitcoind_connect_params).getblockcount() + 2 locator = dispute_txid[:LOCATOR_LEN_HEX] + user_id = get_random_value_hex(16) - return locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end + return locator, dispute_txid, penalty_txid, penalty_rawtx, user_id def create_dummy_tracker(random_txid=False, penalty_rawtx=None): - locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end = create_dummy_tracker_data( - random_txid, penalty_rawtx - ) - return TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end) + locator, dispute_txid, penalty_txid, penalty_rawtx, expiry = create_dummy_tracker_data(random_txid, penalty_rawtx) + return TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, expiry) def test_tracker_init(run_bitcoind): - locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end = create_dummy_tracker_data() - tracker = TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end) + locator, dispute_txid, penalty_txid, penalty_rawtx, user_id = create_dummy_tracker_data() + tracker = TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, user_id) assert ( tracker.dispute_txid == dispute_txid and tracker.penalty_txid == penalty_txid and tracker.penalty_rawtx == penalty_rawtx - and tracker.appointment_end == appointment_end + and tracker.user_id == user_id ) @@ -115,7 +121,7 @@ def test_tracker_to_dict(): assert ( tracker.locator == tracker_dict["locator"] and tracker.penalty_rawtx == tracker_dict["penalty_rawtx"] - and tracker.appointment_end == tracker_dict["appointment_end"] + and tracker.user_id == tracker_dict["user_id"] ) @@ -129,7 +135,7 @@ def test_tracker_from_dict(): def test_tracker_from_dict_invalid_data(): tracker_dict = create_dummy_tracker().to_dict() - for value in ["dispute_txid", "penalty_txid", "penalty_rawtx", "appointment_end"]: + for value in ["dispute_txid", "penalty_txid", "penalty_rawtx", "user_id"]: tracker_dict_copy = deepcopy(tracker_dict) tracker_dict_copy[value] = None @@ -141,17 +147,22 @@ def test_tracker_from_dict_invalid_data(): assert True -def test_init_responder(temp_db_manager, carrier, block_processor): - responder = Responder(temp_db_manager, carrier, block_processor) +def test_init_responder(temp_db_manager, gatekeeper, carrier, block_processor): + responder = Responder(temp_db_manager, gatekeeper, carrier, block_processor) assert isinstance(responder.trackers, dict) and len(responder.trackers) == 0 assert isinstance(responder.tx_tracker_map, dict) and len(responder.tx_tracker_map) == 0 assert isinstance(responder.unconfirmed_txs, list) and len(responder.unconfirmed_txs) == 0 assert isinstance(responder.missed_confirmations, dict) and len(responder.missed_confirmations) == 0 - assert responder.block_queue.empty() + assert isinstance(responder.block_queue, Queue) and responder.block_queue.empty() + assert isinstance(responder.db_manager, AppointmentsDBM) + assert isinstance(responder.gatekeeper, Gatekeeper) + assert isinstance(responder.carrier, Carrier) + assert isinstance(responder.block_processor, BlockProcessor) + assert responder.last_known_block is None or isinstance(responder.last_known_block, str) -def test_handle_breach(db_manager, carrier, block_processor): - responder = Responder(db_manager, carrier, block_processor) +def test_handle_breach(db_manager, gatekeeper, carrier, block_processor): + responder = Responder(db_manager, gatekeeper, carrier, block_processor) uuid = uuid4().hex tracker = create_dummy_tracker() @@ -163,17 +174,17 @@ def test_handle_breach(db_manager, carrier, block_processor): tracker.dispute_txid, tracker.penalty_txid, tracker.penalty_rawtx, - tracker.appointment_end, + tracker.user_id, block_hash=get_random_value_hex(32), ) assert receipt.delivered is True -def test_handle_breach_bad_response(db_manager, block_processor): +def test_handle_breach_bad_response(db_manager, gatekeeper, block_processor): # We need a new carrier here, otherwise the transaction will be flagged as previously sent and receipt.delivered # will be True - responder = Responder(db_manager, Carrier(bitcoind_connect_params), block_processor) + responder = Responder(db_manager, gatekeeper, Carrier(bitcoind_connect_params), block_processor) uuid = uuid4().hex tracker = create_dummy_tracker() @@ -188,7 +199,7 @@ def test_handle_breach_bad_response(db_manager, block_processor): tracker.dispute_txid, tracker.penalty_txid, tracker.penalty_rawtx, - tracker.appointment_end, + tracker.user_id, block_hash=get_random_value_hex(32), ) @@ -199,9 +210,7 @@ def test_add_tracker(responder): for _ in range(20): uuid = uuid4().hex confirmations = 0 - locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end = create_dummy_tracker_data( - random_txid=True - ) + locator, dispute_txid, penalty_txid, penalty_rawtx, user_id = create_dummy_tracker_data(random_txid=True) # Check the tracker is not within the responder trackers before adding it assert uuid not in responder.trackers @@ -209,7 +218,7 @@ def test_add_tracker(responder): assert penalty_txid not in responder.unconfirmed_txs # And that it is afterwards - responder.add_tracker(uuid, locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end, confirmations) + responder.add_tracker(uuid, locator, dispute_txid, penalty_txid, penalty_rawtx, user_id, confirmations) assert uuid in responder.trackers assert penalty_txid in responder.tx_tracker_map assert penalty_txid in responder.unconfirmed_txs @@ -219,18 +228,18 @@ def test_add_tracker(responder): assert ( tracker.get("penalty_txid") == penalty_txid and tracker.get("locator") == locator - and tracker.get("appointment_end") == appointment_end + and tracker.get("user_id") == user_id ) def test_add_tracker_same_penalty_txid(responder): confirmations = 0 - locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end = create_dummy_tracker_data(random_txid=True) + locator, dispute_txid, penalty_txid, penalty_rawtx, user_id = create_dummy_tracker_data(random_txid=True) uuid_1 = uuid4().hex uuid_2 = uuid4().hex - responder.add_tracker(uuid_1, locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end, confirmations) - responder.add_tracker(uuid_2, locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end, confirmations) + responder.add_tracker(uuid_1, locator, dispute_txid, penalty_txid, penalty_rawtx, user_id, confirmations) + responder.add_tracker(uuid_2, locator, dispute_txid, penalty_txid, penalty_rawtx, user_id, confirmations) # Check that both trackers have been added assert uuid_1 in responder.trackers and uuid_2 in responder.trackers @@ -243,7 +252,7 @@ def test_add_tracker_same_penalty_txid(responder): assert ( tracker.get("penalty_txid") == penalty_txid and tracker.get("locator") == locator - and tracker.get("appointment_end") == appointment_end + and tracker.get("user_id") == user_id ) @@ -251,35 +260,42 @@ def test_add_tracker_already_confirmed(responder): for i in range(20): uuid = uuid4().hex confirmations = i + 1 - locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end = create_dummy_tracker_data( + locator, dispute_txid, penalty_txid, penalty_rawtx, user_id = create_dummy_tracker_data( penalty_rawtx=create_dummy_transaction().hex() ) - responder.add_tracker(uuid, locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end, confirmations) + responder.add_tracker(uuid, locator, dispute_txid, penalty_txid, penalty_rawtx, user_id, confirmations) assert penalty_txid not in responder.unconfirmed_txs -def test_do_watch(temp_db_manager, carrier, block_processor): +def test_do_watch(temp_db_manager, gatekeeper, carrier, block_processor): # Create a fresh responder to simplify the test - responder = Responder(temp_db_manager, carrier, block_processor) + responder = Responder(temp_db_manager, gatekeeper, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() trackers = [create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) for _ in range(20)] + subscription_expiry = responder.block_processor.get_block_count() + 110 # Let's set up the trackers first for tracker in trackers: uuid = uuid4().hex + # Simulate user registration + responder.gatekeeper.registered_users[tracker.user_id] = UserInfo( + available_slots=10, subscription_expiry=subscription_expiry + ) + responder.trackers[uuid] = { "locator": tracker.locator, "penalty_txid": tracker.penalty_txid, - "appointment_end": tracker.appointment_end, + "user_id": tracker.user_id, } responder.tx_tracker_map[tracker.penalty_txid] = [uuid] responder.missed_confirmations[tracker.penalty_txid] = 0 responder.unconfirmed_txs.append(tracker.penalty_txid) + responder.gatekeeper.registered_users[tracker.user_id].appointments.append(uuid) # We also need to store the info in the db responder.db_manager.create_triggered_appointment_flag(uuid) @@ -295,32 +311,29 @@ def test_do_watch(temp_db_manager, carrier, block_processor): broadcast_txs.append(tracker.penalty_txid) # Mine a block - generate_block() + generate_block_w_delay() # The transactions we sent shouldn't be in the unconfirmed transaction list anymore assert not set(broadcast_txs).issubset(responder.unconfirmed_txs) - # TODO: test that reorgs can be detected once data persistence is merged (new version of the simulator) + # CONFIRMATIONS_BEFORE_RETRY+1 blocks after, the responder should rebroadcast the unconfirmed txs (15 remaining) + generate_blocks_w_delay(CONFIRMATIONS_BEFORE_RETRY + 1) + assert len(responder.unconfirmed_txs) == 0 + assert len(responder.trackers) == 20 - # Generating 5 additional blocks should complete the 5 trackers - generate_blocks(5) + # Generating 100 - CONFIRMATIONS_BEFORE_RETRY -2 additional blocks should complete the first 5 trackers + generate_blocks_w_delay(100 - CONFIRMATIONS_BEFORE_RETRY - 2) + assert len(responder.unconfirmed_txs) == 0 + assert len(responder.trackers) == 15 - assert not set(broadcast_txs).issubset(responder.tx_tracker_map) - - # Do the rest - broadcast_txs = [] - for tracker in trackers[5:]: - bitcoin_cli(bitcoind_connect_params).sendrawtransaction(tracker.penalty_rawtx) - broadcast_txs.append(tracker.penalty_txid) - - # Mine a block - generate_blocks(6) - - assert len(responder.tx_tracker_map) == 0 + # CONFIRMATIONS_BEFORE_RETRY additional blocks should complete the rest + generate_blocks_w_delay(CONFIRMATIONS_BEFORE_RETRY) + assert len(responder.unconfirmed_txs) == 0 + assert len(responder.trackers) == 0 -def test_check_confirmations(db_manager, carrier, block_processor): - responder = Responder(db_manager, carrier, block_processor) +def test_check_confirmations(db_manager, gatekeeper, carrier, block_processor): + responder = Responder(db_manager, gatekeeper, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() @@ -376,68 +389,69 @@ def test_get_txs_to_rebroadcast(responder): assert txs_to_rebroadcast == list(txs_missing_too_many_conf.keys()) -def test_get_completed_trackers(db_manager, carrier, block_processor): +def test_get_completed_trackers(db_manager, gatekeeper, carrier, block_processor): initial_height = bitcoin_cli(bitcoind_connect_params).getblockcount() - responder = Responder(db_manager, carrier, block_processor) + responder = Responder(db_manager, gatekeeper, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() - # A complete tracker is a tracker that has reached the appointment end with enough confs (> MIN_CONFIRMATIONS) - # We'll create three type of transactions: end reached + enough conf, end reached + no enough conf, end not reached - trackers_end_conf = { + # A complete tracker is a tracker which penalty transaction has been irrevocably resolved (i.e. has reached 100 + # confirmations) + # We'll create 3 type of txs: irrevocably resolved, confirmed but not irrevocably resolved, and unconfirmed + trackers_ir_resolved = { uuid4().hex: create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) for _ in range(10) } - trackers_end_no_conf = {} + trackers_confirmed = { + uuid4().hex: create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) for _ in range(10) + } + + trackers_unconfirmed = {} for _ in range(10): tracker = create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) responder.unconfirmed_txs.append(tracker.penalty_txid) - trackers_end_no_conf[uuid4().hex] = tracker - - trackers_no_end = {} - for _ in range(10): - tracker = create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) - tracker.appointment_end += 10 - trackers_no_end[uuid4().hex] = tracker + trackers_unconfirmed[uuid4().hex] = tracker all_trackers = {} - all_trackers.update(trackers_end_conf) - all_trackers.update(trackers_end_no_conf) - all_trackers.update(trackers_no_end) + all_trackers.update(trackers_ir_resolved) + all_trackers.update(trackers_confirmed) + all_trackers.update(trackers_unconfirmed) # Let's add all to the responder for uuid, tracker in all_trackers.items(): responder.trackers[uuid] = { "locator": tracker.locator, "penalty_txid": tracker.penalty_txid, - "appointment_end": tracker.appointment_end, + "user_id": tracker.user_id, } - for uuid, tracker in all_trackers.items(): + for uuid, tracker in trackers_ir_resolved.items(): bitcoin_cli(bitcoind_connect_params).sendrawtransaction(tracker.penalty_rawtx) - # The dummy appointments have a end_appointment time of current + 2, but trackers need at least 6 confs by default - generate_blocks(6) + generate_block_w_delay() - # And now let's check - completed_trackers = responder.get_completed_trackers(initial_height + 6) - completed_trackers_ids = [tracker_id for tracker_id, confirmations in completed_trackers.items()] - ended_trackers_keys = list(trackers_end_conf.keys()) - assert set(completed_trackers_ids) == set(ended_trackers_keys) + for uuid, tracker in trackers_confirmed.items(): + bitcoin_cli(bitcoind_connect_params).sendrawtransaction(tracker.penalty_rawtx) - # Generating 6 additional blocks should also confirm trackers_no_end - generate_blocks(6) + # ir_resolved have 100 confs and confirmed have 99 + generate_blocks_w_delay(99) - completed_trackers = responder.get_completed_trackers(initial_height + 12) - completed_trackers_ids = [tracker_id for tracker_id, confirmations in completed_trackers.items()] - ended_trackers_keys.extend(list(trackers_no_end.keys())) + # Let's check + completed_trackers = responder.get_completed_trackers() + ended_trackers_keys = list(trackers_ir_resolved.keys()) + assert set(completed_trackers) == set(ended_trackers_keys) - assert set(completed_trackers_ids) == set(ended_trackers_keys) + # Generating 1 additional block should also include confirmed + generate_block_w_delay() + + completed_trackers = responder.get_completed_trackers() + ended_trackers_keys.extend(list(trackers_confirmed.keys())) + assert set(completed_trackers) == set(ended_trackers_keys) -def test_rebroadcast(db_manager, carrier, block_processor): - responder = Responder(db_manager, carrier, block_processor) +def test_rebroadcast(db_manager, gatekeeper, carrier, block_processor): + responder = Responder(db_manager, gatekeeper, carrier, block_processor) chain_monitor = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params) chain_monitor.monitor_chain() @@ -446,17 +460,13 @@ def test_rebroadcast(db_manager, carrier, block_processor): # Rebroadcast calls add_response with retry=True. The tracker data is already in trackers. for i in range(20): uuid = uuid4().hex - locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end = create_dummy_tracker_data( + locator, dispute_txid, penalty_txid, penalty_rawtx, user_id = create_dummy_tracker_data( penalty_rawtx=create_dummy_transaction().hex() ) - tracker = TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end) + tracker = TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, user_id) - responder.trackers[uuid] = { - "locator": locator, - "penalty_txid": penalty_txid, - "appointment_end": appointment_end, - } + responder.trackers[uuid] = {"locator": locator, "penalty_txid": penalty_txid, "user_id": user_id} # We need to add it to the db too responder.db_manager.create_triggered_appointment_flag(uuid) diff --git a/test/teos/unit/test_tools.py b/test/teos/unit/test_tools.py index 9a68a19..024ec45 100644 --- a/test/teos/unit/test_tools.py +++ b/test/teos/unit/test_tools.py @@ -13,13 +13,7 @@ def test_can_connect_to_bitcoind(): assert can_connect_to_bitcoind(bitcoind_connect_params) is True -# def test_can_connect_to_bitcoind_bitcoin_not_running(): -# # Kill the simulator thread and test the check fails -# bitcoind_process.kill() -# assert can_connect_to_bitcoind() is False - - -def test_bitcoin_cli(): +def test_bitcoin_cli(run_bitcoind): try: bitcoin_cli(bitcoind_connect_params).help() assert True diff --git a/test/teos/unit/test_users_dbm.py b/test/teos/unit/test_users_dbm.py index 5066561..43957fc 100644 --- a/test/teos/unit/test_users_dbm.py +++ b/test/teos/unit/test_users_dbm.py @@ -1,15 +1,15 @@ -from teos.appointments_dbm import AppointmentsDBM +from teos.users_dbm import UsersDBM +from teos.gatekeeper import UserInfo from test.teos.unit.conftest import get_random_value_hex - stored_users = {} def open_create_db(db_path): try: - db_manager = AppointmentsDBM(db_path) + db_manager = UsersDBM(db_path) return db_manager @@ -20,17 +20,17 @@ def open_create_db(db_path): def test_store_user(user_db_manager): # Store user should work as long as the user_pk is properly formatted and data is a dictionary user_pk = "02" + get_random_value_hex(32) - user_data = {"available_slots": 42} - stored_users[user_pk] = user_data - assert user_db_manager.store_user(user_pk, user_data) is True + user_info = UserInfo(available_slots=42, subscription_expiry=100) + stored_users[user_pk] = user_info.to_dict() + assert user_db_manager.store_user(user_pk, user_info.to_dict()) is True # Wrong pks should return False on adding user_pk = "04" + get_random_value_hex(32) - user_data = {"available_slots": 42} - assert user_db_manager.store_user(user_pk, user_data) is False + user_info = UserInfo(available_slots=42, subscription_expiry=100) + assert user_db_manager.store_user(user_pk, user_info.to_dict()) is False # Same for wrong types - assert user_db_manager.store_user(42, user_data) is False + assert user_db_manager.store_user(42, user_info.to_dict()) is False # And for wrong type user data assert user_db_manager.store_user(user_pk, 42) is False @@ -71,9 +71,9 @@ def test_load_all_users(user_db_manager): # Adding some and checking we get them all for i in range(10): user_pk = "02" + get_random_value_hex(32) - user_data = {"available_slots": i} - user_db_manager.store_user(user_pk, user_data) - stored_users[user_pk] = user_data + user_info = UserInfo(available_slots=42, subscription_expiry=100) + user_db_manager.store_user(user_pk, user_info.to_dict()) + stored_users[user_pk] = user_info.to_dict() all_users = user_db_manager.load_all_users() diff --git a/test/teos/unit/test_watcher.py b/test/teos/unit/test_watcher.py index f655529..fa1aea2 100644 --- a/test/teos/unit/test_watcher.py +++ b/test/teos/unit/test_watcher.py @@ -4,20 +4,21 @@ from shutil import rmtree from threading import Thread from coincurve import PrivateKey -from teos import LOG_PREFIX from teos.carrier import Carrier -from teos.watcher import Watcher from teos.tools import bitcoin_cli from teos.responder import Responder +from teos.gatekeeper import UserInfo +from teos.gatekeeper import Gatekeeper from teos.chain_monitor import ChainMonitor from teos.appointments_dbm import AppointmentsDBM from teos.block_processor import BlockProcessor +from teos.watcher import Watcher, AppointmentLimitReached from common.tools import compute_locator from common.cryptographer import Cryptographer from test.teos.unit.conftest import ( - generate_blocks, + generate_blocks_w_delay, generate_dummy_appointment, get_random_value_hex, generate_keypair, @@ -27,8 +28,6 @@ from test.teos.unit.conftest import ( ) APPOINTMENTS = 5 -START_TIME_OFFSET = 1 -END_TIME_OFFSET = 1 TEST_SET_SIZE = 200 config = get_config() @@ -51,14 +50,12 @@ def temp_db_manager(): @pytest.fixture(scope="module") -def watcher(db_manager): +def watcher(db_manager, gatekeeper): block_processor = BlockProcessor(bitcoind_connect_params) carrier = Carrier(bitcoind_connect_params) - responder = Responder(db_manager, carrier, block_processor) - watcher = Watcher( - db_manager, block_processor, responder, signing_key.to_der(), MAX_APPOINTMENTS, config.get("EXPIRY_DELTA") - ) + responder = Responder(db_manager, gatekeeper, carrier, block_processor) + watcher = Watcher(db_manager, gatekeeper, block_processor, responder, signing_key.to_der(), MAX_APPOINTMENTS) chain_monitor = ChainMonitor( watcher.block_queue, watcher.responder.block_queue, block_processor, bitcoind_feed_params @@ -84,9 +81,7 @@ def create_appointments(n): dispute_txs = [] for i in range(n): - appointment, dispute_tx = generate_dummy_appointment( - start_time_offset=START_TIME_OFFSET, end_time_offset=END_TIME_OFFSET - ) + appointment, dispute_tx = generate_dummy_appointment() uuid = uuid4().hex appointments[uuid] = appointment @@ -103,82 +98,79 @@ def test_init(run_bitcoind, watcher): assert isinstance(watcher.block_processor, BlockProcessor) assert isinstance(watcher.responder, Responder) assert isinstance(watcher.max_appointments, int) - assert isinstance(watcher.expiry_delta, int) + assert isinstance(watcher.gatekeeper, Gatekeeper) assert isinstance(watcher.signing_key, PrivateKey) -def test_get_appointment_summary(watcher): - # get_appointment_summary returns an appointment summary if found, else None. - random_uuid = get_random_value_hex(16) - appointment_summary = {"locator": get_random_value_hex(16), "end_time": 10, "size": 200} - watcher.appointments[random_uuid] = appointment_summary - assert watcher.get_appointment_summary(random_uuid) == appointment_summary - - # Requesting a non-existing appointment - assert watcher.get_appointment_summary(get_random_value_hex(16)) is None - - def test_add_appointment(watcher): - # We should be able to add appointments up to the limit - for _ in range(10): - appointment, dispute_tx = generate_dummy_appointment( - start_time_offset=START_TIME_OFFSET, end_time_offset=END_TIME_OFFSET - ) - user_pk = get_random_value_hex(33) + # Simulate the user is registered + user_sk, user_pk = generate_keypair() + available_slots = 100 + user_id = Cryptographer.get_compressed_pk(user_pk) + watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=available_slots, subscription_expiry=10) - added_appointment, sig = watcher.add_appointment(appointment, user_pk) + appointment, dispute_tx = generate_dummy_appointment() + appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) - assert added_appointment is True - assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( - Cryptographer.recover_pk(appointment.serialize(), sig) - ) + response = watcher.add_appointment(appointment, appointment_signature) + assert response.get("locator") == appointment.locator + assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( + Cryptographer.recover_pk(appointment.serialize(), response.get("signature")) + ) + assert response.get("available_slots") == available_slots - 1 - # Check that we can also add an already added appointment (same locator) - added_appointment, sig = watcher.add_appointment(appointment, user_pk) + # Check that we can also add an already added appointment (same locator) + response = watcher.add_appointment(appointment, appointment_signature) + assert response.get("locator") == appointment.locator + assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( + Cryptographer.recover_pk(appointment.serialize(), response.get("signature")) + ) + # The slot count should not have been reduced and only one copy is kept. + assert response.get("available_slots") == available_slots - 1 + assert len(watcher.locator_uuid_map[appointment.locator]) == 1 - assert added_appointment is True - assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( - Cryptographer.recover_pk(appointment.serialize(), sig) - ) + # If two appointments with the same locator come from different users, they are kept. + another_user_sk, another_user_pk = generate_keypair() + another_user_id = Cryptographer.get_compressed_pk(another_user_pk) + watcher.gatekeeper.registered_users[another_user_id] = UserInfo( + available_slots=available_slots, subscription_expiry=10 + ) - # If two appointments with the same locator from the same user are added, they are overwritten, but if they come - # from different users, they are kept. - assert len(watcher.locator_uuid_map[appointment.locator]) == 1 - - different_user_pk = get_random_value_hex(33) - added_appointment, sig = watcher.add_appointment(appointment, different_user_pk) - assert added_appointment is True - assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( - Cryptographer.recover_pk(appointment.serialize(), sig) - ) - assert len(watcher.locator_uuid_map[appointment.locator]) == 2 + appointment_signature = Cryptographer.sign(appointment.serialize(), another_user_sk) + response = watcher.add_appointment(appointment, appointment_signature) + assert response.get("locator") == appointment.locator + assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( + Cryptographer.recover_pk(appointment.serialize(), response.get("signature")) + ) + assert response.get("available_slots") == available_slots - 1 + assert len(watcher.locator_uuid_map[appointment.locator]) == 2 def test_add_too_many_appointments(watcher): - # Any appointment on top of those should fail + # Simulate the user is registered + user_sk, user_pk = generate_keypair() + available_slots = 100 + user_id = Cryptographer.get_compressed_pk(user_pk) + watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=available_slots, subscription_expiry=10) + + # Appointments on top of the limit should be rejected watcher.appointments = dict() - for _ in range(MAX_APPOINTMENTS): - appointment, dispute_tx = generate_dummy_appointment( - start_time_offset=START_TIME_OFFSET, end_time_offset=END_TIME_OFFSET - ) - user_pk = get_random_value_hex(33) + for i in range(MAX_APPOINTMENTS): + appointment, dispute_tx = generate_dummy_appointment() + appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) - added_appointment, sig = watcher.add_appointment(appointment, user_pk) - - assert added_appointment is True + response = watcher.add_appointment(appointment, appointment_signature) + assert response.get("locator") == appointment.locator assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( - Cryptographer.recover_pk(appointment.serialize(), sig) + Cryptographer.recover_pk(appointment.serialize(), response.get("signature")) ) + assert response.get("available_slots") == available_slots - (i + 1) - appointment, dispute_tx = generate_dummy_appointment( - start_time_offset=START_TIME_OFFSET, end_time_offset=END_TIME_OFFSET - ) - user_pk = get_random_value_hex(33) - added_appointment, sig = watcher.add_appointment(appointment, user_pk) - - assert added_appointment is False - assert sig is None + with pytest.raises(AppointmentLimitReached): + appointment, dispute_tx = generate_dummy_appointment() + appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) + watcher.add_appointment(appointment, appointment_signature) def test_do_watch(watcher, temp_db_manager): @@ -190,9 +182,18 @@ def test_do_watch(watcher, temp_db_manager): # Set the data into the Watcher and in the db watcher.locator_uuid_map = locator_uuid_map watcher.appointments = {} + watcher.gatekeeper.registered_users = {} + # Simulate a register + user_id = get_random_value_hex(16) + watcher.gatekeeper.registered_users[user_id] = UserInfo( + available_slots=100, subscription_expiry=watcher.block_processor.get_block_count() + 10 + ) + + # Add the appointments for uuid, appointment in appointments.items(): - watcher.appointments[uuid] = {"locator": appointment.locator, "end_time": appointment.end_time, "size": 200} + watcher.appointments[uuid] = {"locator": appointment.locator, "user_id": user_id, "size": 200} + watcher.gatekeeper.registered_users[user_id].appointments.append(uuid) watcher.db_manager.store_watcher_appointment(uuid, appointment.to_dict()) watcher.db_manager.create_append_locator_map(appointment.locator, uuid) @@ -203,14 +204,14 @@ def test_do_watch(watcher, temp_db_manager): for dispute_tx in dispute_txs[:2]: bitcoin_cli(bitcoind_connect_params).sendrawtransaction(dispute_tx) - # After generating enough blocks, the number of appointments should have reduced by two - generate_blocks(START_TIME_OFFSET + END_TIME_OFFSET) + # After generating a block, the appointment count should have been reduced by 2 (two breaches) + generate_blocks_w_delay(1) assert len(watcher.appointments) == APPOINTMENTS - 2 - # The rest of appointments will timeout after the end (2) + EXPIRY_DELTA + # The rest of appointments will timeout after the subscription times-out (9 more blocks) + EXPIRY_DELTA # Wait for an additional block to be safe - generate_blocks(config.get("EXPIRY_DELTA") + START_TIME_OFFSET + END_TIME_OFFSET) + generate_blocks_w_delay(10 + config.get("EXPIRY_DELTA")) assert len(watcher.appointments) == 0 @@ -242,7 +243,7 @@ def test_filter_valid_breaches_random_data(watcher): for i in range(TEST_SET_SIZE): dummy_appointment, _ = generate_dummy_appointment() uuid = uuid4().hex - appointments[uuid] = {"locator": dummy_appointment.locator, "end_time": dummy_appointment.end_time} + appointments[uuid] = {"locator": dummy_appointment.locator, "user_id": dummy_appointment.user_id} watcher.db_manager.store_watcher_appointment(uuid, dummy_appointment.to_dict()) watcher.db_manager.create_append_locator_map(dummy_appointment.locator, uuid) @@ -282,7 +283,7 @@ def test_filter_valid_breaches(watcher): breaches = {dummy_appointment.locator: dispute_txid} for uuid, appointment in appointments.items(): - watcher.appointments[uuid] = {"locator": appointment.locator, "end_time": appointment.end_time} + watcher.appointments[uuid] = {"locator": appointment.locator, "user_id": appointment.user_id} watcher.db_manager.store_watcher_appointment(uuid, dummy_appointment.to_dict()) watcher.db_manager.create_append_locator_map(dummy_appointment.locator, uuid)