Adaps unit tests. Speeds up tests by removing unnecesary sleeps

This commit is contained in:
Sergi Delgado Segura
2020-04-16 19:05:07 +02:00
parent 8fad4d79cc
commit eb8ffb4916
12 changed files with 303 additions and 443 deletions

View File

@@ -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 # The appointment has no checks whatsoever, since the inspector is the one taking care or that, and the only one
# creating appointments. # creating appointments.
appointment = Appointment( appointment = Appointment(
appointment_data["locator"], appointment_data["locator"], appointment_data["to_self_delay"], appointment_data["encrypted_blob"]
appointment_data["start_time"],
appointment_data["end_time"],
appointment_data["to_self_delay"],
appointment_data["encrypted_blob"],
) )
assert ( assert (
appointment_data["locator"] == appointment.locator 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["to_self_delay"] == appointment.to_self_delay
and appointment_data["encrypted_blob"] == appointment.encrypted_blob and appointment_data["encrypted_blob"] == appointment.encrypted_blob
) )
@@ -49,19 +43,13 @@ def test_init_appointment(appointment_data):
def test_to_dict(appointment_data): def test_to_dict(appointment_data):
appointment = Appointment( appointment = Appointment(
appointment_data["locator"], appointment_data["locator"], appointment_data["to_self_delay"], appointment_data["encrypted_blob"]
appointment_data["start_time"],
appointment_data["end_time"],
appointment_data["to_self_delay"],
appointment_data["encrypted_blob"],
) )
dict_appointment = appointment.to_dict() dict_appointment = appointment.to_dict()
assert ( assert (
appointment_data["locator"] == dict_appointment["locator"] 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["to_self_delay"] == dict_appointment["to_self_delay"]
and appointment_data["encrypted_blob"] == dict_appointment["encrypted_blob"] and appointment_data["encrypted_blob"] == dict_appointment["encrypted_blob"]
) )
@@ -94,13 +82,9 @@ def test_serialize(appointment_data):
assert isinstance(serialized_appointment, bytes) assert isinstance(serialized_appointment, bytes)
locator = serialized_appointment[:16] locator = serialized_appointment[:16]
start_time = serialized_appointment[16:20] to_self_delay = serialized_appointment[16:20]
end_time = serialized_appointment[20:24] encrypted_blob = serialized_appointment[20:]
to_self_delay = serialized_appointment[24:28]
encrypted_blob = serialized_appointment[28:]
assert binascii.hexlify(locator).decode() == appointment.locator 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 struct.unpack(">I", to_self_delay)[0] == appointment.to_self_delay
assert binascii.hexlify(encrypted_blob).decode() == appointment.encrypted_blob assert binascii.hexlify(encrypted_blob).decode() == appointment.encrypted_blob

View File

@@ -12,15 +12,14 @@ from bitcoind_mock.transaction import create_dummy_transaction
from teos import DEFAULT_CONF from teos import DEFAULT_CONF
from teos.carrier import Carrier from teos.carrier import Carrier
from teos.tools import bitcoin_cli
from teos.users_dbm import UsersDBM from teos.users_dbm import UsersDBM
from teos.gatekeeper import Gatekeeper from teos.gatekeeper import Gatekeeper
from teos.responder import TransactionTracker from teos.responder import TransactionTracker
from teos.block_processor import BlockProcessor from teos.block_processor import BlockProcessor
from teos.appointments_dbm import AppointmentsDBM from teos.appointments_dbm import AppointmentsDBM
from teos.extended_appointment import ExtendedAppointment
from common.tools import compute_locator from common.tools import compute_locator
from common.appointment import Appointment
from common.constants import LOCATOR_LEN_HEX from common.constants import LOCATOR_LEN_HEX
from common.config_loader import ConfigLoader from common.config_loader import ConfigLoader
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
@@ -81,8 +80,14 @@ def block_processor():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def gatekeeper(user_db_manager): def gatekeeper(user_db_manager, block_processor):
return Gatekeeper(user_db_manager, get_config().get("DEFAULT_SLOTS")) 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(): def generate_keypair():
@@ -98,11 +103,21 @@ def get_random_value_hex(nbytes):
return prv_hex.zfill(2 * 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) requests.post(url="http://{}:{}/generate".format(BTC_RPC_HOST, BTC_RPC_PORT), timeout=5)
sleep(0.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): def generate_blocks(n):
for _ in range(n): for _ in range(n):
generate_block() generate_block()
@@ -113,38 +128,23 @@ def fork(block_hash):
requests.post(fork_endpoint, json={"parent": 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): def generate_dummy_appointment():
if real_height:
current_height = bitcoin_cli(bitcoind_connect_params).getblockcount()
else:
current_height = 10
dispute_tx = create_dummy_transaction() dispute_tx = create_dummy_transaction()
dispute_txid = dispute_tx.tx_id.hex() dispute_txid = dispute_tx.tx_id.hex()
penalty_tx = create_dummy_transaction(dispute_txid) 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) 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")) encrypted_blob = Cryptographer.encrypt(dummy_appointment_data.get("tx"), dummy_appointment_data.get("tx_id"))
appointment_data = { appointment_data = {
"locator": locator, "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"), "to_self_delay": dummy_appointment_data.get("to_self_delay"),
"encrypted_blob": encrypted_blob, "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(): def generate_dummy_tracker():
@@ -158,7 +158,7 @@ def generate_dummy_tracker():
dispute_txid=dispute_txid, dispute_txid=dispute_txid,
penalty_txid=penalty_txid, penalty_txid=penalty_txid,
penalty_rawtx=penalty_rawtx, penalty_rawtx=penalty_rawtx,
appointment_end=100, user_id=get_random_value_hex(16),
) )
return TransactionTracker.from_dict(tracker_data) return TransactionTracker.from_dict(tracker_data)

View File

