Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/smoke_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
pwd
./tests/smoke_test.sh

- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: web
path: output
24 changes: 11 additions & 13 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,25 @@ concurrency:

jobs:
test:
# Run directly on the host (not inside a python container) so the Docker CLI
# and the Compose v2 plugin are available to testcontainers 4.x, which drives
# `docker compose` as a subprocess.
runs-on: ubuntu-22.04
container:
image: python:3.11
volumes:
- /var/run/docker.sock:/var/run/docker.sock
env:
TESTCONTAINERS_HOST_OVERRIDE: "host.docker.internal"
steps:
- name: Checkout Code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install and configure Poetry
uses: snok/install-poetry@v1
with:
version: 1.5.1
version: 1.8.5
virtualenvs-create: true
virtualenvs-in-project: true
- name: Run tests
run: |
# PyYaml is required until docker-compose is migrated to v2
pip install pyyaml==5.3.1
pip install docker==6.1.3 docker-compose requests==2.31.0
poetry install --no-interaction --no-ansi
docker-compose version
docker compose version
poetry install --no-interaction --no-ansi --no-root
poetry run python -m pytest
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ COPY poetry.lock pyproject.toml /app/
WORKDIR /app
RUN pip install poetry && \
poetry config virtualenvs.in-project true && \
poetry install --no-interaction --no-ansi
poetry install --no-interaction --no-ansi --no-root
COPY . /app
EXPOSE 5000
26 changes: 20 additions & 6 deletions app/lib/network_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import logging
from datetime import datetime, timedelta

from substrateinterface import Keypair

from app.config.network_configuration import get_relay_chain_rpc_url, network_sudo_seed, derivation_root_seed, \
node_http_endpoint, get_network, relay_chain_consensus, node_ws_endpoint
from app.lib.balance_utils import fund_accounts
Expand Down Expand Up @@ -264,12 +266,16 @@ async def setup_validators_session_keys(node_name):
return ''
else:
log.info("Rotating session key on {} ({}) session key".format(node_name, stash_account_address))
node_session_key = rotate_node_session_keys(node_endpoint)
node_stash_account_mnemonic = get_node_stash_account_mnemonic(validators_root_seed, node_name)
# `owner` binds the ownership proof to the stash calling `set_keys`: the
# SCALE-encoded account id = the raw 32-byte public key (polkadot-sdk#1739).
owner = '0x' + Keypair.create_from_uri(node_stash_account_mnemonic).public_key.hex()
node_session_key = rotate_node_session_keys(node_endpoint, owner=owner)
# If rotate_key result from node is not empty, set session key and mark account to be registered
if node_session_key:
log.info("Setting session key for {} ({})".format(stash_account_address, node_session_key))
node_stash_account_mnemonic = get_node_stash_account_mnemonic(validators_root_seed, node_name)
session_key_set_status = set_node_session_key(ws_endpoint, node_stash_account_mnemonic, node_session_key)
log.info("Setting session key for {} ({})".format(stash_account_address, node_session_key['keys']))
session_key_set_status = set_node_session_key(ws_endpoint, node_stash_account_mnemonic,
node_session_key['keys'], proof=node_session_key['proof'])
# Don't add the account to the validator set if the setKey operation failed
if session_key_set_status:
log.info('Successfully set session keys'.format(stash_account_address))
Expand Down Expand Up @@ -334,12 +340,20 @@ async def register_validator_pods(pods):
else:
for node in nodes_to_register:
validator_stash_mnemonic = get_node_stash_account_mnemonic(validators_root_seed, node)
# `owner` binds the ownership proof to the account calling `set_keys`
# (the stash); it is the SCALE-encoded account id = the raw 32-byte
# public key. Required by runtimes carrying polkadot-sdk#1739.
owner = '0x' + Keypair.create_from_uri(validator_stash_mnemonic).public_key.hex()

log.info(f'Rotate node session keys for {node}')
node_session_key = rotate_node_session_keys(node_http_endpoint(node))
rotated = rotate_node_session_keys(node_http_endpoint(node), owner=owner)
if not rotated:
log.error(f'Fail to set up PoS Validator: {node} (session key rotation failed)')
continue

