def test_add_appointment(watcher, generate_dummy_appointment): # 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=watcher.block_processor.get_block_count() + 1) appointment, dispute_tx = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), 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( receipts.create_appointment_receipt( appointment_signature, response.get("start_block")), response.get("signature"), )) assert response.get("available_slots") == available_slots - 1 # 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( receipts.create_appointment_receipt( appointment_signature, response.get("start_block")), 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 # 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=watcher.block_processor.get_block_count() + 1) 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( receipts.create_appointment_receipt( appointment_signature, response.get("start_block")), response.get("signature"), )) assert response.get("available_slots") == available_slots - 1 assert len(watcher.locator_uuid_map[appointment.locator]) == 2
def test_add_appointment_multiple_times_different_users( internal_api, client, appointment, block_processor, n=MULTIPLE_APPOINTMENTS): # If the same appointment comes from different users, all are kept # Create user keys and appointment signatures user_keys = [generate_keypair() for _ in range(n)] signatures = [ Cryptographer.sign(appointment.serialize(), key[0]) for key in user_keys ] tmp_user_ids = [Cryptographer.get_compressed_pk(pk) for _, pk in user_keys] # Add one slot per public key for pair in user_keys: user_id = Cryptographer.get_compressed_pk(pair[1]) internal_api.watcher.gatekeeper.registered_users[user_id] = UserInfo( available_slots=1, subscription_expiry=block_processor.get_block_count() + 1) # Send the appointments for compressed_pk, signature in zip(tmp_user_ids, signatures): r = add_appointment(client, { "appointment": appointment.to_dict(), "signature": signature }, compressed_pk) assert r.status_code == HTTP_OK assert r.json.get("available_slots") == 0 assert r.json.get("start_block") == block_processor.get_block_count() # Check that all the appointments have been added and that there are no duplicates assert len(set( internal_api.watcher.locator_uuid_map[appointment.locator])) == n
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, gatekeeper, carrier, block_processor), sk_der=generate_keypair()[0].to_der(), max_appointments=config.get("MAX_APPOINTMENTS"), blocks_in_cache=config.get("LOCATOR_CACHE_SIZE"), ) blocks = [] for _ in range(5): generate_block() blocks.append(bitcoin_cli(bitcoind_connect_params).getbestblockhash()) # Updating the states should bring both to the same last known block. w.awake() w.responder.awake() Builder.update_states(w, blocks, blocks[1:]) assert db_manager.load_last_block_hash_watcher() == blocks[-1] assert w.responder.last_known_block == blocks[-1]
def test_add_too_many_appointments(watcher): # 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 i in range(MAX_APPOINTMENTS): appointment, dispute_tx = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), 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 - (i + 1) 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_add_appointment_in_cache_invalid_transaction( watcher, generate_dummy_appointment): # Generate an appointment that cannot be decrypted and add the dispute txid to the cache user_sk, user_pk = generate_keypair() user_id = Cryptographer.get_compressed_pk(user_pk) watcher.gatekeeper.registered_users[user_id] = UserInfo( available_slots=1, subscription_expiry=watcher.block_processor.get_block_count() + 1) appointment, dispute_tx = generate_dummy_appointment() appointment.encrypted_blob = appointment.encrypted_blob[::-1] dispute_txid = watcher.block_processor.decode_raw_transaction( dispute_tx).get("txid") watcher.locator_cache.cache[appointment.locator] = dispute_txid # Try to add the appointment user_signature = Cryptographer.sign(appointment.serialize(), user_sk) response = watcher.add_appointment(appointment, user_signature) appointment_receipt = receipts.create_appointment_receipt( user_signature, response.get("start_block")) # The appointment is accepted but dropped (same as an invalid appointment that gets triggered) assert (response and response.get("locator") == appointment.locator and Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( Cryptographer.recover_pk(appointment_receipt, response.get("signature")))) assert not watcher.locator_uuid_map.get(appointment.locator) assert appointment.locator not in [ tracker.get("locator") for tracker in watcher.responder.trackers.values() ]
def test_get_appointment_non_registered(internal_api, stub, generate_dummy_appointment): # If the user is not registered or the appointment does not belong to him the response should be NOT_FOUND stub.register(RegisterRequest(user_id=user_id)) another_user_sk, another_user_pk = generate_keypair() # Send the appointment first appointment, _ = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) send_appointment(stub, appointment, appointment_signature) # Request it back message = f"get appointment {appointment.locator}" request_signature = Cryptographer.sign(message.encode("utf-8"), another_user_sk) with pytest.raises(grpc.RpcError) as e: stub.get_appointment( GetAppointmentRequest(locator=appointment.locator, signature=request_signature)) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert "Appointment not found" in e.value.details() # Notice how the request will succeed if `user` (user_id) requests it request_signature = Cryptographer.sign(message.encode("utf-8"), user_sk) response = stub.get_appointment( GetAppointmentRequest(locator=appointment.locator, signature=request_signature)) assert isinstance(response, GetAppointmentResponse)
def test_inspect(run_bitcoind): # At this point every single check function has been already tested, let's test inspect with an invalid and a valid # appointments. client_sk, client_pk = generate_keypair() client_pk_hex = client_pk.format().hex() # Valid appointment locator = get_random_value_hex(LOCATOR_LEN_BYTES) start_time = block_processor.get_block_count() + 5 end_time = start_time + 20 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, } signature = Cryptographer.sign( Appointment.from_dict(appointment_data).serialize(), client_sk) appointment = inspector.inspect(appointment_data, signature, client_pk_hex) assert (type(appointment) == Appointment 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.data == encrypted_blob)
def test_add_appointment_in_cache(watcher): # Generate an appointment and add the dispute txid to the cache user_sk, user_pk = generate_keypair() user_id = Cryptographer.get_compressed_pk(user_pk) watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=1, subscription_expiry=10) appointment, dispute_tx = generate_dummy_appointment() dispute_txid = watcher.block_processor.decode_raw_transaction(dispute_tx).get("txid") watcher.locator_cache.cache[appointment.locator] = dispute_txid # Try to add the appointment response = watcher.add_appointment(appointment, Cryptographer.sign(appointment.serialize(), user_sk)) # The appointment is accepted but it's not in the Watcher assert ( response and response.get("locator") == appointment.locator and Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(Cryptographer.recover_pk(appointment.serialize(), response.get("signature"))) ) assert not watcher.locator_uuid_map.get(appointment.locator) # It went to the Responder straightaway assert appointment.locator in [tracker.get("locator") for tracker in watcher.responder.trackers.values()] # Trying to send it again should fail since it is already in the Responder with pytest.raises(AppointmentAlreadyTriggered): watcher.add_appointment(appointment, Cryptographer.sign(appointment.serialize(), user_sk))
def test_get_appointment_not_registered_user(client): # Not registered users have no associated appointments, so this should fail tmp_sk, tmp_pk = generate_keypair() # The tower is designed so a not found appointment and a request from a non-registered user return the same error to # prevent probing. test_get_random_appointment_registered_user(client, tmp_sk)
def test_add_appointment(watcher, generate_dummy_appointment, monkeypatch): # A registered user with no subscription issues should be able to add an appointment appointment = generate_dummy_appointment() # Mock a registered user expiry = 100 user_info = UserInfo(MAX_APPOINTMENTS, expiry) monkeypatch.setattr(watcher.gatekeeper, "authenticate_user", lambda x, y: user_id) monkeypatch.setattr(watcher.gatekeeper, "has_subscription_expired", lambda x: (False, expiry)) monkeypatch.setattr(watcher.responder, "has_tracker", lambda x: False) monkeypatch.setattr(watcher.gatekeeper, "add_update_appointment", lambda x, y, z: MAX_APPOINTMENTS - 1) monkeypatch.setattr(watcher.gatekeeper, "get_user_info", lambda x: user_info) response = watcher.add_appointment(appointment, appointment.user_signature) assert response.get("locator") == appointment.locator assert Cryptographer.get_compressed_pk( watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( Cryptographer.recover_pk( receipts.create_appointment_receipt( appointment.user_signature, response.get("start_block")), response.get("signature"), )) # Check that we can also add an already added appointment (same locator) response = watcher.add_appointment(appointment, appointment.user_signature) assert response.get("locator") == appointment.locator assert Cryptographer.get_compressed_pk( watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( Cryptographer.recover_pk( receipts.create_appointment_receipt( appointment.user_signature, response.get("start_block")), response.get("signature"), )) # One one copy is kept since the appointments were the same # (the slot count should have not been reduced, but that's something to be tested in the Gatekeeper) assert len(watcher.locator_uuid_map[appointment.locator]) == 1 # 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) monkeypatch.setattr(watcher.gatekeeper, "authenticate_user", lambda x, y: another_user_id) 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( receipts.create_appointment_receipt( appointment_signature, response.get("start_block")), response.get("signature"), )) assert len(watcher.locator_uuid_map[appointment.locator]) == 2
def test_identify_user_non_registered(gatekeeper): # Non-registered user won't be identified sk, pk = generate_keypair() message = "Hey, it's me" signature = Cryptographer.sign(message.encode("utf-8"), sk) with pytest.raises(AuthenticationFailure): gatekeeper.authenticate_user(message.encode("utf-8"), signature)
def test_add_appointment_non_registered(watcher): # Appointments from non-registered users should fail user_sk, user_pk = generate_keypair() appointment, dispute_tx = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) with pytest.raises(AuthenticationFailure, match="User not found"): watcher.add_appointment(appointment, appointment_signature)
def test_add_appointment_not_registered(api, client, appointment): # Properly formatted appointment, user is not registered tmp_sk, tmp_pk = generate_keypair() tmp_compressed_pk = hexlify(tmp_pk.format(compressed=True)).decode("utf-8") appointment_signature = Cryptographer.sign(appointment.serialize(), tmp_sk) r = add_appointment( client, {"appointment": appointment.to_dict(), "signature": appointment_signature}, tmp_compressed_pk ) assert r.status_code == HTTP_BAD_REQUEST assert errors.APPOINTMENT_INVALID_SIGNATURE_OR_INSUFFICIENT_SLOTS == r.json.get("error_code")
def test_add_appointment_no_slots(watcher): # Appointments from register users with no available slots should aso fail user_sk, user_pk = generate_keypair() user_id = Cryptographer.get_compressed_pk(user_pk) watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=0, subscription_expiry=10) appointment, dispute_tx = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) with pytest.raises(NotEnoughSlots): watcher.add_appointment(appointment, appointment_signature)
def test_check_appointment_signature(): # The inspector receives the public key as hex client_sk, client_pk = generate_keypair() client_pk_hex = client_pk.format().hex() dummy_appointment_data, _ = generate_dummy_appointment_data( real_height=False) assert Inspector.check_appointment_signature( dummy_appointment_data["appointment"], dummy_appointment_data["signature"], dummy_appointment_data["public_key"]) fake_sk, _ = generate_keypair() # Create a bad signature to make sure inspector rejects it bad_signature = Cryptographer.sign( Appointment.from_dict( dummy_appointment_data["appointment"]).serialize(), fake_sk) assert (Inspector.check_appointment_signature( dummy_appointment_data["appointment"], bad_signature, client_pk_hex)[0] == APPOINTMENT_INVALID_SIGNATURE)
def test_add_appointment_non_registered(internal_api, stub, generate_dummy_appointment): # If the user is not registered we should get UNAUTHENTICATED + the proper message another_user_sk, another_user_pk = generate_keypair() appointment, _ = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), another_user_sk) e = send_wrong_appointment(stub, appointment, appointment_signature) assert e.value.code() == grpc.StatusCode.UNAUTHENTICATED assert "Invalid signature or user does not have enough slots available" in e.value.details( )
def test_identify_user(gatekeeper): # Identify user should return a user_pk for registered users. It raises # IdentificationFailure for invalid parameters or non-registered users. # Let's first register a user sk, pk = generate_keypair() user_id = Cryptographer.get_compressed_pk(pk) gatekeeper.add_update_user(user_id) message = "Hey, it's me" signature = Cryptographer.sign(message.encode(), sk) assert gatekeeper.authenticate_user(message.encode(), signature) == user_id
def test_add_appointment_not_registered(internal_api, client, appointment): # Properly formatted appointment, user is not registered tmp_sk, tmp_pk = generate_keypair() tmp_user_id = Cryptographer.get_compressed_pk(tmp_pk) appointment_signature = Cryptographer.sign(appointment.serialize(), tmp_sk) r = add_appointment(client, { "appointment": appointment.to_dict(), "signature": appointment_signature }, tmp_user_id) assert r.status_code == HTTP_BAD_REQUEST assert errors.APPOINTMENT_INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR == r.json.get( "error_code")
def test_add_update_appointment(gatekeeper, generate_dummy_appointment): # add_update_appointment should decrease the slot count if a new appointment is added # let's add a new user sk, pk = generate_keypair() user_id = Cryptographer.get_compressed_pk(pk) gatekeeper.add_update_user(user_id) # And now update add a new appointment appointment, _ = generate_dummy_appointment() appointment_uuid = get_random_value_hex(16) remaining_slots = gatekeeper.add_update_appointment( user_id, appointment_uuid, appointment) # This is a standard size appointment, so it should have reduced the slots by one assert appointment_uuid in gatekeeper.registered_users[ user_id].appointments assert remaining_slots == config.get("SUBSCRIPTION_SLOTS") - 1 # Updates can leave the count as is, decrease it, or increase it, depending on the appointment size (modulo # ENCRYPTED_BLOB_MAX_SIZE_HEX) # Appointments of the same size leave it as is appointment_same_size, _ = generate_dummy_appointment() remaining_slots = gatekeeper.add_update_appointment( user_id, appointment_uuid, appointment) assert appointment_uuid in gatekeeper.registered_users[ user_id].appointments assert remaining_slots == config.get("SUBSCRIPTION_SLOTS") - 1 # Bigger appointments decrease it appointment_x2_size = appointment_same_size appointment_x2_size.encrypted_blob = "A" * (ENCRYPTED_BLOB_MAX_SIZE_HEX + 1) remaining_slots = gatekeeper.add_update_appointment( user_id, appointment_uuid, appointment_x2_size) assert appointment_uuid in gatekeeper.registered_users[ user_id].appointments assert remaining_slots == config.get("SUBSCRIPTION_SLOTS") - 2 # Smaller appointments increase it remaining_slots = gatekeeper.add_update_appointment( user_id, appointment_uuid, appointment) assert remaining_slots == config.get("SUBSCRIPTION_SLOTS") - 1 # If the appointment needs more slots than there's free, it should fail gatekeeper.registered_users[user_id].available_slots = 1 appointment_uuid = get_random_value_hex(16) with pytest.raises(NotEnoughSlots): gatekeeper.add_update_appointment(user_id, appointment_uuid, appointment_x2_size)
def test_register_top_up(client, api): # Calling register more than once will give us SUBSCRIPTION_SLOTS * number_of_calls slots. # It will also refresh the expiry. temp_sk, tmp_pk = generate_keypair() tmp_user_id = hexlify(tmp_pk.format(compressed=True)).decode("utf-8") current_height = api.watcher.block_processor.get_block_count() data = {"public_key": tmp_user_id} for i in range(10): r = client.post(register_endpoint, json=data) assert r.status_code == HTTP_OK assert r.json.get("public_key") == tmp_user_id assert r.json.get("available_slots") == config.get("SUBSCRIPTION_SLOTS") * (i + 1) assert r.json.get("subscription_expiry") == current_height + config.get("SUBSCRIPTION_DURATION")
def test_add_appointment_in_cache_invalid_blob(watcher): # Generate an appointment with an invalid transaction and add the dispute txid to the cache user_sk, user_pk = generate_keypair() user_id = Cryptographer.get_compressed_pk(user_pk) watcher.gatekeeper.registered_users[user_id] = UserInfo( available_slots=1, subscription_expiry=watcher.block_processor.get_block_count() + 1) # We need to create the appointment manually commitment_tx, commitment_txid, penalty_tx = create_txs() locator = compute_locator(commitment_tx) dummy_appointment_data = { "tx": penalty_tx, "tx_id": commitment_txid, "to_self_delay": 20 } encrypted_blob = Cryptographer.encrypt(penalty_tx[::-1], commitment_txid) appointment_data = { "locator": locator, "to_self_delay": dummy_appointment_data.get("to_self_delay"), "encrypted_blob": encrypted_blob, "user_id": get_random_value_hex(16), } appointment = Appointment.from_dict(appointment_data) watcher.locator_cache.cache[appointment.locator] = commitment_txid # Try to add the appointment user_signature = Cryptographer.sign(appointment.serialize(), user_sk) response = watcher.add_appointment(appointment, user_signature) appointment_receipt = receipts.create_appointment_receipt( user_signature, response.get("start_block")) # The appointment is accepted but dropped (same as an invalid appointment that gets triggered) assert (response and response.get("locator") == appointment.locator and Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( Cryptographer.recover_pk(appointment_receipt, response.get("signature")))) assert not watcher.locator_uuid_map.get(appointment.locator) assert appointment.locator not in [ tracker.get("locator") for tracker in watcher.responder.trackers.values() ]
def test_add_appointment_expired_subscription(watcher, generate_dummy_appointment): # Appointments from registered users with expired subscriptions fail as well user_sk, user_pk = generate_keypair() user_id = Cryptographer.get_compressed_pk(user_pk) watcher.gatekeeper.registered_users[user_id] = UserInfo( available_slots=10, subscription_expiry=watcher.block_processor.get_block_count() - watcher.gatekeeper.expiry_delta - 1, ) appointment, dispute_tx = generate_dummy_appointment() appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk) with pytest.raises(SubscriptionExpired): watcher.add_appointment(appointment, appointment_signature)
def api(db_manager, carrier, block_processor, gatekeeper, run_bitcoind): sk, pk = generate_keypair() responder = Responder(db_manager, gatekeeper, carrier, block_processor) watcher = Watcher( db_manager, gatekeeper, block_processor, responder, sk.to_der(), MAX_APPOINTMENTS, config.get("LOCATOR_CACHE_SIZE"), ) inspector = Inspector(block_processor, config.get("MIN_TO_SELF_DELAY")) api = API(config.get("API_HOST"), config.get("API_PORT"), inspector, watcher) return api
def ext_appointment_data(): sk, pk = generate_keypair() locator = get_random_value_hex(LOCATOR_LEN_BYTES) encrypted_blob_data = get_random_value_hex(100) to_self_delay = 20 user_id = Cryptographer.get_compressed_pk(pk) user_signature = Cryptographer.sign(encrypted_blob_data.encode("utf-8"), sk) start_block = 300 return { "locator": locator, "to_self_delay": to_self_delay, "encrypted_blob": encrypted_blob_data, "user_id": user_id, "user_signature": user_signature, "start_block": start_block, }
def test_identify_user_wrong(gatekeeper): # Wrong parameters shouldn't verify either sk, pk = generate_keypair() message = "Hey, it's me" signature = Cryptographer.sign(message.encode(), sk) # Non-byte message and str sig with pytest.raises(AuthenticationFailure): gatekeeper.authenticate_user(message, signature) # byte message and non-str sig with pytest.raises(AuthenticationFailure): gatekeeper.authenticate_user(message.encode(), signature.encode()) # non-byte message and non-str sig with pytest.raises(AuthenticationFailure): gatekeeper.authenticate_user(message, signature.encode())
def test_authenticate_user(gatekeeper, monkeypatch): # Authenticate user should return a user_pk for registered users. It raises IdentificationFailure for invalid # parameters or non-registered users. # Mock the required BlockProcessor calls from the Gatekeeper monkeypatch.setattr(gatekeeper.block_processor, "get_block_count", lambda: 0) # Let's first register a user sk, pk = generate_keypair() user_id = Cryptographer.get_compressed_pk(pk) # Mock the user registration monkeypatch.setitem(gatekeeper.registered_users, user_id, {}) message = "Hey, it's me" signature = Cryptographer.sign(message.encode("utf-8"), sk) assert gatekeeper.authenticate_user(message.encode("utf-8"), signature) == user_id
def test_add_appointment_multiple_times_different_users(api, client, appointment, n=MULTIPLE_APPOINTMENTS): # If the same appointment comes from different users, all are kept # Create user keys and appointment signatures user_keys = [generate_keypair() for _ in range(n)] signatures = [Cryptographer.sign(appointment.serialize(), key[0]) for key in user_keys] compressed_pks = [hexlify(pk.format(compressed=True)).decode("utf-8") for sk, pk in user_keys] # Add one slot per public key for pair in user_keys: tmp_compressed_pk = hexlify(pair[1].format(compressed=True)).decode("utf-8") 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): r = add_appointment(client, {"appointment": appointment.to_dict(), "signature": signature}, compressed_pk) assert r.status_code == HTTP_OK assert r.json.get("available_slots") == 1 assert r.json.get("start_block") == api.watcher.last_known_block # Check that all the appointments have been added and that there are no duplicates assert len(set(api.watcher.locator_uuid_map[appointment.locator])) == n
def test_get_subscription_info(block_processor, watcher, generate_dummy_appointment, generate_dummy_tracker): user_sk, user_pk = generate_keypair() user_id = Cryptographer.get_compressed_pk(user_pk) # Need to simulate registration for this user. available_slots = 1 subscription_expiry = block_processor.get_block_count() + 1 watcher.gatekeeper.registered_users[user_id] = UserInfo( available_slots, subscription_expiry) message = "get subscription info" signature = Cryptographer.sign(message.encode("utf-8"), user_sk) sub_info, locators = watcher.get_subscription_info(signature) assert len(locators) == 0 assert sub_info.available_slots == available_slots assert sub_info.subscription_expiry == subscription_expiry uuid = get_random_value_hex(32) uuid2 = get_random_value_hex(32) appointment, _ = generate_dummy_appointment() tracker = generate_dummy_tracker() watcher.appointments = {uuid: appointment.get_summary()} watcher.responder.trackers = {uuid2: tracker.get_summary()} # The gatekeeper appointments dict format is of the form uuid:required_slots watcher.gatekeeper.registered_users[user_id].appointments[uuid] = {uuid: 1} watcher.gatekeeper.registered_users[user_id].appointments[uuid2] = { uuid2: 1 } sub_info, locators = watcher.get_subscription_info(signature) assert set(locators) == set([appointment.locator, tracker.locator]) assert sub_info.available_slots == available_slots assert sub_info.subscription_expiry == subscription_expiry
def run_api(db_manager, carrier, block_processor): sk, pk = generate_keypair() responder = Responder(db_manager, carrier, block_processor) watcher = Watcher(db_manager, block_processor, responder, sk.to_der(), config.get("MAX_APPOINTMENTS"), config.get("EXPIRY_DELTA")) chain_monitor = ChainMonitor(watcher.block_queue, watcher.responder.block_queue, block_processor, bitcoind_feed_params) watcher.awake() chain_monitor.monitor_chain() api_thread = Thread( target=API(Inspector(block_processor, config.get("MIN_TO_SELF_DELAY")), watcher).start) api_thread.daemon = True api_thread.start() # It takes a little bit of time to start the API (otherwise the requests are sent too early and they fail) sleep(0.1)
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, gatekeeper, carrier, block_processor), sk_der=generate_keypair()[0].to_der(), max_appointments=config.get("MAX_APPOINTMENTS"), blocks_in_cache=config.get("LOCATOR_CACHE_SIZE"), ) missed_blocks_watcher = [] missed_blocks_responder = [get_random_value_hex(32)] # Any combination of empty list must raise a ValueError with pytest.raises(ValueError): Builder.update_states(w, missed_blocks_watcher, missed_blocks_responder) with pytest.raises(ValueError): Builder.update_states(w, missed_blocks_responder, missed_blocks_watcher)