testnet manager: start multiple gateways

This commit is contained in:
Jędrzej Stuczyński
2024-07-23 14:10:58 +01:00
parent 1463ddd339
commit 38663e41c3
11 changed files with 290 additions and 130 deletions
@@ -36,5 +36,16 @@ CREATE TABLE contract (
CREATE TABLE account (
address TEXT NOT NULL UNIQUE,
-- for the future 'import' feature this will have to be nullable
mnemonic TEXT NOT NULL
);
CREATE TABLE node (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
identity_key TEXT NOT NULL,
network_id INTEGER NOT NULL REFERENCES network(id),
-- i.e. mixnode or gateway
bonded_type TEXT NOT NULL,
owner_address TEXT NOT NULL REFERENCES account(address)
);
@@ -15,6 +15,12 @@ pub(crate) struct Args {
#[clap(long)]
nym_node_bin: PathBuf,
#[clap(long, default_value_t = 3)]
num_mixnodes: u16,
#[clap(long, default_value_t = 1)]
num_gateways: u16,
#[clap(long)]
network_name: Option<String>,
@@ -27,7 +33,12 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkManagerError> {
let network = manager.load_existing_network(args.network_name).await?;
let run_cmds = manager
.init_local_nym_nodes(args.nym_node_bin, &network)
.init_local_nym_nodes(
args.nym_node_bin,
&network,
args.num_mixnodes,
args.num_gateways,
)
.await?;
if !args.output.is_text() {
@@ -178,6 +178,9 @@ impl NetworkManager {
let id = &ctx.client_id;
self.wait_for_gateway(ctx).await?;
let mut rng = thread_rng();
let mut port = rng.next_u32();
port = (port + 1000) % (u16::MAX as u32);
ctx.set_pb_message(format!("initialising client {id}..."));
ctx.println(format!("\tinitialising client {id}..."));
@@ -190,6 +193,8 @@ impl NetworkManager {
id,
"--enabled-credentials-mode",
"true",
"--port",
&port.to_string(),
])
.stdout(Stdio::null())
.stdin(Stdio::null())
@@ -3,17 +3,12 @@
use crate::error::NetworkManagerError;
use crate::helpers::{ProgressCtx, ProgressTracker, RunCommands};
use crate::manager::contract::Account;
use crate::manager::network::LoadedNetwork;
use crate::manager::node::NymNode;
use crate::manager::NetworkManager;
use console::style;
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::{
construct_gateway_bonding_sign_payload, construct_mixnode_bonding_sign_payload, Addr, Gateway,
Layer, LayerAssignment, MixNode, MixNodeCostParams, Percent,
};
use nym_mixnet_contract_common::{Layer, LayerAssignment};
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
use nym_validator_client::nyxd::CosmWasmCoin;
use nym_validator_client::DirectSigningHttpRpcNyxdClient;
use serde::{Deserialize, Serialize};
use std::fs;
@@ -23,99 +18,6 @@ use std::process::Stdio;
use tokio::process::Command;
use zeroize::Zeroizing;
struct NymNode {
// host is always 127.0.0.1
mix_port: u16,
verloc_port: u16,
http_port: u16,
clients_port: u16,
sphinx_key: String,
identity_key: String,
version: String,
owner: Account,
bonding_signature: String,
}
impl NymNode {
fn new_empty() -> NymNode {
NymNode {
mix_port: 0,
verloc_port: 0,
http_port: 0,
clients_port: 0,
sphinx_key: "".to_string(),
identity_key: "".to_string(),
version: "".to_string(),
owner: Account::new(),
bonding_signature: "".to_string(),
}
}
fn pledge(&self) -> CosmWasmCoin {
CosmWasmCoin::new(100_000000, "unym")
}
fn gateway(&self) -> Gateway {
Gateway {
host: "127.0.0.1".to_string(),
mix_port: self.mix_port,
clients_port: self.clients_port,
location: "foomp".to_string(),
sphinx_key: self.sphinx_key.clone(),
identity_key: self.identity_key.clone(),
version: self.version.clone(),
}
}
fn mixnode(&self) -> MixNode {
MixNode {
host: "127.0.0.1".to_string(),
mix_port: self.mix_port,
verloc_port: self.verloc_port,
http_api_port: self.http_port,
sphinx_key: self.sphinx_key.clone(),
identity_key: self.identity_key.clone(),
version: self.version.clone(),
}
}
fn cost_params(&self) -> MixNodeCostParams {
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: CosmWasmCoin::new(40_000000, "unym"),
}
}
fn bonding_signature(&self) -> MessageSignature {
// this is a valid bs58
self.bonding_signature.parse().unwrap()
}
fn mixnode_bonding_payload(&self) -> String {
let payload = construct_mixnode_bonding_sign_payload(
0,
Addr::unchecked(self.owner.address.to_string()),
None,
self.pledge(),
self.mixnode(),
self.cost_params(),
);
payload.to_base58_string().unwrap()
}
fn gateway_bonding_payload(&self) -> String {
let payload = construct_gateway_bonding_sign_payload(
0,
Addr::unchecked(self.owner.address.to_string()),
None,
self.pledge(),
self.gateway(),
);
payload.to_base58_string().unwrap()
}
}
struct LocalNodesCtx<'a> {
nym_node_binary: PathBuf,
@@ -124,7 +26,7 @@ struct LocalNodesCtx<'a> {
admin: DirectSigningHttpRpcNyxdClient,
mix_nodes: Vec<NymNode>,
gateway: Option<NymNode>,
gateways: Vec<NymNode>,
}
impl<'a> ProgressCtx for LocalNodesCtx<'a> {
@@ -135,7 +37,7 @@ impl<'a> ProgressCtx for LocalNodesCtx<'a> {
impl<'a> LocalNodesCtx<'a> {
fn nym_node_id(&self, node: &NymNode) -> String {
format!("{}-{}", node.owner.address, self.network.name)
format!("{}-{}", self.network.name, node.identity_key)
}
fn new(
@@ -158,7 +60,7 @@ impl<'a> LocalNodesCtx<'a> {
)?,
mix_nodes: Vec::new(),
progress,
gateway: None,
gateways: Vec::new(),
})
}
@@ -335,7 +237,7 @@ impl NetworkManager {
));
if is_gateway {
ctx.gateway = Some(node)
ctx.gateways.push(node)
} else {
ctx.mix_nodes.push(node)
}
@@ -345,16 +247,24 @@ impl NetworkManager {
async fn initialise_nym_nodes<'a>(
&self,
ctx: &mut LocalNodesCtx<'a>,
num_mixnodes: u16,
num_gateways: u16,
) -> Result<(), NetworkManagerError> {
const OFFSET: u16 = 100;
if num_mixnodes > OFFSET {
panic!("seriously? over 100 mixnodes?")
}
ctx.println(format!(
"🔏 {}Initialising local nym-nodes...",
style("[1/4]").bold().dim()
style("[1/5]").bold().dim()
));
// 3 mixnodes, 1 gateway; maybe at some point make it configurable
for i in 0..4 {
let is_gateway = i == 0;
self.initialise_nym_node(ctx, i, is_gateway).await?;
for i in 0..num_mixnodes {
self.initialise_nym_node(ctx, i, false).await?;
}
for i in 0..num_gateways {
self.initialise_nym_node(ctx, i + OFFSET, true).await?;
}
ctx.println("\t✅ all nym nodes got initialised!");
@@ -368,15 +278,11 @@ impl NetworkManager {
) -> Result<(), NetworkManagerError> {
ctx.println(format!(
"💸 {}Transferring tokens to the bond owners...",
style("[2/4]").bold().dim()
style("[2/5]").bold().dim()
));
let mut receivers = Vec::new();
for node in ctx
.mix_nodes
.iter()
.chain(std::iter::once(ctx.gateway.as_ref().unwrap()))
{
for node in ctx.mix_nodes.iter().chain(ctx.gateways.iter()) {
// send 101nym to the owner
receivers.push((node.owner.address.clone(), ctx.admin.mix_coins(101_000000)))
}
@@ -439,14 +345,15 @@ impl NetworkManager {
async fn bond_nym_nodes<'a>(&self, ctx: &LocalNodesCtx<'a>) -> Result<(), NetworkManagerError> {
ctx.println(format!(
"⛓️ {}Bonding the local nym-nodes...",
style("[3/4]").bold().dim()
style("[3/5]").bold().dim()
));
self.bond_node(ctx, ctx.gateway.as_ref().unwrap(), true)
.await?;
for mix_node in &ctx.mix_nodes {
self.bond_node(ctx, mix_node, false).await?;
}
for gateway in &ctx.gateways {
self.bond_node(ctx, gateway, true).await?;
}
ctx.println("\t✅ all nym nodes got bonded!");
@@ -459,7 +366,7 @@ impl NetworkManager {
) -> Result<(), NetworkManagerError> {
ctx.println(format!(
"🔌 {}Assigning mixnodes to the active set...",
style("[4/4]").bold().dim()
style("[4/5]").bold().dim()
));
let rewarder = ctx.signing_rewarder()?;
@@ -500,12 +407,23 @@ impl NetworkManager {
let env_canon_display = env_canon.display();
let mut cmds = Vec::new();
for node in ctx
.mix_nodes
.iter()
.chain(std::iter::once(ctx.gateway.as_ref().unwrap()))
{
let id = ctx.nym_node_id(node);
for mixnode in ctx.mix_nodes.iter() {
ctx.println(format!(
"\tpreparing node {} (mixnode)",
mixnode.identity_key
));
let id = ctx.nym_node_id(mixnode);
cmds.push(format!(
"{bin_canon_display} -c {env_canon_display} run --id {id} --local"
));
}
for gateway in ctx.gateways.iter() {
ctx.println(format!(
"\tpreparing node {} (mixnode)",
gateway.identity_key
));
let id = ctx.nym_node_id(gateway);
cmds.push(format!(
"{bin_canon_display} -c {env_canon_display} run --id {id} --local"
));
@@ -518,10 +436,36 @@ impl NetworkManager {
ctx.progress.output_run_commands(cmds)
}
async fn persist_nodes_in_database<'a>(
&self,
ctx: &LocalNodesCtx<'a>,
) -> Result<(), NetworkManagerError> {
ctx.println(format!(
"📦 {}Storing the node information in the database",
style("[5/5]").bold().dim()
));
ctx.set_pb_message("attempting to persist node information...");
let mix_save_future = self
.storage
.persist_mixnodes(&ctx.mix_nodes, ctx.network.id);
let gw_save_future = self.storage.persist_gateways(&ctx.gateways, ctx.network.id);
ctx.async_with_progress(mix_save_future).await?;
ctx.async_with_progress(gw_save_future).await?;
ctx.println(
"\t✅ the bonded node information got persisted in the database for future use",
);
Ok(())
}
pub(crate) async fn init_local_nym_nodes<P: AsRef<Path>>(
&self,
nym_node_binary: P,
network: &LoadedNetwork,
num_mixnodes: u16,
num_gateways: u16,
) -> Result<RunCommands, NetworkManagerError> {
let mut ctx = LocalNodesCtx::new(
nym_node_binary.as_ref().to_path_buf(),
@@ -534,10 +478,12 @@ impl NetworkManager {
return Err(NetworkManagerError::EnvFileNotGenerated);
}
self.initialise_nym_nodes(&mut ctx).await?;
self.initialise_nym_nodes(&mut ctx, num_mixnodes, num_gateways)
.await?;
self.transfer_bonding_tokens(&ctx).await?;
self.bond_nym_nodes(&ctx).await?;
self.assign_to_active_set(&ctx).await?;
self.persist_nodes_in_database(&ctx).await?;
let cmds = self.prepare_nym_nodes_run_commands(&ctx)?;
self.output_nym_nodes_run_commands(&ctx, &cmds);
@@ -24,6 +24,7 @@ mod local_client;
mod local_nodes;
pub(crate) mod network;
mod network_init;
mod node;
pub(crate) mod storage;
pub(crate) struct NetworkManager {
@@ -34,6 +34,7 @@ impl Network {
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct LoadedNetwork {
pub(crate) id: i64,
pub(crate) name: String,
pub(crate) rpc_endpoint: Url,
@@ -49,6 +50,7 @@ pub(crate) struct LoadedNetwork {
impl From<Network> for LoadedNetwork {
fn from(value: Network) -> Self {
LoadedNetwork {
id: i64::MAX,
name: value.name,
rpc_endpoint: value.rpc_endpoint,
created_at: value.created_at,
@@ -662,7 +662,7 @@ impl NetworkManager {
Ok(())
}
async fn persist_in_database(&self, ctx: &InitCtx) -> Result<(), NetworkManagerError> {
async fn persist_network_in_database(&self, ctx: &InitCtx) -> Result<(), NetworkManagerError> {
ctx.println(format!(
"📦 {}Storing all the results in the database",
style("[8/8]").bold().dim()
@@ -694,7 +694,7 @@ impl NetworkManager {
.await?;
self.perform_final_migrations(&mut ctx).await?;
self.get_build_info(&mut ctx).await?;
self.persist_in_database(&ctx).await?;
self.persist_network_in_database(&ctx).await?;
Ok(ctx.network.clone())
}
@@ -0,0 +1,105 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::manager::contract::Account;
use nym_coconut_dkg_common::types::Addr;
use nym_contracts_common::signing::MessageSignature;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::{
construct_gateway_bonding_sign_payload, construct_mixnode_bonding_sign_payload, Gateway,
MixNode, MixNodeCostParams,
};
use nym_validator_client::nyxd::CosmWasmCoin;
pub(crate) struct NymNode {
// host is always 127.0.0.1
pub(crate) mix_port: u16,
pub(crate) verloc_port: u16,
pub(crate) http_port: u16,
pub(crate) clients_port: u16,
pub(crate) sphinx_key: String,
pub(crate) identity_key: String,
pub(crate) version: String,
pub(crate) owner: Account,
pub(crate) bonding_signature: String,
}
impl NymNode {
pub(crate) fn new_empty() -> NymNode {
NymNode {
mix_port: 0,
verloc_port: 0,
http_port: 0,
clients_port: 0,
sphinx_key: "".to_string(),
identity_key: "".to_string(),
version: "".to_string(),
owner: Account::new(),
bonding_signature: "".to_string(),
}
}
pub(crate) fn pledge(&self) -> CosmWasmCoin {
CosmWasmCoin::new(100_000000, "unym")
}
pub(crate) fn gateway(&self) -> Gateway {
Gateway {
host: "127.0.0.1".to_string(),
mix_port: self.mix_port,
clients_port: self.clients_port,
location: "foomp".to_string(),
sphinx_key: self.sphinx_key.clone(),
identity_key: self.identity_key.clone(),
version: self.version.clone(),
}
}
pub(crate) fn mixnode(&self) -> MixNode {
MixNode {
host: "127.0.0.1".to_string(),
mix_port: self.mix_port,
verloc_port: self.verloc_port,
http_api_port: self.http_port,
sphinx_key: self.sphinx_key.clone(),
identity_key: self.identity_key.clone(),
version: self.version.clone(),
}
}
pub(crate) fn cost_params(&self) -> MixNodeCostParams {
MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: CosmWasmCoin::new(40_000000, "unym"),
}
}
pub(crate) fn bonding_signature(&self) -> MessageSignature {
// this is a valid bs58 string
self.bonding_signature.parse().unwrap()
}
pub(crate) fn mixnode_bonding_payload(&self) -> String {
let payload = construct_mixnode_bonding_sign_payload(
0,
Addr::unchecked(self.owner.address.to_string()),
None,
self.pledge(),
self.mixnode(),
self.cost_params(),
);
payload.to_base58_string().unwrap()
}
pub(crate) fn gateway_bonding_payload(&self) -> String {
let payload = construct_gateway_bonding_sign_payload(
0,
Addr::unchecked(self.owner.address.to_string()),
None,
self.pledge(),
self.gateway(),
);
payload.to_base58_string().unwrap()
}
}
@@ -180,4 +180,26 @@ impl StorageManager {
.fetch_one(&self.connection_pool)
.await
}
pub(crate) async fn save_node(
&self,
identity_key: &str,
network_id: i64,
bonded_type: &str,
owner_address: &str,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO node(identity_key, network_id, bonded_type, owner_address)
VALUES (?, ?, ?, ?)
"#,
identity_key,
network_id,
bonded_type,
owner_address
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
}
@@ -4,6 +4,7 @@ use std::fs;
use crate::error::NetworkManagerError;
use crate::manager::contract::{Account, Contract, LoadedNymContracts};
use crate::manager::network::{LoadedNetwork, Network, SpecialAddresses};
use crate::manager::node::NymNode;
use crate::manager::storage::manager::StorageManager;
use sqlx::ConnectOptions;
use std::path::Path;
@@ -117,6 +118,38 @@ impl NetworkManagerStorage {
.await?)
}
async fn persist_mixnode(
&self,
node: &NymNode,
network_id: i64,
) -> Result<(), NetworkManagerError> {
Ok(self
.manager
.save_node(
&node.identity_key,
network_id,
"mixnode",
node.owner.address.as_ref(),
)
.await?)
}
async fn persist_gateway(
&self,
node: &NymNode,
network_id: i64,
) -> Result<(), NetworkManagerError> {
Ok(self
.manager
.save_node(
&node.identity_key,
network_id,
"gateway",
node.owner.address.as_ref(),
)
.await?)
}
async fn persist_account(&self, account: &Account) -> Result<(), NetworkManagerError> {
let as_str = Zeroizing::new(account.mnemonic.to_string());
Ok(self
@@ -125,6 +158,30 @@ impl NetworkManagerStorage {
.await?)
}
pub(crate) async fn persist_mixnodes(
&self,
nodes: &[NymNode],
network_id: i64,
) -> Result<(), NetworkManagerError> {
for node in nodes {
self.persist_account(&node.owner).await?;
self.persist_mixnode(node, network_id).await?;
}
Ok(())
}
pub(crate) async fn persist_gateways(
&self,
nodes: &[NymNode],
network_id: i64,
) -> Result<(), NetworkManagerError> {
for node in nodes {
self.persist_account(&node.owner).await?;
self.persist_gateway(node, network_id).await?;
}
Ok(())
}
pub(crate) async fn persist_network(
&self,
network: &Network,
@@ -191,6 +248,7 @@ impl NetworkManagerStorage {
.ok_or_else(|| NetworkManagerError::RpcEndpointNotSet)?;
Ok(LoadedNetwork {
id: base_network.id,
name: base_network.name,
rpc_endpoint,
created_at: base_network.created_at,
@@ -60,7 +60,6 @@ impl TryFrom<RawContract> for LoadedContract {
#[derive(FromRow)]
pub(crate) struct RawNetwork {
#[allow(unused)]
pub(crate) id: i64,
pub(crate) name: String,
pub(crate) created_at: OffsetDateTime,