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
613 lines
16 KiB
Rust
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);
|
|
}
|
|
}
|