From 76f65c7de882a30ebb005e0889b04598c4d215b4 Mon Sep 17 00:00:00 2001 From: Bernat Date: Tue, 3 Dec 2024 14:11:06 +0100 Subject: [PATCH 1/4] add summary and detailed modes --- swarm/__main__.py | 1 + swarm/relay_check.py | 131 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 106 insertions(+), 26 deletions(-) diff --git a/swarm/__main__.py b/swarm/__main__.py index 0312a86..0ba5993 100644 --- a/swarm/__main__.py +++ b/swarm/__main__.py @@ -38,6 +38,7 @@ parser_exit_monitor.set_defaults(func=exit.automated_exit) parser_relay_check = subparsers.add_parser('relay-check', help='Check validator relay coverage against allowed mev-boost relays') + parser_relay_check.add_argument('--detailed', action='store_true', help='Show detailed registration information') parser_relay_check.set_defaults(func=relay_check.check_relays) args = parser.parse_args() diff --git a/swarm/relay_check.py b/swarm/relay_check.py index 3eeacf5..829e213 100644 --- a/swarm/relay_check.py +++ b/swarm/relay_check.py @@ -21,12 +21,13 @@ async def check_validator_registration(session, relay_url, pubkey): """Check if a validator is registered with a specific relay""" url = f"{relay_url}/relay/v1/data/validator_registration" try: - async with session.get(url, params={'pubkey': pubkey}) as response: + async with session.get(url, params={'pubkey': pubkey}, timeout=5) as response: if response.status == 200: return True return False - except: - raise RelayRequestException(f"Failed to request validator registration from {relay_url}") + except Exception as e: + print(f"Warning: Could not check registration status for {relay_url}: {str(e)}") + return None # Return None to indicate unknown status async def get_validator_keys_from_csm(config): csm = CSM(config) @@ -39,9 +40,13 @@ async def check_relays(config, args): # Get whitelisted relays relay_tuples = await get_whitelisted_relays(config) - # Extract just the URLs from the relay tuples relay_urls = [relay[0] for relay in relay_tuples] - print(f"Found {len(relay_urls)} whitelisted relays") + + # Separate mandatory and optional relays + mandatory_relays = [relay[0] for relay in relay_tuples if relay[2]] # relay[2] is the mandatory flag + optional_relays = [relay[0] for relay in relay_tuples if not relay[2]] + + print(f"Found {len(relay_urls)} whitelisted relays ({len(mandatory_relays)} mandatory, {len(optional_relays)} optional)") # Get validator public keys from CSM validator_keys = await get_validator_keys_from_csm(config) @@ -50,26 +55,100 @@ async def check_relays(config, args): # Check registration for each validator with each relay async with aiohttp.ClientSession() as session: results = {} - for validator in validator_keys: - results[validator] = {'total': 0, 'relays': []} + + # Process validators in chunks to limit concurrency + chunk_size = 10 + for i in range(0, len(validator_keys), chunk_size): + validator_chunk = validator_keys[i:i + chunk_size] + + tasks = [] + for validator in validator_chunk: + results[validator] = { + 'mandatory_total': 0, + 'optional_total': 0, + 'mandatory_relays': [], + 'optional_relays': [], + 'unknown': [] + } + for relay_url in relay_urls: + tasks.append(check_validator_registration(session, relay_url, validator)) + + chunk_results = await asyncio.gather(*tasks) - for relay_url in relay_urls: - is_registered = await check_validator_registration(session, relay_url, validator) - if is_registered: - results[validator]['total'] += 1 - results[validator]['relays'].append(relay_url) + for validator_idx, validator in enumerate(validator_chunk): + for relay_idx, is_registered in enumerate(chunk_results[validator_idx * len(relay_urls):(validator_idx + 1) * len(relay_urls)]): + relay_url = relay_urls[relay_idx] + if is_registered is True: + if relay_url in mandatory_relays: + results[validator]['mandatory_total'] += 1 + results[validator]['mandatory_relays'].append(relay_url) + else: + results[validator]['optional_total'] += 1 + results[validator]['optional_relays'].append(relay_url) + elif is_registered is None: + results[validator]['unknown'].append(relay_url) + + print(f"Processed {min(i + chunk_size, len(validator_keys))}/{len(validator_keys)} validators...") + + # Print results based on mode + if hasattr(args, 'detailed') and args.detailed: + print_detailed_report(results, relay_tuples, mandatory_relays, optional_relays) + else: + print_summary_report(results, mandatory_relays, optional_relays) + +def print_summary_report(results, mandatory_relays, optional_relays): + """Print condensed summary of validator relay registration status""" + print("\nRelay Coverage Summary:") + print("-" * 50) + + ok_count = 0 + not_ok_count = 0 + + for validator, data in results.items(): + is_ok = data['mandatory_total'] >= 2 # At least 2 mandatory relays required + status = "OK" if is_ok else "NOT OK" + + if is_ok: + ok_count += 1 + else: + not_ok_count += 1 + + mandatory_coverage = f"{data['mandatory_total']}/{len(mandatory_relays)}" + optional_coverage = f"{data['optional_total']}/{len(optional_relays)}" + + print(f"Validator {validator[:12]}... : {status:6} (Mandatory: {mandatory_coverage}, Optional: {optional_coverage})") + + print(f"\nSummary: {ok_count} validators OK, {not_ok_count} validators NOT OK") + +def print_detailed_report(results, relay_tuples, mandatory_relays, optional_relays): + """Print detailed report of validator relay registration status""" + print("\nDetailed Relay Coverage Report:") + print("-" * 50) + + for validator, data in results.items(): + is_ok = data['mandatory_total'] >= 2 + status = "OK" if is_ok else "NOT OK" + + print(f"\nValidator {validator[:12]}... : {status}") + print(f"Registered with {data['mandatory_total']}/{len(mandatory_relays)} mandatory relays") + print(f"Registered with {data['optional_total']}/{len(optional_relays)} optional relays") + + if data['unknown']: + print(f"Could not check status for {len(data['unknown'])} relay(s):") + for relay in data['unknown']: + relay_info = next(r for r in relay_tuples if r[0] == relay) + print(f" ? {relay} ({relay_info[1]} - {relay_info[3]})") + + missing_mandatory = set(mandatory_relays) - set(data['mandatory_relays']) + if missing_mandatory: + print("Missing mandatory registrations:") + for relay in missing_mandatory: + relay_info = next(r for r in relay_tuples if r[0] == relay) + print(f" - {relay} ({relay_info[1]} - {relay_info[3]})") - # Print results - print("\nRelay coverage report:") - print("-" * 50) - for validator, data in results.items(): - coverage_pct = (data['total'] / len(relay_urls)) * 100 - print(f"\nValidator {validator[:12]}...") - print(f"Registered with {data['total']}/{len(relay_urls)} relays ({coverage_pct:.1f}%)") - if data['total'] < len(relay_urls): - print("Missing registrations for relays:") - missing_relays = set(relay_urls) - set(data['relays']) - for relay in missing_relays: - # Find the full relay info for prettier printing - relay_info = next(r for r in relay_tuples if r[0] == relay) - print(f" - {relay} ({relay_info[1]} - {relay_info[3]})") + missing_optional = set(optional_relays) - set(data['optional_relays']) + if missing_optional: + print("Missing optional registrations:") + for relay in missing_optional: + relay_info = next(r for r in relay_tuples if r[0] == relay) + print(f" - {relay} ({relay_info[1]} - {relay_info[3]})") From 257870b8ef497ac475eae9dd46ba604660ef9292 Mon Sep 17 00:00:00 2001 From: Bernat Date: Tue, 3 Dec 2024 14:15:06 +0100 Subject: [PATCH 2/4] update readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f496b6..4b15cf5 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,9 @@ The state check subcommand will retrieve all keys registered in CSM, validator c The relay check subcommand will check if the validator keys are registered with all whitelisted relays in the Lido Relay Allowlist. -`python -m swarm relay-check` +`python -m swarm relay-check [--detailed]` + +The `--detailed` flag will print a more detailed report with specific relay URLs per validator. ### Manual exit From 86d989a7cfeb16554ebd49ae5710d88136fd2fdf Mon Sep 17 00:00:00 2001 From: Bernat Date: Tue, 3 Dec 2024 14:16:58 +0100 Subject: [PATCH 3/4] report the full pubkey --- swarm/relay_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swarm/relay_check.py b/swarm/relay_check.py index 829e213..8943abb 100644 --- a/swarm/relay_check.py +++ b/swarm/relay_check.py @@ -116,7 +116,7 @@ def print_summary_report(results, mandatory_relays, optional_relays): mandatory_coverage = f"{data['mandatory_total']}/{len(mandatory_relays)}" optional_coverage = f"{data['optional_total']}/{len(optional_relays)}" - print(f"Validator {validator[:12]}... : {status:6} (Mandatory: {mandatory_coverage}, Optional: {optional_coverage})") + print(f"Validator {validator} : {status:6} (Mandatory: {mandatory_coverage}, Optional: {optional_coverage})") print(f"\nSummary: {ok_count} validators OK, {not_ok_count} validators NOT OK") @@ -129,7 +129,7 @@ def print_detailed_report(results, relay_tuples, mandatory_relays, optional_rela is_ok = data['mandatory_total'] >= 2 status = "OK" if is_ok else "NOT OK" - print(f"\nValidator {validator[:12]}... : {status}") + print(f"\nValidator {validator} : {status}") print(f"Registered with {data['mandatory_total']}/{len(mandatory_relays)} mandatory relays") print(f"Registered with {data['optional_total']}/{len(optional_relays)} optional relays") From 1c0f36ead0b0044863d88cb4c0688702fe59db92 Mon Sep 17 00:00:00 2001 From: Bernat Date: Tue, 3 Dec 2024 14:26:03 +0100 Subject: [PATCH 4/4] add pubkey flag --- README.md | 4 +++- swarm/__main__.py | 1 + swarm/relay_check.py | 16 +++++++++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4b15cf5..8705a5d 100644 --- a/README.md +++ b/README.md @@ -174,10 +174,12 @@ The state check subcommand will retrieve all keys registered in CSM, validator c The relay check subcommand will check if the validator keys are registered with all whitelisted relays in the Lido Relay Allowlist. -`python -m swarm relay-check [--detailed]` +`python -m swarm relay-check [--detailed] [--pubkey ]` The `--detailed` flag will print a more detailed report with specific relay URLs per validator. +The `--pubkey` flag allows checking a specific validator, showing a detailed report for just that validator. + ### Manual exit This subcommand will submit an exit request for a validator with a given public key. diff --git a/swarm/__main__.py b/swarm/__main__.py index 0ba5993..69aac43 100644 --- a/swarm/__main__.py +++ b/swarm/__main__.py @@ -39,6 +39,7 @@ parser_relay_check = subparsers.add_parser('relay-check', help='Check validator relay coverage against allowed mev-boost relays') parser_relay_check.add_argument('--detailed', action='store_true', help='Show detailed registration information') + parser_relay_check.add_argument('--pubkey', type=str, help='the public key of the validator to report on') parser_relay_check.set_defaults(func=relay_check.check_relays) args = parser.parse_args() diff --git a/swarm/relay_check.py b/swarm/relay_check.py index 8943abb..2a16dac 100644 --- a/swarm/relay_check.py +++ b/swarm/relay_check.py @@ -50,7 +50,16 @@ async def check_relays(config, args): # Get validator public keys from CSM validator_keys = await get_validator_keys_from_csm(config) - print(f"Found {len(validator_keys)} validators in CSM") + + # If pubkey is specified, only check that validator + if hasattr(args, 'pubkey') and args.pubkey: + if args.pubkey not in validator_keys: + print(f"Error: Validator {args.pubkey} not found in CSM") + return + validator_keys = [args.pubkey] + print(f"Checking specific validator: {args.pubkey}") + else: + print(f"Found {len(validator_keys)} validators in CSM") # Check registration for each validator with each relay async with aiohttp.ClientSession() as session: @@ -88,10 +97,11 @@ async def check_relays(config, args): elif is_registered is None: results[validator]['unknown'].append(relay_url) - print(f"Processed {min(i + chunk_size, len(validator_keys))}/{len(validator_keys)} validators...") + if not args.pubkey: # Only show progress for bulk checks + print(f"Processed {min(i + chunk_size, len(validator_keys))}/{len(validator_keys)} validators...") # Print results based on mode - if hasattr(args, 'detailed') and args.detailed: + if args.pubkey or (hasattr(args, 'detailed') and args.detailed): print_detailed_report(results, relay_tuples, mandatory_relays, optional_relays) else: print_summary_report(results, mandatory_relays, optional_relays)