chore: Refactor RLN tests (#164)

* test: uncomment RLN tests

* test: single node registration

* test: demo could not generate rln-v2 proof

* fix: update env file example

* fix: update images

* fix: temp pr for testing

* fix: chain id optional and lock v0.38.0

* feat: fix automatic chain ID

* fix: uncomment on chain tests 3208 resolved

* fix: improve assertions

* fix: RLN ready guard

* fix: workaround to wait for RLN

* fix: lightpush test assertion

* fix: reuse creds for the second test

* chore: delay for removing flaky behaviuor

* WIP on chore-refactor-rln-tests

fix: revert changes to docker manager

* fix: undo chore: delay for removing flaky behaviuor

* fix: check for RLN state only when required

* chore: test workflow for RLN

* fix: open permissions

* fix: silent chmod by default

* fix: remove unnecessary code

* fix: reduce CI script to RLN only

* fix: add missing fi

* fix: sync wf with master

* fix: test wf file

* fix: undefined rln creds set

* fix: run together with other tests

* fix: remove test workflows
- reset env file

* fix: sync wrappers manager

* fix: clean up redundant debug lines

---------

Co-authored-by: Darshan <35736874+darshankabariya@users.noreply.github.com>
Co-authored-by: darshankabariya <darshan@status.im>
This commit is contained in:
Roman Zajic 2026-04-18 08:34:20 +08:00 committed by GitHub
parent 5ca3ec6fe9
commit 44dbf628e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 212 additions and 45 deletions

View File

@ -33,4 +33,4 @@ PG_USER = get_env_var("POSTGRES_USER", "postgres")
PG_PASS = get_env_var("POSTGRES_PASSWORD", "test123") PG_PASS = get_env_var("POSTGRES_PASSWORD", "test123")
# example for .env file # example for .env file
# RLN_CREDENTIALS = {"rln-relay-cred-password": "password", "rln-relay-eth-client-address": "wss://sepolia.infura.io/ws/v3/api_key", "rln-relay-eth-contract-address": "0xF471d71E9b1455bBF4b85d475afb9BB0954A29c4", "rln-relay-eth-private-key-1": "1111111111111111111111111111111111111111111111111111111111111111", "rln-relay-eth-private-key-2": "1111111111111111111111111111111111111111111111111111111111111111"} # RLN_CREDENTIALS = {"rln-relay-cred-password": "password", "rln-relay-eth-client-address": "https://rpc.sepolia.linea.build", "rln-relay-eth-contract-address": "0xB9cd878C90E49F797B4431fBF4fb333108CB90e6", "rln-relay-eth-private-key-1": "", "rln-relay-eth-private-key-2": "", "rln-relay-eth-private-key-3": "", "rln-relay-eth-private-key-4": "", "rln-relay-eth-private-key-5": ""}

View File

@ -5,11 +5,13 @@ import random
import re import re
import shutil import shutil
import string import string
import subprocess
import pytest import pytest
import requests import requests
from src.libs.common import delay from src.libs.common import delay
from src.libs.custom_logger import get_custom_logger from src.libs.custom_logger import get_custom_logger
from tenacity import retry, stop_after_delay, wait_fixed from tenacity import retry, stop_after_delay, wait_fixed, sleep
from docker.errors import NotFound as DockerNotFound
from src.node.api_clients.rest import REST from src.node.api_clients.rest import REST
from src.node.docker_mananger import DockerManager from src.node.docker_mananger import DockerManager
from src.env_vars import DOCKER_LOG_DIR from src.env_vars import DOCKER_LOG_DIR
@ -38,8 +40,24 @@ def sanitize_docker_flags(input_flags):
@retry(stop=stop_after_delay(180), wait=wait_fixed(0.5), reraise=True) @retry(stop=stop_after_delay(180), wait=wait_fixed(0.5), reraise=True)
def rln_credential_store_ready(creds_file_path, single_check=False): def rln_credential_store_ready(creds_file_path, single_check=False, require_credentials=False):
if os.path.exists(creds_file_path): if os.path.exists(creds_file_path):
subprocess.run(["sudo", "-n", "chmod", "a+r", creds_file_path], check=False)
if require_credentials:
try:
with open(creds_file_path, "r", encoding="utf-8") as creds_file:
keystore_data = json.load(creds_file)
except (OSError, json.JSONDecodeError) as ex:
if single_check:
return False
raise ValueError(f"Failed to parse RLN keystore at {creds_file_path}: {ex}")
credentials = keystore_data.get("credentials", {}) if isinstance(keystore_data, dict) else {}
if not credentials:
if single_check:
return False
raise ValueError(f"RLN keystore exists but has no credentials yet: {creds_file_path}")
return True return True
elif not single_check: elif not single_check:
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), creds_file_path) raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), creds_file_path)
@ -83,8 +101,10 @@ class WakuNode:
self._log_path = os.path.join(DOCKER_LOG_DIR, f"{docker_log_prefix}__{self._image_name.replace('/', '_')}.log") self._log_path = os.path.join(DOCKER_LOG_DIR, f"{docker_log_prefix}__{self._image_name.replace('/', '_')}.log")
self._docker_manager = DockerManager(self._image_name) self._docker_manager = DockerManager(self._image_name)
self._container = None self._container = None
self.rln_membership_index = None
self.start_args = {} self.start_args = {}
self._wrapper_node = None self._wrapper_node = None
self._rln_creds_set = False
logger.debug(f"WakuNode instance initialized with log path {self._log_path}") logger.debug(f"WakuNode instance initialized with log path {self._log_path}")
@property @property
@ -178,13 +198,14 @@ class WakuNode:
del default_args["pubsub-topic"] del default_args["pubsub-topic"]
rln_args, rln_creds_set, keystore_path = self.parse_rln_credentials(default_args, False) rln_args, rln_creds_set, keystore_path = self.parse_rln_credentials(default_args, False)
self._rln_creds_set = rln_creds_set
default_args.pop("rln-creds-id", None) default_args.pop("rln-creds-id", None)
default_args.pop("rln-creds-source", None) default_args.pop("rln-creds-source", None)
default_args.pop("rln-keystore-prefix", None) default_args.pop("rln-keystore-prefix", None)
if rln_creds_set: if rln_creds_set:
rln_credential_store_ready(keystore_path) rln_credential_store_ready(keystore_path, require_credentials=True)
default_args.update(rln_args) default_args.update(rln_args)
else: else:
logger.info(f"RLN credentials not set or credential store not available, starting without RLN") logger.info(f"RLN credentials not set or credential store not available, starting without RLN")
@ -208,7 +229,7 @@ class WakuNode:
DS.waku_nodes.append(self) DS.waku_nodes.append(self)
delay(1) delay(1)
try: try:
self.ensure_ready(timeout_duration=wait_for_node_sec) self.ensure_ready(timeout_duration=wait_for_node_sec, rln_required=self._rln_creds_set)
except Exception as ex: except Exception as ex:
logger.error(f"REST service did not become ready in time: {ex}") logger.error(f"REST service did not become ready in time: {ex}")
raise raise
@ -284,13 +305,32 @@ class WakuNode:
logger.debug(f"Waiting for keystore {keystore_path}") logger.debug(f"Waiting for keystore {keystore_path}")
try: try:
rln_credential_store_ready(keystore_path) rln_credential_store_ready(keystore_path, require_credentials=True)
self.rln_membership_index = str(self.get_rln_membership_index_from_log())
logger.debug(f"Detected RLN membership index from registration logs: {self.rln_membership_index}")
self.stop()
except Exception as ex: except Exception as ex:
logger.error(f"File {keystore_path} with RLN credentials did not become available in time {ex}") logger.error(f"File {keystore_path} with RLN credentials did not become available in time {ex}")
raise raise
else: else:
logger.warn("RLN credentials not set, no action performed") logger.warn("RLN credentials not set, no action performed")
return self.rln_membership_index
@retry(stop=stop_after_delay(10), wait=wait_fixed(0.2), reraise=True)
def get_rln_membership_index_from_log(self):
if not os.path.exists(self._log_path):
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), self._log_path)
with open(self._log_path, "r", encoding="utf-8", errors="ignore") as log_file:
log_data = log_file.read()
matches = re.findall(r"membershipIndex=(\d+)", log_data)
if not matches:
raise ValueError("Could not infer RLN membership index from registration logs")
return int(matches[-1])
@retry(stop=stop_after_delay(5), wait=wait_fixed(0.1), reraise=True) @retry(stop=stop_after_delay(5), wait=wait_fixed(0.1), reraise=True)
def stop(self): def stop(self):
if self._is_wrapper: if self._is_wrapper:
@ -301,7 +341,12 @@ class WakuNode:
def _stop_docker(self): def _stop_docker(self):
if self._container: if self._container:
logger.debug(f"Stopping container with id {self._container.short_id}") logger.debug(f"Stopping container with id {self._container.short_id}")
self._container.stop() try:
self._container.stop()
except DockerNotFound:
logger.debug(f"Container {self._container.short_id} already exited and removed, treating as stopped.")
self._container = None
return
try: try:
self._container.remove() self._container.remove()
except: except:
@ -344,7 +389,7 @@ class WakuNode:
logger.debug(f"Unpause container with id {self._container.short_id}") logger.debug(f"Unpause container with id {self._container.short_id}")
self._container.unpause() self._container.unpause()
def ensure_ready(self, timeout_duration=10): def ensure_ready(self, timeout_duration=10, rln_required=False):
@retry(stop=stop_after_delay(timeout_duration), wait=wait_fixed(0.1), reraise=True) @retry(stop=stop_after_delay(timeout_duration), wait=wait_fixed(0.1), reraise=True)
def check_healthy(node=self): def check_healthy(node=self):
self.health_response = node.health() self.health_response = node.health()
@ -357,9 +402,12 @@ class WakuNode:
if self.health_response.get("nodeHealth") != "READY": if self.health_response.get("nodeHealth") != "READY":
raise AssertionError("Waiting for the node health status: READY") raise AssertionError("Waiting for the node health status: READY")
# for p in self.health_response.get("protocolsHealth"): for p in self.health_response.get("protocolsHealth"):
# if p.get("Rln Relay") != "READY": if rln_required and "Rln Relay" in p:
# raise AssertionError("Waiting for the Rln relay status: READY") if p["Rln Relay"] != "READY":
raise AssertionError("Waiting for the Rln relay status: READY")
# TODO: Remove once Rln Relay reflects true RLN status
sleep(20)
logger.info("Node protocols are initialized !!") logger.info("Node protocols are initialized !!")
@ -532,6 +580,21 @@ class WakuNode:
def is_nwaku(self): def is_nwaku(self):
return "nwaku" in self.image return "nwaku" in self.image
def prepare_rln_storage_paths(self, cwd, keystore_prefix, selected_id, reset_existing=False):
keystore_dir = os.path.join(cwd, f"keystore_{keystore_prefix}_{selected_id}")
rln_tree_dir = os.path.join(cwd, f"rln_tree_{keystore_prefix}_{selected_id}")
if reset_existing:
for path, path_name in [(keystore_dir, "keystore"), (rln_tree_dir, "rln tree")]:
if os.path.exists(path):
logger.warning(f"Resetting existing RLN {path_name} directory before registration: {path}")
shutil.rmtree(path, ignore_errors=True)
os.makedirs(keystore_dir, exist_ok=True)
os.makedirs(rln_tree_dir, exist_ok=True)
return keystore_dir, rln_tree_dir
def parse_rln_credentials(self, default_args, is_registration): def parse_rln_credentials(self, default_args, is_registration):
rln_args = {} rln_args = {}
keystore_path = None keystore_path = None
@ -544,6 +607,13 @@ class WakuNode:
return rln_args, False, keystore_path return rln_args, False, keystore_path
imported_creds = json.loads(rln_creds_source) imported_creds = json.loads(rln_creds_source)
rln_chain_id = imported_creds.get("rln-relay-chain-id")
if rln_chain_id is None:
eth_client_address = imported_creds.get("rln-relay-eth-client-address", "")
if "linea" in eth_client_address:
rln_chain_id = "59141"
elif "sepolia" in eth_client_address:
rln_chain_id = "11155111"
if len(imported_creds) < 4 or any(value is None for value in imported_creds.values()): if len(imported_creds) < 4 or any(value is None for value in imported_creds.values()):
logger.warn(f"One or more of required RLN credentials were not set properly") logger.warn(f"One or more of required RLN credentials were not set properly")
@ -552,6 +622,13 @@ class WakuNode:
eth_private_key = select_private_key(imported_creds, selected_id) eth_private_key = select_private_key(imported_creds, selected_id)
cwd = os.getcwd() cwd = os.getcwd()
keystore_prefix = default_args.get("rln-keystore-prefix")
if not keystore_prefix:
logger.warn("rln-keystore-prefix is missing, cannot mount RLN state and keystore")
return rln_args, False, keystore_path
keystore_dir, rln_tree_dir = self.prepare_rln_storage_paths(cwd, keystore_prefix, selected_id, reset_existing=is_registration)
if self.is_nwaku(): if self.is_nwaku():
if is_registration: if is_registration:
@ -574,6 +651,8 @@ class WakuNode:
{ {
"rln-relay-cred-path": "/keystore/keystore.json", "rln-relay-cred-path": "/keystore/keystore.json",
"rln-relay-cred-password": imported_creds["rln-relay-cred-password"], "rln-relay-cred-password": imported_creds["rln-relay-cred-password"],
"rln-relay-eth-client-address": imported_creds["rln-relay-eth-client-address"],
"rln-relay-eth-contract-address": imported_creds["rln-relay-eth-contract-address"],
} }
) )
else: else:
@ -587,12 +666,15 @@ class WakuNode:
} }
) )
keystore_path = cwd + "/keystore_" + default_args["rln-keystore-prefix"] + "_" + selected_id + "/keystore.json" if rln_chain_id is not None:
rln_args["rln-relay-chain-id"] = str(rln_chain_id)
keystore_path = os.path.join(keystore_dir, "keystore.json")
self._volumes.extend( self._volumes.extend(
[ [
cwd + "/rln_tree_" + default_args["rln-keystore-prefix"] + "_" + selected_id + ":/etc/rln_tree", f"{rln_tree_dir}:/etc/rln_tree",
cwd + "/keystore_" + default_args["rln-keystore-prefix"] + "_" + selected_id + ":/keystore", f"{keystore_dir}:/keystore",
] ]
) )