@@ -6,6 +6,7 @@ from teos.api import API
import teos.errors as errors import teos.errors as errors
from teos.watcher import Watcher from teos.watcher import Watcher
from teos.inspector import Inspector from teos.inspector import Inspector
from teos.gatekeeper import UserInfo
from teos.appointments_dbm import AppointmentsDBM from teos.appointments_dbm import AppointmentsDBM
from teos.responder import Responder, TransactionTracker 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): def api(db_manager, carrier, block_processor, gatekeeper, run_bitcoind):
sk, pk = generate_keypair() sk, pk = generate_keypair()
responder = Responder(db_manager, carrier, block_processor) responder = Responder(db_manager, gatekeeper, carrier, block_processor)
watcher = Watcher(db_manager, block_processor, responder, sk.to_der(), MAX_APPOINTMENTS, config.get("EXPIRY_DELTA")) watcher = Watcher(db_manager, gatekeeper, block_processor, responder, sk.to_der(), MAX_APPOINTMENTS)
inspector = Inspector(block_processor, config.get("MIN_TO_SELF_DELAY")) 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 return api
@@ -141,8 +142,8 @@ def test_register_json_no_inner_dict(client):
def test_add_appointment(api, client, appointment): def test_add_appointment(api, client, appointment):
# Simulate the user registration # Simulate the user registration (end time does not matter here)
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)
# Properly formatted appointment # Properly formatted appointment
appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) 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): def test_add_appointment_no_json(api, client, appointment):
# Simulate the user registration # Simulate the user registration (end time does not matter here)
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)
# Properly formatted appointment # Properly formatted appointment
r = client.post(add_appointment_endpoint, data="random_message") 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): def test_add_appointment_json_no_inner_dict(api, client, appointment):
# Simulate the user registration # Simulate the user registration (end time does not matter here)
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)
# Properly formatted appointment # Properly formatted appointment
r = client.post(add_appointment_endpoint, json="random_message") 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): def test_add_appointment_wrong(api, client, appointment):
# Simulate the user registration # Simulate the user registration (end time does not matter here)
api.gatekeeper.registered_users[compressed_client_pk] = 1 api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=1, subscription_expiry=0)
# Incorrect appointment # Incorrect appointment
appointment.to_self_delay = 0 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): def test_add_appointment_registered_no_free_slots(api, client, appointment):
# Empty the user slots # Empty the user slots (end time does not matter here)
api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": 0} api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=0, subscription_expiry=0)
# Properly formatted appointment # Properly formatted appointment
appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) 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): def test_add_appointment_registered_not_enough_free_slots(api, client, appointment):
# Give some slots to the user # Give some slots to the user (end time does not matter here)
api.gatekeeper.registered_users[compressed_client_pk] = 1 api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=1, subscription_expiry=0)
# Properly formatted appointment # Properly formatted appointment
appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) 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 # Multiple appointments with the same locator should be valid and counted as updates
appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk)
# Simulate registering enough slots # Simulate registering enough slots (end time does not matter here)
api.gatekeeper.registered_users[compressed_client_pk] = {"available_slots": n} api.watcher.gatekeeper.registered_users[compressed_client_pk] = UserInfo(available_slots=n, subscription_expiry=0)
for _ in range(n): for _ in range(n):
r = add_appointment( r = add_appointment(
client, {"appointment": appointment.to_dict(), "signature": appointment_signature}, compressed_client_pk 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 # Add one slot per public key
for pair in user_keys: for pair in user_keys:
tmp_compressed_pk = hexlify(pair[1].format(compressed=True)).decode("utf-8") 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 # Send the appointments
for compressed_pk, signature in zip(compressed_pks, signatures): 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): 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 # 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) 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( r = add_appointment(
client, {"appointment": appointment.to_dict(), "signature": appointment_signature}, compressed_client_pk 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): def test_add_appointment_update_bigger(api, client, appointment):
# Update an appointment by one bigger, and check additional slots are filled # 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) appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk)
r = add_appointment( 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): def test_add_appointment_update_smaller(api, client, appointment):
# Update an appointment by one bigger, and check slots are freed # 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 # This should take 2 slots
appointment.encrypted_blob = TWO_SLOTS_BLOTS appointment.encrypted_blob = TWO_SLOTS_BLOTS
appointment_signature = Cryptographer.sign(appointment.serialize(), client_sk) 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): def test_add_too_many_appointment(api, client):
# Give slots to the user # 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) 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" assert r.json.get("status") == "being_watched"
# Check the the sent appointment matches the received one # 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 r.json.get("locator") == appointment.locator
assert appointment.to_dict() == r.json.get("appointment") 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), "dispute_txid": get_random_value_hex(32),
"penalty_txid": get_random_value_hex(32), "penalty_txid": get_random_value_hex(32),
"penalty_rawtx": get_random_value_hex(250), "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) 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.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_txid == r.json.get("appointment").get("penalty_txid")
assert tx_tracker.penalty_rawtx == r.json.get("appointment").get("penalty_rawtx") 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 # Let's reset the dbs so we can test this clean
api.watcher.db_manager = get_all_db_manager api.watcher.db_manager = get_all_db_manager
api.watcher.responder.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 = {} non_triggered_appointments = {}
for _ in range(10): for _ in range(10):
uuid = get_random_value_hex(16) uuid = get_random_value_hex(16)
appointment, _ = generate_dummy_appointment()
appointment.locator = get_random_value_hex(16) appointment.locator = get_random_value_hex(16)
non_triggered_appointments[uuid] = appointment.to_dict() non_triggered_appointments[uuid] = appointment.to_dict()
api.watcher.db_manager.store_watcher_appointment(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 = {} triggered_appointments = {}
for _ in range(10): for _ in range(10):
uuid = get_random_value_hex(16) uuid = get_random_value_hex(16)
appointment, _ = generate_dummy_appointment()
appointment.locator = get_random_value_hex(16) appointment.locator = get_random_value_hex(16)
triggered_appointments[uuid] = appointment.to_dict() triggered_appointments[uuid] = appointment.to_dict()
api.watcher.db_manager.store_watcher_appointment(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), "dispute_txid": get_random_value_hex(32),
"penalty_txid": get_random_value_hex(32), "penalty_txid": get_random_value_hex(32),
"penalty_rawtx": get_random_value_hex(250), "penalty_rawtx": get_random_value_hex(250),
"appointment_end": 20, "user_id": get_random_value_hex(16),
} }
tracker = TransactionTracker.from_dict(tracker_data) tracker = TransactionTracker.from_dict(tracker_data)
tx_trackers[uuid] = tracker.to_dict() tx_trackers[uuid] = tracker.to_dict()

