Skip to content

Commit

Permalink
Merge pull request #297 from KPrasch/dkg-init
Browse files Browse the repository at this point in the history
Rework ritual initiation script for use on mainnet
  • Loading branch information
KPrasch authored Aug 9, 2024
2 parents 686ae10 + 18785e8 commit 0dc8055
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 55 deletions.
32 changes: 21 additions & 11 deletions deployment/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

import deployment

#
# Filesystem
#

DEPLOYMENT_DIR = Path(deployment.__file__).parent
CONSTRUCTOR_PARAMS_DIR = DEPLOYMENT_DIR / "constructor_params"
ARTIFACTS_DIR = DEPLOYMENT_DIR / "artifacts"
OZ_DEPENDENCY = project.dependencies["openzeppelin"]["5.0.0"]

#
# Domains
Expand All @@ -20,7 +23,7 @@
SUPPORTED_TACO_DOMAINS = [LYNX, TAPIR, MAINNET]

#
# Nodes
# Testnet
#

LYNX_NODES = {
Expand All @@ -39,18 +42,25 @@
"0xcbE2F626d84c556AbA674FABBbBDdbED6B39d87b": "0xb057B982fB575509047e90cf5087c9B863a2022d",
}

# EIP1967
#
# Contracts
#

OZ_DEPENDENCY = project.dependencies["openzeppelin"]["5.0.0"]

# EIP1967 Admin slot - https://eips.ethereum.org/EIPS/eip-1967#admin-address
EIP1967_ADMIN_SLOT = 0xB53127684A568B3173AE13B9F8A6016E243E63B6E8EE1178D6A717850B5D6103

# Admin slot - https://eips.ethereum.org/EIPS/eip-1967#admin-address
EIP1967_ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
ACCESS_CONTROLLERS = ["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"]

FEE_MODELS = ["FreeFeeModel", "BqETHSubscription"]

#
# Contracts
# Sampling
#

ACCESS_CONTROLLERS = [
"GlobalAllowList",
"OpenAccessAuthorizer",
"ManagedAllowList"
]
PORTER_SAMPLING_ENDPOINTS = {
MAINNET: "https://porter.nucypher.io/bucket_sampling",
LYNX: "https://porter-lynx.nucypher.io/get_ursulas",
TAPIR: "https://porter-tapir.nucypher.io/get_ursulas",
}
32 changes: 32 additions & 0 deletions deployment/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import click
from eth_utils import to_checksum_address


class MinInt(click.ParamType):
name = "minint"

def __init__(self, min_value):
self.min_value = min_value

def convert(self, value, param, ctx):
try:
ivalue = int(value)
except ValueError:
self.fail(f"{value} is not a valid integer", param, ctx)
if ivalue < self.min_value:
self.fail(
f"{value} is less than the minimum allowed value of {self.min_value}", param, ctx
)
return ivalue


class ChecksumAddress(click.ParamType):
name = "checksum_address"

def convert(self, value, param, ctx):
try:
value = to_checksum_address(value=value)
except ValueError:
self.fail("Invalid ethereum address")
else:
return value
31 changes: 28 additions & 3 deletions deployment/utils.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import json
import os
from pathlib import Path
from typing import Dict, List
from typing import Dict, List, Optional

import requests
import yaml
from ape import networks, project
from ape.contracts import ContractContainer, ContractInstance
from ape_etherscan.utils import API_KEY_ENV_KEY_MAP

from deployment.constants import ARTIFACTS_DIR
from deployment.constants import ARTIFACTS_DIR, LYNX, MAINNET, PORTER_SAMPLING_ENDPOINTS, TAPIR
from deployment.networks import is_local_network


Expand Down Expand Up @@ -48,7 +49,7 @@ def validate_config(config: Dict) -> Path:
config_chain_id = deployment.get("chain_id")
if not config_chain_id:
raise ValueError("chain_id is not set in params file.")

