Skip to content

Commit

Permalink
Merge pull request #33 from Maharacha/sessionkeys
Browse files Browse the repository at this point in the history
Using substrate-interface lib for session key actions.
  • Loading branch information
Maharacha authored Jan 9, 2024
2 parents 1a9f150 + 0c75f86 commit 1713216
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 22 deletions.
22 changes: 21 additions & 1 deletion actions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ get-session-key:
description: "Runs author_rotateKeys and returns a new session-key."

has-session-key:
description: "Checks if node has a session key, using a RPC call."
description: "Checks if node has a session key, using an RPC call."
params:
key:
description: "Key to check if exist on node. E.g. key='0xhjd39djk309'"
Expand Down Expand Up @@ -43,6 +43,26 @@ set-node-key:
type: string
required: [ key ]

find-validator-address:
description: |
Checks if this node is currently validating for any validator address found on-chain.
It does so by checking if any session key on-chain is present on this node.
Returns the validator address and the session key if this node is validating.
is-validating-this-era:
description: |
Checks if this node is currently validating for the validator address set in config.
It does so by checking if the session key used for that validator is present on this node.
Returns a message whether this node is validating or not. If it is validating, the session key is returned as well.
REQUIRES validator-address config parameter to be set.
is-validating-next-era:
description: |
Checks if this node will be validating next era for the validator address set in config.
It does so by checking if the session key that will be used for that validator is present on this node.
Returns a message whether this node will be validating next era or not. If it will, the session key is returned as well.
REQUIRES validator-address config parameter to be set.
get-node-info:
description: |
Gets system information about the node and its container.
Expand Down
12 changes: 8 additions & 4 deletions charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ type: charm
bases:
- build-on:
- name: ubuntu
channel: "22.04"
channel: "20.04"
run-on:
- name: ubuntu
channel: "20.04"
architectures: [amd64]
- build-on:
- name: ubuntu
channel: "21.10"
architectures: [amd64]
channel: "22.04"
run-on:
- name: ubuntu
channel: "22.04"
architectures: [amd64]
Expand All @@ -18,6 +19,9 @@ bases:
# This below is needed for charmhelpers 0.20.24 to build
parts:
charm:
charm-python-packages: [setuptools < 58]
charm-python-packages:
- setuptools < 58
charm-binary-python-packages:
- substrate-interface
prime:
- files/*
6 changes: 6 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ options:
Extra arguments that the service should run with.
'--chain=... --rpc-port=...' are required to set for the charm to run.
validator-address:
type: string
default: ""
description: |
If this is set, update-status will check if this node is currently validating for this address.
HINT: if this config parameter is set, the actions is-validating-this-era and is-validating-next-era can be used.
55 changes: 52 additions & 3 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ def __init__(self, *args):
self.framework.observe(self.on.start_node_service_action, self._on_start_node_service_action)
self.framework.observe(self.on.stop_node_service_action, self._on_stop_node_service_action)
self.framework.observe(self.on.set_node_key_action, self._on_set_node_key_action)
self.framework.observe(self.on.find_validator_address_action, self._on_find_validator_address_action)
self.framework.observe(self.on.is_validating_this_era_action, self._on_is_validating_this_era_action)
self.framework.observe(self.on.is_validating_next_era_action, self._on_is_validating_next_era_action)
self.framework.observe(self.on.get_node_info_action, self._on_get_node_info_action)
self.framework.observe(self.on.get_node_help_action, self._on_get_node_help_action)

Expand Down Expand Up @@ -125,9 +128,17 @@ def update_status(self, connection_attempts: int = 4) -> None:
for i in range(connection_attempts):
time.sleep(5)
try:
self.unit.status = ops.ActiveStatus("Syncing: {}, Validating: {}".format(
str(PolkadotRpcWrapper(rpc_port).is_syncing()),
str(PolkadotRpcWrapper(rpc_port).is_validating())))
is_syncing = str(PolkadotRpcWrapper(rpc_port).is_syncing())
status_message = f'Syncing: {is_syncing}'
address = self.config.get('validator-address')
if address:
if PolkadotRpcWrapper(rpc_port).is_validating_this_era(address):
status_message += ", Validating: Yes"
elif PolkadotRpcWrapper(rpc_port).is_validating_next_era(address):
status_message += ", Validating: Next Era"
else:
status_message += ", Validating: No"
self.unit.status = ops.ActiveStatus(status_message)
self.unit.set_workload_version(PolkadotRpcWrapper(rpc_port).get_version())
break
except RequestsConnectionError as e:
Expand Down Expand Up @@ -199,6 +210,44 @@ def _on_set_node_key_action(self, event: ops.ActionEvent) -> None:
utils.write_node_key_file(key)
utils.start_service()

def _on_find_validator_address_action(self, event: ops.ActionEvent) -> None:
event.log("Checking sessions key through rpc...")
rpc_port = ServiceArgs(self._stored.service_args).rpc_port
result = PolkadotRpcWrapper(rpc_port).find_validator_address()
if result:
event.set_results(results={'validator': result["validator"]})
event.set_results(results={'session-key': result["session_key"]})
else:
event.set_results(results={'message': 'This node is not currently validating for any address.'})

def _on_is_validating_this_era_action(self, event: ops.ActionEvent) -> None:
validator_address = self.config.get("validator-address")
if not validator_address:
event.fail("Set validator-address config parameter to use this action!")
return
event.log("Checking sessions key through rpc...")
rpc_port = ServiceArgs(self._stored.service_args).rpc_port
session_key = PolkadotRpcWrapper(rpc_port).is_validating_this_era(validator_address)
if session_key:
event.set_results(results={'message': f'This node is currently validating for address {validator_address}.'})
event.set_results(results={'session_key': session_key})
else:
event.set_results(results={'message': f'This node is not currently validating for address {validator_address}.'})

def _on_is_validating_next_era_action(self, event: ops.ActionEvent) -> None:
validator_address = self.config.get("validator-address")
if not validator_address:
event.fail("Set validator-address config parameter to use this action!")
return
event.log("Checking sessions key through rpc...")
rpc_port = ServiceArgs(self._stored.service_args).rpc_port
session_key = PolkadotRpcWrapper(rpc_port).is_validating_next_era(validator_address)
if session_key:
event.set_results(results={'message': f'This node will be validating next era for address {validator_address}.'})
event.set_results(results={'session_key': session_key})
else:
event.set_results(results={'message': f'This node will not be validating next era for address {validator_address}.'})

# TODO: this action is getting quite large and specialized, perhaps move all actions to an `actions.py` file?
def _on_get_node_info_action(self, event: ops.ActionEvent) -> None:
# Disk usage
Expand Down
70 changes: 56 additions & 14 deletions src/polkadot_rpc_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import json
import re
from typing import Tuple

from substrateinterface import SubstrateInterface

class PolkadotRpcWrapper():

Expand Down Expand Up @@ -34,19 +34,6 @@ def is_syncing(self) -> str:
response_json = json.loads(response.text)
return response_json['result']['isSyncing']

def is_validating(self) -> bool:
"""
Checks if polkadot service is started as Authority (E.g. is_validating() -> True)
:return: boolean
"""
data = '{"id":1, "jsonrpc":"2.0", "method": "system_nodeRoles", "params": []}'
response = requests.post(url=self.__server_address, headers=self.__headers, data=data)
response_json = json.loads(response.text)
if response_json['result'][0] == 'Authority':
return True
else:
return False

def get_version(self) -> str:
"""
Checks which version polkadot service is running (E.g. get_version() -> '0.9.3')
Expand Down Expand Up @@ -107,3 +94,58 @@ def insert_key(self, mnemonic, address):
"""
data = '{"id": 1,"jsonrpc":"2.0", "method": "author_insertKey", "params":["aura","' + mnemonic + '","' + address + '"]}'
requests.post(url=self.__server_address, headers=self.__headers, data=data)

def find_validator_address(self):
"""
Check if this node is currently producing block for a validator/collator.
It does so by checking if any session key currently on-chain is present on this node.
:return: the validator/collator address or False.
"""
substrate = SubstrateInterface(url=self.__server_address)
result = substrate.query("Session", "QueuedKeys").value_serialized
for validator in result:
keys = validator[1]
session_key = '0x'
for k in keys.values():
# Some chains uses multiple keys. Before checking if it exist on the node they need to be concatenated removing preceding '0x'.
session_key += k[2:]
if self.has_session_key(session_key):
return {"validator": validator[0], "session_key": session_key}
return False

def is_validating_next_era(self, address):
"""
Check if this node has the intetion to validate for validator/collator 'address' next era.
It checks on-chain which session key is set to be used for validating next era for 'address'.
And if that session key exist on this node.
:return: the session key if found on this node, else False.
"""
substrate = SubstrateInterface(url=self.__server_address)
result = substrate.query("Session", "NextKeys", [address]).value_serialized
if result:
session_key = '0x'
for k in result.values():
session_key += k[2:]
if self.has_session_key(session_key):
return session_key
return False

def is_validating_this_era(self, address):
"""
Check if this node is currently producing block for a validator/collator 'address.
It checks on-chain which session key is set to be used for validating this era for 'address'.
And if that session key exist on this node.
:return: the session key if validating, else False.
"""
substrate = SubstrateInterface(url=self.__server_address)
result = substrate.query("Session", "QueuedKeys").value_serialized
for validator in result:
if validator[0] == address:
keys = validator[1]
session_key = '0x'
for k in keys.values():
# Some chains uses multiple keys. Before checking if it exist on the node they need to be concatenated removing preceding '0x'.
session_key += k[2:]
if self.has_session_key(session_key):
return session_key
return False

0 comments on commit 1713216

Please sign in to comment.