View File

@@ -19,7 +19,7 @@ from test.teos.unit.conftest import get_random_value_hex, generate_dummy_appoint
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def watcher_appointments(): 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") @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 assert db_watcher_appointments == db_watcher_appointments_with_triggered
# Create an appointment flagged as triggered # Create an appointment flagged as triggered
triggered_appointment, _ = generate_dummy_appointment(real_height=False) triggered_appointment, _ = generate_dummy_appointment()
uuid = uuid4().hex uuid = uuid4().hex
assert db_manager.store_watcher_appointment(uuid, triggered_appointment.to_dict()) is True assert db_manager.store_watcher_appointment(uuid, triggered_appointment.to_dict()) is True
db_manager.create_triggered_appointment_flag(uuid) db_manager.create_triggered_appointment_flag(uuid)

View File

@@ -4,6 +4,7 @@ from queue import Queue
from teos.builder import Builder from teos.builder import Builder
from teos.watcher import Watcher from teos.watcher import Watcher
from teos.tools import bitcoin_cli
from teos.responder import Responder from teos.responder import Responder
from test.teos.unit.conftest import ( from test.teos.unit.conftest import (
@@ -11,7 +12,6 @@ from test.teos.unit.conftest import (
generate_dummy_appointment, generate_dummy_appointment,
generate_dummy_tracker, generate_dummy_tracker,
generate_block, generate_block,
bitcoin_cli,
get_config, get_config,
bitcoind_connect_params, bitcoind_connect_params,
generate_keypair, generate_keypair,
@@ -25,7 +25,7 @@ def test_build_appointments():
# Create some appointment data # Create some appointment data
for i in range(10): for i in range(10):
appointment, _ = generate_dummy_appointment(real_height=False) appointment, _ = generate_dummy_appointment()
uuid = uuid4().hex uuid = uuid4().hex
appointments_data[uuid] = appointment.to_dict() 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 # Add some additional appointments that share the same locator to test all the builder's cases
if i % 2 == 0: if i % 2 == 0:
locator = appointment.locator locator = appointment.locator
appointment, _ = generate_dummy_appointment(real_height=False) appointment, _ = generate_dummy_appointment()
uuid = uuid4().hex uuid = uuid4().hex
appointment.locator = locator appointment.locator = locator
@@ -46,7 +46,7 @@ def test_build_appointments():
for uuid, appointment in appointments.items(): for uuid, appointment in appointments.items():
assert uuid in appointments_data.keys() assert uuid in appointments_data.keys()
assert appointments_data[uuid].get("locator") == appointment.get("locator") 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 len(appointments_data[uuid].get("encrypted_blob")) == appointment.get("size")
assert uuid in locator_uuid_map[appointment.get("locator")] 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("penalty_txid") == trackers_data[uuid].get("penalty_txid")
assert tracker.get("locator") == trackers_data[uuid].get("locator") 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")] assert uuid in tx_tracker_map[tracker.get("penalty_txid")]
@@ -95,14 +95,14 @@ def test_populate_block_queue():
assert len(blocks) == 0 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( w = Watcher(
db_manager=db_manager, db_manager=db_manager,
gatekeeper=gatekeeper,
block_processor=block_processor, 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(), sk_der=generate_keypair()[0].to_der(),
max_appointments=config.get("MAX_APPOINTMENTS"), max_appointments=config.get("MAX_APPOINTMENTS"),
expiry_delta=config.get("EXPIRY_DELTA"),
) )
missed_blocks_watcher = [] 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) 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( w = Watcher(
db_manager=db_manager, db_manager=db_manager,
gatekeeper=gatekeeper,
block_processor=block_processor, 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(), sk_der=generate_keypair()[0].to_der(),
max_appointments=config.get("MAX_APPOINTMENTS"), max_appointments=config.get("MAX_APPOINTMENTS"),
expiry_delta=config.get("EXPIRY_DELTA"),
) )
blocks = [] 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] 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 # Same as before, but data is now in the Responder
w = Watcher( w = Watcher(
db_manager=db_manager, db_manager=db_manager,
gatekeeper=gatekeeper,
block_processor=block_processor, 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(), sk_der=generate_keypair()[0].to_der(),
max_appointments=config.get("MAX_APPOINTMENTS"), max_appointments=config.get("MAX_APPOINTMENTS"),
expiry_delta=config.get("EXPIRY_DELTA"),
) )
blocks = [] blocks = []

View File

@@ -23,7 +23,7 @@ def set_up_appointments(db_manager, total_appointments):
uuid = uuid4().hex uuid = uuid4().hex
locator = get_random_value_hex(LOCATOR_LEN_BYTES) 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} appointments[uuid] = {"locator": appointment.locator}
locator_uuid_map[locator] = [uuid] locator_uuid_map[locator] = [uuid]
@@ -156,7 +156,8 @@ def test_flag_triggered_appointments(db_manager):
assert set(triggered_appointments).issubset(db_appointments) 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 height = 0
for _ in range(ITERATIONS): 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} 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()) 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 height = 0
for _ in range(ITERATIONS): 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} completed_trackers = {tracker: 6 for tracker in selected_trackers}
# We should be able to delete the correct ones and not fail in the others # 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()) assert not set(completed_trackers).issubset(trackers.keys())

View File

