From a322d1e4c50b0a98b3d7c70fcc03293b1dc04a29 Mon Sep 17 00:00:00 2001 From: Iulian Barbu Date: Fri, 31 Jul 2026 14:13:04 +0000 Subject: [PATCH] fix(validators): attribute genesis-authority pods by session keys Authority pods without a validatorAccount label get their address derived from the pod name, which is wrong for validators whose stash was baked into the chainspec (e.g. bootnodes registered as genesis authorities). Such pods showed up with an address absent from the validator set, while their real address was listed as unknown-validator-N. Before creating unknown-validator rows, match each unclaimed validator-set address to an in-cluster pod by checking (via author_hasKey) that the pod's keystore holds the session keys registered on-chain for that address. Also fix the header's unknown validator count: unknown rows are created with location 'unknown', not 'deleted_from_cluster', so the count was always 0. Signed-off-by: Iulian Barbu --- app/lib/network_utils.py | 58 +++++++++++++++++++++++++++++++++------- app/routers/views.py | 2 +- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/app/lib/network_utils.py b/app/lib/network_utils.py index b2fc1f7..01e50b3 100644 --- a/app/lib/network_utils.py +++ b/app/lib/network_utils.py @@ -110,20 +110,58 @@ def list_validators(stateful_set_name=''): for validator in validators: if validator['is_validator']: known_validators_addresses.append(validator['address']) + unclaimed_addresses = [address for address in validator_set + if address not in known_validators_addresses] + + # Before declaring these addresses unknown, try to attribute each one to an + # in-cluster pod by checking which node holds the session keys registered + # on-chain for it. This covers validators whose stash account is not derived + # from the pod name (e.g. genesis authorities such as bootnodes) and which + # don't carry a validatorAccount label. + if unclaimed_addresses: + queued_session_keys = get_queued_keys(get_relay_chain_client()) + unmatched_pod_validators = [validator for validator in validators + if validator['location'] == 'in_cluster' and not validator['is_validator']] + for validator_address in list(unclaimed_addresses): + account_session_keys = queued_session_keys.get(validator_address) + if not account_session_keys: + continue + for validator in unmatched_pod_validators: + if node_has_session_keys(validator['name'], account_session_keys): + log.info('matched validator address {} to pod {} via session keys'.format( + validator_address, validator['name'])) + validator['address'] = validator_address + validator['is_validator'] = get_validator_status(validator_address, validator_set, + validators_to_add, validators_to_retire) + unclaimed_addresses.remove(validator_address) + unmatched_pod_validators.remove(validator) + break + i = 0 - for validator_address in validator_set: - if not validator_address in known_validators_addresses: - is_validator = get_validator_status(validator_address, validator_set, validators_to_add, - validators_to_retire) - validators.append( - {'name': 'unknown-validator-' + str(i), 'location': 'unknown', - 'address': validator_address, - # 'funds': node_stash_account_funds, - 'is_validator': is_validator, 'status': 'Missing', 'version': '?'}) - i = i + 1 + for validator_address in unclaimed_addresses: + is_validator = get_validator_status(validator_address, validator_set, validators_to_add, + validators_to_retire) + validators.append( + {'name': 'unknown-validator-' + str(i), 'location': 'unknown', + 'address': validator_address, + # 'funds': node_stash_account_funds, + 'is_validator': is_validator, 'status': 'Missing', 'version': '?'}) + i = i + 1 return validators +def node_has_session_keys(node_name, account_session_keys): + try: + node_client = get_node_client(node_name) + if not node_client: + return False + has_keys = check_has_session_keys(node_client, account_session_keys) + return bool(has_keys) and all(has_keys.values()) + except Exception as err: + log.warning('failed to check session keys on {}: {}'.format(node_name, err)) + return False + + def get_node_info_from_pod(pod): node_name = pod.metadata.name node_labels = pod.metadata.labels diff --git a/app/routers/views.py b/app/routers/views.py index 9f3f03a..5b9f482 100644 --- a/app/routers/views.py +++ b/app/routers/views.py @@ -60,7 +60,7 @@ async def validators( inactive_validator_count = len( list(filter(lambda val: not val['is_validator'] and val['location'] == 'in_cluster', validators))) unknown_validator_count = len( - list(filter(lambda val: val['is_validator'] and val['location'] == 'deleted_from_cluster', validators))) + list(filter(lambda val: val['is_validator'] and val['location'] == 'unknown', validators))) session_keys = get_session_queued_keys() for index in range(len(validators)):