log.info(f'Registering PoS Validator: {node}')
status = setup_pos_validator(ws_endpoint, validator_stash_mnemonic, node_session_key)
status = setup_pos_validator(ws_endpoint, validator_stash_mnemonic,
rotated['keys'], proof=rotated['proof'])
if status:
log.info(f'Successfully set up PoS Validator: {node}')
else:
Expand Down
50 changes: 41 additions & 9 deletions app/lib/session_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,46 @@


# substrate_client must be connected to the node on which we want to run rotate_keys
def rotate_node_session_keys(node_http_endpoint):
def rotate_node_session_keys(node_http_endpoint, owner=None):
"""Rotate the node's session keys.

When `owner` (the hex-encoded stash account id) is given, call
`author_rotateKeysWithOwner` so the node also returns a `proof` of key
ownership. Runtimes carrying polkadot-sdk#1739 verify this proof in
`session.set_keys` and reject an empty one with `InvalidProof`. Nodes
whose binary predates that RPC (and runtimes that don't require a proof)
are handled by falling back to plain `author_rotateKeys`.

Returns `{'keys': '0x..', 'proof': '0x..'}` (proof is `'0x'` when the
chain/binary doesn't produce one), or `''` on failure.
"""
try:
rotate_keys = requests.post(node_http_endpoint,
data='{"id":1, "jsonrpc":"2.0", "method": "author_rotateKeys", "params":[]}',
headers={'Content-type': 'application/json'}, timeout=10)
if rotate_keys.status_code == 200:
log.info("{} session keys rotated successfully".format(node_http_endpoint))
return rotate_keys.json()['result']
if owner:
req = {"id": 1, "jsonrpc": "2.0",
"method": "author_rotateKeysWithOwner", "params": [owner]}
else:
req = {"id": 1, "jsonrpc": "2.0", "method": "author_rotateKeys", "params": []}
rotate_keys = requests.post(node_http_endpoint, json=req,
headers={'Content-type': 'application/json'}, timeout=10)
if rotate_keys.status_code != 200:
return ''
body = rotate_keys.json()
if 'error' in body:
# -32601 = "Method not found": the node binary predates
# `author_rotateKeysWithOwner`, retry with the legacy RPC.
if owner and body['error'].get('code') == -32601:
log.warning("{} lacks author_rotateKeysWithOwner, falling back to "
"author_rotateKeys (no ownership proof)".format(node_http_endpoint))
return rotate_node_session_keys(node_http_endpoint, owner=None)
log.error("Failed to rotate key on {}; RPC error: {}".format(node_http_endpoint, body['error']))
return ''
result = body['result']
log.info("{} session keys rotated successfully".format(node_http_endpoint))
# `author_rotateKeysWithOwner` returns {keys, proof}; `author_rotateKeys`
# returns the keys blob directly.
if isinstance(result, dict):
return {'keys': result['keys'], 'proof': result.get('proof') or '0x'}
return {'keys': result, 'proof': '0x'}
except Exception as err:
log.error("Failed to rotate key on {}; Error: {}".format(node_http_endpoint, err))
return ''
Expand All @@ -29,7 +59,7 @@ def decode_session_key(substrate_client, session_key):


# stash keypair account must have some funds
def set_node_session_key(ws_endpoint, stash_seed, session_key):
def set_node_session_key(ws_endpoint, stash_seed, session_key, proof='0x'):
substrate_client = get_substrate_client(ws_endpoint)

if type(session_key) == str:
Expand All @@ -41,7 +71,9 @@ def set_node_session_key(ws_endpoint, stash_seed, session_key):
call_function='set_keys',
call_params={
'keys': session_key,
'proof': 'None'
# `proof` (Vec<u8>) is the ownership proof from `rotateKeysWithOwner`;
# verified by runtimes carrying polkadot-sdk#1739, ignored by older ones.
'proof': proof
}
)
result = substrate_call(substrate_client, keypair, call, wait=True)
Expand Down
7 changes: 5 additions & 2 deletions app/lib/validator_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def get_validators_pending_deletion(ws_endpoint):
return list(set(active_validators) - set(staking_validators))