@@ -1,8 +1,9 @@
import pytest import pytest
from teos.gatekeeper import IdentificationFailure, NotEnoughSlots from teos.gatekeeper import AuthenticationFailure, NotEnoughSlots
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
from common.exceptions import InvalidParameter
from test.teos.unit.conftest import get_random_value_hex, generate_keypair, get_config 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() 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.default_slots, int) and gatekeeper.default_slots == config.get("DEFAULT_SLOTS")
assert isinstance(gatekeeper.registered_users, dict) and len(gatekeeper.registered_users) == 0 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) user_pk = "02" + get_random_value_hex(32)
for _ in range(10): for _ in range(10):
current_slots = gatekeeper.registered_users.get(user_pk) user = gatekeeper.registered_users.get(user_pk)
current_slots = current_slots.get("available_slots") if current_slots is not None else 0 current_slots = user.available_slots if user is not None else 0
gatekeeper.add_update_user(user_pk) gatekeeper.add_update_user(user_pk)
assert gatekeeper.registered_users.get(user_pk).get("available_slots") == current_slots + config.get( assert gatekeeper.registered_users.get(user_pk).available_slots == current_slots + config.get("DEFAULT_SLOTS")
"DEFAULT_SLOTS"
)
# The same can be checked for multiple users # The same can be checked for multiple users
for _ in range(10): for _ in range(10):
@@ -35,14 +34,14 @@ def test_add_update_user(gatekeeper):
user_pk = "03" + get_random_value_hex(32) user_pk = "03" + get_random_value_hex(32)
gatekeeper.add_update_user(user_pk) 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): 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. # Passing a wrong pk defaults to the errors in check_user_pk. We can try with one.
wrong_pk = get_random_value_hex(32) wrong_pk = get_random_value_hex(32)
with pytest.raises(ValueError): with pytest.raises(InvalidParameter):
gatekeeper.add_update_user(wrong_pk) 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 # Prefixes must be 02 or 03, anything else should fail
wrong_pk = "04" + get_random_value_hex(32) wrong_pk = "04" + get_random_value_hex(32)
with pytest.raises(ValueError): with pytest.raises(InvalidParameter):
gatekeeper.add_update_user(wrong_pk) gatekeeper.add_update_user(wrong_pk)
@@ -66,7 +65,7 @@ def test_identify_user(gatekeeper):
message = "Hey, it's me" message = "Hey, it's me"
signature = Cryptographer.sign(message.encode(), sk) 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): def test_identify_user_non_registered(gatekeeper):
@@ -76,8 +75,8 @@ def test_identify_user_non_registered(gatekeeper):
message = "Hey, it's me" message = "Hey, it's me"
signature = Cryptographer.sign(message.encode(), sk) signature = Cryptographer.sign(message.encode(), sk)
with pytest.raises(IdentificationFailure): with pytest.raises(AuthenticationFailure):
gatekeeper.identify_user(message.encode(), signature) gatekeeper.authenticate_user(message.encode(), signature)
def test_identify_user_invalid_signature(gatekeeper): def test_identify_user_invalid_signature(gatekeeper):
@@ -85,8 +84,8 @@ def test_identify_user_invalid_signature(gatekeeper):
message = "Hey, it's me" message = "Hey, it's me"
signature = get_random_value_hex(72) signature = get_random_value_hex(72)
with pytest.raises(IdentificationFailure): with pytest.raises(AuthenticationFailure):
gatekeeper.identify_user(message.encode(), signature) gatekeeper.authenticate_user(message.encode(), signature)
def test_identify_user_wrong(gatekeeper): def test_identify_user_wrong(gatekeeper):
@@ -97,41 +96,16 @@ def test_identify_user_wrong(gatekeeper):
signature = Cryptographer.sign(message.encode(), sk) signature = Cryptographer.sign(message.encode(), sk)
# Non-byte message and str sig # Non-byte message and str sig
with pytest.raises(IdentificationFailure): with pytest.raises(AuthenticationFailure):
gatekeeper.identify_user(message, signature) gatekeeper.authenticate_user(message, signature)
# byte message and non-str sig # byte message and non-str sig
with pytest.raises(IdentificationFailure): with pytest.raises(AuthenticationFailure):
gatekeeper.identify_user(message.encode(), signature.encode()) gatekeeper.authenticate_user(message.encode(), signature.encode())
# non-byte message and non-str sig # non-byte message and non-str sig
with pytest.raises(IdentificationFailure): with pytest.raises(AuthenticationFailure):
gatekeeper.identify_user(message, signature.encode()) gatekeeper.authenticate_user(message, signature.encode())
def test_fill_slots(gatekeeper): # FIXME: MISSING TESTS
# 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

View File

@@ -4,8 +4,8 @@ from binascii import unhexlify
import teos.errors as errors import teos.errors as errors
from teos.block_processor import BlockProcessor from teos.block_processor import BlockProcessor
from teos.inspector import Inspector, InspectionFailed 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 common.constants import LOCATOR_LEN_BYTES, LOCATOR_LEN_HEX
from test.teos.unit.conftest import get_random_value_hex, bitcoind_connect_params, get_config 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 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(): def test_check_to_self_delay():
# Right value, right format # Right value, right format
to_self_delays = [MIN_TO_SELF_DELAY, MIN_TO_SELF_DELAY + 1, MIN_TO_SELF_DELAY + 1000] 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) encrypted_blob = get_random_value_hex(120)
assert inspector.check_blob(encrypted_blob) is None 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 # Wrong type
encrypted_blobs = WRONG_TYPES_NO_STR encrypted_blobs = WRONG_TYPES_NO_STR
for encrypted_blob in encrypted_blobs: for encrypted_blob in encrypted_blobs:
@@ -279,21 +180,13 @@ def test_inspect(run_bitcoind):
to_self_delay = MIN_TO_SELF_DELAY to_self_delay = MIN_TO_SELF_DELAY
encrypted_blob = get_random_value_hex(64) encrypted_blob = get_random_value_hex(64)
appointment_data = { appointment_data = {"locator": locator, "to_self_delay": to_self_delay, "encrypted_blob": encrypted_blob}
"locator": locator,
"start_time": start_time,
"end_time": end_time,
"to_self_delay": to_self_delay,
"encrypted_blob": encrypted_blob,
}
appointment = inspector.inspect(appointment_data) appointment = inspector.inspect(appointment_data)
assert ( assert (
type(appointment) == Appointment type(appointment) == ExtendedAppointment
and appointment.locator == locator 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.to_self_delay == to_self_delay
and appointment.encrypted_blob == encrypted_blob and appointment.encrypted_blob == encrypted_blob
) )

