diff --git a/Cargo.lock b/Cargo.lock index 93645dce5..5285f84d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -489,7 +489,7 @@ dependencies = [ "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "regex", @@ -1681,7 +1681,7 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "guinea" -version = "0.4.2" +version = "0.5.0" dependencies = [ "bincode", "magicblock-magic-program-api", @@ -2605,7 +2605,7 @@ dependencies = [ [[package]] name = "magicblock-account-cloner" -version = "0.4.2" +version = "0.5.0" dependencies = [ "async-trait", "bincode", @@ -2637,7 +2637,7 @@ dependencies = [ [[package]] name = "magicblock-accounts" -version = "0.4.2" +version = "0.5.0" dependencies = [ "async-trait", "log", @@ -2659,7 +2659,7 @@ dependencies = [ [[package]] name = "magicblock-accounts-db" -version = "0.4.2" +version = "0.5.0" dependencies = [ "env_logger 0.11.8", "lmdb-rkv", @@ -2677,7 +2677,7 @@ dependencies = [ [[package]] name = "magicblock-aperture" -version = "0.4.2" +version = "0.5.0" dependencies = [ "arc-swap", "base64 0.21.7", @@ -2727,7 +2727,7 @@ dependencies = [ [[package]] name = "magicblock-api" -version = "0.4.2" +version = "0.5.0" dependencies = [ "anyhow", "borsh 1.5.7", @@ -2782,7 +2782,7 @@ dependencies = [ [[package]] name = "magicblock-chainlink" -version = "0.4.2" +version = "0.5.0" dependencies = [ "arc-swap", "assert_matches", @@ -2831,7 +2831,7 @@ dependencies = [ [[package]] name = "magicblock-committor-program" -version = "0.4.2" +version = "0.5.0" dependencies = [ "borsh 1.5.7", "paste", @@ -2843,7 +2843,7 @@ dependencies = [ [[package]] name = "magicblock-committor-service" -version = "0.4.2" +version = "0.5.0" dependencies = [ "async-trait", "base64 0.21.7", @@ -2889,7 +2889,7 @@ dependencies = [ [[package]] name = "magicblock-config" -version = "0.4.2" +version = "0.5.0" dependencies = [ "clap 4.5.40", "derive_more", @@ -2909,7 +2909,7 @@ dependencies = [ [[package]] name = "magicblock-core" -version = "0.4.2" +version = "0.5.0" dependencies = [ "flume", "magicblock-magic-program-api", @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "magicblock-ledger" -version = "0.4.2" +version = "0.5.0" dependencies = [ "arc-swap", "bincode", @@ -2988,7 +2988,7 @@ dependencies = [ [[package]] name = "magicblock-magic-program-api" -version = "0.4.2" +version = "0.5.0" dependencies = [ "bincode", "serde", @@ -2997,7 +2997,7 @@ dependencies = [ [[package]] name = "magicblock-metrics" -version = "0.4.2" +version = "0.5.0" dependencies = [ "http-body-util", "hyper 1.6.0", @@ -3011,7 +3011,7 @@ dependencies = [ [[package]] name = "magicblock-processor" -version = "0.4.2" +version = "0.5.0" dependencies = [ "bincode", "guinea", @@ -3050,7 +3050,7 @@ dependencies = [ [[package]] name = "magicblock-program" -version = "0.4.2" +version = "0.5.0" dependencies = [ "assert_matches", "bincode", @@ -3085,7 +3085,7 @@ dependencies = [ [[package]] name = "magicblock-rpc-client" -version = "0.4.2" +version = "0.5.0" dependencies = [ "log", "solana-account", @@ -3106,7 +3106,7 @@ dependencies = [ [[package]] name = "magicblock-table-mania" -version = "0.4.2" +version = "0.5.0" dependencies = [ "ed25519-dalek", "log", @@ -3132,7 +3132,7 @@ dependencies = [ [[package]] name = "magicblock-task-scheduler" -version = "0.4.2" +version = "0.5.0" dependencies = [ "bincode", "chrono", @@ -3163,7 +3163,7 @@ dependencies = [ [[package]] name = "magicblock-validator" -version = "0.4.2" +version = "0.5.0" dependencies = [ "console-subscriber", "env_logger 0.11.8", @@ -3178,7 +3178,7 @@ dependencies = [ [[package]] name = "magicblock-validator-admin" -version = "0.4.2" +version = "0.5.0" dependencies = [ "log", "magicblock-delegation-program", @@ -3195,7 +3195,7 @@ dependencies = [ [[package]] name = "magicblock-version" -version = "0.4.2" +version = "0.5.0" dependencies = [ "git-version", "rustc_version", @@ -6601,7 +6601,7 @@ dependencies = [ [[package]] name = "solana-storage-proto" -version = "0.4.2" +version = "0.5.0" dependencies = [ "bincode", "bs58", @@ -7623,7 +7623,7 @@ dependencies = [ [[package]] name = "test-kit" -version = "0.4.2" +version = "0.5.0" dependencies = [ "env_logger 0.11.8", "guinea", diff --git a/Cargo.toml b/Cargo.toml index 6c5a3839d..05fe94cc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ resolver = "2" [workspace.package] # Solana Version (2.2.x) -version = "0.4.2" +version = "0.5.0" authors = ["MagicBlock Maintainers "] repository = "https://github.com/magicblock-labs/ephemeral-validator" homepage = "https://www.magicblock.xyz" diff --git a/config.example.toml b/config.example.toml index 581c36f5e..aae1a3df6 100644 --- a/config.example.toml +++ b/config.example.toml @@ -31,47 +31,31 @@ # Env: MBV_LIFECYCLE lifecycle = "ephemeral" -# Remote connections for RPC, WebSocket, and gRPC. -# You can define multiple remotes of different kinds. -# The first RPC remote will be used for JSON-RPC calls. -# Each WebSocket/gRPC remote will create a pubsub client. +# Remote endpoints for syncing with the base chain. +# You can specify multiple remotes of different types. +# The first HTTP/HTTPS remote will be used for JSON-RPC calls. +# Each WebSocket/gRPC remote creates a subscription client. # -# Available kinds: -# - "rpc": JSON-RPC HTTP connection (typically port 8899) -# - "websocket": WebSocket connection for PubSub (typically wss://) -# - "grpc": gRPC connection for streaming (typically port 50051) +# Supported URL schemes: +# - "http", "https": JSON-RPC HTTP connections +# - "ws", "wss": WebSocket connections for PubSub +# - "grpc", "grpcs": gRPC connections for streaming # -# URL Aliases (automatically resolved based on kind): -# RPC aliases: "mainnet", "devnet", "local" -# WebSocket aliases: "mainnet", "devnet", "local" +# URL Aliases (resolved during parsing): +# - "mainnet": resolves to https://api.mainnet-beta.solana.com/ +# - "devnet": resolves to https://api.devnet.solana.com/ +# - "testnet": resolves to https://api.testnet.solana.com/ +# - "localhost": resolves to http://localhost:8899/ (only for http/https schemes) # -# Example 1: Using aliases -# [[remote]] -# kind = "rpc" -# url = "devnet" +# Examples: +# remotes = ["devnet"] # Single devnet HTTP endpoint +# remotes = ["mainnet", "wss://mainnet-beta.solana.com"] # Mainnet with explicit WebSocket +# remotes = ["http://localhost:8899", "ws://localhost:8900"] # Local endpoints # -# [[remote]] -# kind = "websocket" -# url = "devnet" -# -# Example 2: Using full URLs with optional API key -# [[remote]] -# kind = "rpc" -# url = "https://api.devnet.solana.com" -# api-key = "optional-key" -# -# [[remote]] -# kind = "websocket" -# url = "wss://api.devnet.solana.com" -# -# [[remote]] -# kind = "grpc" -# url = "http://grpc.example.com:50051" -# api-key = "optional-key" - -[[remote]] -kind = "rpc" -url = "devnet" +# If no remotes are specified, defaults to ["devnet"] with an auto-added WebSocket endpoint. +# Default: ["https://api.devnet.solana.com/"] +# Env: Not supported (must be configured via TOML or CLI) +remotes = ["devnet", "wss://devnet.solana.com", "grpcs://solana.helius.com"] # Root directory for application storage (ledger, accountsdb, snapshots). # Default: "magicblock-test-storage" (created in current working directory) diff --git a/magicblock-api/src/magic_validator.rs b/magicblock-api/src/magic_validator.rs index 9760030d5..d254e3910 100644 --- a/magicblock-api/src/magic_validator.rs +++ b/magicblock-api/src/magic_validator.rs @@ -37,8 +37,6 @@ use magicblock_config::{ config::{ ChainOperationConfig, LedgerConfig, LifecycleMode, LoadableProgram, }, - consts::DEFAULT_REMOTE, - types::{resolve_url, RemoteKind}, ValidatorParams, }; use magicblock_core::{ @@ -339,7 +337,7 @@ impl MagicValidator { config.validator.keypair.insecure_clone(), committor_persist_path, ChainConfig { - rpc_uri: config.rpc_url_or_default(), + rpc_uri: config.rpc_url().to_owned(), commitment: CommitmentConfig::confirmed(), compute_budget_config: ComputeBudgetConfig::new( config.commit.compute_unit_price, @@ -370,22 +368,14 @@ impl MagicValidator { faucet_pubkey: Pubkey, ) -> ApiResult { use magicblock_chainlink::remote_account_provider::Endpoint; - let rpc_url = config.rpc_url_or_default(); - let endpoints = if config.has_subscription_url() { - config - .websocket_urls() - .map(|pubsub_url| Endpoint { - rpc_url: rpc_url.clone(), - pubsub_url: pubsub_url.to_string(), - }) - .collect::>() - } else { - let ws_url = resolve_url(RemoteKind::Websocket, DEFAULT_REMOTE); - vec![Endpoint { + let rpc_url = config.rpc_url().to_owned(); + let endpoints = config + .websocket_urls() + .map(|pubsub_url| Endpoint { rpc_url: rpc_url.clone(), - pubsub_url: ws_url, - }] - }; + pubsub_url: pubsub_url.to_string(), + }) + .collect::>(); let cloner = ChainlinkCloner::new( committor_service, @@ -537,7 +527,7 @@ impl MagicValidator { }); DomainRegistryManager::handle_registration_static( - self.config.rpc_url_or_default(), + self.config.rpc_url(), &validator_keypair, validator_info, ) @@ -550,7 +540,7 @@ impl MagicValidator { let validator_keypair = validator_authority(); DomainRegistryManager::handle_unregistration_static( - self.config.rpc_url_or_default(), + self.config.rpc_url(), &validator_keypair, ) .map_err(|err| { @@ -563,7 +553,7 @@ impl MagicValidator { const MIN_BALANCE_SOL: u64 = 5; let lamports = RpcClient::new_with_commitment( - self.config.rpc_url_or_default(), + self.config.rpc_url().to_owned(), CommitmentConfig::confirmed(), ) .get_balance(&self.identity) @@ -610,7 +600,7 @@ impl MagicValidator { .map(|co| co.claim_fees_frequency) { self.claim_fees_task - .start(frequency, self.config.rpc_url_or_default()); + .start(frequency, self.config.rpc_url().to_owned()); } self.slot_ticker = Some(init_slot_ticker( diff --git a/magicblock-committor-program/src/state/chunks.rs b/magicblock-committor-program/src/state/chunks.rs index 173667789..0fa245d85 100644 --- a/magicblock-committor-program/src/state/chunks.rs +++ b/magicblock-committor-program/src/state/chunks.rs @@ -15,7 +15,9 @@ pub struct Chunks { bits: Vec, /// The tracking capacity which is /// ```rust - /// let capacity = bits.len() * BIT_FIELD_SIZE + /// # const BIT_FIELD_SIZE: usize = 8; + /// # let bits = vec![0u8; 1]; + /// let capacity = bits.len() * BIT_FIELD_SIZE; /// ``` /// The amount of tracked chunks could be a bit smaller as it might only use /// part of the last bit in [Chunks::bits]. diff --git a/magicblock-config/src/config/cli.rs b/magicblock-config/src/config/cli.rs index dd175129f..36774e71c 100644 --- a/magicblock-config/src/config/cli.rs +++ b/magicblock-config/src/config/cli.rs @@ -5,9 +5,7 @@ use serde::Serialize; use crate::{ config::LifecycleMode, - types::{ - remote::parse_remote_config, BindAddress, RemoteConfig, SerdeKeypair, - }, + types::{network::Remote, BindAddress, SerdeKeypair}, }; /// CLI Arguments mirroring the structure of ValidatorParams. @@ -18,13 +16,22 @@ pub struct CliParams { /// Path to the TOML configuration file. pub config: Option, - /// Remote Solana cluster connections. Can be specified multiple times. - /// Format: --remote : or --remote : - /// Examples: --remote rpc:devnet --remote websocket:devnet --remote grpc:http://localhost:50051 - /// Aliases: mainnet, devnet, local (resolved based on kind) - #[arg(long, short, value_parser = parse_remote_config)] - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub remote: Vec, + /// List of remote endpoints for syncing with the base chain. + /// Can be specified multiple times. + /// + /// SUPPORTED SCHEMES: http(s), ws(s), grpc(s) + /// + /// ALIASES: mainnet, devnet, testnet, localhost + /// + /// EXAMPLES: + /// - `--remote devnet` + /// - `--remote wss://devnet.solana.com` + /// - `--remote grpcs://grpc.example.com` + /// + /// DEFAULT: devnet (HTTP endpoint with auto-added WS endpoint) + #[arg(long)] + #[serde(skip_serializing_if = "Option::is_none")] + pub remotes: Option>, /// The application's operational mode. #[arg(long)] @@ -49,6 +56,10 @@ pub struct CliParams { /// Validator-specific arguments. #[command(flatten)] pub validator: CliValidatorConfig, + + /// Ledger-specific arguments. + #[command(flatten)] + pub ledger: CliLedgerConfig, } #[derive(Args, Serialize, Debug)] @@ -63,3 +74,15 @@ pub struct CliValidatorConfig { #[serde(skip_serializing_if = "Option::is_none")] pub keypair: Option, } + +#[derive(Args, Serialize, Debug, Default)] +pub struct CliLedgerConfig { + /// Reset the ledger on startup (wipe existing ledger database). + #[arg(long)] + #[serde(skip_serializing_if = "is_false")] + pub reset: bool, +} + +fn is_false(v: &bool) -> bool { + !*v +} diff --git a/magicblock-config/src/consts.rs b/magicblock-config/src/consts.rs index edf01fc64..4eb88d48b 100644 --- a/magicblock-config/src/consts.rs +++ b/magicblock-config/src/consts.rs @@ -1,40 +1,65 @@ -// CLI Default Values -pub const DEFAULT_REMOTE: &str = "devnet"; +/// CLI Default Values +/// Default remote endpoint: devnet HTTP URL +pub const DEFAULT_REMOTE: &str = DEVNET_URL; + +/// Default RPC address for the validator service pub const DEFAULT_RPC_ADDR: &str = "127.0.0.1:8899"; -// Struct Default Values +/// Struct Default Values +/// Default storage directory for ledger and accounts data pub const DEFAULT_STORAGE_DIRECTORY: &str = "magicblock-test-storage/"; -// WARNING: This keypair is for development/testing only. Production deployments -// MUST provide their own keypair via config file, env var or CLI argument +/// WARNING: This keypair is for development/testing only. +/// Production deployments MUST provide their own keypair via config file, env var, or CLI argument. pub const DEFAULT_VALIDATOR_KEYPAIR: &str = - "9Vo7TbA5YfC5a33JhAi9Fb41usA6JwecHNRw3f9MzzHAM8hFnXTzL5DcEHwsAFjuUZ8vNQcJ4XziRFpMc3gTgBQ"; + "9Vo7TbA5YfC5a33JhAi9Fb41usA6JwecHNRw3f9MzzHAM8hFnXTzL5DcEHwsAFjuUZ8vNQcJ4XziRFpMc3gTgBQ"; + +/// Default base fee in lamports for transactions pub const DEFAULT_BASE_FEE: u64 = 0; + +/// Default compute unit price in microlamports pub const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 1_000_000; -// Remote URL Aliases - RPC -pub const RPC_MAINNET: &str = "https://api.mainnet-beta.solana.com/"; -pub const RPC_DEVNET: &str = "https://api.devnet.solana.com/"; -pub const RPC_LOCAL: &str = "http://localhost:8899/"; +/// Remote URL Aliases - Mainnet, Testnet, Devnet, and Localhost +/// Solana mainnet-beta RPC endpoint +pub const MAINNET_URL: &str = "https://api.mainnet-beta.solana.com/"; + +/// Solana testnet RPC endpoint +pub const TESTNET_URL: &str = "https://api.testnet.solana.com/"; + +/// Solana devnet RPC endpoint (default for development) +pub const DEVNET_URL: &str = "https://api.devnet.solana.com/"; -// Remote URL Aliases - WebSocket -pub const WS_MAINNET: &str = "wss://api.mainnet-beta.solana.com/"; -pub const WS_DEVNET: &str = "wss://api.devnet.solana.com/"; -pub const WS_LOCAL: &str = "ws://localhost:8899/"; +/// Localhost RPC endpoint for local development +pub const LOCALHOST_URL: &str = "http://localhost:8899/"; -// Figment Configuration +/// Figment Configuration +/// Environment variable prefix for configuration (MBV_) pub const ENV_VAR_PREFIX: &str = "MBV_"; -// Accounts DB Defaults -pub const DEFAULT_ACCOUNTS_DB_SIZE: usize = 100 * 1024 * 1024; // 100 MB -pub const DEFAULT_ACCOUNTS_INDEX_SIZE: usize = 16 * 1024 * 1024; // 16 MB +/// Accounts DB Defaults +/// Default size of the accounts database (100 MB) +pub const DEFAULT_ACCOUNTS_DB_SIZE: usize = 100 * 1024 * 1024; + +/// Default size of the accounts index (16 MB) +pub const DEFAULT_ACCOUNTS_INDEX_SIZE: usize = 16 * 1024 * 1024; + +/// Maximum number of account snapshots to retain pub const DEFAULT_ACCOUNTS_MAX_SNAPSHOTS: u16 = 4; + +/// Frequency of account snapshots (every N slots) pub const DEFAULT_ACCOUNTS_SNAPSHOT_FREQUENCY: u64 = 1024; -// Ledger Defaults +/// Ledger Defaults +/// Default block time in milliseconds pub const DEFAULT_LEDGER_BLOCK_TIME_MS: u64 = 50; -pub const DEFAULT_LEDGER_SIZE: u64 = 100 * 1024 * 1024 * 1024; // 100 GB -// Metrics Defaults +/// Default ledger size (100 GB) +pub const DEFAULT_LEDGER_SIZE: u64 = 100 * 1024 * 1024 * 1024; + +/// Metrics Defaults +/// Default address for the metrics endpoint (Prometheus format) pub const DEFAULT_METRICS_ADDR: &str = "0.0.0.0:9000"; + +/// Default frequency of metrics collection in seconds pub const DEFAULT_METRICS_COLLECT_FREQUENCY_SEC: u64 = 30; diff --git a/magicblock-config/src/lib.rs b/magicblock-config/src/lib.rs index 3ff421ca0..5de7369b9 100644 --- a/magicblock-config/src/lib.rs +++ b/magicblock-config/src/lib.rs @@ -24,7 +24,7 @@ use crate::{ CommittorConfig, LedgerConfig, LoadableProgram, TaskSchedulerConfig, ValidatorConfig, }, - types::{resolve_url, BindAddress, RemoteConfig, RemoteKind}, + types::{network::Remote, BindAddress}, }; /// Top-level configuration, assembled from multiple sources. @@ -34,10 +34,9 @@ pub struct ValidatorParams { /// Path to the TOML configuration file (overrides CLI args). pub config: Option, - /// Array-based remote configurations for RPC, WebSocket, and gRPC. - /// Configured via [[remote]] sections in TOML (array-of-tables syntax). - #[serde(default, rename = "remote")] - pub remotes: Vec, + /// Remote endpoints for syncing with the base chain. + /// Can include HTTP (for JSON-RPC), WebSocket (for PubSub), and gRPC (for streaming) connections. + pub remotes: Vec, /// The application's operational mode. pub lifecycle: LifecycleMode, @@ -65,17 +64,25 @@ pub struct ValidatorParams { } impl ValidatorParams { - /// Assembles the final configuration. - /// Precedence: CLI (if set) > Environment > TOML File > Defaults + /// Assembles the final configuration from multiple sources. + /// + /// Configuration is merged in the following precedence order (highest to lowest): + /// 1. Command-line arguments + /// 2. Environment variables (with `MBV_` prefix) + /// 3. TOML configuration file + /// 4. Serde defaults for each field + /// + /// After merging, automatic guarantees are enforced: + /// - At least one HTTP endpoint is configured (for JSON-RPC calls) + /// - At least one WebSocket endpoint is configured (for subscriptions) pub fn try_new( args: impl Iterator, ) -> Result> { // 1. Parse CLI arguments into the "Overlay" struct let cli = CliParams::parse_from(args); - // 2. Start with system defaults - let mut figment = Figment::new() - .merge(Serialized::defaults(ValidatorParams::default())); + // 2. Start with system defaults (Figment will use serde defaults for each field) + let mut figment = Figment::new(); // 3. Merge TOML File if let Some(path) = &cli.config { @@ -93,45 +100,78 @@ impl ValidatorParams { // 5. Merge CLI "Overlay" (Highest Priority) figment = figment.merge(Serialized::from(&cli, Profile::Default)); - figment.extract().map_err(Box::new) + let mut params: Self = figment.extract().map_err(Box::new)?; + params.ensure_http(); + params.ensure_websocket(); + Ok(params) } - /// Returns the first RPC remote URL as an Option. - pub fn rpc_url(&self) -> Option<&str> { + /// Ensures at least one HTTP endpoint is configured. + /// If no HTTP remote is present, adds the default HTTP remote (devnet). + fn ensure_http(&mut self) { + let mut remotes = self.remotes.iter(); + if remotes.any(|r| matches!(r, Remote::Http(_))) { + return; + } self.remotes + .push(Remote::Http(consts::DEFAULT_REMOTE.parse().unwrap())); + } + + /// Ensures at least one WebSocket endpoint is configured. + /// If no WebSocket remote is present, derives one from the first HTTP remote. + /// This satisfies the requirement for a subscription-capable endpoint. + fn ensure_websocket(&mut self) { + // Check if a websocket remote already exists + if self + .remotes + .iter() + .any(|r| matches!(r, Remote::Websocket(_))) + { + return; + } + + // Find the first HTTP remote and convert it to WebSocket + if let Some(websocket) = self + .remotes .iter() - .find(|r| r.kind == RemoteKind::Rpc) - .map(|r| r.url.as_str()) + .find(|r| matches!(r, Remote::Http(_))) + .and_then(|r| r.to_websocket()) + { + self.remotes.push(websocket); + } else { + // Fallback: if no HTTP remote exists (unexpected, since ensure_http() was called first), + // create a default WebSocket remote from the default HTTP remote. + let default_http = + Remote::Http(consts::DEFAULT_REMOTE.parse().unwrap()); + if let Some(default_websocket) = default_http.to_websocket() { + self.remotes.push(default_websocket); + } + } + } + + /// Returns the first HTTP remote URL for JSON-RPC calls. + /// Falls back to the default remote if none is configured. + pub fn rpc_url(&self) -> &str { + self.remotes + .iter() + .find_map(|r| matches!(r, Remote::Http(_)).then(|| r.url_str())) + .unwrap_or(consts::DEFAULT_REMOTE) } /// Returns an iterator over all WebSocket remote URLs. pub fn websocket_urls(&self) -> impl Iterator + '_ { self.remotes .iter() - .filter(|r| r.kind == RemoteKind::Websocket) - .map(|r| r.url.as_str()) + .filter(|r| matches!(r, Remote::Websocket(_))) + .map(|r| r.url_str()) } - /// Returns an iterator over all gRPC remote URLs. + /// Returns an iterator over all gRPC remote URLs for streaming subscriptions. pub fn grpc_urls(&self) -> impl Iterator + '_ { self.remotes .iter() - .filter(|r| r.kind == RemoteKind::Grpc) - .map(|r| r.url.as_str()) - } - - pub fn has_subscription_url(&self) -> bool { - self.remotes.iter().any(|r| { - r.kind == RemoteKind::Websocket || r.kind == RemoteKind::Grpc - }) - } - - /// Returns the RPC URL, using DEFAULT_REMOTE as fallback if not - /// configured. - pub fn rpc_url_or_default(&self) -> String { - self.rpc_url().map(|s| s.to_string()).unwrap_or_else(|| { - resolve_url(RemoteKind::Rpc, consts::DEFAULT_REMOTE) - }) + .filter(|r| matches!(r, Remote::Grpc(_))) + .map(|r| r.url_str()) } } diff --git a/magicblock-config/src/tests.rs b/magicblock-config/src/tests.rs index e0cdee1ce..c55cc0622 100644 --- a/magicblock-config/src/tests.rs +++ b/magicblock-config/src/tests.rs @@ -8,7 +8,7 @@ use tempfile::TempDir; use crate::{ config::{BlockSize, LifecycleMode}, consts::{self, DEFAULT_VALIDATOR_KEYPAIR}, - types::{remote::resolve_url, RemoteConfig, RemoteKind}, + types::network::Remote, ValidatorParams, }; @@ -63,8 +63,8 @@ fn test_defaults_are_sane() { // Verify key defaults used in production assert_eq!(config.validator.basefee, consts::DEFAULT_BASE_FEE); - // Remotes default to empty when not specified - assert_eq!(config.remotes.len(), 0); + // Remotes default to [devnet HTTP] + [devnet WS] (added by ensure_websocket) + assert_eq!(config.remotes.len(), 2); assert_eq!(config.listen.0.port(), 8899); assert_eq!(config.lifecycle, LifecycleMode::Ephemeral); @@ -292,6 +292,36 @@ fn test_ledger_and_commit_settings() { assert_eq!(config.commit.compute_unit_price, 123456); } +#[test] +#[parallel] +fn test_cli_ledger_reset() { + // Verify CLI --reset flag sets reset to true + let config = run_cli(vec!["--reset"]); + + assert!(config.ledger.reset); + + // Verify ledger reset defaults to false when flag is not provided + let config = run_cli(vec![]); + + assert!(!config.ledger.reset); +} + +#[test] +#[parallel] +fn test_cli_ledger_reset_overrides_toml() { + // Verify CLI --ledger-reset flag overrides TOML setting + let (_dir, config_path) = create_temp_config( + r#" + [ledger] + reset = false + "#, + ); + + let config = run_cli(vec![config_path.to_str().unwrap(), "--reset"]); + + assert!(config.ledger.reset); +} + #[test] #[serial] fn test_task_scheduler_bool_env() { @@ -329,10 +359,9 @@ fn test_example_config_full_coverage() { // 3. Core & Network // ======================================================================== assert_eq!(config.lifecycle, LifecycleMode::Ephemeral); - // Example config has one RPC remote with "devnet" alias resolved - assert_eq!(config.remotes.len(), 1); - assert_eq!(config.remotes[0].url, consts::RPC_DEVNET); - assert_eq!(config.remotes[0].kind, RemoteKind::Rpc); + // Example config has 3 remotes: devnet HTTP, devnet WebSocket, and Helius gRPC + assert_eq!(config.remotes.len(), 3); + assert_eq!(config.remotes[0].url_str(), consts::DEVNET_URL); assert_eq!(config.listen.0.port(), 8899); // Check that storage path is set (contains the expected folder name) assert!(config @@ -463,8 +492,8 @@ fn test_env_vars_full_coverage() { // Core assert_eq!(config.lifecycle, LifecycleMode::Replica); - // Remotes must be configured via TOML, not env vars - assert_eq!(config.remotes.len(), 0); + // Remotes default to devnet (HTTP) + devnet WebSocket (added by ensure_websocket) + assert_eq!(config.remotes.len(), 2); assert_eq!(config.storage.to_string_lossy(), "/tmp/env-test-storage"); assert_eq!(config.listen.0.port(), 9999); @@ -512,237 +541,46 @@ fn test_env_vars_full_coverage() { } // ============================================================================ -// 9. New Remote Config Parsing +// 9. Remote Type Parsing // ============================================================================ #[test] #[parallel] -fn test_parse_single_rpc_remote() { - let (_dir, config_path) = create_temp_config( - r#" - [[remote]] - kind = "rpc" - url = "http://localhost:8899" - "#, - ); - - let config = run_cli(vec![config_path.to_str().unwrap()]); - - assert_eq!(config.remotes.len(), 1); - assert_eq!(config.remotes[0].kind, RemoteKind::Rpc); - assert_eq!(config.remotes[0].url, "http://localhost:8899"); - assert_eq!(config.remotes[0].api_key, None); +fn test_parse_http_remote() { + let remote: Remote = "http://localhost:8899".parse().unwrap(); + assert!(matches!(remote, Remote::Http(_))); + assert_eq!(remote.url_str(), "http://localhost:8899/"); } #[test] #[parallel] -fn test_parse_rpc_remote_with_api_key() { - let (_dir, config_path) = create_temp_config( - r#" - [[remote]] - kind = "rpc" - url = "https://api.example.com" - api-key = "secret-key-123" - "#, - ); - - let config = run_cli(vec![config_path.to_str().unwrap()]); - - assert_eq!(config.remotes.len(), 1); - assert_eq!(config.remotes[0].kind, RemoteKind::Rpc); - assert_eq!(config.remotes[0].url, "https://api.example.com"); - assert_eq!( - config.remotes[0].api_key, - Some("secret-key-123".to_string()) - ); +fn test_parse_websocket_remote() { + let remote: Remote = "ws://localhost:8900".parse().unwrap(); + assert!(matches!(remote, Remote::Websocket(_))); } #[test] #[parallel] -fn test_parse_multiple_remotes_mixed_kinds() { - let (_dir, config_path) = create_temp_config( - r#" - [[remote]] - kind = "rpc" - url = "http://localhost:8899" - - [[remote]] - kind = "websocket" - url = "wss://mainnet-beta.solana.com" - - [[remote]] - kind = "websocket" - url = "wss://backup-node.example.com" - - [[remote]] - kind = "grpc" - url = "http://grpc.example.com:50051" - api-key = "grpc-secret" - "#, - ); - - let config = run_cli(vec![config_path.to_str().unwrap()]); - - assert_eq!(config.remotes.len(), 4); - - // First: RPC - assert_eq!(config.remotes[0].kind, RemoteKind::Rpc); - assert_eq!(config.remotes[0].url, "http://localhost:8899"); - - // Second: WebSocket - assert_eq!(config.remotes[1].kind, RemoteKind::Websocket); - assert_eq!(config.remotes[1].url, "wss://mainnet-beta.solana.com"); - assert_eq!(config.remotes[1].api_key, None); - - // Third: WebSocket (multiple remotes of the same kind allowed) - assert_eq!(config.remotes[2].kind, RemoteKind::Websocket); - assert_eq!(config.remotes[2].url, "wss://backup-node.example.com"); - - // Fourth: gRPC - assert_eq!(config.remotes[3].kind, RemoteKind::Grpc); - assert_eq!(config.remotes[3].url, "http://grpc.example.com:50051"); - assert_eq!(config.remotes[3].api_key, Some("grpc-secret".to_string())); +fn test_parse_grpc_remote_converts_scheme() { + let remote: Remote = "grpc://localhost:50051/".parse().unwrap(); + assert!(matches!(remote, Remote::Grpc(_))); + // Scheme should be converted to http + assert_eq!(remote.url_str(), "http://localhost:50051/"); } #[test] #[parallel] -fn test_parse_remotes_empty_when_not_provided() { - let (_dir, config_path) = create_temp_config( - r#" - lifecycle = "ephemeral" - "#, - ); - - let config = run_cli(vec![config_path.to_str().unwrap()]); - - assert_eq!(config.remotes.len(), 0); -} - -#[test] -#[parallel] -fn test_deserialization_resolves_aliases() { - // Verify that aliases are resolved during deserialization, - // not just in resolved_url() method - let (_dir, config_path) = create_temp_config( - r#" - [[remote]] - kind = "rpc" - url = "devnet" - - [[remote]] - kind = "websocket" - url = "mainnet" - "#, - ); - - let config = run_cli(vec![config_path.to_str().unwrap()]); - - assert_eq!(config.remotes.len(), 2); - - // RPC remote should have devnet alias resolved to actual URL - assert_eq!(config.remotes[0].kind, RemoteKind::Rpc); - assert_eq!(config.remotes[0].url, consts::RPC_DEVNET); - - // WebSocket remote should have mainnet alias resolved to actual URL - assert_eq!(config.remotes[1].kind, RemoteKind::Websocket); - assert_eq!(config.remotes[1].url, consts::WS_MAINNET); -} - -#[test] -#[parallel] -fn test_remote_config_parse_url_method() { - let remote = RemoteConfig { - kind: RemoteKind::Rpc, - url: "https://api.example.com".to_string(), - api_key: None, - }; - - let parsed_url = remote.parse_url(); - assert!(parsed_url.is_ok()); - let url = parsed_url.unwrap(); - assert_eq!(url.scheme(), "https"); - assert_eq!(url.host_str(), Some("api.example.com")); -} - -#[test] -#[parallel] -fn test_remote_config_invalid_url() { - let remote = RemoteConfig { - kind: RemoteKind::Rpc, - url: "not a valid url".to_string(), - api_key: None, - }; - - let parsed_url = remote.parse_url(); - assert!(parsed_url.is_err()); -} - -#[test] -#[parallel] -fn test_rpc_alias_resolution() { - assert_eq!(resolve_url(RemoteKind::Rpc, "mainnet"), consts::RPC_MAINNET); - assert_eq!(resolve_url(RemoteKind::Rpc, "devnet"), consts::RPC_DEVNET); - assert_eq!(resolve_url(RemoteKind::Rpc, "local"), consts::RPC_LOCAL); -} - -#[test] -#[parallel] -fn test_websocket_alias_resolution() { - assert_eq!( - resolve_url(RemoteKind::Websocket, "mainnet"), - consts::WS_MAINNET - ); - assert_eq!( - resolve_url(RemoteKind::Websocket, "devnet"), - consts::WS_DEVNET - ); - assert_eq!( - resolve_url(RemoteKind::Websocket, "local"), - consts::WS_LOCAL - ); -} - -#[test] -#[parallel] -fn test_alias_resolution_same_alias_different_kinds() { - let rpc_resolved = resolve_url(RemoteKind::Rpc, "mainnet"); - let ws_resolved = resolve_url(RemoteKind::Websocket, "mainnet"); - - assert_eq!(rpc_resolved, consts::RPC_MAINNET); - assert_eq!(ws_resolved, consts::WS_MAINNET); - // They should be different - assert_ne!(rpc_resolved, ws_resolved); -} - -#[test] -#[parallel] -fn test_full_url_not_treated_as_alias() { - assert_eq!( - resolve_url(RemoteKind::Rpc, "https://custom-node.example.com"), - "https://custom-node.example.com" - ); - assert_eq!( - resolve_url(RemoteKind::Websocket, "wss://custom-node.example.com"), - "wss://custom-node.example.com" - ); +fn test_parse_alias() { + let remote: Remote = "devnet".parse().unwrap(); + assert!(matches!(remote, Remote::Http(_))); + assert_eq!(remote.url_str(), consts::DEVNET_URL); } #[test] #[parallel] -fn test_parse_url_with_alias() { - let resolved = resolve_url(RemoteKind::Rpc, "devnet"); - let remote = RemoteConfig { - kind: RemoteKind::Rpc, - url: resolved, - api_key: None, - }; - - let parsed = remote.parse_url(); - assert!(parsed.is_ok()); - let url = parsed.unwrap(); - - // Extract expected host from the canonical constant - let expected_url = url::Url::parse(consts::RPC_DEVNET) - .expect("Failed to parse RPC_DEVNET constant"); - assert_eq!(url.host_str(), expected_url.host_str()); +fn test_to_websocket_from_http() { + let remote: Remote = "http://localhost:8899".parse().unwrap(); + let ws_remote = remote.to_websocket().unwrap(); + assert!(matches!(ws_remote, Remote::Websocket(_))); + assert_eq!(ws_remote.url_str(), "ws://localhost:8900/"); } diff --git a/magicblock-config/src/types/mod.rs b/magicblock-config/src/types/mod.rs index 4e3255eb9..bef4cce81 100644 --- a/magicblock-config/src/types/mod.rs +++ b/magicblock-config/src/types/mod.rs @@ -1,16 +1,20 @@ use std::{fmt::Display, path::PathBuf}; pub mod crypto; -pub mod remote; +pub mod network; // Re-export types for easy access pub use crypto::{SerdeKeypair, SerdePubkey}; use derive_more::{Deref, FromStr}; -pub use remote::{resolve_url, BindAddress, RemoteConfig, RemoteKind}; +pub use network::BindAddress; use serde_with::{DeserializeFromStr, SerializeDisplay}; use crate::consts; +/// A parsed storage directory path for application data. +/// +/// Can be deserialized from a string path, which allows flexible configuration +/// via TOML files, environment variables, and CLI arguments. #[derive( Clone, Debug, DeserializeFromStr, SerializeDisplay, FromStr, Deref, )] @@ -23,6 +27,7 @@ impl Display for StorageDirectory { } impl Default for StorageDirectory { + /// Returns the default storage directory path. fn default() -> Self { Self(consts::DEFAULT_STORAGE_DIRECTORY.parse().unwrap()) } diff --git a/magicblock-config/src/types/network.rs b/magicblock-config/src/types/network.rs new file mode 100644 index 000000000..0194e5482 --- /dev/null +++ b/magicblock-config/src/types/network.rs @@ -0,0 +1,119 @@ +use std::{net::SocketAddr, str::FromStr}; + +use derive_more::{Deref, Display, FromStr}; +use serde::{Deserialize, Serialize}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use url::Url; + +use crate::consts; + +/// A network bind address that can be parsed from a string like "0.0.0.0:8080". +#[derive( + Clone, Copy, Debug, Deserialize, Serialize, FromStr, Display, Deref, +)] +#[serde(transparent)] +pub struct BindAddress(pub SocketAddr); + +impl Default for BindAddress { + fn default() -> Self { + consts::DEFAULT_RPC_ADDR.parse().unwrap() + } +} + +/// A remote endpoint for syncing with the base chain. +/// +/// Supported types: +/// - **Http**: JSON-RPC HTTP endpoint (scheme: `http` or `https`) +/// - **Websocket**: WebSocket endpoint for PubSub subscriptions (scheme: `ws` or `wss`) +/// - **Grpc**: gRPC endpoint for streaming (schemes `grpc`/`grpcs` are converted to `http`/`https`) +#[derive(Clone, DeserializeFromStr, SerializeDisplay, Display, Debug)] +pub enum Remote { + Http(AliasedUrl), + Websocket(AliasedUrl), + Grpc(AliasedUrl), +} + +impl FromStr for Remote { + type Err = url::ParseError; + + fn from_str(s: &str) -> Result { + // Handle non-standard schemes by detecting them before parsing + let mut s = s.to_owned(); + let is_grpc = s.starts_with("grpc"); + if is_grpc { + // SAFETY: + // We made sure that "grpc" is the prefix and we are not violating Unicode invariants + unsafe { s.as_bytes_mut()[0..4].copy_from_slice(b"http") }; + } + + let parsed = AliasedUrl::from_str(&s)?; + let remote = match parsed.0.scheme() { + _ if is_grpc => Self::Grpc(parsed), + "http" | "https" => Self::Http(parsed), + "ws" | "wss" => Self::Websocket(parsed), + _ => return Err(url::ParseError::InvalidDomainCharacter), + }; + Ok(remote) + } +} + +impl Remote { + /// Returns the URL as a string reference. + pub fn url_str(&self) -> &str { + match self { + Self::Http(u) => u.as_str(), + Self::Websocket(u) => u.as_str(), + Self::Grpc(u) => u.as_str(), + } + } + + /// Converts an HTTP remote to a WebSocket remote by deriving the appropriate WebSocket URL. + pub(crate) fn to_websocket(&self) -> Option { + let mut url = match self { + Self::Websocket(_) => return Some(self.clone()), + Self::Grpc(_) => return None, + Self::Http(u) => u.0.clone(), + }; + let _ = if url.scheme() == "http" { + url.set_scheme("ws") + } else { + url.set_scheme("wss") + }; + if let Some(port) = url.port() { + // As per solana convention websocket port is one greater than http + let _ = url.set_port(Some(port + 1)); + } + Some(Self::Websocket(AliasedUrl(url))) + } +} + +/// A URL that can be aliased with shortcuts like "mainnet". +/// +/// Aliases are resolved during parsing and replaced with their full URLs. +#[derive( + Clone, Debug, Deserialize, SerializeDisplay, Display, PartialEq, Deref, +)] +pub struct AliasedUrl(pub Url); + +impl AliasedUrl { + /// Returns the URL as a string reference. + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl FromStr for AliasedUrl { + type Err = url::ParseError; + + /// Parses a string into an AliasedUrl, resolving known aliases to their full URLs. + fn from_str(s: &str) -> Result { + let url_str = match s { + "mainnet" => consts::MAINNET_URL, + "devnet" => consts::DEVNET_URL, + "testnet" => consts::TESTNET_URL, + "localhost" | "dev" => consts::LOCALHOST_URL, + custom => custom, + }; + Url::parse(url_str).map(Self) + } +} diff --git a/magicblock-config/src/types/remote.rs b/magicblock-config/src/types/remote.rs deleted file mode 100644 index b4b43a71d..000000000 --- a/magicblock-config/src/types/remote.rs +++ /dev/null @@ -1,467 +0,0 @@ -use std::net::SocketAddr; - -use derive_more::{Deref, Display, FromStr}; -use serde::{ - de::{self, MapAccess, Visitor}, - Deserialize, Deserializer, Serialize, -}; -use url::Url; - -use crate::consts; - -/// A network bind address that can be parsed from a string like "0.0.0.0:8080". -#[derive( - Clone, Copy, Debug, Deserialize, Serialize, FromStr, Display, Deref, -)] -#[serde(transparent)] -pub struct BindAddress(pub SocketAddr); - -impl Default for BindAddress { - fn default() -> Self { - consts::DEFAULT_RPC_ADDR.parse().unwrap() - } -} - -/// The kind of remote connection. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum RemoteKind { - /// JSON-RPC HTTP connection. - Rpc, - /// WebSocket connection used for subscriptions. - Websocket, - /// gRPC connection used for subscriptions. - Grpc, -} - -/// Configuration for a single remote connection. -/// Aliases in the URL field are automatically resolved during deserialization. -#[derive(Debug, Clone, Serialize, PartialEq, Eq)] -pub struct RemoteConfig { - /// The kind of remote connection (rpc, websocket, grpc). - pub kind: RemoteKind, - - /// The resolved URL for this remote connection. - /// If an alias was used in the config, it is automatically expanded during deserialization. - pub url: String, - - /// Optional API key for authentication. - #[serde(skip_serializing_if = "Option::is_none")] - pub api_key: Option, -} - -impl<'de> Deserialize<'de> for RemoteConfig { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct RemoteConfigVisitor; - - impl<'de> Visitor<'de> for RemoteConfigVisitor { - type Value = RemoteConfig; - - fn expecting( - &self, - formatter: &mut std::fmt::Formatter, - ) -> std::fmt::Result { - formatter.write_str("a remote configuration object") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut kind: Option = None; - let mut url: Option = None; - let mut api_key: Option = None; - - while let Some(key) = map.next_key::()? { - match key.as_str() { - "kind" => kind = Some(map.next_value()?), - "url" => url = Some(map.next_value()?), - "api-key" => api_key = map.next_value()?, - _ => { - // Ignore unknown fields - let the value be consumed - let _ = map.next_value::()?; - } - } - } - - let kind = - kind.ok_or_else(|| de::Error::missing_field("kind"))?; - let url = url.ok_or_else(|| de::Error::missing_field("url"))?; - - // Resolve the URL alias based on the kind - let resolved_url = resolve_url(kind, &url); - - Ok(RemoteConfig { - kind, - url: resolved_url, - api_key, - }) - } - } - - deserializer.deserialize_map(RemoteConfigVisitor) - } -} - -impl RemoteConfig { - /// Parses the resolved URL and returns a valid `Url` object. - pub fn parse_url(&self) -> Result { - Url::parse(&self.url) - } -} - -/// Resolves aliases to a URL and passes through custom URLs unchanged. -pub fn resolve_url(kind: RemoteKind, url: &str) -> String { - match kind { - RemoteKind::Rpc => match url { - "mainnet" => consts::RPC_MAINNET.to_string(), - "devnet" => consts::RPC_DEVNET.to_string(), - "local" => consts::RPC_LOCAL.to_string(), - _ => url.to_string(), - }, - RemoteKind::Websocket => match url { - "mainnet" => consts::WS_MAINNET.to_string(), - "devnet" => consts::WS_DEVNET.to_string(), - "local" => consts::WS_LOCAL.to_string(), - _ => url.to_string(), - }, - RemoteKind::Grpc => url.to_string(), - } -} - -/// Parses CLI remote config argument in format: kind:url[?api-key=value] -/// Example: rpc:devnet, websocket:https://api.devnet.solana.com -/// Important: The URL can contain colons (https://), so we match 'kind:' -/// only if it's a valid kind at the start. -pub fn parse_remote_config(s: &str) -> Result { - // Find the kind by looking for a valid kind followed by a colon - let kinds = ["rpc", "websocket", "grpc"]; - let kind_and_rest = kinds - .iter() - .find_map(|k| { - let prefix = format!("{}:", k); - if s.starts_with(&prefix) { - Some((*k, &s[prefix.len()..])) - } else { - None - } - }) - .ok_or_else(|| { - "Remote format must start with 'kind:url' where kind is \ - one of: rpc, websocket, grpc. Example: 'rpc:devnet'" - .to_string() - })?; - - let kind = match kind_and_rest.0 { - "rpc" => RemoteKind::Rpc, - "websocket" => RemoteKind::Websocket, - "grpc" => RemoteKind::Grpc, - // SAFETY: we already excluded invalid kinds above - _ => unreachable!(), - }; - - let rest = kind_and_rest.1; - let (url, api_key) = if let Some((url, query)) = rest.split_once('?') { - // Parse query parameters to extract api-key regardless of order or other - // parameters. Split on '&' to get individual key=value pairs. - let api_key = query.split('&').find_map(|pair| { - pair.split_once('=').and_then(|(k, v)| { - if k == "api-key" { - Some(v.to_string()) - } else { - None - } - }) - }); - (url.to_string(), api_key) - } else { - (rest.to_string(), None) - }; - - // Validate that URL is not empty - if url.trim().is_empty() { - return Err( - "URL cannot be empty. Provide a valid URL or alias (mainnet, \ - devnet, local)" - .to_string(), - ); - } - - // Resolve the URL alias based on the kind - let resolved_url = resolve_url(kind, &url); - // Validate URL format for non-alias URLs - if !["mainnet", "devnet", "local"].contains(&url.as_str()) - && Url::parse(&resolved_url).is_err() - { - return Err(format!( - "Invalid URL format: '{}'. Expected a valid URL like 'http://localhost:8899'", - resolved_url - )); - } - - Ok(RemoteConfig { - kind, - url: resolved_url, - api_key, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_rpc_without_api_key() { - let result = parse_remote_config("rpc:https://api.devnet.solana.com"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Rpc); - assert_eq!(config.url, "https://api.devnet.solana.com"); - assert_eq!(config.api_key, None); - } - - #[test] - fn test_parse_rpc_with_api_key() { - let result = parse_remote_config( - "rpc:https://api.devnet.solana.com?api-key=secret123", - ); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Rpc); - assert_eq!(config.url, "https://api.devnet.solana.com"); - assert_eq!(config.api_key, Some("secret123".to_string())); - } - - #[test] - fn test_parse_websocket_without_api_key() { - let result = - parse_remote_config("websocket:wss://api.devnet.solana.com"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Websocket); - assert_eq!(config.url, "wss://api.devnet.solana.com"); - assert_eq!(config.api_key, None); - } - - #[test] - fn test_parse_websocket_with_api_key() { - let result = parse_remote_config( - "websocket:wss://api.devnet.solana.com?api-key=mykey", - ); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Websocket); - assert_eq!(config.url, "wss://api.devnet.solana.com"); - assert_eq!(config.api_key, Some("mykey".to_string())); - } - - #[test] - fn test_parse_grpc_without_api_key() { - let result = parse_remote_config("grpc:http://localhost:50051"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Grpc); - assert_eq!(config.url, "http://localhost:50051"); - assert_eq!(config.api_key, None); - } - - #[test] - fn test_parse_grpc_with_api_key() { - let result = - parse_remote_config("grpc:http://localhost:50051?api-key=xyz"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Grpc); - assert_eq!(config.url, "http://localhost:50051"); - assert_eq!(config.api_key, Some("xyz".to_string())); - } - - #[test] - fn test_parse_missing_kind() { - let result = parse_remote_config("https://api.devnet.solana.com"); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .contains("Remote format must start with")); - } - - #[test] - fn test_parse_invalid_kind() { - let result = parse_remote_config("http:https://api.devnet.solana.com"); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .contains("Remote format must start with")); - } - - #[test] - fn test_parse_empty_url() { - let result = parse_remote_config("rpc:"); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("URL cannot be empty")); - } - - #[test] - fn test_parse_api_key_with_special_chars() { - let result = parse_remote_config( - "rpc:http://localhost:8899?api-key=abc_123-xyz", - ); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.api_key, Some("abc_123-xyz".to_string())); - } - - #[test] - fn test_parse_url_with_port() { - let result = parse_remote_config("rpc:http://localhost:8899"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.url, "http://localhost:8899"); - } - - #[test] - fn test_parse_url_with_path() { - let result = parse_remote_config("rpc:http://localhost:8899/v1"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.url, "http://localhost:8899/v1"); - } - - // ================================================================ - // Aliases - // ================================================================ - - #[test] - fn test_parse_rpc_mainnet_alias() { - let result = parse_remote_config("rpc:mainnet"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Rpc); - assert_eq!(config.url, consts::RPC_MAINNET); - } - - #[test] - fn test_parse_rpc_devnet_alias() { - let result = parse_remote_config("rpc:devnet"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Rpc); - assert_eq!(config.url, consts::RPC_DEVNET); - } - - #[test] - fn test_parse_rpc_local_alias() { - let result = parse_remote_config("rpc:local"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Rpc); - assert_eq!(config.url, consts::RPC_LOCAL); - } - - #[test] - fn test_parse_websocket_mainnet_alias() { - let result = parse_remote_config("websocket:mainnet"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Websocket); - assert_eq!(config.url, consts::WS_MAINNET); - } - - #[test] - fn test_parse_websocket_devnet_alias() { - let result = parse_remote_config("websocket:devnet"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Websocket); - assert_eq!(config.url, consts::WS_DEVNET); - } - - #[test] - fn test_parse_websocket_local_alias() { - let result = parse_remote_config("websocket:local"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Websocket); - assert_eq!(config.url, consts::WS_LOCAL); - } - - #[test] - fn test_parse_rpc_alias_with_api_key() { - let result = parse_remote_config("rpc:mainnet?api-key=secret"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Rpc); - assert_eq!(config.url, consts::RPC_MAINNET); - assert_eq!(config.api_key, Some("secret".to_string())); - } - - #[test] - fn test_parse_websocket_alias_with_api_key() { - let result = parse_remote_config("websocket:devnet?api-key=mytoken"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.kind, RemoteKind::Websocket); - assert_eq!(config.url, consts::WS_DEVNET); - assert_eq!(config.api_key, Some("mytoken".to_string())); - } - - #[test] - fn test_parse_different_kinds_same_alias() { - // Same alias "mainnet" should resolve to different URLs based on kind - let rpc = parse_remote_config("rpc:mainnet").unwrap(); - let ws = parse_remote_config("websocket:mainnet").unwrap(); - - assert_eq!(rpc.kind, RemoteKind::Rpc); - assert_eq!(ws.kind, RemoteKind::Websocket); - assert_eq!(rpc.url, consts::RPC_MAINNET); - assert_eq!(ws.url, consts::WS_MAINNET); - assert_ne!(rpc.url, ws.url); - } - - #[test] - fn test_parse_api_key_with_other_parameters() { - // Test api-key is correctly extracted when other query parameters - // are present - let result = parse_remote_config( - "rpc:http://localhost:8899?timeout=30&api-key=secret123&retry=3", - ); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.api_key, Some("secret123".to_string())); - } - - #[test] - fn test_parse_api_key_first_parameter() { - // Test api-key is correctly extracted when it's the first parameter - let result = parse_remote_config( - "rpc:http://localhost:8899?api-key=mykey&timeout=30", - ); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.api_key, Some("mykey".to_string())); - } - - #[test] - fn test_parse_api_key_last_parameter() { - // Test api-key is correctly extracted when it's the last parameter - let result = parse_remote_config( - "rpc:http://localhost:8899?timeout=30&api-key=lastkey", - ); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.api_key, Some("lastkey".to_string())); - } - - #[test] - fn test_parse_other_parameters_without_api_key() { - // Test that other query parameters don't break when api-key is absent - let result = - parse_remote_config("rpc:http://localhost:8899?timeout=30&retry=3"); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.api_key, None); - } -} diff --git a/magicblock-ledger/src/database/columns.rs b/magicblock-ledger/src/database/columns.rs index 4b3799d36..1732b3816 100644 --- a/magicblock-ledger/src/database/columns.rs +++ b/magicblock-ledger/src/database/columns.rs @@ -37,7 +37,7 @@ pub struct TransactionStatus; /// The address signatures column /// /// * index type: `(`[`Pubkey`]`, `[`Slot`]`, u32, `[`Signature`]`)` -/// * account addr, slot, tx index, tx signature +/// - account addr, slot, tx index, tx signature /// * value type: [`blockstore_meta::AddressSignatureMeta`] pub struct AddressSignatures; @@ -52,7 +52,7 @@ pub struct AddressSignatures; /// the transaction of that signature did not include the address. /// /// * index type: `(`[`Slot`]`, u32)` -/// * slot, tx index +/// - slot, tx index /// * value type: [`[`solana_signature::signature::Signature`]`] pub struct SlotSignatures; diff --git a/magicblock-ledger/src/store/api.rs b/magicblock-ledger/src/store/api.rs index 786d4fcd5..281bb54b9 100644 --- a/magicblock-ledger/src/store/api.rs +++ b/magicblock-ledger/src/store/api.rs @@ -437,12 +437,12 @@ impl Ledger { /// /// Specifying the following: /// - /// ```rust - /// let pubkey = ""; - /// let highest_slot = 0; - /// let upper_limit_signature = Some(sig_upper);; - /// let lower_limit_signature = Some(sig_lower); - /// let limit = 100; + /// ```text + /// pubkey: "" + /// highest_slot: 0 + /// upper_limit_signature: Some() + /// lower_limit_signature: Some() + /// limit: 100 /// ``` /// /// will find up to 100 signatures that are between upper and lower limit signatures diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index f2720c8d7..f2f0eea2a 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -558,7 +558,7 @@ dependencies = [ "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "regex", @@ -2049,10 +2049,10 @@ dependencies = [ [[package]] name = "guinea" -version = "0.4.1" +version = "0.5.0" dependencies = [ "bincode", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "serde", "solana-program", ] @@ -3009,7 +3009,7 @@ dependencies = [ [[package]] name = "magicblock-account-cloner" -version = "0.4.1" +version = "0.5.0" dependencies = [ "async-trait", "bincode", @@ -3020,10 +3020,10 @@ dependencies = [ "magicblock-config", "magicblock-core", "magicblock-ledger", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "magicblock-program", "magicblock-rpc-client", - "rand 0.8.5", + "rand 0.9.1", "solana-account", "solana-hash", "solana-instruction", @@ -3041,7 +3041,7 @@ dependencies = [ [[package]] name = "magicblock-accounts" -version = "0.4.1" +version = "0.5.0" dependencies = [ "async-trait", "log", @@ -3063,7 +3063,7 @@ dependencies = [ [[package]] name = "magicblock-accounts-db" -version = "0.4.1" +version = "0.5.0" dependencies = [ "lmdb-rkv", "log", @@ -3079,7 +3079,7 @@ dependencies = [ [[package]] name = "magicblock-aperture" -version = "0.4.1" +version = "0.5.0" dependencies = [ "arc-swap", "base64 0.21.7", @@ -3124,7 +3124,7 @@ dependencies = [ [[package]] name = "magicblock-api" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "borsh 1.5.7", @@ -3140,7 +3140,7 @@ dependencies = [ "magicblock-config", "magicblock-core", "magicblock-ledger", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "magicblock-metrics", "magicblock-processor", "magicblock-program", @@ -3179,7 +3179,7 @@ dependencies = [ [[package]] name = "magicblock-chainlink" -version = "0.4.1" +version = "0.5.0" dependencies = [ "arc-swap", "async-trait", @@ -3191,7 +3191,7 @@ dependencies = [ "magicblock-config", "magicblock-core", "magicblock-delegation-program", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "magicblock-metrics", "solana-account", "solana-account-decoder", @@ -3226,7 +3226,7 @@ dependencies = [ [[package]] name = "magicblock-committor-program" -version = "0.4.1" +version = "0.5.0" dependencies = [ "borsh 1.5.7", "paste", @@ -3238,7 +3238,7 @@ dependencies = [ [[package]] name = "magicblock-committor-service" -version = "0.4.1" +version = "0.5.0" dependencies = [ "async-trait", "base64 0.21.7", @@ -3281,7 +3281,7 @@ dependencies = [ [[package]] name = "magicblock-config" -version = "0.4.1" +version = "0.5.0" dependencies = [ "clap", "derive_more", @@ -3299,10 +3299,10 @@ dependencies = [ [[package]] name = "magicblock-core" -version = "0.4.1" +version = "0.5.0" dependencies = [ "flume", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "solana-account", "solana-account-decoder", "solana-hash", @@ -3337,7 +3337,7 @@ dependencies = [ [[package]] name = "magicblock-ledger" -version = "0.4.1" +version = "0.5.0" dependencies = [ "arc-swap", "bincode", @@ -3387,7 +3387,7 @@ dependencies = [ [[package]] name = "magicblock-magic-program-api" -version = "0.4.1" +version = "0.5.0" dependencies = [ "bincode", "serde", @@ -3396,7 +3396,7 @@ dependencies = [ [[package]] name = "magicblock-metrics" -version = "0.4.1" +version = "0.5.0" dependencies = [ "http-body-util", "hyper 1.6.0", @@ -3410,7 +3410,7 @@ dependencies = [ [[package]] name = "magicblock-processor" -version = "0.4.1" +version = "0.5.0" dependencies = [ "bincode", "log", @@ -3420,6 +3420,7 @@ dependencies = [ "magicblock-metrics", "magicblock-program", "parking_lot", + "rustc-hash 2.1.1", "solana-account", "solana-bpf-loader-program", "solana-compute-budget-program", @@ -3443,12 +3444,12 @@ dependencies = [ [[package]] name = "magicblock-program" -version = "0.4.1" +version = "0.5.0" dependencies = [ "bincode", "lazy_static", "magicblock-core", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "num-derive", "num-traits", "parking_lot", @@ -3475,7 +3476,7 @@ dependencies = [ [[package]] name = "magicblock-rpc-client" -version = "0.4.1" +version = "0.5.0" dependencies = [ "log", "solana-account", @@ -3496,13 +3497,13 @@ dependencies = [ [[package]] name = "magicblock-table-mania" -version = "0.4.1" +version = "0.5.0" dependencies = [ "ed25519-dalek", "log", "magicblock-metrics", "magicblock-rpc-client", - "rand 0.8.5", + "rand 0.9.1", "sha3", "solana-address-lookup-table-interface", "solana-clock", @@ -3522,7 +3523,7 @@ dependencies = [ [[package]] name = "magicblock-task-scheduler" -version = "0.4.1" +version = "0.5.0" dependencies = [ "bincode", "chrono", @@ -3549,7 +3550,7 @@ dependencies = [ [[package]] name = "magicblock-validator-admin" -version = "0.4.1" +version = "0.5.0" dependencies = [ "log", "magicblock-delegation-program", @@ -3566,7 +3567,7 @@ dependencies = [ [[package]] name = "magicblock-version" -version = "0.4.1" +version = "0.5.0" dependencies = [ "git-version", "rustc_version", @@ -4402,7 +4403,7 @@ dependencies = [ "bincode", "borsh 1.5.7", "ephemeral-rollups-sdk", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "serde", "solana-program", ] @@ -4426,7 +4427,7 @@ dependencies = [ "borsh 1.5.7", "ephemeral-rollups-sdk", "magicblock-delegation-program", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "solana-program", ] @@ -5276,7 +5277,7 @@ dependencies = [ "integration-test-tools", "log", "magicblock-core", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "program-schedulecommit", "schedulecommit-client", "solana-program", @@ -5292,7 +5293,7 @@ version = "0.0.0" dependencies = [ "integration-test-tools", "magicblock-core", - "magicblock-magic-program-api 0.4.1", + "magicblock-magic-program-api 0.5.0", "program-schedulecommit", "program-schedulecommit-security", "schedulecommit-client", @@ -5657,7 +5658,7 @@ dependencies = [ [[package]] name = "solana-account" version = "2.2.1" -source = "git+https://github.com/magicblock-labs/solana-account.git?rev=1beed4c#1beed4c0bc368bc0bed819fbdf551d1a4bf00001" +source = "git+https://github.com/magicblock-labs/solana-account.git?rev=57158728#571587284a525b66666291aee5174783139a1595" dependencies = [ "bincode", "qualifier_attr", @@ -7972,7 +7973,7 @@ dependencies = [ [[package]] name = "solana-storage-proto" -version = "0.4.1" +version = "0.5.0" dependencies = [ "bincode", "bs58", @@ -9439,7 +9440,7 @@ dependencies = [ [[package]] name = "test-kit" -version = "0.4.1" +version = "0.5.0" dependencies = [ "env_logger 0.11.8", "guinea", diff --git a/test-integration/configs/api-conf.ephem.toml b/test-integration/configs/api-conf.ephem.toml index 00480a5f0..6fe93ed65 100644 --- a/test-integration/configs/api-conf.ephem.toml +++ b/test-integration/configs/api-conf.ephem.toml @@ -2,13 +2,7 @@ lifecycle = "ephemeral" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:8899" -[[remote]] -kind = "rpc" -url = "http://127.0.0.1:7799" - -[[remote]] -kind = "websocket" -url = "ws://127.0.0.1:7800" +remotes = ["http://127.0.0.1:7799", "ws://127.0.0.1:7800"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/chainlink-conf.devnet.toml b/test-integration/configs/chainlink-conf.devnet.toml index ebac4d7af..eed2730f5 100644 --- a/test-integration/configs/chainlink-conf.devnet.toml +++ b/test-integration/configs/chainlink-conf.devnet.toml @@ -2,13 +2,7 @@ lifecycle = "offline" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:7799" -[[remote]] -kind = "rpc" -url = "devnet" - -[[remote]] -kind = "websocket" -url = "devnet" +remotes = ["devnet"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/claim-fees-test.toml b/test-integration/configs/claim-fees-test.toml index a6d2756c4..12e0445aa 100644 --- a/test-integration/configs/claim-fees-test.toml +++ b/test-integration/configs/claim-fees-test.toml @@ -2,13 +2,7 @@ lifecycle = "offline" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:7799" -[[remote]] -kind = "rpc" -url = "devnet" - -[[remote]] -kind = "websocket" -url = "devnet" +remotes = ["devnet"] [accountsdb] database-size = 1048576000 diff --git a/test-integration/configs/cloning-conf.devnet.toml b/test-integration/configs/cloning-conf.devnet.toml index 5f7203788..a3e86ba80 100644 --- a/test-integration/configs/cloning-conf.devnet.toml +++ b/test-integration/configs/cloning-conf.devnet.toml @@ -2,13 +2,7 @@ lifecycle = "offline" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:7799" -[[remote]] -kind = "rpc" -url = "devnet" - -[[remote]] -kind = "websocket" -url = "devnet" +remotes = ["devnet"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/cloning-conf.ephem.toml b/test-integration/configs/cloning-conf.ephem.toml index ca3ca0842..e4537421a 100644 --- a/test-integration/configs/cloning-conf.ephem.toml +++ b/test-integration/configs/cloning-conf.ephem.toml @@ -2,13 +2,7 @@ lifecycle = "ephemeral" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:8899" -[[remote]] -kind = "rpc" -url = "http://0.0.0.0:7799" - -[[remote]] -kind = "websocket" -url = "ws://0.0.0.0:7800" +remotes = ["http://0.0.0.0:7799", "ws://0.0.0.0:7800"] [chainlink] max-monitored-accounts = 3 diff --git a/test-integration/configs/committor-conf.devnet.toml b/test-integration/configs/committor-conf.devnet.toml index 638f29fe9..e58bec684 100644 --- a/test-integration/configs/committor-conf.devnet.toml +++ b/test-integration/configs/committor-conf.devnet.toml @@ -2,13 +2,7 @@ lifecycle = "offline" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:7799" -[[remote]] -kind = "rpc" -url = "devnet" - -[[remote]] -kind = "websocket" -url = "devnet" +remotes = ["devnet"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/config-conf.devnet.toml b/test-integration/configs/config-conf.devnet.toml index 36157ac24..6a18de264 100644 --- a/test-integration/configs/config-conf.devnet.toml +++ b/test-integration/configs/config-conf.devnet.toml @@ -2,13 +2,7 @@ lifecycle = "offline" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:7799" -[[remote]] -kind = "rpc" -url = "devnet" - -[[remote]] -kind = "websocket" -url = "devnet" +remotes = ["devnet"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/restore-ledger-conf.devnet.toml b/test-integration/configs/restore-ledger-conf.devnet.toml index 716cc64a0..66ab8c30d 100644 --- a/test-integration/configs/restore-ledger-conf.devnet.toml +++ b/test-integration/configs/restore-ledger-conf.devnet.toml @@ -2,13 +2,7 @@ lifecycle = "offline" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:7799" -[[remote]] -kind = "rpc" -url = "devnet" - -[[remote]] -kind = "websocket" -url = "devnet" +remotes = ["devnet"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/schedule-task.devnet.toml b/test-integration/configs/schedule-task.devnet.toml index 2c7461b6d..167bbdba6 100644 --- a/test-integration/configs/schedule-task.devnet.toml +++ b/test-integration/configs/schedule-task.devnet.toml @@ -2,13 +2,7 @@ lifecycle = "offline" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:7799" -[[remote]] -kind = "rpc" -url = "devnet" - -[[remote]] -kind = "websocket" -url = "devnet" +remotes = ["devnet"] [ledger] block-time = "50ms" diff --git a/test-integration/configs/schedule-task.ephem.toml b/test-integration/configs/schedule-task.ephem.toml index 85d819d24..e6d7dd573 100644 --- a/test-integration/configs/schedule-task.ephem.toml +++ b/test-integration/configs/schedule-task.ephem.toml @@ -2,13 +2,7 @@ lifecycle = "ephemeral" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:8899" -[[remote]] -kind = "rpc" -url = "http://0.0.0.0:7799" - -[[remote]] -kind = "websocket" -url = "ws://0.0.0.0:7800" +remotes = ["http://0.0.0.0:7799", "ws://0.0.0.0:7800"] [ledger] reset = true diff --git a/test-integration/configs/schedulecommit-conf-fees.ephem.toml b/test-integration/configs/schedulecommit-conf-fees.ephem.toml index 4052b1611..f9920a2e9 100644 --- a/test-integration/configs/schedulecommit-conf-fees.ephem.toml +++ b/test-integration/configs/schedulecommit-conf-fees.ephem.toml @@ -2,13 +2,7 @@ lifecycle = "ephemeral" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:8899" -[[remote]] -kind = "rpc" -url = "http://0.0.0.0:7799" - -[[remote]] -kind = "websocket" -url = "ws://0.0.0.0:7800" +remotes = ["http://0.0.0.0:7799", "ws://0.0.0.0:7800"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/schedulecommit-conf.devnet.toml b/test-integration/configs/schedulecommit-conf.devnet.toml index 114b12802..a0fd36d6f 100644 --- a/test-integration/configs/schedulecommit-conf.devnet.toml +++ b/test-integration/configs/schedulecommit-conf.devnet.toml @@ -2,13 +2,7 @@ lifecycle = "offline" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:7799" -[[remote]] -kind = "rpc" -url = "devnet" - -[[remote]] -kind = "websocket" -url = "devnet" +remotes = ["devnet"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/schedulecommit-conf.ephem.frequent-commits.toml b/test-integration/configs/schedulecommit-conf.ephem.frequent-commits.toml index c5fce6e88..dd97438d5 100644 --- a/test-integration/configs/schedulecommit-conf.ephem.frequent-commits.toml +++ b/test-integration/configs/schedulecommit-conf.ephem.frequent-commits.toml @@ -2,13 +2,7 @@ lifecycle = "ephemeral" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:8899" -[[remote]] -kind = "rpc" -url = "http://0.0.0.0:7799" - -[[remote]] -kind = "websocket" -url = "ws://0.0.0.0:7800" +remotes = ["http://0.0.0.0:7799", "ws://0.0.0.0:7800"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/schedulecommit-conf.ephem.toml b/test-integration/configs/schedulecommit-conf.ephem.toml index f076492c7..afac5647a 100644 --- a/test-integration/configs/schedulecommit-conf.ephem.toml +++ b/test-integration/configs/schedulecommit-conf.ephem.toml @@ -2,13 +2,7 @@ lifecycle = "ephemeral" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:8899" -[[remote]] -kind = "rpc" -url = "http://0.0.0.0:7799" - -[[remote]] -kind = "websocket" -url = "ws://0.0.0.0:7800" +remotes = ["http://0.0.0.0:7799", "ws://0.0.0.0:7800"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/configs/validator-offline.devnet.toml b/test-integration/configs/validator-offline.devnet.toml index cf66953a3..4e85c45cc 100644 --- a/test-integration/configs/validator-offline.devnet.toml +++ b/test-integration/configs/validator-offline.devnet.toml @@ -2,13 +2,7 @@ lifecycle = "offline" commit = { compute-unit-price = 1_000_000 } listen = "0.0.0.0:7799" -[[remote]] -kind = "rpc" -url = "devnet" - -[[remote]] -kind = "websocket" -url = "devnet" +remotes = ["devnet"] [accountsdb] # size of the main storage, we have to preallocate in advance diff --git a/test-integration/test-config/src/lib.rs b/test-integration/test-config/src/lib.rs index 0ea602f97..8a1940b02 100644 --- a/test-integration/test-config/src/lib.rs +++ b/test-integration/test-config/src/lib.rs @@ -1,4 +1,4 @@ -use std::process::Child; +use std::{process::Child, str::FromStr}; use integration_test_tools::{ dlp_interface, expect, @@ -14,7 +14,7 @@ use magicblock_config::{ accounts::AccountsDbConfig, chain::ChainLinkConfig, ledger::LedgerConfig, LifecycleMode, LoadableProgram, }, - types::{crypto::SerdePubkey, RemoteConfig, RemoteKind}, + types::{crypto::SerdePubkey, network::Remote}, ValidatorParams, }; use program_flexi_counter::instruction::{ @@ -45,16 +45,8 @@ pub fn start_validator_with_clone_config( programs, lifecycle: LifecycleMode::Ephemeral, remotes: vec![ - RemoteConfig { - kind: RemoteKind::Rpc, - url: IntegrationTestContext::url_chain().to_string(), - api_key: None, - }, - RemoteConfig { - kind: RemoteKind::Websocket, - url: IntegrationTestContext::ws_url_chain().to_string(), - api_key: None, - }, + Remote::from_str(IntegrationTestContext::url_chain()).unwrap(), + Remote::from_str(IntegrationTestContext::ws_url_chain()).unwrap(), ], chainlink: ChainLinkConfig { prepare_lookup_tables, diff --git a/test-integration/test-config/tests/auto_airdrop_feepayer.rs b/test-integration/test-config/tests/auto_airdrop_feepayer.rs index 805fb9bd1..5989e580f 100644 --- a/test-integration/test-config/tests/auto_airdrop_feepayer.rs +++ b/test-integration/test-config/tests/auto_airdrop_feepayer.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use integration_test_tools::{ expect, loaded_accounts::LoadedAccounts, validator::start_magicblock_validator_with_config_struct, @@ -8,7 +10,7 @@ use magicblock_config::{ accounts::AccountsDbConfig, chain::ChainLinkConfig, ledger::LedgerConfig, LifecycleMode, }, - types::{RemoteConfig, RemoteKind}, + types::network::Remote, ValidatorParams, }; use solana_sdk::{signature::Keypair, signer::Signer, system_instruction}; @@ -22,16 +24,8 @@ fn test_auto_airdrop_feepayer_balance_after_tx() { let config = ValidatorParams { lifecycle: LifecycleMode::Ephemeral, remotes: vec![ - RemoteConfig { - kind: RemoteKind::Rpc, - url: IntegrationTestContext::url_chain().to_string(), - api_key: None, - }, - RemoteConfig { - kind: RemoteKind::Websocket, - url: IntegrationTestContext::ws_url_chain().to_string(), - api_key: None, - }, + Remote::from_str(IntegrationTestContext::url_chain()).unwrap(), + Remote::from_str(IntegrationTestContext::ws_url_chain()).unwrap(), ], accountsdb: AccountsDbConfig::default(), chainlink: ChainLinkConfig { diff --git a/test-integration/test-ledger-restore/src/lib.rs b/test-integration/test-ledger-restore/src/lib.rs index 26fc7f54c..58db033b5 100644 --- a/test-integration/test-ledger-restore/src/lib.rs +++ b/test-integration/test-ledger-restore/src/lib.rs @@ -1,4 +1,6 @@ -use std::{path::Path, process::Child, thread::sleep, time::Duration}; +use std::{ + path::Path, process::Child, str::FromStr, thread::sleep, time::Duration, +}; use cleanass::{assert, assert_eq}; use integration_test_tools::{ @@ -18,7 +20,7 @@ use magicblock_config::{ LifecycleMode, LoadableProgram, }, consts::DEFAULT_LEDGER_BLOCK_TIME_MS, - types::{crypto::SerdePubkey, RemoteConfig, RemoteKind, StorageDirectory}, + types::{crypto::SerdePubkey, network::Remote, StorageDirectory}, ValidatorParams, }; use program_flexi_counter::{ @@ -146,16 +148,8 @@ pub fn setup_validator_with_local_remote_and_resume_strategy( task_scheduler: TaskSchedulerConfig { reset: true }, lifecycle: LifecycleMode::Ephemeral, remotes: vec![ - RemoteConfig { - kind: RemoteKind::Rpc, - url: IntegrationTestContext::url_chain().to_string(), - api_key: None, - }, - RemoteConfig { - kind: RemoteKind::Websocket, - url: IntegrationTestContext::ws_url_chain().to_string(), - api_key: None, - }, + Remote::from_str(IntegrationTestContext::url_chain()).unwrap(), + Remote::from_str(IntegrationTestContext::ws_url_chain()).unwrap(), ], storage: StorageDirectory(ledger_path.to_path_buf()), ..Default::default() diff --git a/test-integration/test-magicblock-api/src/lib.rs b/test-integration/test-magicblock-api/src/lib.rs index 623abbea0..723744521 100644 --- a/test-integration/test-magicblock-api/src/lib.rs +++ b/test-integration/test-magicblock-api/src/lib.rs @@ -52,7 +52,6 @@ pub fn start_magicblock_validator_with_config( &test_paths, "EPHEM", loaded_accounts, - true, ) { Some(validator) => validator, None => { diff --git a/test-integration/test-runner/bin/run_tests.rs b/test-integration/test-runner/bin/run_tests.rs index a810ab832..6a5a6e9c7 100644 --- a/test-integration/test-runner/bin/run_tests.rs +++ b/test-integration/test-runner/bin/run_tests.rs @@ -851,7 +851,6 @@ fn start_validator( &test_runner_paths, log_suffix, loaded_chain_accounts, - false, ), } } diff --git a/test-integration/test-task-scheduler/src/lib.rs b/test-integration/test-task-scheduler/src/lib.rs index 88155378e..d819f7ed6 100644 --- a/test-integration/test-task-scheduler/src/lib.rs +++ b/test-integration/test-task-scheduler/src/lib.rs @@ -1,4 +1,4 @@ -use std::{process::Child, time::Duration}; +use std::{process::Child, str::FromStr, time::Duration}; use integration_test_tools::{ expect, @@ -16,7 +16,7 @@ use magicblock_config::{ scheduler::TaskSchedulerConfig, validator::ValidatorConfig, LifecycleMode, }, - types::{RemoteConfig, RemoteKind, StorageDirectory}, + types::{network::Remote, StorageDirectory}, ValidatorParams, }; use program_flexi_counter::instruction::{ @@ -35,16 +35,8 @@ pub fn setup_validator() -> (TempDir, Child, IntegrationTestContext) { let config = ValidatorParams { lifecycle: LifecycleMode::Ephemeral, remotes: vec![ - RemoteConfig { - kind: RemoteKind::Rpc, - url: IntegrationTestContext::url_chain().to_string(), - api_key: None, - }, - RemoteConfig { - kind: RemoteKind::Websocket, - url: IntegrationTestContext::ws_url_chain().to_string(), - api_key: None, - }, + Remote::from_str(IntegrationTestContext::url_chain()).unwrap(), + Remote::from_str(IntegrationTestContext::ws_url_chain()).unwrap(), ], accountsdb: AccountsDbConfig::default(), task_scheduler: TaskSchedulerConfig { reset: true }, diff --git a/test-integration/test-tools/src/toml_to_args.rs b/test-integration/test-tools/src/toml_to_args.rs index 698b00c12..08b5af4f7 100644 --- a/test-integration/test-tools/src/toml_to_args.rs +++ b/test-integration/test-tools/src/toml_to_args.rs @@ -1,9 +1,10 @@ use std::{ fs, path::{Path, PathBuf}, + str::FromStr, }; -use magicblock_config::types::{resolve_url, RemoteKind}; +use magicblock_config::types::network::Remote; use serde::Deserialize; #[derive(Deserialize)] @@ -22,18 +23,24 @@ struct RemoteConfig { } impl RemoteConfig { - /// Returns the URL for this remote, resolving aliases based on kind. + /// Returns the URL for this remote, parsing the kind and url into a Remote instance. fn url(&self) -> String { - // Convert string kind to RemoteKind enum - let kind = match self.kind.as_str() { - "rpc" => RemoteKind::Rpc, - "websocket" => RemoteKind::Websocket, - "grpc" => RemoteKind::Grpc, - // Default to rpc for unknown kinds - _ => RemoteKind::Rpc, + // Construct the full remote URL with the appropriate scheme + let full_url = match self.kind.as_str() { + "rpc" => format!("http://{}", self.url), + "websocket" => format!("ws://{}", self.url), + "grpc" => format!("grpc://{}", self.url), + // Default to http for unknown kinds + _ => format!("http://{}", self.url), }; - // Use the production resolve_url function from magicblock-config - resolve_url(kind, &self.url) + + // Parse the full URL into a Remote instance to resolve aliases + if let Ok(remote) = Remote::from_str(&full_url) { + remote.url_str().to_string() + } else { + // If parsing fails, return the raw URL + self.url.clone() + } } } diff --git a/test-integration/test-tools/src/validator.rs b/test-integration/test-tools/src/validator.rs index 11ae293df..3841a0886 100644 --- a/test-integration/test-tools/src/validator.rs +++ b/test-integration/test-tools/src/validator.rs @@ -24,7 +24,6 @@ pub fn start_magic_block_validator_with_config( test_runner_paths: &TestRunnerPaths, log_suffix: &str, loaded_chain_accounts: &LoadedAccounts, - release: bool, ) -> Option { let TestRunnerPaths { config_path, @@ -38,9 +37,6 @@ pub fn start_magic_block_validator_with_config( let mut command = process::Command::new("cargo"); let keypair_base58 = loaded_chain_accounts.validator_authority_base58(); command.arg("build"); - if release { - command.arg("--release"); - } let build_res = command.current_dir(root_dir.clone()).output(); if build_res.is_ok_and(|output| !output.status.success()) { @@ -51,9 +47,6 @@ pub fn start_magic_block_validator_with_config( // Start validator via `cargo run -- ` let mut command = process::Command::new("cargo"); command.arg("run"); - if release { - command.arg("--release"); - } let rust_log_style = std::env::var("RUST_LOG_STYLE").unwrap_or(log_suffix.to_string()); command @@ -218,7 +211,6 @@ pub fn start_magicblock_validator_with_config_struct( let workspace_dir = resolve_workspace_dir(); let (default_tmpdir, temp_dir) = resolve_tmp_dir(TMP_DIR_CONFIG); - let release = std::env::var("RELEASE").is_ok(); let config_path = temp_dir.join("config.toml"); let config_toml = config.to_string(); fs::write(&config_path, config_toml).unwrap(); @@ -239,7 +231,6 @@ pub fn start_magicblock_validator_with_config_struct( &paths, "TEST", loaded_chain_accounts, - release, ), rpc_port, ) @@ -260,7 +251,6 @@ pub fn start_magicblock_validator_with_config_struct_and_temp_dir( BindAddress(SocketAddr::new(config.metrics.address.ip(), metrics_port)); let workspace_dir = resolve_workspace_dir(); - let release = std::env::var("RELEASE").is_ok(); let config_path = temp_dir.join("config.toml"); let config_toml = config.to_string(); fs::write(&config_path, config_toml).unwrap(); @@ -281,7 +271,6 @@ pub fn start_magicblock_validator_with_config_struct_and_temp_dir( &paths, "TEST", loaded_chain_accounts, - release, ), rpc_port, )