def setup_pos_validator(ws_endpoint, stash_seed, session_key=None, controller_seed=None):
def setup_pos_validator(ws_endpoint, stash_seed, session_key=None, controller_seed=None, proof='0x'):
batch_call = []
substrate_client = get_substrate_client(ws_endpoint)
stash_keypair = Keypair.create_from_uri(stash_seed)
Expand Down Expand Up @@ -91,7 +91,10 @@ def setup_pos_validator(ws_endpoint, stash_seed, session_key=None, controller_se
call_function='set_keys',
call_params={
'keys': session_key,
'proof': ''
# `proof` (Vec<u8>) is the ownership proof from
# `rotateKeysWithOwner`; verified by runtimes carrying
# polkadot-sdk#1739, ignored by older ones.
'proof': proof
}
)
)
Expand Down
14 changes: 3 additions & 11 deletions local-kubernetes/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# ============================================================================================

KUBERNETES_CONTEXT = minikube # minikube or rancher-desktop
# rococo or wococo (don't put space at the end of the variable it will be used as is)
# don't put a space at the end of the variable, it will be used as is
CHAIN_NAMESPACE = rococo
# docker or podman
MINIKUBE_DRIVER = docker
Expand All @@ -25,7 +25,7 @@ kube: kube_${KUBERNETES_CONTEXT}
kube_minikube:
@minikube start \
--addons ingress --addons metrics-server --addons registry --driver docker \
--kubernetes-version=1.27.4 --memory=6g --cpus=2
--kubernetes-version=1.31.0 --memory=6g --cpus=2

kube_minikube-podman:
@minikube start \
Expand Down Expand Up @@ -67,14 +67,6 @@ rpc:
@xdg-open 'https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/explorer'
@kubectl --context ${KUBERNETES_CONTEXT} port-forward service/local-${CHAIN_NAMESPACE}-bootnode 9944:9944 -n ${CHAIN_NAMESPACE}

para-shell:
@xdg-open 'https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9948#/explorer'
@kubectl --context ${KUBERNETES_CONTEXT} port-forward service/local-${CHAIN_NAMESPACE}-shell-collator-node 9948:9944 -n ${CHAIN_NAMESPACE}

para-moon:
@xdg-open 'https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9949#/explorer'
@kubectl --context ${KUBERNETES_CONTEXT} port-forward service/local-${CHAIN_NAMESPACE}-moonbase-alice-node 9949:9944 -n ${CHAIN_NAMESPACE}

para-mint:
@xdg-open 'https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9950#/explorer'
@kubectl --context ${KUBERNETES_CONTEXT} port-forward service/local-${CHAIN_NAMESPACE}-statemint-alice-node 9950:9944 -n ${CHAIN_NAMESPACE}
Expand Down Expand Up @@ -120,4 +112,4 @@ uninstall: check
@kubectl --context ${KUBERNETES_CONTEXT} delete pvc -n ${CHAIN_NAMESPACE} --all

cleanup: check
@kubectl --context ${KUBERNETES_CONTEXT} delete namespace rococo wococo
@kubectl --context ${KUBERNETES_CONTEXT} delete namespace rococo
30 changes: 0 additions & 30 deletions local-kubernetes/charts/helmfile-rococo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,6 @@ releases:
seed: "test test test test test test test test test test test junk"
extraDerivation: "//validator//${HOSTNAME}"

## Para chain Shell ##
- name: local-rococo-shell-collator
namespace: rococo
chart: parity/node
version: *node_version
values:
- values-shell-collator.yaml

## Para chain Statemint ##
- name: local-rococo-statemint-alice
namespace: rococo
Expand Down Expand Up @@ -106,28 +98,6 @@ releases:
scheme: "sr25519"
seed: "test test test test test test test test test test test junk"
extraDerivation: "//collator//${HOSTNAME}"
## Para chain Moonbase ##
- name: local-rococo-moonbase-alice
namespace: rococo
chart: parity/node
version: *node_version
values:
- values-moonbase-collator.yml
- node:
flags:
- "--allow-private-ipv4"
- "--alice"
extraLabels:
paraId: "1001"
collatorAccount: "0xf24ff3a9cf04c71dbc94d0b566f7a27b94566cac"
# collators
- name: local-rococo-moonbase-collator
namespace: rococo
chart: parity/node
version: *node_version
values:
- values-moonbase-collator.yml

## Para chain Tick ##
- name: local-rococo-tick-collator
namespace: rococo
Expand Down
36 changes: 0 additions & 36 deletions local-kubernetes/charts/helmfile-wococo.yaml

This file was deleted.

62 changes: 0 additions & 62 deletions local-kubernetes/charts/values-local-wococo-bootnode.yaml

This file was deleted.

Loading
Loading