View File

@@ -9,23 +9,31 @@ from threading import Thread
from teos.carrier import Carrier from teos.carrier import Carrier
from teos.tools import bitcoin_cli from teos.tools import bitcoin_cli
from teos.chain_monitor import ChainMonitor 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.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 common.constants import LOCATOR_LEN_HEX
from bitcoind_mock.transaction import create_dummy_transaction, create_tx_from_hex from bitcoind_mock.transaction import create_dummy_transaction, create_tx_from_hex
from test.teos.unit.conftest import ( from test.teos.unit.conftest import (
generate_block, generate_block,
generate_blocks, generate_blocks,
generate_block_w_delay,
generate_blocks_w_delay,
get_random_value_hex, get_random_value_hex,
bitcoind_connect_params, bitcoind_connect_params,
bitcoind_feed_params, bitcoind_feed_params,
get_config,
) )
config = get_config()
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def responder(db_manager, carrier, block_processor): def responder(db_manager, gatekeeper, carrier, block_processor):
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 = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params)
chain_monitor.monitor_chain() chain_monitor.monitor_chain()
@@ -66,28 +74,26 @@ def create_dummy_tracker_data(random_txid=False, penalty_rawtx=None):
if random_txid is True: if random_txid is True:
penalty_txid = get_random_value_hex(32) penalty_txid = get_random_value_hex(32)
appointment_end = bitcoin_cli(bitcoind_connect_params).getblockcount() + 2
locator = dispute_txid[:LOCATOR_LEN_HEX] 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): def create_dummy_tracker(random_txid=False, penalty_rawtx=None):
locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end = create_dummy_tracker_data( locator, dispute_txid, penalty_txid, penalty_rawtx, expiry = create_dummy_tracker_data(random_txid, penalty_rawtx)
random_txid, penalty_rawtx return TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, expiry)
)
return TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end)
def test_tracker_init(run_bitcoind): def test_tracker_init(run_bitcoind):
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()
tracker = TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end) tracker = TransactionTracker(locator, dispute_txid, penalty_txid, penalty_rawtx, user_id)
assert ( assert (
tracker.dispute_txid == dispute_txid tracker.dispute_txid == dispute_txid
and tracker.penalty_txid == penalty_txid and tracker.penalty_txid == penalty_txid
and tracker.penalty_rawtx == penalty_rawtx 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 ( assert (
tracker.locator == tracker_dict["locator"] tracker.locator == tracker_dict["locator"]
and tracker.penalty_rawtx == tracker_dict["penalty_rawtx"] 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(): def test_tracker_from_dict_invalid_data():
tracker_dict = create_dummy_tracker().to_dict() 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 = deepcopy(tracker_dict)
tracker_dict_copy[value] = None tracker_dict_copy[value] = None
@@ -141,17 +147,22 @@ def test_tracker_from_dict_invalid_data():
assert True assert True
def test_init_responder(temp_db_manager, carrier, block_processor): def test_init_responder(temp_db_manager, gatekeeper, carrier, block_processor):
responder = Responder(temp_db_manager, 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.trackers, dict) and len(responder.trackers) == 0
assert isinstance(responder.tx_tracker_map, dict) and len(responder.tx_tracker_map) == 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.unconfirmed_txs, list) and len(responder.unconfirmed_txs) == 0
assert isinstance(responder.missed_confirmations, dict) and len(responder.missed_confirmations) == 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): def test_handle_breach(db_manager, gatekeeper, carrier, block_processor):
responder = Responder(db_manager, carrier, block_processor) responder = Responder(db_manager, gatekeeper, carrier, block_processor)
uuid = uuid4().hex uuid = uuid4().hex
tracker = create_dummy_tracker() tracker = create_dummy_tracker()
@@ -163,17 +174,17 @@ def test_handle_breach(db_manager, carrier, block_processor):
tracker.dispute_txid, tracker.dispute_txid,
tracker.penalty_txid, tracker.penalty_txid,
tracker.penalty_rawtx, tracker.penalty_rawtx,
tracker.appointment_end, tracker.user_id,
block_hash=get_random_value_hex(32), block_hash=get_random_value_hex(32),
) )
assert receipt.delivered is True 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 # We need a new carrier here, otherwise the transaction will be flagged as previously sent and receipt.delivered
# will be True # 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 uuid = uuid4().hex
tracker = create_dummy_tracker() tracker = create_dummy_tracker()
@@ -188,7 +199,7 @@ def test_handle_breach_bad_response(db_manager, block_processor):
tracker.dispute_txid, tracker.dispute_txid,
tracker.penalty_txid, tracker.penalty_txid,
tracker.penalty_rawtx, tracker.penalty_rawtx,
tracker.appointment_end, tracker.user_id,
block_hash=get_random_value_hex(32), block_hash=get_random_value_hex(32),
) )
@@ -199,9 +210,7 @@ def test_add_tracker(responder):
for _ in range(20): for _ in range(20):
uuid = uuid4().hex uuid = uuid4().hex
confirmations = 0 confirmations = 0
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(random_txid=True)
random_txid=True
)
# Check the tracker is not within the responder trackers before adding it # Check the tracker is not within the responder trackers before adding it
assert uuid not in responder.trackers assert uuid not in responder.trackers
@@ -209,7 +218,7 @@ def test_add_tracker(responder):
assert penalty_txid not in responder.unconfirmed_txs assert penalty_txid not in responder.unconfirmed_txs
# And that it is afterwards # 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 uuid in responder.trackers
assert penalty_txid in responder.tx_tracker_map assert penalty_txid in responder.tx_tracker_map
assert penalty_txid in responder.unconfirmed_txs assert penalty_txid in responder.unconfirmed_txs
@@ -219,18 +228,18 @@ def test_add_tracker(responder):
assert ( assert (
tracker.get("penalty_txid") == penalty_txid tracker.get("penalty_txid") == penalty_txid
and tracker.get("locator") == locator 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): def test_add_tracker_same_penalty_txid(responder):
confirmations = 0 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_1 = uuid4().hex
uuid_2 = 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_1, locator, dispute_txid, penalty_txid, penalty_rawtx, user_id, confirmations)
responder.add_tracker(uuid_2, locator, dispute_txid, penalty_txid, penalty_rawtx, appointment_end, confirmations) responder.add_tracker(uuid_2, locator, dispute_txid, penalty_txid, penalty_rawtx, user_id, confirmations)
# Check that both trackers have been added # Check that both trackers have been added
assert uuid_1 in responder.trackers and uuid_2 in responder.trackers 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 ( assert (
tracker.get("penalty_txid") == penalty_txid tracker.get("penalty_txid") == penalty_txid
and tracker.get("locator") == locator 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): for i in range(20):
uuid = uuid4().hex uuid = uuid4().hex
confirmations = i + 1 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() 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 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 # 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 = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params)
chain_monitor.monitor_chain() chain_monitor.monitor_chain()
trackers = [create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) for _ in range(20)] 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 # Let's set up the trackers first
for tracker in trackers: for tracker in trackers:
uuid = uuid4().hex uuid = uuid4().hex
# Simulate user registration
responder.gatekeeper.registered_users[tracker.user_id] = UserInfo(
available_slots=10, subscription_expiry=subscription_expiry
)
responder.trackers[uuid] = { responder.trackers[uuid] = {
"locator": tracker.locator, "locator": tracker.locator,
"penalty_txid": tracker.penalty_txid, "penalty_txid": tracker.penalty_txid,
"appointment_end": tracker.appointment_end, "user_id": tracker.user_id,
} }
responder.tx_tracker_map[tracker.penalty_txid] = [uuid] responder.tx_tracker_map[tracker.penalty_txid] = [uuid]
responder.missed_confirmations[tracker.penalty_txid] = 0 responder.missed_confirmations[tracker.penalty_txid] = 0
responder.unconfirmed_txs.append(tracker.penalty_txid) 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 # We also need to store the info in the db
responder.db_manager.create_triggered_appointment_flag(uuid) 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) broadcast_txs.append(tracker.penalty_txid)
# Mine a block # Mine a block
generate_block() generate_block_w_delay()
# The transactions we sent shouldn't be in the unconfirmed transaction list anymore # The transactions we sent shouldn't be in the unconfirmed transaction list anymore
assert not set(broadcast_txs).issubset(responder.unconfirmed_txs) 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 # Generating 100 - CONFIRMATIONS_BEFORE_RETRY -2 additional blocks should complete the first 5 trackers
generate_blocks(5) 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) # CONFIRMATIONS_BEFORE_RETRY additional blocks should complete the rest
generate_blocks_w_delay(CONFIRMATIONS_BEFORE_RETRY)
# Do the rest assert len(responder.unconfirmed_txs) == 0
broadcast_txs = [] assert len(responder.trackers) == 0
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
def test_check_confirmations(db_manager, carrier, block_processor): def test_check_confirmations(db_manager, gatekeeper, carrier, block_processor):
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 = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params)
chain_monitor.monitor_chain() 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()) 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() 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 = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params)
chain_monitor.monitor_chain() chain_monitor.monitor_chain()
# A complete tracker is a tracker that has reached the appointment end with enough confs (> MIN_CONFIRMATIONS) # A complete tracker is a tracker which penalty transaction has been irrevocably resolved (i.e. has reached 100
# We'll create three type of transactions: end reached + enough conf, end reached + no enough conf, end not reached # confirmations)
trackers_end_conf = { # 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) 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): for _ in range(10):
tracker = create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex()) tracker = create_dummy_tracker(penalty_rawtx=create_dummy_transaction().hex())
responder.unconfirmed_txs.append(tracker.penalty_txid) responder.unconfirmed_txs.append(tracker.penalty_txid)
trackers_end_no_conf[uuid4().hex] = tracker trackers_unconfirmed[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
all_trackers = {} all_trackers = {}
all_trackers.update(trackers_end_conf) all_trackers.update(trackers_ir_resolved)
all_trackers.update(trackers_end_no_conf) all_trackers.update(trackers_confirmed)
all_trackers.update(trackers_no_end) all_trackers.update(trackers_unconfirmed)
# Let's add all to the responder # Let's add all to the responder
for uuid, tracker in all_trackers.items(): for uuid, tracker in all_trackers.items():
responder.trackers[uuid] = { responder.trackers[uuid] = {
"locator": tracker.locator, "locator": tracker.locator,
"penalty_txid": tracker.penalty_txid, "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) 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_block_w_delay()
generate_blocks(6)
# And now let's check for uuid, tracker in trackers_confirmed.items():
completed_trackers = responder.get_completed_trackers(initial_height + 6) bitcoin_cli(bitcoind_connect_params).sendrawtransaction(tracker.penalty_rawtx)
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)
# Generating 6 additional blocks should also confirm trackers_no_end # ir_resolved have 100 confs and confirmed have 99
generate_blocks(6) generate_blocks_w_delay(99)
completed_trackers = responder.get_completed_trackers(initial_height + 12) # Let's check
completed_trackers_ids = [tracker_id for tracker_id, confirmations in completed_trackers.items()] completed_trackers = responder.get_completed_trackers()
ended_trackers_keys.extend(list(trackers_no_end.keys())) 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): def test_rebroadcast(db_manager, gatekeeper, carrier, block_processor):
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 = ChainMonitor(Queue(), responder.block_queue, block_processor, bitcoind_feed_params)
chain_monitor.monitor_chain() 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. # Rebroadcast calls add_response with retry=True. The tracker data is already in trackers.
for i in range(20): for i in range(20):
uuid = uuid4().hex 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() 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] = { responder.trackers[uuid] = {"locator": locator, "penalty_txid": penalty_txid, "user_id": user_id}
"locator": locator,
"penalty_txid": penalty_txid,
"appointment_end": appointment_end,
}
# We need to add it to the db too # We need to add it to the db too
responder.db_manager.create_triggered_appointment_flag(uuid) responder.db_manager.create_triggered_appointment_flag(uuid)