View File

@ -26,6 +26,7 @@ class StepsRLN(StepsCommon):
multiaddr_list = [] multiaddr_list = []
lightpush_nodes = [] lightpush_nodes = []
keystore_prefixes = [] keystore_prefixes = []
rln_membership_indexes = []
@allure.step @allure.step
def generate_keystore_prefixes(self, count=2): def generate_keystore_prefixes(self, count=2):
@ -38,15 +39,19 @@ class StepsRLN(StepsCommon):
@allure.step @allure.step
def register_rln_relay_nodes(self, count, orig_prefixes): def register_rln_relay_nodes(self, count, orig_prefixes):
if count > 0: if count > 0:
logger.debug(111111111111111)
self.keystore_prefixes = self.generate_keystore_prefixes(count) self.keystore_prefixes = self.generate_keystore_prefixes(count)
self.rln_membership_indexes = []
for i, prefix in enumerate(self.keystore_prefixes): for i, prefix in enumerate(self.keystore_prefixes):
logger.debug(000000000000000000000) membership_index = self.register_rln_single_node(prefix=prefix, rln_creds_source=RLN_CREDENTIALS, rln_creds_id=f"{i+1}")
self.register_rln_single_node(prefix=prefix, rln_creds_source=RLN_CREDENTIALS, rln_creds_id=f"{i+1}") self.rln_membership_indexes.append(membership_index)
else: else:
self.keystore_prefixes = orig_prefixes self.keystore_prefixes = orig_prefixes.get("keystore_prefixes", [])
self.rln_membership_indexes = orig_prefixes.get("rln_membership_indexes", [])
return self.keystore_prefixes return {
"keystore_prefixes": self.keystore_prefixes,
"rln_membership_indexes": self.rln_membership_indexes,
}
@allure.step @allure.step
def setup_main_rln_relay_nodes(self, **kwargs): def setup_main_rln_relay_nodes(self, **kwargs):
@ -60,7 +65,7 @@ class StepsRLN(StepsCommon):
relay="true", relay="true",
rln_creds_source=RLN_CREDENTIALS, rln_creds_source=RLN_CREDENTIALS,
rln_creds_id="1", rln_creds_id="1",
rln_relay_membership_index="1", rln_relay_membership_index=self.resolve_rln_membership_index(0, **kwargs),
rln_keystore_prefix=self.keystore_prefixes[0], rln_keystore_prefix=self.keystore_prefixes[0],
**kwargs, **kwargs,
) )
@ -78,7 +83,7 @@ class StepsRLN(StepsCommon):
discv5_bootstrap_node=self.enr_uri, discv5_bootstrap_node=self.enr_uri,
rln_creds_source=RLN_CREDENTIALS, rln_creds_source=RLN_CREDENTIALS,
rln_creds_id="2", rln_creds_id="2",
rln_relay_membership_index="1", rln_relay_membership_index=self.resolve_rln_membership_index(1, **kwargs),
rln_keystore_prefix=self.keystore_prefixes[1], rln_keystore_prefix=self.keystore_prefixes[1],
**kwargs, **kwargs,
) )
@ -101,7 +106,7 @@ class StepsRLN(StepsCommon):
discv5_bootstrap_node=self.enr_uri, discv5_bootstrap_node=self.enr_uri,
rln_creds_source=RLN_CREDENTIALS, rln_creds_source=RLN_CREDENTIALS,
rln_creds_id=f"{index + 3}", rln_creds_id=f"{index + 3}",
rln_relay_membership_index="1", rln_relay_membership_index=self.resolve_rln_membership_index(index + 2, **kwargs),
rln_keystore_prefix=self.keystore_prefixes[index + 2], rln_keystore_prefix=self.keystore_prefixes[index + 2],
**kwargs, **kwargs,
) )
@ -118,7 +123,7 @@ class StepsRLN(StepsCommon):
lightpushnode=self.multiaddr_list[0], lightpushnode=self.multiaddr_list[0],
rln_creds_source=RLN_CREDENTIALS, rln_creds_source=RLN_CREDENTIALS,
rln_creds_id="2", rln_creds_id="2",
rln_relay_membership_index="1", rln_relay_membership_index=self.resolve_rln_membership_index(1, **kwargs),
rln_keystore_prefix=self.keystore_prefixes[1], rln_keystore_prefix=self.keystore_prefixes[1],
**kwargs, **kwargs,
) )
@ -131,7 +136,23 @@ class StepsRLN(StepsCommon):
def register_rln_single_node(self, prefix="", **kwargs): def register_rln_single_node(self, prefix="", **kwargs):
logger.debug("Registering RLN credentials for single node") logger.debug("Registering RLN credentials for single node")
self.node = WakuNode(DEFAULT_NWAKU, f"node_{gen_step_id()}") self.node = WakuNode(DEFAULT_NWAKU, f"node_{gen_step_id()}")
self.node.register_rln(rln_keystore_prefix=prefix, rln_creds_source=kwargs["rln_creds_source"], rln_creds_id=kwargs["rln_creds_id"]) return self.node.register_rln(rln_keystore_prefix=prefix, rln_creds_source=kwargs["rln_creds_source"], rln_creds_id=kwargs["rln_creds_id"])
@allure.step
def resolve_rln_membership_index(self, index, **kwargs):
explicit_index = kwargs.get("rln_relay_membership_index")
if explicit_index is not None:
return str(explicit_index)
if len(self.rln_membership_indexes) > index and self.rln_membership_indexes[index] is not None:
inferred_index = str(self.rln_membership_indexes[index])
logger.debug(f"Using inferred RLN membership index for position {index}: {inferred_index}")
return inferred_index
raise ValueError(
f"RLN membership index for position {index} is not available. "
"Register credentials and persist rln_membership_indexes together with keystore_prefixes before node startup."
)
@allure.step @allure.step
def check_rln_registration(self, prefix, key_id): def check_rln_registration(self, prefix, key_id):

