From 096baf000121d5a6dcd5ef3e40b1c926ce3bc91c Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 25 Feb 2025 15:05:39 +1100 Subject: [PATCH 01/15] test: spam protection for upload --- tests/dos_robustness/__init__.py | 0 tests/dos_robustness/dos_robustness.py | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/dos_robustness/__init__.py create mode 100644 tests/dos_robustness/dos_robustness.py diff --git a/tests/dos_robustness/__init__.py b/tests/dos_robustness/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/dos_robustness/dos_robustness.py b/tests/dos_robustness/dos_robustness.py new file mode 100644 index 0000000..f051d1c --- /dev/null +++ b/tests/dos_robustness/dos_robustness.py @@ -0,0 +1,23 @@ +from src.libs.common import delay, to_app_id, to_index +from src.libs.custom_logger import get_custom_logger +from src.steps.da import StepsDataAvailability +from src.test_data import DATA_TO_DISPERSE + +logger = get_custom_logger(__name__) + + +class TestDosRobustness(StepsDataAvailability): + main_nodes = [] + + def test_spam_protection_data_uploads(self): + delay(5) + successful_dispersals = 0 + for i in range(1000): + try: + self.disperse_data(DATA_TO_DISPERSE[0], to_app_id(1), to_index(0)) + successful_dispersals = i + except Exception as ex: + logger.debug(f"Dispersal #{i} was not successful with error {ex}") + break + + assert successful_dispersals < 1000, "More than 1000 consecutive dispersals were successful without any constraint" From bf7644bcf48bf8689f151c662df2d55e7d6169f9 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 25 Feb 2025 15:11:31 +1100 Subject: [PATCH 02/15] fix: add fixture --- tests/dos_robustness/dos_robustness.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/dos_robustness/dos_robustness.py b/tests/dos_robustness/dos_robustness.py index f051d1c..38c2d8b 100644 --- a/tests/dos_robustness/dos_robustness.py +++ b/tests/dos_robustness/dos_robustness.py @@ -1,3 +1,5 @@ +import pytest + from src.libs.common import delay, to_app_id, to_index from src.libs.custom_logger import get_custom_logger from src.steps.da import StepsDataAvailability @@ -9,7 +11,8 @@ logger = get_custom_logger(__name__) class TestDosRobustness(StepsDataAvailability): main_nodes = [] - def test_spam_protection_data_uploads(self): + @pytest.mark.parametrize("setup_2_node_cluster", [2], indirect=True) + def test_spam_protection_data_uploads(self, setup_2_node_cluster): delay(5) successful_dispersals = 0 for i in range(1000): From 48847ce5ccc27a5864006c918c8553c055a19eaf Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 25 Feb 2025 16:39:55 +1100 Subject: [PATCH 03/15] fix: add optional timeout to disperse and get range --- src/steps/da.py | 42 ++++++++++++++------------ tests/dos_robustness/dos_robustness.py | 2 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/steps/da.py b/src/steps/da.py index 779742b..82960e8 100644 --- a/src/steps/da.py +++ b/src/steps/da.py @@ -75,28 +75,30 @@ class StepsDataAvailability(StepsCommon): return executor @allure.step - @retry(stop=stop_after_delay(65), wait=wait_fixed(1), reraise=True) - def disperse_data(self, data, app_id, index): - response = [] - request = prepare_dispersal_request(data, app_id, index) - executor = self.find_executor_node() - try: - response = executor.send_dispersal_request(request) - except Exception as ex: - assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex) + def disperse_data(self, data, app_id, index, timeout_duration=65): + @retry(stop=stop_after_delay(timeout_duration), wait=wait_fixed(1), reraise=True) + def disperse(my_self=self): + response = [] + request = prepare_dispersal_request(data, app_id, index) + executor = my_self.find_executor_node() + try: + response = executor.send_dispersal_request(request) + except Exception as ex: + assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex) - assert response.status_code == 200, "Send dispersal finished with unexpected response code" + assert response.status_code == 200, "Send dispersal finished with unexpected response code" @allure.step - @retry(stop=stop_after_delay(45), wait=wait_fixed(1), reraise=True) - def get_data_range(self, node, app_id, start, end): - response = [] - query = prepare_get_range_request(app_id, start, end) - try: - response = node.send_get_data_range_request(query) - except Exception as ex: - assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex) + def get_data_range(self, node, app_id, start, end, timeout_duration=45): + @retry(stop=stop_after_delay(timeout_duration), wait=wait_fixed(1), reraise=True) + def get_range(): + response = [] + query = prepare_get_range_request(app_id, start, end) + try: + response = node.send_get_data_range_request(query) + except Exception as ex: + assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex) - assert response_contains_data(response), "Get data range response is empty" + assert response_contains_data(response), "Get data range response is empty" - return response + return response diff --git a/tests/dos_robustness/dos_robustness.py b/tests/dos_robustness/dos_robustness.py index 38c2d8b..f368fbf 100644 --- a/tests/dos_robustness/dos_robustness.py +++ b/tests/dos_robustness/dos_robustness.py @@ -17,7 +17,7 @@ class TestDosRobustness(StepsDataAvailability): successful_dispersals = 0 for i in range(1000): try: - self.disperse_data(DATA_TO_DISPERSE[0], to_app_id(1), to_index(0)) + self.disperse_data(DATA_TO_DISPERSE[0], to_app_id(1), to_index(0), timeout_duration=0) successful_dispersals = i except Exception as ex: logger.debug(f"Dispersal #{i} was not successful with error {ex}") From 0b6fc270e768dbb00d2b946b8c302022f96d9fee Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 25 Feb 2025 16:56:36 +1100 Subject: [PATCH 04/15] fix: call the functions --- src/steps/da.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/steps/da.py b/src/steps/da.py index 82960e8..394b94b 100644 --- a/src/steps/da.py +++ b/src/steps/da.py @@ -88,6 +88,8 @@ class StepsDataAvailability(StepsCommon): assert response.status_code == 200, "Send dispersal finished with unexpected response code" + disperse() + @allure.step def get_data_range(self, node, app_id, start, end, timeout_duration=45): @retry(stop=stop_after_delay(timeout_duration), wait=wait_fixed(1), reraise=True) @@ -102,3 +104,5 @@ class StepsDataAvailability(StepsCommon): assert response_contains_data(response), "Get data range response is empty" return response + + get_range() From fa41c8fde62f98efb3b11ac79232d075be76472c Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 26 Feb 2025 02:03:17 +0000 Subject: [PATCH 05/15] test: signing with new gpg key --- tests/networking_privacy/test_networking_privacy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/networking_privacy/test_networking_privacy.py b/tests/networking_privacy/test_networking_privacy.py index 30ae389..1396f54 100644 --- a/tests/networking_privacy/test_networking_privacy.py +++ b/tests/networking_privacy/test_networking_privacy.py @@ -24,7 +24,7 @@ class TestNetworkingPrivacy(StepsDataAvailability): self.disperse_data(DATA_TO_DISPERSE[7], to_app_id(1), to_index(0)) successful_dispersals += 1 except Exception as ex: - logger.warning(f"Dispersal #{i} was not successful with error {ex}") + logger.warning(f"Dispersal #{i+1} was not successful with error {ex}") if successful_dispersals == 10: break From bcedf4b83d490e87fb3a1194c70dada93358dca0 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 27 Feb 2025 00:18:11 +0000 Subject: [PATCH 06/15] fix: add balancer_interval_secs property --- cluster_config/cfgsync-template.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/cluster_config/cfgsync-template.yaml b/cluster_config/cfgsync-template.yaml index 308c315..59e93e6 100644 --- a/cluster_config/cfgsync-template.yaml +++ b/cluster_config/cfgsync-template.yaml @@ -13,6 +13,7 @@ num_samples: 1 num_subnets: 2 old_blobs_check_interval_secs: 5 blobs_validity_duration_secs: 60 +balancer_interval_secs: 1 global_params_path: "/kzgrs_test_params" # Tracing From 9e45d0d89014eb3c5dcc1302f92f1a24c7c8fc26 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 27 Feb 2025 03:23:32 +0000 Subject: [PATCH 07/15] fix: add balancer_interval_secs property to sample file --- cluster_config/cfgsync.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cluster_config/cfgsync.yaml b/cluster_config/cfgsync.yaml index 64b1df0..e1a0fad 100644 --- a/cluster_config/cfgsync.yaml +++ b/cluster_config/cfgsync.yaml @@ -7,12 +7,13 @@ security_param: 10 active_slot_coeff: 0.9 # DaConfig related parameters -subnetwork_size: 1024 +subnetwork_size: 2 dispersal_factor: 2 num_samples: 1 num_subnets: 2 old_blobs_check_interval_secs: 5 blobs_validity_duration_secs: 60 +balancer_interval_secs: 1 global_params_path: "/kzgrs_test_params" # Tracing From 768bc30201e0bedd74f675d511014c075a892171 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 27 Feb 2025 05:56:14 +0000 Subject: [PATCH 08/15] test: spam protection with valid uploads - move delay(5) to fixtures --- src/env_vars.py | 2 +- src/steps/common.py | 5 ++++ tests/data_integrity/test_data_integrity.py | 2 -- tests/dos_robustness/dos_robustness.py | 25 +++++++++++++++---- .../test_networking_privacy.py | 1 - 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/env_vars.py b/src/env_vars.py index 1c05782..01706e0 100644 --- a/src/env_vars.py +++ b/src/env_vars.py @@ -19,7 +19,7 @@ NOMOS = "nomos" NOMOS_EXECUTOR = "nomos_executor" CFGSYNC = "cfgsync" -DEFAULT_IMAGE = "ghcr.io/logos-co/nomos-node:latest" +DEFAULT_IMAGE = "ghcr.io/logos-co/nomos-node:testnet" NODE_1 = get_env_var("NODE_1", NOMOS) NODE_2 = get_env_var("NODE_2", NOMOS_EXECUTOR) diff --git a/src/steps/common.py b/src/steps/common.py index 2ba7c8a..dcd593a 100644 --- a/src/steps/common.py +++ b/src/steps/common.py @@ -5,6 +5,7 @@ import shutil import pytest from src.env_vars import CFGSYNC, NOMOS, NOMOS_EXECUTOR +from src.libs.common import delay from src.libs.custom_logger import get_custom_logger from src.node.nomos_node import NomosNode @@ -65,6 +66,8 @@ class StepsCommon: logger.error(f"REST service did not become ready in time: {ex}") raise + delay(5) + @pytest.fixture(scope="function") def setup_4_node_cluster(self, request): logger.debug(f"Running fixture setup: {inspect.currentframe().f_code.co_name}") @@ -82,3 +85,5 @@ class StepsCommon: except Exception as ex: logger.error(f"REST service did not become ready in time: {ex}") raise + + delay(5) diff --git a/tests/data_integrity/test_data_integrity.py b/tests/data_integrity/test_data_integrity.py index 9e5ed9c..e68544d 100644 --- a/tests/data_integrity/test_data_integrity.py +++ b/tests/data_integrity/test_data_integrity.py @@ -17,7 +17,6 @@ class TestDataIntegrity(StepsDataAvailability): @pytest.mark.usefixtures("setup_4_node_cluster") def test_da_identify_retrieve_missing_columns(self): - delay(5) self.disperse_data(DATA_TO_DISPERSE[1], to_app_id(1), to_index(0)) delay(5) # Select one target node at random to get blob data for 1/2 columns @@ -31,7 +30,6 @@ class TestDataIntegrity(StepsDataAvailability): @pytest.mark.usefixtures("setup_2_node_cluster") def test_da_sampling_determines_data_presence(self): - delay(5) self.disperse_data(DATA_TO_DISPERSE[1], to_app_id(1), to_index(0)) delay(5) rcv_data = self.get_data_range(self.node2, to_app_id(1), to_index(0), to_index(5)) diff --git a/tests/dos_robustness/dos_robustness.py b/tests/dos_robustness/dos_robustness.py index f368fbf..7a2221f 100644 --- a/tests/dos_robustness/dos_robustness.py +++ b/tests/dos_robustness/dos_robustness.py @@ -11,16 +11,31 @@ logger = get_custom_logger(__name__) class TestDosRobustness(StepsDataAvailability): main_nodes = [] - @pytest.mark.parametrize("setup_2_node_cluster", [2], indirect=True) - def test_spam_protection_data_uploads(self, setup_2_node_cluster): - delay(5) + @pytest.mark.usefixtures("setup_2_node_cluster") + def test_spam_protection_valid_uploads(self, setup_2_node_cluster): + num_samples = len(DATA_TO_DISPERSE) + missing_dispersals = num_samples + for i in range(num_samples): + try: + self.disperse_data(DATA_TO_DISPERSE[i], to_app_id(1), to_index(0), timeout_duration=0) + missing_dispersals -= 1 + except Exception as ex: + logger.error(f"Dispersal #{i+1} was not successful with error {ex}") + break + + delay(0.1) + + assert missing_dispersals == 0, f"{missing_dispersals} dispersals were not successful" + + @pytest.mark.usefixtures("setup_2_node_cluster") + def test_spam_protection_single_burst(self, setup_2_node_cluster): successful_dispersals = 0 for i in range(1000): try: self.disperse_data(DATA_TO_DISPERSE[0], to_app_id(1), to_index(0), timeout_duration=0) successful_dispersals = i except Exception as ex: - logger.debug(f"Dispersal #{i} was not successful with error {ex}") + logger.debug(f"Dispersal #{i+1} was not successful with error {ex}") break - assert successful_dispersals < 1000, "More than 1000 consecutive dispersals were successful without any constraint" + assert successful_dispersals < 1000, "All 1000 consecutive dispersals were successful without any constraint" diff --git a/tests/networking_privacy/test_networking_privacy.py b/tests/networking_privacy/test_networking_privacy.py index 1396f54..5bc1eb3 100644 --- a/tests/networking_privacy/test_networking_privacy.py +++ b/tests/networking_privacy/test_networking_privacy.py @@ -14,7 +14,6 @@ class TestNetworkingPrivacy(StepsDataAvailability): @pytest.mark.parametrize("setup_2_node_cluster", [2], indirect=True) def test_consumed_bandwidth_dispersal(self, setup_2_node_cluster): - delay(5) net_io = psutil.net_io_counters() prev_total = net_io.bytes_sent + net_io.bytes_recv From 8db3014407905907b070ddd2d115e38b46c57877 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 28 Feb 2025 03:52:02 +0000 Subject: [PATCH 09/15] fix: add params utf8 and padding to disperse_data --- src/steps/da.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/steps/da.py b/src/steps/da.py index 394b94b..c90ae2f 100644 --- a/src/steps/da.py +++ b/src/steps/da.py @@ -2,8 +2,11 @@ import allure from tenacity import retry, stop_after_delay, wait_fixed from src.env_vars import NOMOS_EXECUTOR +from src.libs.custom_logger import get_custom_logger from src.steps.common import StepsCommon +logger = get_custom_logger(__name__) + def add_padding(orig_bytes): """ @@ -46,10 +49,17 @@ def remove_padding(padded_bytes): return padded_bytes[:-padding_len] -def prepare_dispersal_request(data, app_id, index): - data_bytes = data.encode("utf-8") - padded_bytes = add_padding(list(data_bytes)) - dispersal_data = {"data": padded_bytes, "metadata": {"app_id": app_id, "index": index}} +def prepare_dispersal_request(data, app_id, index, utf8=True, padding=True): + if utf8: + data_bytes = data.encode("utf-8") + else: + data_bytes = bytes(data) + + data_list = list(data_bytes) + if padding: + data_list = add_padding(data_list) + + dispersal_data = {"data": data_list, "metadata": {"app_id": app_id, "index": index}} return dispersal_data @@ -75,11 +85,11 @@ class StepsDataAvailability(StepsCommon): return executor @allure.step - def disperse_data(self, data, app_id, index, timeout_duration=65): + def disperse_data(self, data, app_id, index, timeout_duration=65, utf8=True, padding=True): @retry(stop=stop_after_delay(timeout_duration), wait=wait_fixed(1), reraise=True) def disperse(my_self=self): response = [] - request = prepare_dispersal_request(data, app_id, index) + request = prepare_dispersal_request(data, app_id, index, utf8=utf8, padding=padding) executor = my_self.find_executor_node() try: response = executor.send_dispersal_request(request) From 6fd4b083fb9dba5dd37c1ce08f3b96889eb0304c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 28 Feb 2025 05:52:29 +0000 Subject: [PATCH 10/15] fix: remove redundant fixture param --- tests/dos_robustness/dos_robustness.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/dos_robustness/dos_robustness.py b/tests/dos_robustness/dos_robustness.py index 7a2221f..008385e 100644 --- a/tests/dos_robustness/dos_robustness.py +++ b/tests/dos_robustness/dos_robustness.py @@ -12,7 +12,7 @@ class TestDosRobustness(StepsDataAvailability): main_nodes = [] @pytest.mark.usefixtures("setup_2_node_cluster") - def test_spam_protection_valid_uploads(self, setup_2_node_cluster): + def test_spam_protection_valid_uploads(self): num_samples = len(DATA_TO_DISPERSE) missing_dispersals = num_samples for i in range(num_samples): @@ -28,7 +28,7 @@ class TestDosRobustness(StepsDataAvailability): assert missing_dispersals == 0, f"{missing_dispersals} dispersals were not successful" @pytest.mark.usefixtures("setup_2_node_cluster") - def test_spam_protection_single_burst(self, setup_2_node_cluster): + def test_spam_protection_single_burst(self): successful_dispersals = 0 for i in range(1000): try: From 0eb9ac750d6be1846cc832800cee0b4645aa8eb9 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 28 Feb 2025 06:46:26 +0000 Subject: [PATCH 11/15] test: spam protection multiple bursts --- src/libs/common.py | 8 +++++++ tests/dos_robustness/dos_robustness.py | 33 +++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/libs/common.py b/src/libs/common.py index 8f2961c..1f57290 100644 --- a/src/libs/common.py +++ b/src/libs/common.py @@ -38,3 +38,11 @@ def to_app_id(n: int) -> list: if n < 0: raise ValueError("Input must be an unsigned integer (non-negative)") return list(n.to_bytes(32, byteorder="big")) + + +def random_divide_k(n, k): + if n < k: + raise ValueError("n must be at least k to split into k parts.") + cuts = sorted(random.sample(range(1, n), k - 1)) + parts = [cuts[0]] + [cuts[i] - cuts[i - 1] for i in range(1, len(cuts))] + [n - cuts[-1]] + return parts diff --git a/tests/dos_robustness/dos_robustness.py b/tests/dos_robustness/dos_robustness.py index 008385e..04391db 100644 --- a/tests/dos_robustness/dos_robustness.py +++ b/tests/dos_robustness/dos_robustness.py @@ -1,6 +1,6 @@ import pytest -from src.libs.common import delay, to_app_id, to_index +from src.libs.common import delay, to_app_id, to_index, random_divide_k from src.libs.custom_logger import get_custom_logger from src.steps.da import StepsDataAvailability from src.test_data import DATA_TO_DISPERSE @@ -29,8 +29,10 @@ class TestDosRobustness(StepsDataAvailability): @pytest.mark.usefixtures("setup_2_node_cluster") def test_spam_protection_single_burst(self): + rate_limit = 1000 + spam_per_burst = rate_limit + 10 successful_dispersals = 0 - for i in range(1000): + for i in range(spam_per_burst): try: self.disperse_data(DATA_TO_DISPERSE[0], to_app_id(1), to_index(0), timeout_duration=0) successful_dispersals = i @@ -38,4 +40,29 @@ class TestDosRobustness(StepsDataAvailability): logger.debug(f"Dispersal #{i+1} was not successful with error {ex}") break - assert successful_dispersals < 1000, "All 1000 consecutive dispersals were successful without any constraint" + assert successful_dispersals <= rate_limit, "All consecutive dispersals were successful without any constraint" + + @pytest.mark.usefixtures("setup_2_node_cluster") + def test_spam_protection_multiple_bursts(self): + time_interval = 60 + rate_limit = 1000 + bursts = 3 + spam_per_burst = int((rate_limit + 10) / bursts) + + waiting_intervals = random_divide_k(time_interval, bursts) + + logger.debug(f"Waiting intervals: {waiting_intervals}") + + successful_dispersals = 0 + for b in range(bursts): + for i in range(spam_per_burst): + try: + self.disperse_data(DATA_TO_DISPERSE[0], to_app_id(1), to_index(0), timeout_duration=0) + successful_dispersals = i + except Exception as ex: + logger.debug(f"Dispersal #{i+1} was not successful with error {ex}") + break + + delay(waiting_intervals[b]) + + assert successful_dispersals <= 1000, "All dispersals were successful without any constraint" From 34df0f5c7fd618b020bb317a7b7f5ac25333c7e9 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 3 Mar 2025 00:33:41 +0000 Subject: [PATCH 12/15] test: spam protection random bytes single burst - prevent false positives --- src/libs/common.py | 5 ++- src/steps/da.py | 8 ++-- tests/dos_robustness/dos_robustness.py | 52 +++++++++++++++++++------- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/libs/common.py b/src/libs/common.py index 60e69ae..4d6184d 100644 --- a/src/libs/common.py +++ b/src/libs/common.py @@ -46,8 +46,9 @@ def random_divide_k(n, k): cuts = sorted(random.sample(range(1, n), k - 1)) parts = [cuts[0]] + [cuts[i] - cuts[i - 1] for i in range(1, len(cuts))] + [n - cuts[-1]] return parts - + def generate_random_bytes(n=31): + if n < 0: + raise ValueError("Input must be an unsigned integer (non-negative)") return os.urandom(n) - diff --git a/src/steps/da.py b/src/steps/da.py index 66fddff..53a04ed 100644 --- a/src/steps/da.py +++ b/src/steps/da.py @@ -96,10 +96,12 @@ class StepsDataAvailability(StepsCommon): except Exception as ex: assert "Bad Request" in str(ex) or "Internal Server Error" in str(ex) - assert response.status_code == 200, "Send dispersal finished with unexpected response code" + assert hasattr(response, "status_code"), "Missing status_code" + assert response.status_code in (200, 429), "Unexpected status code" - disperse() + return response + return disperse() @allure.step def get_data_range(self, node, app_id, start, end, timeout_duration=45): @@ -116,4 +118,4 @@ class StepsDataAvailability(StepsCommon): return response - get_range() + return get_range() diff --git a/tests/dos_robustness/dos_robustness.py b/tests/dos_robustness/dos_robustness.py index 04391db..c3884b7 100644 --- a/tests/dos_robustness/dos_robustness.py +++ b/tests/dos_robustness/dos_robustness.py @@ -1,6 +1,7 @@ +import random import pytest -from src.libs.common import delay, to_app_id, to_index, random_divide_k +from src.libs.common import delay, to_app_id, to_index, random_divide_k, generate_random_bytes from src.libs.custom_logger import get_custom_logger from src.steps.da import StepsDataAvailability from src.test_data import DATA_TO_DISPERSE @@ -17,11 +18,11 @@ class TestDosRobustness(StepsDataAvailability): missing_dispersals = num_samples for i in range(num_samples): try: - self.disperse_data(DATA_TO_DISPERSE[i], to_app_id(1), to_index(0), timeout_duration=0) - missing_dispersals -= 1 + response = self.disperse_data(DATA_TO_DISPERSE[i], to_app_id(1), to_index(0), timeout_duration=0) + if response.status_code == 200: + missing_dispersals -= 1 except Exception as ex: - logger.error(f"Dispersal #{i+1} was not successful with error {ex}") - break + raise Exception(f"Dispersal #{i+1} was not successful with error {ex}") delay(0.1) @@ -34,11 +35,34 @@ class TestDosRobustness(StepsDataAvailability): successful_dispersals = 0 for i in range(spam_per_burst): try: - self.disperse_data(DATA_TO_DISPERSE[0], to_app_id(1), to_index(0), timeout_duration=0) - successful_dispersals = i + response = self.disperse_data(DATA_TO_DISPERSE[0], to_app_id(1), to_index(0), timeout_duration=0) + if response.status_code == 429: + break + else: + successful_dispersals += 1 except Exception as ex: - logger.debug(f"Dispersal #{i+1} was not successful with error {ex}") - break + raise Exception(f"Dispersal #{i+1} was not successful with error {ex}") + + assert successful_dispersals <= rate_limit, "All consecutive dispersals were successful without any constraint" + + @pytest.mark.usefixtures("setup_2_node_cluster") + def test_spam_protection_random_bytes_single_burst(self): + rate_limit = 1000 + spam_per_burst = rate_limit + 10 + + n = random.randint(1, 256) + data_to_disperse = generate_random_bytes(n) + + successful_dispersals = 0 + for i in range(spam_per_burst): + try: + response = self.disperse_data(data_to_disperse, to_app_id(1), to_index(0), timeout_duration=0, utf8=False) + if response.status_code == 429: + break + else: + successful_dispersals += 1 + except Exception as ex: + raise Exception(f"Dispersal #{i+1} was not successful with error {ex}") assert successful_dispersals <= rate_limit, "All consecutive dispersals were successful without any constraint" @@ -57,11 +81,13 @@ class TestDosRobustness(StepsDataAvailability): for b in range(bursts): for i in range(spam_per_burst): try: - self.disperse_data(DATA_TO_DISPERSE[0], to_app_id(1), to_index(0), timeout_duration=0) - successful_dispersals = i + response = self.disperse_data(DATA_TO_DISPERSE[7], to_app_id(1), to_index(0), timeout_duration=0) + if response.status_code == 429: + break + else: + successful_dispersals += 1 except Exception as ex: - logger.debug(f"Dispersal #{i+1} was not successful with error {ex}") - break + raise Exception(f"Dispersal #{i+1} was not successful with error {ex}") delay(waiting_intervals[b]) From 4efd086b0849c4211fe7c06cf161a86241c1630a Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 4 Mar 2025 01:27:36 +0000 Subject: [PATCH 13/15] fix: wrap up actual work into spam protection module --- .../dos_robustness/{dos_robustness.py => test_spam_protection.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/dos_robustness/{dos_robustness.py => test_spam_protection.py} (100%) diff --git a/tests/dos_robustness/dos_robustness.py b/tests/dos_robustness/test_spam_protection.py similarity index 100% rename from tests/dos_robustness/dos_robustness.py rename to tests/dos_robustness/test_spam_protection.py From b55fb83e5f926c2b4814f6ea0204aaa1c99a6bff Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 4 Mar 2025 03:16:29 +0000 Subject: [PATCH 14/15] fix: change class name --- tests/dos_robustness/test_spam_protection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dos_robustness/test_spam_protection.py b/tests/dos_robustness/test_spam_protection.py index c3884b7..68e663a 100644 --- a/tests/dos_robustness/test_spam_protection.py +++ b/tests/dos_robustness/test_spam_protection.py @@ -9,7 +9,7 @@ from src.test_data import DATA_TO_DISPERSE logger = get_custom_logger(__name__) -class TestDosRobustness(StepsDataAvailability): +class TestSpamProtection(StepsDataAvailability): main_nodes = [] @pytest.mark.usefixtures("setup_2_node_cluster") From f66a5bd394525eb3b4d4092f5f2c9934e6fc1b73 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 5 Mar 2025 11:18:03 +0000 Subject: [PATCH 15/15] fix: reduce fixture repetition - improve comment --- src/libs/common.py | 2 +- tests/dos_robustness/test_spam_protection.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/libs/common.py b/src/libs/common.py index 4d6184d..8047f9c 100644 --- a/src/libs/common.py +++ b/src/libs/common.py @@ -42,7 +42,7 @@ def to_app_id(n: int) -> list: def random_divide_k(n, k): if n < k: - raise ValueError("n must be at least k to split into k parts.") + raise ValueError(f"n={n} must be at least k={k} to split into {k} parts") cuts = sorted(random.sample(range(1, n), k - 1)) parts = [cuts[0]] + [cuts[i] - cuts[i - 1] for i in range(1, len(cuts))] + [n - cuts[-1]] return parts diff --git a/tests/dos_robustness/test_spam_protection.py b/tests/dos_robustness/test_spam_protection.py index 68e663a..0dec50d 100644 --- a/tests/dos_robustness/test_spam_protection.py +++ b/tests/dos_robustness/test_spam_protection.py @@ -9,10 +9,10 @@ from src.test_data import DATA_TO_DISPERSE logger = get_custom_logger(__name__) +@pytest.mark.usefixtures("setup_2_node_cluster") class TestSpamProtection(StepsDataAvailability): main_nodes = [] - @pytest.mark.usefixtures("setup_2_node_cluster") def test_spam_protection_valid_uploads(self): num_samples = len(DATA_TO_DISPERSE) missing_dispersals = num_samples @@ -28,7 +28,6 @@ class TestSpamProtection(StepsDataAvailability): assert missing_dispersals == 0, f"{missing_dispersals} dispersals were not successful" - @pytest.mark.usefixtures("setup_2_node_cluster") def test_spam_protection_single_burst(self): rate_limit = 1000 spam_per_burst = rate_limit + 10 @@ -45,7 +44,6 @@ class TestSpamProtection(StepsDataAvailability): assert successful_dispersals <= rate_limit, "All consecutive dispersals were successful without any constraint" - @pytest.mark.usefixtures("setup_2_node_cluster") def test_spam_protection_random_bytes_single_burst(self): rate_limit = 1000 spam_per_burst = rate_limit + 10 @@ -66,7 +64,6 @@ class TestSpamProtection(StepsDataAvailability): assert successful_dispersals <= rate_limit, "All consecutive dispersals were successful without any constraint" - @pytest.mark.usefixtures("setup_2_node_cluster") def test_spam_protection_multiple_bursts(self): time_interval = 60 rate_limit = 1000