View File

@@ -13,13 +13,7 @@ def test_can_connect_to_bitcoind():
assert can_connect_to_bitcoind(bitcoind_connect_params) is True assert can_connect_to_bitcoind(bitcoind_connect_params) is True
# def test_can_connect_to_bitcoind_bitcoin_not_running(): def test_bitcoin_cli(run_bitcoind):
# # Kill the simulator thread and test the check fails
# bitcoind_process.kill()
# assert can_connect_to_bitcoind() is False
def test_bitcoin_cli():
try: try:
bitcoin_cli(bitcoind_connect_params).help() bitcoin_cli(bitcoind_connect_params).help()
assert True assert True

View File

@@ -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 from test.teos.unit.conftest import get_random_value_hex
stored_users = {} stored_users = {}
def open_create_db(db_path): def open_create_db(db_path):
try: try:
db_manager = AppointmentsDBM(db_path) db_manager = UsersDBM(db_path)
return db_manager return db_manager
@@ -20,17 +20,17 @@ def open_create_db(db_path):
def test_store_user(user_db_manager): 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 # 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_pk = "02" + get_random_value_hex(32)
user_data = {"available_slots": 42} user_info = UserInfo(available_slots=42, subscription_expiry=100)
stored_users[user_pk] = user_data stored_users[user_pk] = user_info.to_dict()
assert user_db_manager.store_user(user_pk, user_data) is True assert user_db_manager.store_user(user_pk, user_info.to_dict()) is True
# Wrong pks should return False on adding # Wrong pks should return False on adding
user_pk = "04" + get_random_value_hex(32) user_pk = "04" + get_random_value_hex(32)
user_data = {"available_slots": 42} user_info = UserInfo(available_slots=42, subscription_expiry=100)
assert user_db_manager.store_user(user_pk, user_data) is False assert user_db_manager.store_user(user_pk, user_info.to_dict()) is False
# Same for wrong types # 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 # And for wrong type user data
assert user_db_manager.store_user(user_pk, 42) is False 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 # Adding some and checking we get them all
for i in range(10): for i in range(10):
user_pk = "02" + get_random_value_hex(32) user_pk = "02" + get_random_value_hex(32)
user_data = {"available_slots": i} user_info = UserInfo(available_slots=42, subscription_expiry=100)
user_db_manager.store_user(user_pk, user_data) user_db_manager.store_user(user_pk, user_info.to_dict())
stored_users[user_pk] = user_data stored_users[user_pk] = user_info.to_dict()
all_users = user_db_manager.load_all_users() all_users = user_db_manager.load_all_users()