contracts = config.get("contracts")
if not contracts:
raise ValueError("Constructor parameters file missing 'contracts' field.")
Expand Down Expand Up @@ -156,3 +157,27 @@ def registry_filepath_from_domain(domain: str) -> Path:
raise ValueError(f"No registry found for domain '{domain}'")

return p


def sample_nodes(
domain: str, num_nodes: int, random_seed: Optional[int] = None, duration: Optional[int] = None
):
porter_endpoint = PORTER_SAMPLING_ENDPOINTS.get(domain)
if not porter_endpoint:
raise ValueError(f"Porter endpoint not found for domain '{domain}'")

params = {
"quantity": num_nodes,
}
if duration:
params["duration"] = duration
if random_seed:
if domain != MAINNET:
raise ValueError("'random_seed' is only a valid parameter for mainnet")
params["random_seed"] = random_seed

response = requests.get(porter_endpoint, params=params)
data = response.json()
result = sorted(data["result"]["ursulas"], key=lambda x: x.lower())

return result
127 changes: 90 additions & 37 deletions scripts/initiate_ritual.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
#!/usr/bin/python3

import click
from ape import project
from ape.cli import ConnectedProviderCommand, account_option, network_option

from deployment.constants import LYNX, LYNX_NODES, SUPPORTED_TACO_DOMAINS, TAPIR, TAPIR_NODES
from deployment import registry
from deployment.constants import ACCESS_CONTROLLERS, FEE_MODELS, SUPPORTED_TACO_DOMAINS
from deployment.params import Transactor
from deployment.registry import contracts_from_registry
from deployment.utils import check_plugins, registry_filepath_from_domain
from deployment.types import ChecksumAddress, MinInt
from deployment.utils import check_plugins, sample_nodes


