diff --git a/.dockerignore b/.dockerignore index 395b16e0d..de3738c7c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,4 @@ ci target +tempo-data diff --git a/Cargo.lock b/Cargo.lock index b194cc3ac..8ac3e4853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4272,6 +4272,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_yaml", + "time", "tokio", "tracing", ] diff --git a/compose.devnet.run.yml b/compose.devnet.run.yml new file mode 100644 index 000000000..6b3af2f21 --- /dev/null +++ b/compose.devnet.run.yml @@ -0,0 +1,222 @@ +version: '3.8' + +services: + + explorer: + container_name: explorer + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + depends_on: + - logos-blockchain-node-0 + environment: + - NBE_NODE_API_HOST=logos-blockchain-node-0 + - NBE_NODE_API_PORT=${NODE0_API_PORT} + ports: + - "8000:8000/tcp" + entrypoint: /opt/scripts/run_explorer.sh + + cfgsync: + container_name: cfgsync + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - ./testnet:/etc/logos-blockchain + depends_on: + - grafana + ports: + - "4400:4400/tcp" + entrypoint: /etc/logos-blockchain/scripts/run_cfgsync_server.sh + + logos-blockchain-node-0: + container_name: logos_blockchain_node_0 + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - node-data:/node-data + - ./testnet:/etc/logos-blockchain + - ./tests/kzgrs/kzgrs_test_params:/kzgrs_test_params:z + depends_on: + - cfgsync + environment: + - LB_HOST_IDX=0 + - CFG_HOST_IP=${PUBLIC_IP_ADDR} + - CFG_NETWORK_PORT=${NODE0_NET_PORT} + - CFG_BLEND_PORT=${NODE0_BLEND_PORT} + - CFG_API_PORT=${NODE0_API_PORT} + - FAUCET_PORT=${NODE0_FAUCET_PORT} + ports: + - "3000:3000/udp" + - "3400:3400/udp" + - "18085:18085/tcp" + entrypoint: /etc/logos-blockchain/scripts/run_node.sh + + logos-blockchain-node-1: + container_name: logos_blockchain_node_1 + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - node-data:/node-data + - ./testnet:/etc/logos-blockchain + - ./tests/kzgrs/kzgrs_test_params:/kzgrs_test_params:z + depends_on: + - cfgsync + environment: + - LB_HOST_IDX=1 + - CFG_HOST_IP=${PUBLIC_IP_ADDR} + - CFG_NETWORK_PORT=${NODE1_NET_PORT} + - CFG_BLEND_PORT=${NODE1_BLEND_PORT} + - CFG_API_PORT=${NODE1_API_PORT} + - FAUCET_PORT=${NODE1_FAUCET_PORT} + ports: + - "3001:3001/udp" + - "3401:3401/udp" + - "18081:18081/tcp" + entrypoint: /etc/logos-blockchain/scripts/run_node.sh + + logos-blockchain-node-2: + container_name: logos_blockchain_node_2 + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - node-data:/node-data + - ./testnet:/etc/logos-blockchain + - ./tests/kzgrs/kzgrs_test_params:/kzgrs_test_params:z + depends_on: + - cfgsync + environment: + - LB_HOST_IDX=2 + - CFG_HOST_IP=${PUBLIC_IP_ADDR} + - CFG_NETWORK_PORT=${NODE2_NET_PORT} + - CFG_BLEND_PORT=${NODE2_BLEND_PORT} + - CFG_API_PORT=${NODE2_API_PORT} + - FAUCET_PORT=${NODE2_FAUCET_PORT} + ports: + - "3002:3002/udp" + - "3402:3402/udp" + - "18082:18082/tcp" + entrypoint: /etc/logos-blockchain/scripts/run_node.sh + + logos-blockchain-node-3: + container_name: logos_blockchain_node_3 + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - node-data:/node-data + - ./testnet:/etc/logos-blockchain + - ./tests/kzgrs/kzgrs_test_params:/kzgrs_test_params:z + depends_on: + - cfgsync + environment: + - LB_HOST_IDX=3 + - CFG_HOST_IP=${PUBLIC_IP_ADDR} + - CFG_NETWORK_PORT=${NODE3_NET_PORT} + - CFG_BLEND_PORT=${NODE3_BLEND_PORT} + - CFG_API_PORT=${NODE3_API_PORT} + - FAUCET_PORT=${NODE3_FAUCET_PORT} + ports: + - "3003:3003/udp" + - "3403:3403/udp" + - "18083:18083/tcp" + entrypoint: /etc/logos-blockchain/scripts/run_node.sh + + prometheus: + container_name: prometheus + image: prom/prometheus:v3.0.1 + volumes: + - ./testnet/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:z + command: + - --config.file=/etc/prometheus/prometheus.yml + - --storage.tsdb.retention.time=7d + - --web.enable-otlp-receiver + ports: + - 0.0.0.0:9090:9090 + restart: on-failure + + grafana: + container_name: grafana + image: grafana/grafana:latest + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_DISABLE_LOGIN_FORM=true + - GF_FEATURE_TOGGLES_ENABLE=traceqlEditor traceQLStreaming metricsSummary + env_file: + - ./testnet/monitoring/grafana/plugins.env + volumes: + - ./testnet/monitoring/grafana/grafana.ini:/etc/grafana/grafana.ini:z + - ./testnet/monitoring/grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:z + - ./testnet/monitoring/grafana/dashboards.yml:/etc/grafana/provisioning/dashboards/dashboards.yml:z + - ./testnet/monitoring/grafana/dashboards:/var/lib/grafana/dashboards/:z + ports: + - 9091:3000 + restart: on-failure + depends_on: + - prometheus + + loki: + image: grafana/loki:2.9.2 + ports: + - "3100:3100" + command: -config.file=/etc/loki/local-config.yaml + + # Tempo runs as unpriviliged user, volumes need to be chowned before running. + tempo-init: + image: busybox:latest + user: root + entrypoint: + - "chown" + - "10001:10001" + - "/var/tempo" + volumes: + - ./tempo-data:/var/tempo + + tempo: + image: grafana/tempo:latest + container_name: tempo + command: [ "-config.file=/etc/tempo.yaml" ] + volumes: + - ./testnet/monitoring/tempo.yaml:/etc/tempo.yaml:z + - ./tempo-data:/var/tempo + ports: + - "4317:4317" # otlp grpc + depends_on: + - tempo-init + + # L2 Sequencer Demo Services + l2-nginx: + container_name: l2_nginx + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + ports: + - "18080:18080" + depends_on: + - cfgsync + - explorer + volumes: + - ./testnet/nginx.conf:/etc/nginx/nginx.conf:ro + - ./testnet/static:/usr/share/nginx/html:ro + entrypoint: [ "nginx", "-g", "daemon off;" ] + + l2-sequencer: + container_name: l2_sequencer + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + environment: + - SEQUENCER_LISTEN_ADDR=0.0.0.0:8080 + - SEQUENCER_NODE_ENDPOINT=http://logos-blockchain-node-0:18081 + - SEQUENCER_DB_PATH=/data/sequencer.db + - SEQUENCER_SIGNING_KEY_PATH=/data/sequencer.key + - SEQUENCER_CHANNEL_ID=6d656d636f696e00000000000000000000000000000000000000000000000001 + - SEQUENCER_INITIAL_BALANCE=1000 + volumes: + - l2-sequencer-data:/data + entrypoint: [ "/usr/bin/logos-blockchain-demo-sequencer" ] + + l2-archiver: + container_name: l2_archiver + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + environment: + - TESTNET_ENDPOINT=http://logos-blockchain-node-1:18081 + - CHANNEL_ID=6d656d636f696e00000000000000000000000000000000000000000000000001 + - TOKEN_NAME=MEM + entrypoint: [ "/usr/bin/logos-blockchain-demo-archiver" ] + +# Node data volume is marked as external and assumes +# that configuration files were placed by compose.setup.yml +volumes: + l2-sequencer-data: + node-data: + name: logos-node-data + external: true diff --git a/compose.devnet.setup.yml b/compose.devnet.setup.yml new file mode 100644 index 000000000..37cf9e4c2 --- /dev/null +++ b/compose.devnet.setup.yml @@ -0,0 +1,81 @@ +version: '3.8' + +services: + + cfgsync: + container_name: cfgsync + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - node-data:/node-data + - ./testnet:/etc/logos-blockchain + ports: + - "4400:4400/tcp" + entrypoint: /etc/logos-blockchain/scripts/setup_cfgsync_server.sh + + client-0: + container_name: client_0 + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - node-data:/node-data + - ./testnet:/etc/logos-blockchain + depends_on: + - cfgsync + environment: + - LB_HOST_IDX=0 + - CFG_HOST_IP=${PUBLIC_IP_ADDR} + - CFG_NETWORK_PORT=${NODE0_NET_PORT} + - CFG_BLEND_PORT=${NODE0_BLEND_PORT} + - CFG_API_PORT=${NODE0_API_PORT} + entrypoint: /etc/logos-blockchain/scripts/setup_cfgsync_client.sh + + client-1: + container_name: client_1 + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - node-data:/node-data + - ./testnet:/etc/logos-blockchain + depends_on: + - cfgsync + environment: + - LB_HOST_IDX=1 + - CFG_HOST_IP=${PUBLIC_IP_ADDR} + - CFG_NETWORK_PORT=${NODE1_NET_PORT} + - CFG_BLEND_PORT=${NODE1_BLEND_PORT} + - CFG_API_PORT=${NODE1_API_PORT} + entrypoint: /etc/logos-blockchain/scripts/setup_cfgsync_client.sh + + client-2: + container_name: client_2 + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - node-data:/node-data + - ./testnet:/etc/logos-blockchain + depends_on: + - cfgsync + environment: + - LB_HOST_IDX=2 + - CFG_HOST_IP=${PUBLIC_IP_ADDR} + - CFG_NETWORK_PORT=${NODE2_NET_PORT} + - CFG_BLEND_PORT=${NODE2_BLEND_PORT} + - CFG_API_PORT=${NODE2_API_PORT} + entrypoint: /etc/logos-blockchain/scripts/setup_cfgsync_client.sh + + client-3: + container_name: client_3 + image: ghcr.io/logos-blockchain/logos-blockchain:devnet + volumes: + - node-data:/node-data + - ./testnet:/etc/logos-blockchain + depends_on: + - cfgsync + environment: + - LB_HOST_IDX=3 + - CFG_HOST_IP=${PUBLIC_IP_ADDR} + - CFG_NETWORK_PORT=${NODE3_NET_PORT} + - CFG_BLEND_PORT=${NODE3_BLEND_PORT} + - CFG_API_PORT=${NODE3_API_PORT} + entrypoint: /etc/logos-blockchain/scripts/setup_cfgsync_client.sh + +volumes: + node-data: + name: logos-node-data diff --git a/compose.static.yml b/compose.static.yml deleted file mode 100644 index 001ab95d0..000000000 --- a/compose.static.yml +++ /dev/null @@ -1,209 +0,0 @@ -version: '3.8' - -services: - - explorer: - container_name: explorer - image: ghcr.io/logos-blockchain/logos-blockchain:devnet - depends_on: - - logos-blockchain-node-0 - environment: - - NBE_NODE_API_HOST=logos-blockchain-node-0 - - NBE_NODE_API_PORT=${NODE0_API_PORT} - ports: - - "8000:8000/tcp" - entrypoint: /opt/scripts/run_explorer.sh - - cfgsync: - container_name: cfgsync - image: ghcr.io/logos-blockchain/logos-blockchain:devnet - volumes: - - ./testnet:/etc/logos-blockchain - depends_on: - - grafana - ports: - - "4400:4400/tcp" - entrypoint: /etc/logos-blockchain/scripts/run_cfgsync.sh - - logos-blockchain-node-0: - container_name: logos_blockchain_node_0 - image: ghcr.io/logos-blockchain/logos-blockchain:devnet - volumes: - - ./testnet:/etc/logos-blockchain - - ./tests/kzgrs/kzgrs_test_params:/kzgrs_test_params:z - depends_on: - - cfgsync - environment: - - CFG_HOST_IP=${PUBLIC_IP_ADDR} - - CFG_NETWORK_PORT=${NODE0_NET_PORT} - - CFG_BLEND_PORT=${NODE0_BLEND_PORT} - - CFG_API_PORT=${NODE0_API_PORT} - - FAUCET_PORT=${NODE0_FAUCET_PORT} - ports: - - "3000:3000/udp" - - "3400:3400/udp" - - "18085:18085/tcp" - entrypoint: /etc/logos-blockchain/scripts/run_node.sh - - logos-blockchain-node-1: - container_name: logos_blockchain_node_1 - image: ghcr.io/logos-blockchain/logos-blockchain:devnet - volumes: - - ./testnet:/etc/logos-blockchain - - ./tests/kzgrs/kzgrs_test_params:/kzgrs_test_params:z - depends_on: - - cfgsync - environment: - - CFG_HOST_IP=${PUBLIC_IP_ADDR} - - CFG_NETWORK_PORT=${NODE1_NET_PORT} - - CFG_BLEND_PORT=${NODE1_BLEND_PORT} - - CFG_API_PORT=${NODE1_API_PORT} - - FAUCET_PORT=${NODE1_FAUCET_PORT} - ports: - - "3001:3001/udp" - - "3401:3401/udp" - - "18081:18081/tcp" - entrypoint: /etc/logos-blockchain/scripts/run_node.sh - - logos-blockchain-node-2: - container_name: logos_blockchain_node_2 - image: ghcr.io/logos-blockchain/logos-blockchain:devnet - volumes: - - ./testnet:/etc/logos-blockchain - - ./tests/kzgrs/kzgrs_test_params:/kzgrs_test_params:z - depends_on: - - cfgsync - environment: - - CFG_HOST_IP=${PUBLIC_IP_ADDR} - - CFG_NETWORK_PORT=${NODE2_NET_PORT} - - CFG_BLEND_PORT=${NODE2_BLEND_PORT} - - CFG_API_PORT=${NODE2_API_PORT} - - FAUCET_PORT=${NODE2_FAUCET_PORT} - ports: - - "3002:3002/udp" - - "3402:3402/udp" - - "18082:18082/tcp" - entrypoint: /etc/logos-blockchain/scripts/run_node.sh - - logos-blockchain-node-3: - container_name: logos_blockchain_node_3 - image: ghcr.io/logos-blockchain/logos-blockchain:devnet - volumes: - - ./testnet:/etc/logos-blockchain - - ./tests/kzgrs/kzgrs_test_params:/kzgrs_test_params:z - depends_on: - - cfgsync - environment: - - CFG_HOST_IP=${PUBLIC_IP_ADDR} - - CFG_NETWORK_PORT=${NODE3_NET_PORT} - - CFG_BLEND_PORT=${NODE3_BLEND_PORT} - - CFG_API_PORT=${NODE3_API_PORT} - - FAUCET_PORT=${NODE3_FAUCET_PORT} - ports: - - "3003:3003/udp" - - "3403:3403/udp" - - "18083:18083/tcp" - entrypoint: /etc/logos-blockchain/scripts/run_node.sh - - prometheus: - container_name: prometheus - image: prom/prometheus:v3.0.1 - volumes: - - ./testnet/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:z - command: - - --config.file=/etc/prometheus/prometheus.yml - - --storage.tsdb.retention.time=7d - - --web.enable-otlp-receiver - ports: - - 0.0.0.0:9090:9090 - restart: on-failure - - grafana: - container_name: grafana - image: grafana/grafana:latest - environment: - - GF_AUTH_ANONYMOUS_ENABLED=true - - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin - - GF_AUTH_DISABLE_LOGIN_FORM=true - - GF_FEATURE_TOGGLES_ENABLE=traceqlEditor traceQLStreaming metricsSummary - env_file: - - ./testnet/monitoring/grafana/plugins.env - volumes: - - ./testnet/monitoring/grafana/grafana.ini:/etc/grafana/grafana.ini:z - - ./testnet/monitoring/grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:z - - ./testnet/monitoring/grafana/dashboards.yml:/etc/grafana/provisioning/dashboards/dashboards.yml:z - - ./testnet/monitoring/grafana/dashboards:/var/lib/grafana/dashboards/:z - ports: - - 9091:3000 - restart: on-failure - depends_on: - - prometheus - - loki: - image: grafana/loki:2.9.2 - ports: - - "3100:3100" - command: -config.file=/etc/loki/local-config.yaml - - # Tempo runs as unpriviliged user, volumes need to be chowned before running. - tempo-init: - image: busybox:latest - user: root - entrypoint: - - "chown" - - "10001:10001" - - "/var/tempo" - volumes: - - ./tempo-data:/var/tempo - - tempo: - image: grafana/tempo:latest - container_name: tempo - command: [ "-config.file=/etc/tempo.yaml" ] - volumes: - - ./testnet/monitoring/tempo.yaml:/etc/tempo.yaml:z - - ./tempo-data:/var/tempo - ports: - - "4317:4317" # otlp grpc - depends_on: - - tempo-init - - # L2 Sequencer Demo Services - l2-nginx: - container_name: l2_nginx - image: ghcr.io/logos-blockchain/logos-blockchain:devnet - ports: - - "18080:18080" - depends_on: - - cfgsync - - explorer - volumes: - - ./testnet/nginx.conf:/etc/nginx/nginx.conf:ro - - ./testnet/static:/usr/share/nginx/html:ro - entrypoint: [ "nginx", "-g", "daemon off;" ] - - l2-sequencer: - container_name: l2_sequencer - image: ghcr.io/logos-blockchain/logos-blockchain:devnet - environment: - - SEQUENCER_LISTEN_ADDR=0.0.0.0:8080 - - SEQUENCER_NODE_ENDPOINT=http://logos-blockchain-node-0:18081 - - SEQUENCER_DB_PATH=/data/sequencer.db - - SEQUENCER_SIGNING_KEY_PATH=/data/sequencer.key - - SEQUENCER_CHANNEL_ID=6d656d636f696e00000000000000000000000000000000000000000000000001 - - SEQUENCER_INITIAL_BALANCE=1000 - volumes: - - l2-sequencer-data:/data - entrypoint: [ "/usr/bin/logos-blockchain-demo-sequencer" ] - - l2-archiver: - container_name: l2_archiver - image: ghcr.io/logos-blockchain/logos-blockchain:devnet - environment: - - TESTNET_ENDPOINT=http://logos-blockchain-node-1:18081 - - CHANNEL_ID=6d656d636f696e00000000000000000000000000000000000000000000000001 - - TOKEN_NAME=MEM - entrypoint: [ "/usr/bin/logos-blockchain-demo-archiver" ] - -volumes: - l2-sequencer-data: diff --git a/compose.static.yml b/compose.static.yml new file mode 120000 index 000000000..0a93e8147 --- /dev/null +++ b/compose.static.yml @@ -0,0 +1 @@ +compose.devnet.run.yml \ No newline at end of file diff --git a/compose.yml b/compose.yml index dd12adcb7..5d1c4d50d 100644 --- a/compose.yml +++ b/compose.yml @@ -8,7 +8,7 @@ services: image: logos-blockchain:latest volumes: - ./testnet:/etc/logos-blockchain - entrypoint: /etc/logos-blockchain/scripts/run_cfgsync.sh + entrypoint: /etc/logos-blockchain/scripts/run_cfgsync_server.sh logos-blockchain-node: build: diff --git a/testnet/cfgsync.yaml b/testnet/cfgsync.yaml index e7ab63f60..0b1e1ff72 100644 --- a/testnet/cfgsync.yaml +++ b/testnet/cfgsync.yaml @@ -6,6 +6,11 @@ faucet_settings: note_count: 3 note_value: 10000000 +deployment_settings_storage_path: + deployment-settings.yaml + +mode: Run + # Tracing tracing_settings: logger: Stdout diff --git a/testnet/cfgsync/Cargo.toml b/testnet/cfgsync/Cargo.toml index 0c2bd0765..68def4000 100644 --- a/testnet/cfgsync/Cargo.toml +++ b/testnet/cfgsync/Cargo.toml @@ -28,6 +28,7 @@ serde = { workspace = true } serde_json = { default-features = false, version = "1.0" } serde_path_to_error = "0.1" serde_yaml = "0.9" +time = { default-features = false, version = "0.3" } tokio = { default-features = false, features = ["macros", "net", "rt-multi-thread"], version = "1" } tracing = { workspace = true } diff --git a/testnet/cfgsync/src/bin/cfgsync-server.rs b/testnet/cfgsync/src/bin/cfgsync-server.rs index 3ea5e6ad4..e5c62a349 100644 --- a/testnet/cfgsync/src/bin/cfgsync-server.rs +++ b/testnet/cfgsync/src/bin/cfgsync-server.rs @@ -1,26 +1,54 @@ use std::{path::PathBuf, process}; use clap::Parser; -use logos_blockchain_cfgsync::server::{CfgSyncConfig, cfgsync_app}; +use logos_blockchain_cfgsync::{ + CfgsyncMode, + server::{CfgSyncConfig, cfgsync_app}, +}; +use time::{OffsetDateTime, format_description::well_known::Rfc3339}; use tokio::net::TcpListener; +fn parse_rfc3339(s: &str) -> Result { + OffsetDateTime::parse(s, &Rfc3339) + .map_err(|e| format!("Invalid RFC3339 format (2026-02-12T04:45:00Z): {e}")) +} + #[derive(Parser, Debug)] #[command(about = "CfgSync")] struct Args { config: PathBuf, + #[arg(short, long, env = "CHAIN_START_TIME", value_parser = parse_rfc3339)] + chain_start_time: Option, + #[arg(short, long, env = "CFG_SERVER_MODE")] + mode: Option, + #[arg(short, long, env = "CFG_SERVER_STORAGE_PATH")] + storage_path: Option, } #[tokio::main] async fn main() { let cli = Args::parse(); - let config = CfgSyncConfig::load_from_file(&cli.config).unwrap_or_else(|err| { + let mut config = CfgSyncConfig::load_from_file(&cli.config).unwrap_or_else(|err| { eprintln!("{err}"); process::exit(1); }); + if let Some(chain_start_time) = cli.chain_start_time { + config.chain_start_time = Some(chain_start_time); + } + + if let Some(storage_path) = cli.storage_path { + config.deployment_settings_storage_path = storage_path; + } + + if let Some(mode) = cli.mode { + config.mode = mode; + } + let port = config.port; - let app = cfgsync_app(config.into()); + let mode = config.mode; + let app = cfgsync_app(config.into(), mode); println!("Server running on http://0.0.0.0:{port}"); let listener = TcpListener::bind(&format!("0.0.0.0:{port}")).await.unwrap(); diff --git a/testnet/cfgsync/src/config/mod.rs b/testnet/cfgsync/src/config/mod.rs index 59818c9f7..20693ef7d 100644 --- a/testnet/cfgsync/src/config/mod.rs +++ b/testnet/cfgsync/src/config/mod.rs @@ -48,6 +48,11 @@ pub fn create_node_configs( ids.push(id_bytes); } + // Clippy in 1.93.0: + // > an unstable sort typically performs faster without any + // > observable difference for this data type. [stable_sort_primitive] + ids.sort_unstable(); + let (consensus_configs, faucet_note_keys, genesis_tx) = create_consensus_configs(&ids, SHORT_PROLONGED_BOOTSTRAP_PERIOD, faucet_settings); let network_configs = create_network_configs(&ids, &NetworkParams::default()); diff --git a/testnet/cfgsync/src/lib.rs b/testnet/cfgsync/src/lib.rs index 19ed2ff80..8e038c49a 100644 --- a/testnet/cfgsync/src/lib.rs +++ b/testnet/cfgsync/src/lib.rs @@ -5,6 +5,7 @@ pub mod server; use std::net::Ipv4Addr; +use clap::ValueEnum; use serde::{Deserialize, Serialize}; const DEFAULT_LIBP2P_NETWORK_PORT: u16 = 3000; @@ -68,3 +69,10 @@ pub struct FaucetSettings { pub note_count: usize, pub note_value: u64, } + +#[derive(Clone, Copy, Debug, Deserialize, ValueEnum)] +#[clap(rename_all = "kebab-case")] +pub enum CfgsyncMode { + Setup, + Run, +} diff --git a/testnet/cfgsync/src/repo.rs b/testnet/cfgsync/src/repo.rs index 22982350b..a82558e0a 100644 --- a/testnet/cfgsync/src/repo.rs +++ b/testnet/cfgsync/src/repo.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + path::PathBuf, sync::{Arc, Mutex}, time::Duration, }; @@ -9,6 +10,7 @@ use lb_node::config::{ deployment::{DeploymentSettings, WellKnownDeployment}, }; use lb_tests::topology::configs::GeneralConfig; +use time::OffsetDateTime; use tokio::{sync::oneshot::Sender, time::timeout}; use crate::{ @@ -26,22 +28,25 @@ pub struct ConfigRepo { waiting_hosts: Mutex>>, generated_user_configs: Mutex>, deployment_settings: Mutex>, + pub deployment_settings_storage_path: PathBuf, n_hosts: usize, faucet_settings: FaucetSettings, tracing_settings: TracingConfig, + chain_start_time: OffsetDateTime, timeout_duration: Duration, } impl From for Arc { fn from(config: CfgSyncConfig) -> Self { - let faucet_settings = config.faucet_settings(); - let tracing_settings = config.tracing_settings(); - ConfigRepo::new( config.n_hosts, - faucet_settings, - tracing_settings, + config.faucet_settings(), + config + .chain_start_time + .unwrap_or_else(OffsetDateTime::now_utc), + config.tracing_settings(), Duration::from_secs(config.timeout), + config.deployment_settings_storage_path, ) } } @@ -51,15 +56,19 @@ impl ConfigRepo { pub fn new( n_hosts: usize, faucet_settings: FaucetSettings, + chain_start_time: OffsetDateTime, tracing_settings: TracingConfig, timeout_duration: Duration, + deployment_settings_storage_path: PathBuf, ) -> Arc { let repo = Arc::new(Self { waiting_hosts: Mutex::new(HashMap::new()), generated_user_configs: Mutex::new(HashMap::new()), deployment_settings: Mutex::new(None), + deployment_settings_storage_path, n_hosts, faucet_settings, + chain_start_time, tracing_settings, timeout_duration, }); @@ -107,6 +116,14 @@ impl ConfigRepo { None } + fn persist_deployment_settings(&self, settings: &DeploymentSettings) -> Result<(), String> { + let yaml = serde_yaml::to_string(settings) + .map_err(|e| format!("Error: Failed to serialize deployment settings: {e}"))?; + std::fs::write(&self.deployment_settings_storage_path, yaml) + .map_err(|err| format!("Failed to write config to file: {err}"))?; + Ok(()) + } + async fn run(&self) { let timeout_duration = self.timeout_duration; @@ -121,6 +138,7 @@ impl ConfigRepo { let devnet_settings = { let mut default_settings = DeploymentSettings::from(WellKnownDeployment::Devnet); default_settings.cryptarchia.genesis_state = genesis_tx; + default_settings.time.chain_start_time = self.chain_start_time; default_settings }; @@ -134,6 +152,9 @@ impl ConfigRepo { *deployment_settings = Some(devnet_settings.clone()); }; + self.persist_deployment_settings(&devnet_settings) + .expect("Settings should be persisted"); + for (host, sender) in waiting_hosts.drain() { let config = configs.get(&host).expect("host should have a config"); drop(sender.send(RepoResponse::Config(Box::new(( diff --git a/testnet/cfgsync/src/server.rs b/testnet/cfgsync/src/server.rs index 0980d1171..e5ad382e8 100644 --- a/testnet/cfgsync/src/server.rs +++ b/testnet/cfgsync/src/server.rs @@ -2,8 +2,9 @@ use std::{fs, path::PathBuf, sync::Arc}; use axum::{ Json, Router, + body::Body, extract::State, - http::StatusCode, + http::{Response, StatusCode}, response::IntoResponse, routing::{get, post}, }; @@ -11,10 +12,11 @@ use lb_node::config::TracingConfig; use lb_tests::nodes::create_validator_config; use reqwest::header::CONTENT_TYPE; use serde::Deserialize; +use time::OffsetDateTime; use tokio::sync::oneshot::channel; use crate::{ - FaucetSettings, Host, RegistrationInfo, + CfgsyncMode, FaucetSettings, Host, RegistrationInfo, repo::{ConfigRepo, RepoResponse}, }; @@ -23,6 +25,10 @@ pub struct CfgSyncConfig { pub port: u16, pub n_hosts: usize, pub timeout: u64, + pub chain_start_time: Option, + pub deployment_settings_storage_path: PathBuf, + + pub mode: CfgsyncMode, pub faucet_settings: FaucetSettings, // Tracing params @@ -68,39 +74,38 @@ async fn init_node( ) } -async fn generate_config( - State(repo): State>, - Json(info): Json, -) -> impl IntoResponse { - let host = Host::from(info); - - repo.append(host).map_or_else( - || { - ( - StatusCode::BAD_REQUEST, - "Network not initialized. Initial nodes must sync first.", - ) - .into_response() - }, - |cfg| { - let node_config = create_validator_config(cfg, repo.deployment_settings().unwrap()); - let yaml = serde_yaml::to_string(&node_config).unwrap_or_default(); - - (StatusCode::OK, [(CONTENT_TYPE, "text/yaml")], yaml).into_response() - }, +async fn handle_mode_error() -> (StatusCode, &'static str) { + ( + StatusCode::METHOD_NOT_ALLOWED, + "Setup is disabled: Server is in Read-Only mode.", ) } async fn deployment_settings(State(repo): State>) -> impl IntoResponse { - let deployment_settings = repo.deployment_settings(); - let yaml = serde_yaml::to_string(&deployment_settings).unwrap_or_default(); - (StatusCode::OK, [(CONTENT_TYPE, "text/yaml")], yaml).into_response() + match tokio::fs::read(&repo.deployment_settings_storage_path).await { + Ok(bytes) => Response::builder() + .status(StatusCode::OK) + .header(CONTENT_TYPE, "text/yaml") + .body(Body::from(bytes)) + .unwrap(), + Err(e) => { + eprintln!("Failed to read deployment file: {e}"); + (StatusCode::NOT_FOUND, "Deployment file not found").into_response() + } + } } -pub fn cfgsync_app(config_repo: Arc) -> Router { - Router::new() - .route("/init-with-node", post(init_node)) - .route("/generate-config", post(generate_config)) - .route("/deployment-settings", get(deployment_settings)) - .with_state(config_repo) +pub fn cfgsync_app(config_repo: Arc, mode: CfgsyncMode) -> Router { + let mut router = Router::new().route("/deployment-settings", get(deployment_settings)); + + match mode { + CfgsyncMode::Setup => { + router = router.route("/init-with-node", post(init_node)); + } + CfgsyncMode::Run => { + router = router.route("/init-with-node", post(handle_mode_error)); + } + } + + router.with_state(config_repo) } diff --git a/testnet/cfgsync/tests/cfgsync.yaml b/testnet/cfgsync/tests/cfgsync.yaml index 41053fb28..24c8f0d25 100644 --- a/testnet/cfgsync/tests/cfgsync.yaml +++ b/testnet/cfgsync/tests/cfgsync.yaml @@ -6,6 +6,11 @@ faucet_settings: note_count: 3 note_value: 10000000 +deployment_settings_storage_path: + deployment-settings.yaml + +mode: Run + # Tracing tracing_settings: logger: !Stdout diff --git a/testnet/cfgsync/tests/server_client.rs b/testnet/cfgsync/tests/server_client.rs index 300dc3e0b..00c95c1ac 100644 --- a/testnet/cfgsync/tests/server_client.rs +++ b/testnet/cfgsync/tests/server_client.rs @@ -9,6 +9,8 @@ const SERVER_CFG: &str = "../cfgsync.yaml"; // Use config from "testnet" dir. #[tokio::test] async fn smoke_test_four_clients() { let mut server = std::process::Command::new(SERVER_BIN) + .arg("--mode") + .arg("setup") .arg(SERVER_CFG) .spawn() .expect("server failed"); @@ -38,6 +40,5 @@ async fn smoke_test_four_clients() { fs::remove_file(out).unwrap(); } - sleep(Duration::from_secs(51)).await; server.kill().and_then(|()| server.wait()).unwrap(); } diff --git a/testnet/cfgsync/tests/with_node.rs b/testnet/cfgsync/tests/with_node.rs index ddc1a4dd1..847dc77ec 100644 --- a/testnet/cfgsync/tests/with_node.rs +++ b/testnet/cfgsync/tests/with_node.rs @@ -14,9 +14,11 @@ const SERVER_CFG: &str = "./tests/cfgsync.yaml"; #[ignore = "For local debugging"] #[tokio::test] -async fn test_spawn_nodes_from_cfgsync_custom_ports() { +async fn test_deploy_setup_stage() { // Start the configuration server let mut server = std::process::Command::new(SERVER_BIN) + .arg("--mode") + .arg("setup") .arg(SERVER_CFG) .spawn() .expect("server failed"); @@ -88,3 +90,51 @@ async fn test_spawn_nodes_from_cfgsync_custom_ports() { server.kill().unwrap(); server.wait().unwrap(); } + +#[ignore = "For local debugging"] +#[tokio::test] +async fn test_deploy_run_stage() { + let server = std::process::Command::new(SERVER_BIN) + .arg("--mode") + .arg("run") + .arg("--storage-path") + .arg("cfgsync-deployment-settings.yaml") + .arg(SERVER_CFG) + .spawn() + .expect("server failed to start in run mode"); + + sleep(Duration::from_secs(15)).await; + + let response = reqwest::get("http://127.0.0.1:4400/deployment-settings") + .await + .expect("Failed to hit deployment-settings endpoint"); + let yaml_bytes = response.bytes().await.unwrap(); + let deployment: DeploymentSettings = serde_yaml::from_slice(&yaml_bytes).unwrap(); + + let mut nodes = Vec::new(); + + for i in 0..4 { + let node_cfg_path = format!(".tmp_node_{i}.yaml"); + + let user_yaml = fs::read_to_string(&node_cfg_path).expect("Node config file missing."); + let user_config: UserConfig = + serde_yaml::from_str(&user_yaml).expect("Failed to parse user config"); + + let run_config = RunConfig { + user: user_config, + deployment: deployment.clone(), + }; + + println!(">>>> Spawning Node {i} from disk (.tmp_node_{i}.yaml)"); + let node = Validator::spawn(run_config) + .await + .expect("Node spawn failed"); + nodes.push(node); + } + + println!("\nIndependent network spawned from disk. Press Ctrl+C to terminate.\n"); + + tokio::signal::ctrl_c().await.unwrap(); + drop(nodes); + drop(server); +} diff --git a/testnet/scripts/run_cfgsync.sh b/testnet/scripts/run_cfgsync.sh deleted file mode 100755 index 709d46985..000000000 --- a/testnet/scripts/run_cfgsync.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -set -e - -exec /usr/bin/logos-blockchain-cfgsync-server /etc/logos-blockchain/cfgsync.yaml diff --git a/testnet/scripts/run_cfgsync_server.sh b/testnet/scripts/run_cfgsync_server.sh new file mode 100755 index 000000000..77a42772a --- /dev/null +++ b/testnet/scripts/run_cfgsync_server.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -e + +export CFG_SERVER_MODE="run" \ + CFG_SERVER_STORAGE_PATH="/node-data/cfgsync/deploymen-settings.yaml" + +exec /usr/bin/logos-blockchain-cfgsync-server /etc/logos-blockchain/cfgsync.yaml diff --git a/testnet/scripts/run_node.sh b/testnet/scripts/run_node.sh index 62590a35b..8e8ce8c39 100755 --- a/testnet/scripts/run_node.sh +++ b/testnet/scripts/run_node.sh @@ -2,20 +2,18 @@ set -e -export CFG_FILE_PATH="/config.yaml" \ +export CFG_FILE_PATH="/node-data/${LB_HOST_IDX}/config.yaml" \ CFG_SERVER_ADDR="http://cfgsync:4400" \ CFG_HOST_IDENTIFIER="validator-$CFG_API_PORT" \ - LOG_LEVEL="INFO" \ - POL_PROOF_DEV_MODE=true - -/usr/bin/logos-blockchain-cfgsync-client + CFG_DEPLOYMENT_PATH="/node-data/cfgsync/deployment-settings.yaml" \ + LOG_LEVEL="INFO" echo "Starting Faucet..." /usr/bin/logos-blockchain-faucet \ --port $FAUCET_PORT \ --node-base-url "http://localhost:$CFG_API_PORT"\ - --host-identifier $CFG_HOST_IDENTIFIER + --host-identifier $CFG_HOST_IDENTIFIER \ --drip-amount 1000 & echo "Starting Node..." -exec /usr/bin/logos-blockchain-node /config.yaml +exec /usr/bin/logos-blockchain-node --deployment $CFG_DEPLOYMENT_PATH $CFG_FILE_PATH diff --git a/testnet/scripts/setup_cfgsync_client.sh b/testnet/scripts/setup_cfgsync_client.sh new file mode 100755 index 000000000..26707215a --- /dev/null +++ b/testnet/scripts/setup_cfgsync_client.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +rm -rf /node-data/${LB_HOST_IDX} + +set -e + +export CFG_FILE_PATH="/node-data/${LB_HOST_IDX}/config.yaml" \ + CFG_SERVER_ADDR="http://cfgsync:4400" \ + CFG_HOST_IDENTIFIER="validator-$CFG_NETWORK_PORT" + +mkdir /node-data/${LB_HOST_IDX} + +exec /usr/bin/logos-blockchain-cfgsync-client diff --git a/testnet/scripts/setup_cfgsync_server.sh b/testnet/scripts/setup_cfgsync_server.sh new file mode 100755 index 000000000..99c954816 --- /dev/null +++ b/testnet/scripts/setup_cfgsync_server.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +rm -rf /node-data/cfgsync + +set -e + +export CFG_SERVER_MODE="setup" \ + CFG_SERVER_STORAGE_PATH="/node-data/cfgsync/deployment-settings.yaml" \ + CHAIN_START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + +mkdir /node-data/cfgsync + +exec /usr/bin/logos-blockchain-cfgsync-server /etc/logos-blockchain/cfgsync.yaml