View File

@@ -4,20 +4,21 @@ from shutil import rmtree
from threading import Thread from threading import Thread
from coincurve import PrivateKey from coincurve import PrivateKey
from teos import LOG_PREFIX
from teos.carrier import Carrier from teos.carrier import Carrier
from teos.watcher import Watcher
from teos.tools import bitcoin_cli from teos.tools import bitcoin_cli
from teos.responder import Responder from teos.responder import Responder
from teos.gatekeeper import UserInfo
from teos.gatekeeper import Gatekeeper
from teos.chain_monitor import ChainMonitor from teos.chain_monitor import ChainMonitor
from teos.appointments_dbm import AppointmentsDBM from teos.appointments_dbm import AppointmentsDBM
from teos.block_processor import BlockProcessor from teos.block_processor import BlockProcessor
from teos.watcher import Watcher, AppointmentLimitReached
from common.tools import compute_locator from common.tools import compute_locator
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
from test.teos.unit.conftest import ( from test.teos.unit.conftest import (
generate_blocks, generate_blocks_w_delay,
generate_dummy_appointment, generate_dummy_appointment,
get_random_value_hex, get_random_value_hex,
generate_keypair, generate_keypair,
@@ -27,8 +28,6 @@ from test.teos.unit.conftest import (
) )
APPOINTMENTS = 5 APPOINTMENTS = 5
START_TIME_OFFSET = 1
END_TIME_OFFSET = 1
TEST_SET_SIZE = 200 TEST_SET_SIZE = 200
config = get_config() config = get_config()
@@ -51,14 +50,12 @@ def temp_db_manager():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def watcher(db_manager): def watcher(db_manager, gatekeeper):
block_processor = BlockProcessor(bitcoind_connect_params) block_processor = BlockProcessor(bitcoind_connect_params)
carrier = Carrier(bitcoind_connect_params) carrier = Carrier(bitcoind_connect_params)
responder = Responder(db_manager, carrier, block_processor) responder = Responder(db_manager, gatekeeper, carrier, block_processor)
watcher = Watcher( watcher = Watcher(db_manager, gatekeeper, block_processor, responder, signing_key.to_der(), MAX_APPOINTMENTS)
db_manager, block_processor, responder, signing_key.to_der(), MAX_APPOINTMENTS, config.get("EXPIRY_DELTA")
)
chain_monitor = ChainMonitor( chain_monitor = ChainMonitor(
watcher.block_queue, watcher.responder.block_queue, block_processor, bitcoind_feed_params watcher.block_queue, watcher.responder.block_queue, block_processor, bitcoind_feed_params
@@ -84,9 +81,7 @@ def create_appointments(n):
dispute_txs = [] dispute_txs = []
for i in range(n): for i in range(n):
appointment, dispute_tx = generate_dummy_appointment( appointment, dispute_tx = generate_dummy_appointment()
start_time_offset=START_TIME_OFFSET, end_time_offset=END_TIME_OFFSET
)
uuid = uuid4().hex uuid = uuid4().hex
appointments[uuid] = appointment appointments[uuid] = appointment
@@ -103,82 +98,79 @@ def test_init(run_bitcoind, watcher):
assert isinstance(watcher.block_processor, BlockProcessor) assert isinstance(watcher.block_processor, BlockProcessor)
assert isinstance(watcher.responder, Responder) assert isinstance(watcher.responder, Responder)
assert isinstance(watcher.max_appointments, int) assert isinstance(watcher.max_appointments, int)
assert isinstance(watcher.expiry_delta, int) assert isinstance(watcher.gatekeeper, Gatekeeper)
assert isinstance(watcher.signing_key, PrivateKey) 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): def test_add_appointment(watcher):
# We should be able to add appointments up to the limit # Simulate the user is registered
for _ in range(10): user_sk, user_pk = generate_keypair()
appointment, dispute_tx = generate_dummy_appointment( available_slots = 100
start_time_offset=START_TIME_OFFSET, end_time_offset=END_TIME_OFFSET user_id = Cryptographer.get_compressed_pk(user_pk)
) watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=available_slots, subscription_expiry=10)
user_pk = get_random_value_hex(33)
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 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( 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 - 1
# Check that we can also add an already added appointment (same locator) # Check that we can also add an already added appointment (same locator)
added_appointment, sig = watcher.add_appointment(appointment, user_pk) response = watcher.add_appointment(appointment, appointment_signature)
assert response.get("locator") == appointment.locator
assert added_appointment is True
assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( 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"))
) )
# The slot count should not have been reduced and only one copy is kept.
# If two appointments with the same locator from the same user are added, they are overwritten, but if they come assert response.get("available_slots") == available_slots - 1
# from different users, they are kept.
assert len(watcher.locator_uuid_map[appointment.locator]) == 1 assert len(watcher.locator_uuid_map[appointment.locator]) == 1
different_user_pk = get_random_value_hex(33) # If two appointments with the same locator come from different users, they are kept.
added_appointment, sig = watcher.add_appointment(appointment, different_user_pk) another_user_sk, another_user_pk = generate_keypair()
assert added_appointment is True another_user_id = Cryptographer.get_compressed_pk(another_user_pk)
assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( watcher.gatekeeper.registered_users[another_user_id] = UserInfo(
Cryptographer.recover_pk(appointment.serialize(), sig) available_slots=available_slots, subscription_expiry=10
) )
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 assert len(watcher.locator_uuid_map[appointment.locator]) == 2
def test_add_too_many_appointments(watcher): 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() watcher.appointments = dict()
for _ in range(MAX_APPOINTMENTS): for i in range(MAX_APPOINTMENTS):
appointment, dispute_tx = generate_dummy_appointment( appointment, dispute_tx = generate_dummy_appointment()
start_time_offset=START_TIME_OFFSET, end_time_offset=END_TIME_OFFSET appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk)
)
user_pk = get_random_value_hex(33)
added_appointment, sig = watcher.add_appointment(appointment, user_pk) response = watcher.add_appointment(appointment, appointment_signature)
assert response.get("locator") == appointment.locator
assert added_appointment is True
assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk( 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( with pytest.raises(AppointmentLimitReached):
start_time_offset=START_TIME_OFFSET, end_time_offset=END_TIME_OFFSET appointment, dispute_tx = generate_dummy_appointment()
) appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk)
user_pk = get_random_value_hex(33) watcher.add_appointment(appointment, appointment_signature)
added_appointment, sig = watcher.add_appointment(appointment, user_pk)
assert added_appointment is False
assert sig is None
def test_do_watch(watcher, temp_db_manager): 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 # Set the data into the Watcher and in the db
watcher.locator_uuid_map = locator_uuid_map watcher.locator_uuid_map = locator_uuid_map
watcher.appointments = {} 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(): 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.store_watcher_appointment(uuid, appointment.to_dict())
watcher.db_manager.create_append_locator_map(appointment.locator, uuid) 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]: for dispute_tx in dispute_txs[:2]:
bitcoin_cli(bitcoind_connect_params).sendrawtransaction(dispute_tx) bitcoin_cli(bitcoind_connect_params).sendrawtransaction(dispute_tx)
# After generating enough blocks, the number of appointments should have reduced by two # After generating a block, the appointment count should have been reduced by 2 (two breaches)
generate_blocks(START_TIME_OFFSET + END_TIME_OFFSET) generate_blocks_w_delay(1)
assert len(watcher.appointments) == APPOINTMENTS - 2 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 # 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 assert len(watcher.appointments) == 0
@@ -242,7 +243,7 @@ def test_filter_valid_breaches_random_data(watcher):
for i in range(TEST_SET_SIZE): for i in range(TEST_SET_SIZE):
dummy_appointment, _ = generate_dummy_appointment() dummy_appointment, _ = generate_dummy_appointment()
uuid = uuid4().hex 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.store_watcher_appointment(uuid, dummy_appointment.to_dict())
watcher.db_manager.create_append_locator_map(dummy_appointment.locator, uuid) 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} breaches = {dummy_appointment.locator: dispute_txid}
for uuid, appointment in appointments.items(): 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.store_watcher_appointment(uuid, dummy_appointment.to_dict())
watcher.db_manager.create_append_locator_map(dummy_appointment.locator, uuid) watcher.db_manager.create_append_locator_map(dummy_appointment.locator, uuid)