pub mod storage;
pub use storage::*;
pub mod bitcoin_core_convert;
pub use bitcoin_core_convert::*;
pub mod governance;
pub use governance::*;
pub mod rpc;
pub use rpc::*;
pub mod mempool;
pub use mempool::*;
pub mod ibd;
pub use ibd::*;
pub mod network;
pub use network::*;
#[cfg(feature = "fibre")]
use crate::network::fibre;
use crate::network::transport::TransportPreference;
use serde::de::{Deserializer, Error, MapAccess, Visitor};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;
fn flatten_toml_to_string_map(value: &toml::Value) -> HashMap<String, String> {
use toml::Value;
let mut result = HashMap::new();
fn flatten(prefix: &str, v: &Value, out: &mut HashMap<String, String>) {
match v {
Value::String(s) => {
if !prefix.is_empty() {
out.insert(prefix.to_string(), s.clone());
}
}
Value::Integer(i) => {
out.insert(prefix.to_string(), i.to_string());
}
Value::Float(f) => {
out.insert(prefix.to_string(), f.to_string());
}
Value::Boolean(b) => {
out.insert(prefix.to_string(), b.to_string());
}
Value::Array(arr) => {
let s: Vec<String> = arr
.iter()
.map(|x| match x {
Value::String(s) => s.clone(),
_ => x.to_string(),
})
.collect();
out.insert(prefix.to_string(), s.join(","));
}
Value::Table(t) => {
for (k, val) in t {
let p = if prefix.is_empty() {
k.clone()
} else {
format!("{prefix}.{k}")
};
flatten(&p, val, out);
}
}
Value::Datetime(dt) => {
out.insert(prefix.to_string(), dt.to_string());
}
}
}
if let Value::Table(t) = value {
for (k, v) in t {
flatten(k, v, &mut result);
}
}
result
}
fn deserialize_module_config<'de, D>(deserializer: D) -> Result<ModuleConfig, D::Error>
where
D: Deserializer<'de>,
{
struct ModuleConfigVisitor;
impl<'de> Visitor<'de> for ModuleConfigVisitor {
type Value = ModuleConfig;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("modules config table")
}
fn visit_map<A>(self, mut map: A) -> Result<ModuleConfig, A::Error>
where
A: MapAccess<'de>,
{
let mut enabled = true;
let mut modules_dir = "modules".to_string();
let mut data_dir = "data/modules".to_string();
let mut socket_dir = "data/modules/sockets".to_string();
let mut enabled_modules = Vec::new();
let mut module_configs = HashMap::new();
let mut watch_enabled = true;
let mut watch_auto_load = false;
let mut watch_auto_unload = false;
while let Some(key) = map.next_key::<String>()? {
if MODULE_CONFIG_KNOWN_KEYS.contains(&key.as_str()) {
match key.as_str() {
"enabled" => enabled = map.next_value()?,
"modules_dir" => modules_dir = map.next_value().unwrap_or(modules_dir),
"data_dir" => data_dir = map.next_value().unwrap_or(data_dir),
"socket_dir" => socket_dir = map.next_value().unwrap_or(socket_dir),
"enabled_modules" => enabled_modules = map.next_value().unwrap_or_default(),
"module_configs" => module_configs = map.next_value().unwrap_or_default(),
"watch_enabled" => watch_enabled = map.next_value().unwrap_or(true),
"watch_auto_load" => watch_auto_load = map.next_value().unwrap_or(false),
"watch_auto_unload" => {
watch_auto_unload = map.next_value().unwrap_or(false)
}
_ => {
let _: toml::Value = map.next_value()?;
}
}
} else {
let value: toml::Value = map.next_value()?;
if let toml::Value::Table(_) = &value {
let flat = flatten_toml_to_string_map(&value);
if !flat.is_empty() {
module_configs.insert(key, flat);
}
}
}
}
Ok(ModuleConfig {
enabled,
modules_dir,
data_dir,
socket_dir,
enabled_modules,
module_configs,
watch_enabled,
watch_auto_load,
watch_auto_unload,
})
}
}
deserializer.deserialize_map(ModuleConfigVisitor)
}
const MODULE_CONFIG_KNOWN_KEYS: &[&str] = &[
"enabled",
"modules_dir",
"data_dir",
"socket_dir",
"enabled_modules",
"module_configs",
"watch_enabled",
"watch_auto_load",
"watch_auto_unload",
];
#[derive(Debug, Clone, Serialize)]
pub struct ModuleConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_modules_dir")]
pub modules_dir: String,
#[serde(default = "default_modules_data_dir")]
pub data_dir: String,
#[serde(default = "default_modules_socket_dir")]
pub socket_dir: String,
#[serde(default)]
pub enabled_modules: Vec<String>,
#[serde(default)]
pub module_configs:
std::collections::HashMap<String, std::collections::HashMap<String, String>>,
#[serde(default = "default_true")]
pub watch_enabled: bool,
#[serde(default)]
pub watch_auto_load: bool,
#[serde(default)]
pub watch_auto_unload: bool,
}
pub(crate) fn default_true() -> bool {
true
}
pub(crate) fn default_false() -> bool {
false
}
fn default_modules_dir() -> String {
"modules".to_string()
}
fn default_modules_data_dir() -> String {
"data/modules".to_string()
}
fn default_modules_socket_dir() -> String {
"data/modules/sockets".to_string()
}
impl<'de> Deserialize<'de> for ModuleConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_module_config(deserializer)
}
}
impl Default for ModuleConfig {
fn default() -> Self {
Self {
enabled: true,
modules_dir: "modules".to_string(),
data_dir: "data/modules".to_string(),
socket_dir: "data/modules/sockets".to_string(),
enabled_modules: Vec::new(),
module_configs: std::collections::HashMap::new(),
watch_enabled: true,
watch_auto_load: false,
watch_auto_unload: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkTimingConfig {
#[serde(default = "default_target_peer_count", rename = "target_peer_count")]
pub target_outbound_peers: usize,
#[serde(default = "default_peer_connection_delay")]
pub peer_connection_delay_seconds: u64,
#[serde(default = "default_addr_relay_min_interval")]
pub addr_relay_min_interval_seconds: u64,
#[serde(default = "default_max_addresses_per_addr_message")]
pub max_addresses_per_addr_message: usize,
#[serde(default = "default_max_addresses_from_dns")]
pub max_addresses_from_dns: usize,
}
fn default_target_peer_count() -> usize {
8
}
fn default_peer_connection_delay() -> u64 {
2
}
fn default_addr_relay_min_interval() -> u64 {
8640 }
fn default_max_addresses_per_addr_message() -> usize {
1000
}
fn default_max_addresses_from_dns() -> usize {
100
}
impl Default for NetworkTimingConfig {
fn default() -> Self {
Self {
target_outbound_peers: 8,
peer_connection_delay_seconds: 2,
addr_relay_min_interval_seconds: 8640,
max_addresses_per_addr_message: 1000,
max_addresses_from_dns: 100,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestTimeoutConfig {
#[serde(default = "default_async_request_timeout")]
pub async_request_timeout_seconds: u64,
#[serde(default = "default_utxo_commitment_timeout")]
pub utxo_commitment_request_timeout_seconds: u64,
#[serde(default = "default_request_cleanup_interval")]
pub request_cleanup_interval_seconds: u64,
#[serde(default = "default_pending_request_max_age")]
pub pending_request_max_age_seconds: u64,
#[serde(default = "default_storage_timeout")]
pub storage_timeout_seconds: u64,
#[serde(default = "default_network_timeout")]
pub network_timeout_seconds: u64,
#[serde(default = "default_rpc_timeout")]
pub rpc_timeout_seconds: u64,
#[serde(default = "default_handshake_timeout")]
pub handshake_timeout_secs: u64,
#[serde(default = "default_connect_timeout")]
pub connect_timeout_secs: u64,
#[serde(default = "default_checkpoint_request_timeout")]
pub checkpoint_request_timeout_secs: u64,
#[serde(default = "default_protocol_verify_timeout")]
pub protocol_verify_timeout_secs: u64,
#[serde(default = "default_headers_verify_timeout")]
pub headers_verify_timeout_secs: u64,
}
fn default_async_request_timeout() -> u64 {
300 }
fn default_utxo_commitment_timeout() -> u64 {
30
}
fn default_request_cleanup_interval() -> u64 {
60
}
fn default_pending_request_max_age() -> u64 {
300 }
fn default_storage_timeout() -> u64 {
10 }
fn default_network_timeout() -> u64 {
30 }
fn default_rpc_timeout() -> u64 {
60 }
fn default_handshake_timeout() -> u64 {
10
}
fn default_connect_timeout() -> u64 {
10
}
fn default_checkpoint_request_timeout() -> u64 {
5
}
fn default_protocol_verify_timeout() -> u64 {
5
}
fn default_headers_verify_timeout() -> u64 {
10
}
impl Default for RequestTimeoutConfig {
fn default() -> Self {
Self {
async_request_timeout_seconds: 300,
utxo_commitment_request_timeout_seconds: 30,
request_cleanup_interval_seconds: 60,
pending_request_max_age_seconds: 300,
storage_timeout_seconds: 10,
network_timeout_seconds: 30,
rpc_timeout_seconds: 60,
handshake_timeout_secs: 10,
connect_timeout_secs: 10,
checkpoint_request_timeout_secs: 5,
protocol_verify_timeout_secs: 5,
headers_verify_timeout_secs: 10,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleResourceLimitsConfig {
#[serde(default = "default_module_max_cpu_percent")]
pub default_max_cpu_percent: u32,
#[serde(default = "default_module_max_memory_bytes")]
pub default_max_memory_bytes: u64,
#[serde(default = "default_module_max_file_descriptors")]
pub default_max_file_descriptors: u32,
#[serde(default = "default_module_max_child_processes")]
pub default_max_child_processes: u32,
#[serde(default = "default_module_startup_wait_millis")]
pub module_startup_wait_millis: u64,
#[serde(default = "default_module_socket_timeout")]
pub module_socket_timeout_seconds: u64,
#[serde(default = "default_module_socket_check_interval")]
pub module_socket_check_interval_millis: u64,
#[serde(default = "default_module_socket_max_attempts")]
pub module_socket_max_attempts: usize,
}
fn default_module_max_cpu_percent() -> u32 {
50
}
fn default_module_max_memory_bytes() -> u64 {
512 * 1024 * 1024 }
fn default_module_max_file_descriptors() -> u32 {
256
}
fn default_module_max_child_processes() -> u32 {
10
}
fn default_module_startup_wait_millis() -> u64 {
100
}
fn default_module_socket_timeout() -> u64 {
5
}
fn default_module_socket_check_interval() -> u64 {
100
}
fn default_module_socket_max_attempts() -> usize {
50
}
impl Default for ModuleResourceLimitsConfig {
fn default() -> Self {
Self {
default_max_cpu_percent: 50,
default_max_memory_bytes: 512 * 1024 * 1024,
default_max_file_descriptors: 256,
default_max_child_processes: 10,
module_startup_wait_millis: 100,
module_socket_timeout_seconds: 5,
module_socket_check_interval_millis: 100,
module_socket_max_attempts: 50,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProtocolLimitsConfig {
#[serde(default = "default_max_protocol_message_length")]
pub max_protocol_message_length: usize,
#[serde(default = "default_max_addr_to_send")]
pub max_addr_to_send: usize,
#[serde(default = "default_max_inv_sz")]
pub max_inv_sz: usize,
#[serde(default = "default_max_headers_results")]
pub max_headers_results: usize,
}
fn default_max_protocol_message_length() -> usize {
32 * 1024 * 1024
}
fn default_max_addr_to_send() -> usize {
1000
}
fn default_max_inv_sz() -> usize {
50000
}
fn default_max_headers_results() -> usize {
2000
}
impl Default for ProtocolLimitsConfig {
fn default() -> Self {
Self {
max_protocol_message_length: default_max_protocol_message_length(),
max_addr_to_send: default_max_addr_to_send(),
max_inv_sz: default_max_inv_sz(),
max_headers_results: default_max_headers_results(),
}
}
}
pub fn default_assume_valid_height_for_network(network: &str) -> u64 {
match network.to_lowercase().as_str() {
"mainnet" | "bitcoinv1" => 912_683, "testnet" | "testnet3" => 4_550_000, "signet" => 267_665, _ => 0, }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockValidationNodeConfig {
#[serde(default)]
pub assume_valid_height: u64,
#[serde(default)]
pub assume_valid_hash: Option<[u8; 32]>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeConfig {
pub listen_addr: Option<SocketAddr>,
pub block_validation: Option<BlockValidationNodeConfig>,
#[serde(default)]
pub assumeutxo_blockhash: Option<[u8; 32]>,
pub transport_preference: TransportPreferenceConfig,
#[serde(rename = "max_peers")]
pub max_outbound_peers: Option<usize>,
pub protocol_version: Option<String>,
pub modules: Option<ModuleConfig>,
#[cfg(feature = "stratum-v2")]
pub stratum_v2: Option<StratumV2Config>,
pub rpc: Option<RpcConfig>,
pub rpc_auth: Option<RpcAuthConfig>,
pub ban_list_sharing: Option<BanListSharingConfig>,
#[cfg(feature = "governance")]
pub governance: Option<GovernanceConfig>,
pub storage: Option<StorageConfig>,
#[serde(default)]
pub persistent_peers: Vec<SocketAddr>,
#[serde(default = "default_true")]
pub enable_self_advertisement: bool,
pub dos_protection: Option<DosProtectionConfig>,
pub ibd_protection: Option<IbdProtectionConfig>,
pub ibd: Option<IbdConfig>,
pub spam_ban: Option<SpamBanConfig>,
pub relay: Option<RelayConfig>,
#[cfg(feature = "fibre")]
pub fibre: Option<fibre::FibreConfig>,
pub address_database: Option<AddressDatabaseConfig>,
#[cfg(feature = "dandelion")]
pub dandelion: Option<DandelionConfig>,
pub peer_rate_limiting: Option<PeerRateLimitingConfig>,
pub network_timing: Option<NetworkTimingConfig>,
pub request_timeouts: Option<RequestTimeoutConfig>,
pub protocol_limits: Option<ProtocolLimitsConfig>,
pub background_tasks: Option<BackgroundTaskConfig>,
pub replay_protection: Option<ReplayProtectionConfig>,
pub module_resource_limits: Option<ModuleResourceLimitsConfig>,
#[cfg(feature = "zmq")]
pub zmq: Option<ZmqConfig>,
pub logging: Option<LoggingConfig>,
pub mempool: Option<MempoolPolicyConfig>,
pub rbf: Option<RbfConfig>,
pub payment: Option<PaymentConfig>,
pub rest_api: Option<RestApiConfig>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TransportPreferenceConfig {
TcpOnly,
#[cfg(feature = "quinn")]
QuinnOnly,
#[cfg(feature = "iroh")]
IrohOnly,
#[cfg(feature = "iroh")]
Hybrid,
#[cfg(all(feature = "quinn", feature = "iroh"))]
All,
}
impl Default for TransportPreferenceConfig {
fn default() -> Self {
Self::TcpOnly
}
}
impl From<TransportPreferenceConfig> for TransportPreference {
fn from(config: TransportPreferenceConfig) -> Self {
match config {
TransportPreferenceConfig::TcpOnly => TransportPreference::TCP_ONLY,
#[cfg(feature = "quinn")]
TransportPreferenceConfig::QuinnOnly => TransportPreference::QUINN_ONLY,
#[cfg(feature = "iroh")]
TransportPreferenceConfig::IrohOnly => TransportPreference::IROH_ONLY,
#[cfg(feature = "iroh")]
TransportPreferenceConfig::Hybrid => TransportPreference::hybrid(),
#[cfg(all(feature = "quinn", feature = "iroh"))]
TransportPreferenceConfig::All => TransportPreference::all_transports(),
}
}
}
impl Default for NodeConfig {
fn default() -> Self {
Self {
listen_addr: Some("127.0.0.1:8333".parse().unwrap()),
block_validation: None,
assumeutxo_blockhash: None,
transport_preference: TransportPreferenceConfig::TcpOnly,
max_outbound_peers: Some(100),
protocol_version: Some("BitcoinV1".to_string()),
modules: Some(ModuleConfig::default()),
#[cfg(feature = "stratum-v2")]
stratum_v2: None,
rpc: None,
rpc_auth: None,
ban_list_sharing: None,
#[cfg(feature = "governance")]
governance: None,
storage: None,
persistent_peers: Vec::new(),
enable_self_advertisement: true,
dos_protection: None,
ibd_protection: None,
ibd: None,
relay: None,
#[cfg(feature = "fibre")]
fibre: None,
address_database: None,
#[cfg(feature = "dandelion")]
dandelion: None,
peer_rate_limiting: None,
network_timing: None,
request_timeouts: None,
protocol_limits: None,
background_tasks: None,
replay_protection: None,
module_resource_limits: None,
spam_ban: None,
#[cfg(feature = "zmq")]
zmq: None,
logging: None,
mempool: None,
rbf: None,
payment: None,
rest_api: None,
}
}
}
fn expand_tilde_path(s: &str) -> String {
let s = s.trim();
if s.is_empty() {
return s.to_string();
}
if s == "~" {
return dirs::home_dir()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| "~".to_string());
}
if let (Some(home), Some(rest)) = (
dirs::home_dir(),
s.strip_prefix("~/").or_else(|| s.strip_prefix("~\\")),
) {
return home.join(rest).to_string_lossy().into_owned();
}
s.to_string()
}
impl NodeConfig {
pub fn expand_paths(&mut self) {
if let Some(ref mut s) = self.storage {
s.data_dir = expand_tilde_path(&s.data_dir);
}
if let Some(ref mut m) = self.modules {
m.modules_dir = expand_tilde_path(&m.modules_dir);
m.data_dir = expand_tilde_path(&m.data_dir);
m.socket_dir = expand_tilde_path(&m.socket_dir);
}
if let Some(ref mut ibd) = self.ibd {
if let Some(ref d) = ibd.dump_dir {
ibd.dump_dir = Some(expand_tilde_path(d));
}
if let Some(ref d) = ibd.snapshot_dir {
ibd.snapshot_dir = Some(expand_tilde_path(d));
}
}
}
pub fn from_file(path: &std::path::Path) -> anyhow::Result<Self> {
#[cfg(unix)]
{
if let Ok(metadata) = std::fs::metadata(path) {
use std::os::unix::fs::PermissionsExt;
let permissions = metadata.permissions();
let mode = permissions.mode();
if mode & 0o077 != 0 {
tracing::warn!(
"SECURITY WARNING: Configuration file {:?} is readable by others (mode: {:o}). \
Consider setting permissions to 600 for security. \
Run: chmod 600 {:?}",
path, mode, path
);
}
}
}
let content = std::fs::read_to_string(path)?;
let mut config: NodeConfig = if path.extension().and_then(|s| s.to_str()) == Some("toml") {
toml::from_str(&content)
.map_err(|e| anyhow::anyhow!("Failed to parse TOML config: {}", e))?
} else {
serde_json::from_str(&content)
.map_err(|e| anyhow::anyhow!("Failed to parse JSON config: {}", e))?
};
config.expand_paths();
Ok(config)
}
pub fn from_json_file(path: &std::path::Path) -> anyhow::Result<Self> {
let content = std::fs::read_to_string(path)?;
let mut config: NodeConfig = serde_json::from_str(&content)?;
config.expand_paths();
Ok(config)
}
pub fn from_toml_file(path: &std::path::Path) -> anyhow::Result<Self> {
let content = std::fs::read_to_string(path)?;
let mut config: NodeConfig = toml::from_str(&content)
.map_err(|e| anyhow::anyhow!("Failed to parse TOML config: {}", e))?;
config.expand_paths();
Ok(config)
}
pub fn to_json_file(&self, path: &std::path::Path) -> anyhow::Result<()> {
let content = serde_json::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn to_toml_file(&self, path: &std::path::Path) -> anyhow::Result<()> {
let content = toml::to_string_pretty(self)
.map_err(|e| anyhow::anyhow!("Failed to serialize TOML config: {}", e))?;
std::fs::write(path, content)?;
Ok(())
}
pub fn get_transport_preference(&self) -> TransportPreference {
self.transport_preference.into()
}
#[cfg(feature = "governance")]
pub async fn auto_detect_governance(&mut self) -> Result<(), anyhow::Error> {
use reqwest::Client;
use tracing::{debug, info};
if let Some(ref mut gov_config) = self.governance {
if !gov_config.enabled {
if let Some(ref commons_url) = gov_config.commons_url {
let client = Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()?;
let health_url = format!("{commons_url}/internal/health");
debug!("Auto-detecting governance server at {}", health_url);
match client.get(&health_url).send().await {
Ok(response) if response.status().is_success() => {
info!("Governance server detected and responding at {}, auto-enabling governance", commons_url);
gov_config.enabled = true;
}
Ok(response) => {
debug!("Governance server at {} responded with status {}, keeping governance disabled", commons_url, response.status());
}
Err(e) => {
debug!("Governance server at {} not reachable: {}, keeping governance disabled", commons_url, e);
}
}
}
}
}
Ok(())
}
}
impl NodeConfig {
pub fn validate(&self) -> anyhow::Result<()> {
if let Some(ref storage) = self.storage {
if let Some(ref pruning) = storage.pruning {
pruning.validate()?;
}
}
Ok(())
}
pub fn validate_security(
&self,
rpc_addr: SocketAddr,
rest_api_addr: Option<SocketAddr>,
) -> Vec<String> {
let mut warnings = Vec::new();
let is_localhost =
|addr: &SocketAddr| addr.ip().is_loopback() || addr.ip().to_string() == "127.0.0.1";
if let Some(ref rpc_auth) = self.rpc_auth {
if !rpc_auth.required {
if !is_localhost(&rpc_addr) {
warnings.push(format!(
"SECURITY WARNING: RPC server is binding to {rpc_addr} (non-localhost) but authentication is not required. \
This exposes your node to unauthorized access. Consider setting rpc_auth.required = true"
));
}
} else if rpc_auth.tokens.is_empty() && rpc_auth.certificates.is_empty() {
warnings.push(
"SECURITY WARNING: RPC authentication is required but no tokens or certificates are configured. \
RPC requests will be rejected. Add tokens or certificates to rpc_auth configuration."
.to_string(),
);
}
} else {
if !is_localhost(&rpc_addr) {
warnings.push(format!(
"SECURITY WARNING: RPC server is binding to {rpc_addr} (non-localhost) without authentication. \
This exposes your node to unauthorized access. Consider configuring rpc_auth with required = true"
));
}
}
if let Some(rest_addr) = rest_api_addr {
if !is_localhost(&rest_addr) {
if let Some(ref rpc_auth) = self.rpc_auth {
if !rpc_auth.required {
warnings.push(format!(
"SECURITY WARNING: REST API is binding to {rest_addr} (non-localhost) but authentication is not required. \
This exposes your node to unauthorized access. Consider setting rpc_auth.required = true"
));
}
} else {
warnings.push(format!(
"SECURITY WARNING: REST API is binding to {rest_addr} (non-localhost) without authentication. \
This exposes your node to unauthorized access. Consider configuring rpc_auth with required = true"
));
}
}
}
warnings
}
pub fn max_request_size_bytes(&self) -> usize {
crate::utils::env_int::<usize>("BLVM_RPC_MAX_REQUEST_SIZE_BYTES").unwrap_or_else(|| {
self.rpc
.as_ref()
.map(|r| r.max_request_size_bytes)
.unwrap_or(1_048_576)
})
}
pub fn max_connections_per_ip_per_minute(&self) -> u32 {
crate::utils::env_int::<u32>("BLVM_RPC_MAX_CONNECTIONS_PER_IP_PER_MINUTE").unwrap_or_else(
|| {
self.rpc
.as_ref()
.map(|r| r.max_connections_per_ip_per_minute)
.unwrap_or(10)
},
)
}
pub fn rpc_rate_limit_when_auth_disabled(&self) -> bool {
if std::env::var("BLVM_RPC_RATE_LIMIT_WHEN_AUTH_DISABLED").is_ok() {
crate::utils::env_bool("BLVM_RPC_RATE_LIMIT_WHEN_AUTH_DISABLED")
} else {
self.rpc
.as_ref()
.map(|r| r.rate_limit_when_auth_disabled)
.unwrap_or(true)
}
}
pub fn rpc_ip_rate_limit_burst(&self) -> u32 {
crate::utils::env_int::<u32>("BLVM_RPC_IP_RATE_LIMIT_BURST").unwrap_or_else(|| {
self.rpc
.as_ref()
.map(|r| r.ip_rate_limit_burst)
.unwrap_or(50)
})
}
pub fn rpc_ip_rate_limit_rate(&self) -> u32 {
crate::utils::env_int::<u32>("BLVM_RPC_IP_RATE_LIMIT_RATE")
.unwrap_or_else(|| self.rpc.as_ref().map(|r| r.ip_rate_limit_rate).unwrap_or(5))
}
pub fn rpc_batch_rate_multiplier_cap(&self) -> u32 {
crate::utils::env_int::<u32>("BLVM_RPC_BATCH_RATE_MULTIPLIER_CAP").unwrap_or_else(|| {
self.rpc
.as_ref()
.map(|r| r.batch_rate_multiplier_cap)
.unwrap_or(10)
})
}
pub fn rpc_connection_rate_limit_window_seconds(&self) -> u64 {
crate::utils::env_int::<u64>("BLVM_RPC_CONNECTION_RATE_LIMIT_WINDOW_SECS").unwrap_or_else(
|| {
self.rpc
.as_ref()
.map(|r| r.connection_rate_limit_window_seconds)
.unwrap_or(60)
},
)
}
}
#[cfg(feature = "zmq")]
pub use crate::zmq::ZmqConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentConfig {
#[serde(default = "default_true")]
pub p2p_enabled: bool,
#[serde(default = "default_false")]
pub http_enabled: bool,
#[serde(default = "default_network")]
pub network: Option<String>,
#[serde(default)]
pub merchant_key: Option<String>,
#[serde(default)]
pub node_payment_address: Option<String>,
#[serde(default = "default_payment_store_path")]
pub payment_store_path: String,
#[serde(default = "default_true")]
pub module_payments_enabled: bool,
#[serde(default = "default_safe_confirmation_depth")]
pub safe_confirmation_depth: u32,
}
fn default_safe_confirmation_depth() -> u32 {
6
}
fn default_payment_store_path() -> String {
"data/payments".to_string()
}
fn default_network() -> Option<String> {
Some("mainnet".to_string())
}
impl Default for PaymentConfig {
fn default() -> Self {
Self {
p2p_enabled: true,
http_enabled: false,
network: default_network(),
merchant_key: None,
node_payment_address: None,
payment_store_path: "data/payments".to_string(),
module_payments_enabled: true,
safe_confirmation_depth: default_safe_confirmation_depth(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RestApiConfig {
#[serde(default = "default_false")]
pub enabled: bool,
#[serde(default = "default_false")]
pub payment_endpoints_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LoggingConfig {
#[serde(default, alias = "level")]
pub filter: Option<String>,
#[serde(default)]
pub json_format: bool,
}