synor/apps/synord/src/config.rs
Gulshan Yadav 48949ebb3f Initial commit: Synor blockchain monorepo
A complete blockchain implementation featuring:
- synord: Full node with GHOSTDAG consensus
- explorer-web: Modern React blockchain explorer with 3D DAG visualization
- CLI wallet and tools
- Smart contract SDK and example contracts (DEX, NFT, token)
- WASM crypto library for browser/mobile
2026-01-08 05:22:17 +05:30

613 lines
16 KiB
Rust

//! Node configuration.
use std::fs;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use tracing::info;
/// Complete node configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NodeConfig {
/// Network name (mainnet, testnet, devnet).
pub network: String,
/// Chain ID.
pub chain_id: u64,
/// Data directory.
pub data_dir: PathBuf,
/// Storage configuration.
#[serde(default)]
pub storage: StorageConfig,
/// Network/P2P configuration.
#[serde(default)]
pub p2p: P2PConfig,
/// RPC configuration.
#[serde(default)]
pub rpc: RpcConfig,
/// Mining configuration.
#[serde(default)]
pub mining: MiningConfig,
/// Consensus configuration.
#[serde(default)]
pub consensus: ConsensusConfig,
/// VM configuration.
#[serde(default)]
pub vm: VmConfig,
/// Logging configuration.
#[serde(default)]
pub logging: LoggingConfig,
/// Metrics configuration.
#[serde(default)]
pub metrics: MetricsConfig,
}
impl NodeConfig {
/// Creates default config for a network.
pub fn for_network(network: &str) -> anyhow::Result<Self> {
// Chain IDs match synor-network convention:
// 0 = mainnet, 1 = testnet, 2+ = devnet/local
let (chain_id, data_dir_name) = match network {
"mainnet" => (0, "synor"),
"testnet" => (1, "synor-testnet"),
"devnet" => (2, "synor-devnet"),
_ => anyhow::bail!("Unknown network: {}", network),
};
let data_dir = dirs::data_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(data_dir_name);
Ok(NodeConfig {
network: network.to_string(),
chain_id,
data_dir,
storage: StorageConfig::default(),
p2p: P2PConfig::for_network(network),
rpc: RpcConfig::for_network(network),
mining: MiningConfig::default(),
consensus: ConsensusConfig::for_network(network),
vm: VmConfig::default(),
logging: LoggingConfig::default(),
metrics: MetricsConfig::default(),
})
}
/// Loads config from file or creates default.
pub fn load_or_default(path: &Path, network: &str) -> anyhow::Result<Self> {
if path.exists() {
Self::load(path)
} else {
info!("Config file not found, using defaults");
Self::for_network(network)
}
}
/// Loads config from file.
pub fn load(path: &Path) -> anyhow::Result<Self> {
let content = fs::read_to_string(path)?;
let config: NodeConfig = toml::from_str(&content)?;
Ok(config)
}
/// Saves config to file.
pub fn save(&self, path: &Path) -> anyhow::Result<()> {
let content = toml::to_string_pretty(self)?;
fs::write(path, content)?;
Ok(())
}
/// Sets data directory.
pub fn with_data_dir(mut self, data_dir: Option<PathBuf>) -> Self {
if let Some(dir) = data_dir {
self.data_dir = dir;
}
self
}
/// Sets mining configuration.
pub fn with_mining(
mut self,
enabled: bool,
coinbase: Option<String>,
threads: usize,
) -> Self {
if enabled {
self.mining.enabled = true;
}
if let Some(addr) = coinbase {
self.mining.coinbase_address = Some(addr);
}
if threads > 0 {
self.mining.threads = threads;
}
self
}
/// Sets RPC configuration.
pub fn with_rpc(mut self, host: &str, rpc_port: u16, ws_port: u16) -> Self {
self.rpc.http_addr = format!("{}:{}", host, rpc_port);
self.rpc.ws_addr = format!("{}:{}", host, ws_port);
self
}
/// Sets P2P configuration.
pub fn with_p2p(mut self, host: &str, port: u16, seeds: Vec<String>) -> Self {
self.p2p.listen_addr = format!("{}:{}", host, port);
if !seeds.is_empty() {
self.p2p.seeds = seeds;
}
self
}
/// Returns paths for various data.
pub fn blocks_path(&self) -> PathBuf {
self.data_dir.join("blocks")
}
pub fn chainstate_path(&self) -> PathBuf {
self.data_dir.join("chainstate")
}
pub fn contracts_path(&self) -> PathBuf {
self.data_dir.join("contracts")
}
pub fn keys_path(&self) -> PathBuf {
self.data_dir.join("keys")
}
}
/// Storage configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StorageConfig {
/// Database type (rocksdb, sled).
pub db_type: String,
/// Cache size in MB.
pub cache_size_mb: usize,
/// Max open files.
pub max_open_files: i32,
/// Enable compression.
pub compression: bool,
/// Pruning mode.
pub pruning: PruningConfig,
}
impl Default for StorageConfig {
fn default() -> Self {
StorageConfig {
db_type: "rocksdb".to_string(),
cache_size_mb: 512,
max_open_files: 1024,
compression: true,
pruning: PruningConfig::default(),
}
}
}
/// Pruning configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PruningConfig {
/// Enable pruning.
pub enabled: bool,
/// Keep last N blocks.
pub keep_blocks: u64,
/// Pruning interval in blocks.
pub interval: u64,
}
impl Default for PruningConfig {
fn default() -> Self {
PruningConfig {
enabled: false,
keep_blocks: 100_000,
interval: 1000,
}
}
}
/// P2P network configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct P2PConfig {
/// Listen address.
pub listen_addr: String,
/// External address (for NAT).
pub external_addr: Option<String>,
/// Seed nodes.
pub seeds: Vec<String>,
/// Maximum inbound connections.
pub max_inbound: usize,
/// Maximum outbound connections.
pub max_outbound: usize,
/// Connection timeout in seconds.
pub connection_timeout: u64,
/// Enable UPnP.
pub upnp: bool,
/// Ban duration in seconds.
pub ban_duration: u64,
}
impl Default for P2PConfig {
fn default() -> Self {
P2PConfig {
listen_addr: "0.0.0.0:16100".to_string(),
external_addr: None,
seeds: vec![],
max_inbound: 125,
max_outbound: 8,
connection_timeout: 30,
upnp: true,
ban_duration: 86400, // 24 hours
}
}
}
impl P2PConfig {
/// Creates config for a network.
pub fn for_network(network: &str) -> Self {
let mut config = P2PConfig::default();
match network {
"mainnet" => {
config.listen_addr = "/ip4/0.0.0.0/tcp/16511".to_string();
config.seeds = vec![
// Mainnet seeds - geographically distributed
// Format: /dns4/<hostname>/tcp/<port>/p2p/<peer_id>
// Peer IDs will be populated after seed node deployment
"/dns4/seed1.synor.cc/tcp/16511".to_string(),
"/dns4/seed2.synor.cc/tcp/16511".to_string(),
"/dns4/seed3.synor.cc/tcp/16511".to_string(),
];
}
"testnet" => {
config.listen_addr = "/ip4/0.0.0.0/tcp/17511".to_string();
config.seeds = vec![
// Testnet seeds - geographically distributed
// North America (US-East)
"/dns4/testnet-seed1.synor.cc/tcp/17511".to_string(),
// Europe (Frankfurt)
"/dns4/testnet-seed2.synor.cc/tcp/17511".to_string(),
// Asia (Singapore)
"/dns4/testnet-seed3.synor.cc/tcp/17511".to_string(),
];
}
"devnet" => {
config.listen_addr = "/ip4/0.0.0.0/tcp/18511".to_string();
config.seeds = vec![];
}
_ => {}
};
config
}
}
/// RPC configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RpcConfig {
/// Enable HTTP RPC.
pub http_enabled: bool,
/// HTTP bind address.
pub http_addr: String,
/// Enable WebSocket RPC.
pub ws_enabled: bool,
/// WebSocket bind address.
pub ws_addr: String,
/// Enable CORS.
pub cors: bool,
/// Allowed origins.
pub cors_origins: Vec<String>,
/// Maximum batch size.
pub max_batch_size: usize,
/// Maximum response size.
pub max_response_size: usize,
/// Rate limit (requests per second, 0 = unlimited).
pub rate_limit: u32,
/// Maximum connections.
pub max_connections: u32,
}
impl Default for RpcConfig {
fn default() -> Self {
RpcConfig {
http_enabled: true,
http_addr: "127.0.0.1:16110".to_string(),
ws_enabled: true,
ws_addr: "127.0.0.1:16111".to_string(),
cors: true,
cors_origins: vec!["*".to_string()],
max_batch_size: 100,
max_response_size: 10 * 1024 * 1024, // 10MB
rate_limit: 0,
max_connections: 100,
}
}
}
impl RpcConfig {
/// Creates config for a network.
pub fn for_network(network: &str) -> Self {
let mut config = RpcConfig::default();
match network {
"mainnet" => {
config.http_addr = "127.0.0.1:16110".to_string();
config.ws_addr = "127.0.0.1:16111".to_string();
}
"testnet" => {
config.http_addr = "127.0.0.1:17110".to_string();
config.ws_addr = "127.0.0.1:17111".to_string();
}
"devnet" => {
config.http_addr = "127.0.0.1:18110".to_string();
config.ws_addr = "127.0.0.1:18111".to_string();
}
_ => {}
}
config
}
}
/// Mining configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MiningConfig {
/// Enable mining.
pub enabled: bool,
/// Coinbase address for rewards.
pub coinbase_address: Option<String>,
/// Number of mining threads (0 = auto).
pub threads: usize,
/// Extra data for coinbase.
pub extra_data: String,
/// Mining intensity (0.0 - 1.0).
pub intensity: f32,
/// Enable GPU mining.
pub gpu_enabled: bool,
/// GPU device indices.
pub gpu_devices: Vec<usize>,
}
impl Default for MiningConfig {
fn default() -> Self {
MiningConfig {
enabled: false,
coinbase_address: None,
threads: 0, // Auto-detect
extra_data: "synord".to_string(),
intensity: 1.0,
gpu_enabled: false,
gpu_devices: vec![],
}
}
}
/// Consensus configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConsensusConfig {
/// GHOSTDAG K parameter.
pub ghostdag_k: u8,
/// Merge depth.
pub merge_depth: u64,
/// Finality depth.
pub finality_depth: u64,
/// Target block time in milliseconds.
pub target_time_ms: u64,
/// Difficulty adjustment window.
pub difficulty_window: u64,
/// Max block size.
pub max_block_size: usize,
/// Max block mass.
pub max_block_mass: u64,
}
impl Default for ConsensusConfig {
fn default() -> Self {
ConsensusConfig {
ghostdag_k: 18,
merge_depth: 3600, // ~1 hour
finality_depth: 86400, // ~24 hours
target_time_ms: 1000,
difficulty_window: 2641,
max_block_size: 1_000_000,
max_block_mass: 500_000,
}
}
}
impl ConsensusConfig {
/// Creates config for a network.
pub fn for_network(network: &str) -> Self {
let mut config = ConsensusConfig::default();
match network {
"mainnet" => {
// Mainnet: 1 second blocks, high finality for security
config.target_time_ms = 1000;
config.finality_depth = 86400; // ~24 hours at 1 BPS
config.merge_depth = 3600; // ~1 hour at 1 BPS
}
"testnet" => {
// Testnet: Fast 100ms blocks for development testing
config.target_time_ms = 100;
config.finality_depth = 36000; // ~1 hour at 10 BPS
config.merge_depth = 360; // ~36 seconds at 10 BPS
config.ghostdag_k = 18;
}
"devnet" => {
// Devnet: Very fast for local testing
config.target_time_ms = 100;
config.finality_depth = 100;
config.merge_depth = 36;
}
_ => {}
}
config
}
}
/// VM configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VmConfig {
/// Enable smart contracts.
pub enabled: bool,
/// Maximum gas per block.
pub max_gas_per_block: u64,
/// Maximum contract size.
pub max_contract_size: usize,
/// Maximum call depth.
pub max_call_depth: u32,
/// Maximum memory pages.
pub max_memory_pages: u32,
/// Execution timeout in milliseconds.
pub execution_timeout_ms: u64,
}
impl Default for VmConfig {
fn default() -> Self {
VmConfig {
enabled: true,
max_gas_per_block: 100_000_000,
max_contract_size: 24 * 1024, // 24KB
max_call_depth: 16,
max_memory_pages: 256, // 16MB
execution_timeout_ms: 5000,
}
}
}
/// Logging configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LoggingConfig {
/// Log level (trace, debug, info, warn, error).
pub level: String,
/// Enable JSON format.
pub json: bool,
/// Log file path.
pub file: Option<PathBuf>,
/// Maximum log file size in MB.
pub max_size_mb: usize,
/// Number of log files to keep.
pub max_files: usize,
}
impl Default for LoggingConfig {
fn default() -> Self {
LoggingConfig {
level: "info".to_string(),
json: false,
file: None,
max_size_mb: 100,
max_files: 5,
}
}
}
/// Metrics configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MetricsConfig {
/// Enable metrics.
pub enabled: bool,
/// Metrics bind address.
pub addr: String,
/// Enable Prometheus endpoint.
pub prometheus: bool,
}
impl Default for MetricsConfig {
fn default() -> Self {
MetricsConfig {
enabled: false,
addr: "127.0.0.1:9090".to_string(),
prometheus: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_config_for_network() {
let config = NodeConfig::for_network("mainnet").unwrap();
assert_eq!(config.chain_id, 0);
assert_eq!(config.network, "mainnet");
let config = NodeConfig::for_network("testnet").unwrap();
assert_eq!(config.chain_id, 1);
assert_eq!(config.consensus.target_time_ms, 100); // Fast testnet
let config = NodeConfig::for_network("devnet").unwrap();
assert_eq!(config.chain_id, 2);
}
#[test]
fn test_config_save_load() {
let dir = tempdir().unwrap();
let path = dir.path().join("config.toml");
let config = NodeConfig::for_network("mainnet").unwrap();
config.save(&path).unwrap();
let loaded = NodeConfig::load(&path).unwrap();
assert_eq!(loaded.network, config.network);
assert_eq!(loaded.chain_id, config.chain_id);
}
}