View File

@ -168,7 +168,7 @@ SAMPLE_TIMESTAMPS = [
{"description": "Missing", "value": None, "valid_for": []}, {"description": "Missing", "value": None, "valid_for": []},
] ]
PUBSUB_TOPICS_RLN = ["/waku/2/rs/1/0"] PUBSUB_TOPICS_RLN = [f"/waku/2/rs/{DEFAULT_CLUSTER_ID}/0"]
LOG_ERROR_KEYWORDS = [ LOG_ERROR_KEYWORDS = [
"crash", "crash",

View File

@ -13,7 +13,6 @@ from src.test_data import SAMPLE_INPUTS
logger = get_custom_logger(__name__) logger = get_custom_logger(__name__)
@pytest.mark.skip(reason="RLN functional changes. To be updated by Roman Zajic")
@pytest.mark.xdist_group(name="RLN serial tests") @pytest.mark.xdist_group(name="RLN serial tests")
class TestRelayRLN(StepsRLN, StepsRelay): class TestRelayRLN(StepsRLN, StepsRelay):
SAMPLE_INPUTS_RLN = SAMPLE_INPUTS + SAMPLE_INPUTS + SAMPLE_INPUTS SAMPLE_INPUTS_RLN = SAMPLE_INPUTS + SAMPLE_INPUTS + SAMPLE_INPUTS
@ -22,7 +21,14 @@ class TestRelayRLN(StepsRLN, StepsRelay):
def test_valid_payloads_lightpush_at_spam_rate(self, pytestconfig): def test_valid_payloads_lightpush_at_spam_rate(self, pytestconfig):
message_limit = 1 message_limit = 1
epoch_sec = 1 epoch_sec = 1
pytestconfig.cache.set("keystore-prefixes", self.register_rln_relay_nodes(2, [])) rln_state = self.register_rln_relay_nodes(2, [])
pytestconfig.cache.set(
"keystore-prefixes",
{
"keystore_prefixes": rln_state["keystore_prefixes"],
"rln_membership_indexes": rln_state["rln_membership_indexes"],
},
)
self.setup_first_rln_relay_node(lightpush="true", rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=epoch_sec) self.setup_first_rln_relay_node(lightpush="true", rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=epoch_sec)
self.setup_second_rln_lightpush_node(rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=epoch_sec) self.setup_second_rln_lightpush_node(rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=epoch_sec)
self.subscribe_main_relay_nodes() self.subscribe_main_relay_nodes()
@ -37,11 +43,17 @@ class TestRelayRLN(StepsRLN, StepsRelay):
if i > message_limit and (now - start) <= epoch_sec: if i > message_limit and (now - start) <= epoch_sec:
raise AssertionError("Publish with RLN enabled at spam rate worked!!!") raise AssertionError("Publish with RLN enabled at spam rate worked!!!")
except Exception as e: except Exception as e:
assert "RLN validation failed" or "NonceLimitReached" in str(e) error_str = str(e)
assert "RLN validation failed" in error_str or "NonceLimitReached" in error_str
if "NonceLimitReached" in error_str:
assert "503" in error_str, f"Expected HTTP 503 for NonceLimitReached, got: {error_str}"
def test_valid_payloads_at_slow_rate(self, pytestconfig): def test_valid_payloads_at_slow_rate(self, pytestconfig):
message_limit = 20 message_limit = 20
self.register_rln_relay_nodes(0, pytestconfig.cache.get("keystore-prefixes", [])) self.register_rln_relay_nodes(
0,
pytestconfig.cache.get("keystore-prefixes", {"keystore_prefixes": [], "rln_membership_indexes": []}),
)
self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=600) self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=600)
self.subscribe_main_relay_nodes() self.subscribe_main_relay_nodes()
failed_payloads = [] failed_payloads = []
@ -63,7 +75,10 @@ class TestRelayRLN(StepsRLN, StepsRelay):
def test_valid_payloads_at_spam_rate(self, pytestconfig): def test_valid_payloads_at_spam_rate(self, pytestconfig):
message_limit = 20 message_limit = 20
epoch_sec = 600 epoch_sec = 600
self.register_rln_relay_nodes(0, pytestconfig.cache.get("keystore-prefixes", [])) self.register_rln_relay_nodes(
0,
pytestconfig.cache.get("keystore-prefixes", {"keystore_prefixes": [], "rln_membership_indexes": []}),
)
self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=epoch_sec) self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=epoch_sec)
self.subscribe_main_relay_nodes() self.subscribe_main_relay_nodes()
start = math.trunc(time()) start = math.trunc(time())
@ -77,10 +92,16 @@ class TestRelayRLN(StepsRLN, StepsRelay):
if i > message_limit and (now - start) <= epoch_sec: if i > message_limit and (now - start) <= epoch_sec:
raise AssertionError("Publish with RLN enabled at spam rate worked!!!") raise AssertionError("Publish with RLN enabled at spam rate worked!!!")
except Exception as e: except Exception as e:
assert "RLN validation failed" or "NonceLimitReached" in str(e) error_str = str(e)
assert "RLN validation failed" in error_str or "NonceLimitReached" in error_str
if "NonceLimitReached" in error_str:
assert "500" in error_str, f"Expected HTTP 500 for NonceLimitReached, got: {error_str}"
def test_valid_payload_at_variable_rate(self, pytestconfig): def test_valid_payload_at_variable_rate(self, pytestconfig):
self.register_rln_relay_nodes(0, pytestconfig.cache.get("keystore-prefixes", [])) self.register_rln_relay_nodes(
0,
pytestconfig.cache.get("keystore-prefixes", {"keystore_prefixes": [], "rln_membership_indexes": []}),
)
self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1) self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1)
self.subscribe_main_relay_nodes() self.subscribe_main_relay_nodes()
payload_desc = SAMPLE_INPUTS[0]["description"] payload_desc = SAMPLE_INPUTS[0]["description"]
@ -101,11 +122,17 @@ class TestRelayRLN(StepsRLN, StepsRelay):
else: else:
previous = now previous = now
except Exception as e: except Exception as e:
assert "RLN validation failed" or "NonceLimitReached" in str(e) error_str = str(e)
assert "RLN validation failed" in error_str or "NonceLimitReached" in error_str
if "NonceLimitReached" in error_str:
assert "500" in error_str, f"Expected HTTP 500 for NonceLimitReached, got: {error_str}"
def test_valid_payloads_random_epoch_at_slow_rate(self, pytestconfig): def test_valid_payloads_random_epoch_at_slow_rate(self, pytestconfig):
epoch_sec = random.randint(2, 5) epoch_sec = random.randint(2, 5)
self.register_rln_relay_nodes(0, pytestconfig.cache.get("keystore-prefixes", [])) self.register_rln_relay_nodes(
0,
pytestconfig.cache.get("keystore-prefixes", {"keystore_prefixes": [], "rln_membership_indexes": []}),
)
self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=epoch_sec) self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=epoch_sec)
self.subscribe_main_relay_nodes() self.subscribe_main_relay_nodes()
failed_payloads = [] failed_payloads = []
@ -122,7 +149,10 @@ class TestRelayRLN(StepsRLN, StepsRelay):
def test_valid_payloads_random_user_message_limit(self, pytestconfig): def test_valid_payloads_random_user_message_limit(self, pytestconfig):
user_message_limit = random.randint(2, 4) user_message_limit = random.randint(2, 4)
self.register_rln_relay_nodes(0, pytestconfig.cache.get("keystore-prefixes", [])) self.register_rln_relay_nodes(
0,
pytestconfig.cache.get("keystore-prefixes", {"keystore_prefixes": [], "rln_membership_indexes": []}),
)
self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=user_message_limit, rln_relay_epoch_sec=1) self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=user_message_limit, rln_relay_epoch_sec=1)
self.subscribe_main_relay_nodes() self.subscribe_main_relay_nodes()
failed_payloads = [] failed_payloads = []
@ -136,12 +166,18 @@ class TestRelayRLN(StepsRLN, StepsRelay):
failed_payloads.append(payload["description"]) failed_payloads.append(payload["description"])
assert not failed_payloads, f"Payloads failed: {failed_payloads}" assert not failed_payloads, f"Payloads failed: {failed_payloads}"
@pytest.mark.skip(reason="Waiting for issue resolution https://github.com/waku-org/nwaku/issues/3208")
@pytest.mark.timeout(600) @pytest.mark.timeout(600)
def test_valid_payloads_dynamic_at_spam_rate(self, pytestconfig): def test_valid_payloads_dynamic_at_spam_rate(self, pytestconfig):
message_limit = 100 message_limit = 100
epoch_sec = 600 epoch_sec = 600
pytestconfig.cache.set("keystore-prefixes", self.register_rln_relay_nodes(2, [])) rln_state = self.register_rln_relay_nodes(2, [])
pytestconfig.cache.set(
"keystore-prefixes",
{
"keystore_prefixes": rln_state["keystore_prefixes"],
"rln_membership_indexes": rln_state["rln_membership_indexes"],
},
)
self.setup_main_rln_relay_nodes( self.setup_main_rln_relay_nodes(
rln_relay_user_message_limit=message_limit, rln_relay_user_message_limit=message_limit,
rln_relay_epoch_sec=epoch_sec, rln_relay_epoch_sec=epoch_sec,
@ -160,13 +196,22 @@ class TestRelayRLN(StepsRLN, StepsRelay):
if i > message_limit and (now - start) <= epoch_sec: if i > message_limit and (now - start) <= epoch_sec:
raise AssertionError("Publish with RLN enabled at spam rate worked!!!") raise AssertionError("Publish with RLN enabled at spam rate worked!!!")
except Exception as e: except Exception as e:
assert "RLN validation failed" or "NonceLimitReached" in str(e) error_str = str(e)
assert "RLN validation failed" in error_str or "NonceLimitReached" in error_str
if "NonceLimitReached" in error_str:
assert "500" in error_str, f"Expected HTTP 500 for NonceLimitReached, got: {error_str}"
@pytest.mark.skip(reason="Waiting for issue resolution https://github.com/waku-org/nwaku/issues/3208")
@pytest.mark.timeout(600) @pytest.mark.timeout(600)
def test_valid_payloads_dynamic_at_slow_rate(self, pytestconfig): def test_valid_payloads_dynamic_at_slow_rate(self, pytestconfig):
message_limit = 100 message_limit = 100
pytestconfig.cache.set("keystore-prefixes", self.register_rln_relay_nodes(2, [])) rln_state = self.register_rln_relay_nodes(2, [])
pytestconfig.cache.set(
"keystore-prefixes",
{
"keystore_prefixes": rln_state["keystore_prefixes"],
"rln_membership_indexes": rln_state["rln_membership_indexes"],
},
)
self.setup_main_rln_relay_nodes( self.setup_main_rln_relay_nodes(
rln_relay_user_message_limit=message_limit, rln_relay_user_message_limit=message_limit,
rln_relay_epoch_sec=600, rln_relay_epoch_sec=600,
@ -192,7 +237,10 @@ class TestRelayRLN(StepsRLN, StepsRelay):
def test_valid_payloads_n1_with_rln_n2_without_rln_at_spam_rate(self, pytestconfig): def test_valid_payloads_n1_with_rln_n2_without_rln_at_spam_rate(self, pytestconfig):
message_limit = 1 message_limit = 1
epoch_sec = 1 epoch_sec = 1
self.register_rln_relay_nodes(0, pytestconfig.cache.get("keystore-prefixes", [])) self.register_rln_relay_nodes(
0,
pytestconfig.cache.get("keystore-prefixes", {"keystore_prefixes": [], "rln_membership_indexes": []}),
)
self.setup_first_rln_relay_node(rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=epoch_sec) self.setup_first_rln_relay_node(rln_relay_user_message_limit=message_limit, rln_relay_epoch_sec=epoch_sec)
self.setup_second_relay_node() self.setup_second_relay_node()
self.subscribe_main_relay_nodes() self.subscribe_main_relay_nodes()
@ -206,10 +254,20 @@ class TestRelayRLN(StepsRLN, StepsRelay):
if i > message_limit and (now - start) <= epoch_sec: if i > message_limit and (now - start) <= epoch_sec:
raise AssertionError("Publish with RLN enabled at spam rate worked!!!") raise AssertionError("Publish with RLN enabled at spam rate worked!!!")
except Exception as e: except Exception as e:
assert "RLN validation failed" or "NonceLimitReached" in str(e) error_str = str(e)
assert "RLN validation failed" in error_str or "NonceLimitReached" in error_str
if "NonceLimitReached" in error_str:
assert "500" in error_str, f"Expected HTTP 500 for NonceLimitReached, got: {error_str}"
def test_valid_payloads_with_optional_nodes_at_slow_rate(self, pytestconfig): def test_valid_payloads_with_optional_nodes_at_slow_rate(self, pytestconfig):
pytestconfig.cache.set("keystore-prefixes", self.register_rln_relay_nodes(5, [])) rln_state = self.register_rln_relay_nodes(5, [])
pytestconfig.cache.set(
"keystore-prefixes",
{
"keystore_prefixes": rln_state["keystore_prefixes"],
"rln_membership_indexes": rln_state["rln_membership_indexes"],
},
)
self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1) self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1)
self.setup_optional_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1) self.setup_optional_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1)
self.subscribe_main_relay_nodes() self.subscribe_main_relay_nodes()
@ -228,7 +286,10 @@ class TestRelayRLN(StepsRLN, StepsRelay):
assert not failed_payloads, f"Payloads failed: {failed_payloads}" assert not failed_payloads, f"Payloads failed: {failed_payloads}"
def test_valid_payloads_with_optional_nodes_at_spam_rate(self, pytestconfig): def test_valid_payloads_with_optional_nodes_at_spam_rate(self, pytestconfig):
self.register_rln_relay_nodes(0, pytestconfig.cache.get("keystore-prefixes", [])) self.register_rln_relay_nodes(
0,
pytestconfig.cache.get("keystore-prefixes", {"keystore_prefixes": [], "rln_membership_indexes": []}),
)
self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1) self.setup_main_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1)
self.setup_optional_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1) self.setup_optional_rln_relay_nodes(rln_relay_user_message_limit=1, rln_relay_epoch_sec=1)
self.subscribe_main_relay_nodes() self.subscribe_main_relay_nodes()
@ -246,4 +307,7 @@ class TestRelayRLN(StepsRLN, StepsRelay):
else: else:
previous = now previous = now
except Exception as e: except Exception as e:
assert "RLN validation failed" or "NonceLimitReached" in str(e) error_str = str(e)
assert "RLN validation failed" in error_str or "NonceLimitReached" in error_str
if "NonceLimitReached" in error_str:
assert "500" in error_str, f"Expected HTTP 500 for NonceLimitReached, got: {error_str}"