@click.command(cls=ConnectedProviderCommand)
@network_option(required=True)
@click.command(cls=ConnectedProviderCommand, name="initiate-ritual")
@account_option()
@network_option(required=True)
@click.option(
"--domain",
"-d",
Expand All @@ -23,48 +23,101 @@
@click.option(
"--duration",
"-t",
help="Duration of the ritual",
type=int,
default=86400,
show_default=True,
help="Duration of the ritual in seconds. Must be at least 24h.",
type=MinInt(86400),
required=True,
)
@click.option(
"--access-controller",
"-c",
help="The registry name of an access controller contract.",
type=click.Choice(ACCESS_CONTROLLERS),
required=True,
)
@click.option(
"--fee-model",
"-f",
help="The name of a fee model contract.",
type=click.Choice(FEE_MODELS),
required=True,
)
@click.option(
"--authority",
"-a",
help="global allow list or open access authorizer.",
type=click.Choice(["GlobalAllowList", "OpenAccessAuthorizer", "ManagedAllowList"]),
help="The ethereum address of the ritual authority.",
required=True,
type=ChecksumAddress(),
)
def cli(domain, duration, network, account, access_controller):
@click.option(
"--num-nodes",
"-n",
help="Number of nodes to use for the ritual.",
type=int,
)
@click.option(
"--random-seed",
"-r",
help="Random seed integer for bucket sampling on mainnet.",
type=int,
)
@click.option(
"--handpicked",
help="The filepath of a file containing newline separated staking provider addresses.",
type=click.File("r"),
)
def cli(
domain,
account,
network,
duration,
access_controller,
fee_model,
authority,
num_nodes,
random_seed,
handpicked,
):
"""Initiate a ritual for a TACo domain."""

# Setup
check_plugins()
print(f"Using network: {network}")
print(f"Using domain: {domain}")
print(f"Using account: {account}")
transactor = Transactor(account=account)
click.echo(f"Connected to {network.name} network.")
if not (bool(handpicked) ^ (num_nodes is not None)):
raise click.BadOptionUsage(
option_name="--num-nodes",
message=f"Specify either --num-nodes or --handpicked; got {num_nodes}, {handpicked}.",
)
if handpicked and random_seed:
raise click.BadOptionUsage(
option_name="--random-seed",
message="Cannot specify --random-seed when using --handpicked.",
)

if domain == LYNX:
providers = list(sorted(LYNX_NODES.keys()))
elif domain == TAPIR:
providers = list(sorted(TAPIR_NODES.keys()))
# Get the staking providers in the ritual cohort
if handpicked:
providers = sorted(line.lower() for line in handpicked)
if not providers:
raise ValueError(f"No staking providers found in the handpicked file {handpicked.name}")
else:
# mainnet sampling not currently supported
raise ValueError(f"Sampling of providers not supported for domain '{domain}'")

registry_filepath = registry_filepath_from_domain(domain=domain)

chain_id = project.chain_manager.chain_id
deployments = contracts_from_registry(filepath=registry_filepath, chain_id=chain_id)
coordinator = deployments[project.Coordinator.contract_type.name]
providers = sample_nodes(
domain=domain, num_nodes=num_nodes, duration=duration, random_seed=random_seed
)

access_controller = deployments[getattr(project, access_controller).contract_type.name]
authority = transactor.get_account().address
# Get the contracts from the registry
coordinator = registry.get_contract(domain=domain, contract_name="Coordinator")
access_controller = registry.get_contract(domain=domain, contract_name=access_controller)
fee_model = registry.get_contract(domain=domain, contract_name=fee_model)

while True:
transactor.transact(
coordinator.initiateRitual, providers, authority, duration, access_controller.address
)
if not input("Another? [y/n] ").lower().startswith("y"):
break
# Initiate the ritual
transactor = Transactor(account=account)
transactor.transact(
coordinator.initiateRitual,
fee_model.address,
providers,
authority,
duration,
access_controller.address,
)


if __name__ == "__main__":
Expand Down
14 changes: 10 additions & 4 deletions scripts/manage_subscription.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import click
from ape import Contract
from ape.cli import account_option, ConnectedProviderCommand
from ape.cli import account_option, ConnectedProviderCommand, network_option

from deployment import registry
from deployment.options import (
Expand Down Expand Up @@ -51,6 +51,7 @@ def cli():

@cli.command(cls=ConnectedProviderCommand)
@account_option()
@network_option(required=True)
@domain_option
@subscription_contract_option
@encryptor_slots_option
Expand All @@ -59,9 +60,10 @@ def cli():
default=0,
help="Subscription billing period number to pay for.",
)
def pay_subscription(account, domain, subscription_contract, encryptor_slots, period):
def pay_subscription(account, network, domain, subscription_contract, encryptor_slots, period):
"""Pay for a new subscription period and initial encryptor slots."""
check_plugins()
click.echo(f"Connected to {network.name} network.")
transactor = Transactor(account=account)
subscription_contract = registry.get_contract(
contract_name=subscription_contract,
Expand Down Expand Up @@ -92,12 +94,14 @@ def pay_subscription(account, domain, subscription_contract, encryptor_slots, pe

@cli.command(cls=ConnectedProviderCommand)
@account_option()
@network_option(required=True)
@domain_option
@subscription_contract_option
@encryptor_slots_option
def pay_slots(account, domain, subscription_contract, encryptor_slots):
def pay_slots(account, network, domain, subscription_contract, encryptor_slots):
"""Pay for additional encryptor slots in the current billing period."""
check_plugins()
click.echo(f"Connected to {network.name} network.")
transactor = Transactor(account=account)
subscription_contract = registry.get_contract(
contract_name=subscription_contract,
Expand All @@ -123,12 +127,14 @@ def pay_slots(account, domain, subscription_contract, encryptor_slots):

@cli.command(cls=ConnectedProviderCommand)
@account_option()
@network_option(required=True)
@domain_option
@ritual_id_option
@access_controller_option
@encryptors_option
def add_encryptors(account, domain, ritual_id, access_controller, encryptors):
def add_encryptors(account, network, domain, ritual_id, access_controller, encryptors):
"""Authorize encryptors to the access control contract for a ritual."""
click.echo(f"Connected to {network.name} network.")
access_controller = registry.get_contract(
contract_name=access_controller,
domain=domain
Expand Down

0 comments on commit 0dc8055

Please sign in to comment.