Skip to main content

blvm_consensus/
config.rs

1//! Configuration for blvm-consensus
2//!
3//! Provides configurable parameters for consensus validation, network message limits,
4//! and performance optimizations. These settings can be loaded from config files,
5//! environment variables, or passed programmatically.
6
7use serde::{Deserialize, Serialize};
8
9// Re-export foundational config types from blvm-primitives
10pub use blvm_primitives::config::{BlockValidationConfig, NetworkMessageLimits};
11
12/// Mempool configuration
13///
14/// Controls mempool size limits, fee rates, and transaction expiry.
15/// These are operational parameters, not consensus-critical.
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct MempoolConfig {
18    /// Maximum mempool size in megabytes (default 300 MB)
19    /// Default: 300 MB
20    #[serde(default = "default_max_mempool_mb")]
21    pub max_mempool_mb: u64,
22
23    /// Maximum number of transactions in mempool (alternative to size-based limit)
24    /// Default: 100000
25    #[serde(default = "default_max_mempool_txs")]
26    pub max_mempool_txs: usize,
27
28    /// Mempool transaction expiry in hours (default 336 = 14 days)
29    /// Transactions older than this are removed from mempool
30    /// Default: 336 (14 days)
31    #[serde(default = "default_mempool_expiry_hours")]
32    pub mempool_expiry_hours: u64,
33
34    /// Minimum relay fee rate in satoshis per virtual byte (default 1 sat/vB)
35    /// Transactions with fee rate below this are not relayed
36    /// Default: 1 sat/vB (1000 sat/kB)
37    #[serde(default = "default_min_relay_fee_rate")]
38    pub min_relay_fee_rate: u64,
39
40    /// Minimum transaction fee in satoshis (absolute minimum, regardless of size)
41    /// Default: 1000 satoshis
42    #[serde(default = "default_min_tx_fee")]
43    pub min_tx_fee: i64,
44
45    /// RBF (Replace-By-Fee) minimum fee increment in satoshis (BIP125)
46    /// Replacement transactions must pay at least this much more than the original
47    /// Default: 1000 satoshis
48    #[serde(default = "default_rbf_fee_increment")]
49    pub rbf_fee_increment: i64,
50
51    /// Maximum OP_RETURN data size in bytes (default 80)
52    /// Default: 80 bytes
53    #[serde(default = "default_max_op_return_size")]
54    pub max_op_return_size: u32,
55
56    /// Maximum number of OP_RETURN outputs allowed (default: 1)
57    /// Transactions with more than this are rejected as non-standard
58    #[serde(default = "default_max_op_return_outputs")]
59    pub max_op_return_outputs: u32,
60
61    /// Reject transactions with multiple OP_RETURN outputs
62    /// Default: true
63    #[serde(default = "default_reject_multiple_op_return")]
64    pub reject_multiple_op_return: bool,
65
66    /// Maximum standard script size in bytes
67    /// Default: 200 bytes
68    #[serde(default = "default_max_standard_script_size")]
69    pub max_standard_script_size: u32,
70
71    /// Reject envelope protocol (OP_FALSE OP_IF) scripts
72    /// Default: true
73    #[serde(default = "default_reject_envelope_protocol")]
74    pub reject_envelope_protocol: bool,
75
76    /// Reject spam transactions at mempool entry (opt-in)
77    /// Default: false (spam filtering is opt-in for mempool)
78    ///
79    /// **Admission:** enforced in `blvm-node` by `MempoolPolicyConfig::reject_spam_in_mempool`
80    /// in `MempoolManager::add_transaction` (uses `blvm-protocol` spam filter).
81    #[serde(default = "default_reject_spam_in_mempool")]
82    pub reject_spam_in_mempool: bool,
83
84    /// Spam filter configuration (if reject_spam_in_mempool is enabled)
85    /// Note: Prefer `blvm-node` `MempoolPolicyConfig::spam_filter` + `SpamFilterConfigSerializable`.
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub spam_filter_config: Option<serde_json::Value>,
88
89    /// Minimum fee rate for large transactions (satoshis per vbyte)
90    /// Transactions larger than large_tx_threshold_bytes must pay at least this fee rate
91    /// Default: 2 sat/vB (higher than standard min_relay_fee_rate)
92    #[serde(default = "default_min_fee_rate_large_tx")]
93    pub min_fee_rate_large_tx: u64,
94
95    /// Large transaction threshold (bytes)
96    /// Transactions larger than this require min_fee_rate_large_tx
97    /// Default: 1000 bytes
98    #[serde(default = "default_large_tx_threshold_bytes")]
99    pub large_tx_threshold_bytes: u64,
100}
101
102fn default_rbf_fee_increment() -> i64 {
103    1000
104}
105
106fn default_max_mempool_mb() -> u64 {
107    300
108}
109
110fn default_max_mempool_txs() -> usize {
111    100_000
112}
113
114fn default_mempool_expiry_hours() -> u64 {
115    336 // 14 days
116}
117
118fn default_min_relay_fee_rate() -> u64 {
119    1 // 1 sat/vB = 1000 sat/kB
120}
121
122fn default_min_tx_fee() -> i64 {
123    1000
124}
125
126fn default_max_op_return_size() -> u32 {
127    80
128}
129
130fn default_max_op_return_outputs() -> u32 {
131    1
132}
133
134fn default_reject_multiple_op_return() -> bool {
135    true
136}
137
138fn default_max_standard_script_size() -> u32 {
139    200
140}
141
142fn default_reject_envelope_protocol() -> bool {
143    true
144}
145
146fn default_reject_spam_in_mempool() -> bool {
147    false
148}
149
150fn default_min_fee_rate_large_tx() -> u64 {
151    2 // 2 sat/vB (higher than standard 1 sat/vB)
152}
153
154fn default_large_tx_threshold_bytes() -> u64 {
155    1000 // 1 KB
156}
157
158impl Default for MempoolConfig {
159    fn default() -> Self {
160        Self {
161            max_mempool_mb: 300,
162            max_mempool_txs: 100_000,
163            mempool_expiry_hours: 336,
164            min_relay_fee_rate: 1,
165            min_tx_fee: 1000,
166            rbf_fee_increment: 1000,
167            max_op_return_size: 80,
168            max_op_return_outputs: 1,
169            reject_multiple_op_return: true,
170            max_standard_script_size: 200,
171            reject_envelope_protocol: true,
172            reject_spam_in_mempool: false,
173            spam_filter_config: None,
174            min_fee_rate_large_tx: 2,
175            large_tx_threshold_bytes: 1000,
176        }
177    }
178}
179
180/// UTXO Commitment configuration
181///
182/// Controls UTXO commitment set size, storage limits, and performance tuning.
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
184pub struct UtxoCommitmentConfig {
185    /// Maximum UTXO commitment set size in megabytes
186    /// This limits the in-memory size of the UTXO Merkle tree
187    /// Default: 512 MB (sufficient for ~100M UTXOs)
188    #[serde(default = "default_max_utxo_commitment_set_mb")]
189    pub max_utxo_commitment_set_mb: u64,
190
191    /// Maximum number of UTXOs in commitment set (alternative to size-based limit)
192    /// Default: 100_000_000 (100 million UTXOs)
193    #[serde(default = "default_max_utxo_count")]
194    pub max_utxo_count: u64,
195
196    /// Maximum number of historical commitments to keep in memory
197    /// Older commitments are stored on disk
198    /// Default: 1000 (keeps last ~7 days of commitments at 1 per block)
199    #[serde(default = "default_max_historical_commitments")]
200    pub max_historical_commitments: usize,
201
202    /// Enable incremental commitment updates (recommended)
203    /// Default: true
204    #[serde(default = "default_true")]
205    pub enable_incremental_updates: bool,
206}
207
208fn default_max_utxo_commitment_set_mb() -> u64 {
209    512
210}
211
212fn default_max_utxo_count() -> u64 {
213    100_000_000
214}
215
216fn default_max_historical_commitments() -> usize {
217    1000
218}
219
220impl Default for UtxoCommitmentConfig {
221    fn default() -> Self {
222        Self {
223            max_utxo_commitment_set_mb: 512,
224            max_utxo_count: 100_000_000,
225            max_historical_commitments: 1000,
226            enable_incremental_updates: true,
227        }
228    }
229}
230
231/// Performance and optimization configuration
232///
233/// Controls performance tuning, parallelization, and optimization features.
234/// These are operational parameters that affect performance but not consensus correctness.
235///
236/// IBD batch tuning: When `ibd_chunk_threshold` / `ibd_min_chunk_size` are `None`,
237/// hardware-derived values are used. When `Some(x)`, config overrides hardware.
238#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
239pub struct PerformanceConfig {
240    /// Number of threads for script verification (default: number of CPU cores)
241    /// Default: 0 (auto-detect from CPU count)
242    #[serde(default)]
243    pub script_verification_threads: usize,
244
245    /// Batch size for parallel transaction validation
246    /// Larger batches improve throughput but increase latency
247    /// Default: 8 transactions per batch
248    #[serde(default = "default_parallel_batch_size")]
249    pub parallel_batch_size: usize,
250
251    /// IBD batch: chunk threshold (parallelize when sig count exceeds this).
252    /// None = use hardware-derived; Some(x) = override.
253    #[serde(default, skip_serializing_if = "Option::is_none")]
254    pub ibd_chunk_threshold: Option<usize>,
255
256    /// IBD batch: minimum chunk size for parallel batches.
257    /// None = use hardware-derived; Some(x) = override.
258    #[serde(default, skip_serializing_if = "Option::is_none")]
259    pub ibd_min_chunk_size: Option<usize>,
260
261    /// Enable SIMD/vectorization optimizations (if available)
262    /// Default: true
263    #[serde(default = "default_true")]
264    pub enable_simd_optimizations: bool,
265
266    /// Enable cache-friendly memory layouts
267    /// Default: true
268    #[serde(default = "default_true")]
269    pub enable_cache_optimizations: bool,
270
271    /// Enable batch UTXO lookups (pre-fetch all UTXOs before validation)
272    /// Default: true
273    #[serde(default = "default_true")]
274    pub enable_batch_utxo_lookups: bool,
275}
276
277fn default_parallel_batch_size() -> usize {
278    8
279}
280
281impl Default for PerformanceConfig {
282    fn default() -> Self {
283        Self {
284            script_verification_threads: 0, // Auto-detect
285            parallel_batch_size: 8,
286            ibd_chunk_threshold: None,
287            ibd_min_chunk_size: None,
288            enable_simd_optimizations: true,
289            enable_cache_optimizations: true,
290            enable_batch_utxo_lookups: true,
291        }
292    }
293}
294
295/// Debug and development configuration
296///
297/// Controls debug assertions, runtime checks, and development features.
298/// These options are safe to enable in production but may impact performance.
299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
300pub struct DebugConfig {
301    /// Enable runtime assertions (debug_assert! statements)
302    /// Default: false (enabled automatically in debug builds)
303    #[serde(default = "default_false")]
304    pub enable_runtime_assertions: bool,
305
306    /// Enable runtime invariant checks (additional safety checks)
307    /// Default: false
308    #[serde(default = "default_false")]
309    pub enable_runtime_invariants: bool,
310
311    /// Enable verbose logging for consensus operations
312    /// Default: false
313    #[serde(default = "default_false")]
314    pub enable_verbose_logging: bool,
315
316    /// Enable performance profiling (timing measurements)
317    /// Default: false
318    #[serde(default = "default_false")]
319    pub enable_performance_profiling: bool,
320
321    /// Log all rejected transactions/blocks (for debugging)
322    /// Default: false
323    #[serde(default = "default_false")]
324    pub log_rejections: bool,
325}
326
327fn default_false() -> bool {
328    false
329}
330
331/// Feature flags configuration
332///
333/// Controls optional features and experimental functionality.
334/// These are safe to enable/disable without affecting consensus.
335#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
336pub struct FeatureFlagsConfig {
337    /// Enable experimental optimizations (may be unstable)
338    /// Default: false
339    #[serde(default = "default_false")]
340    pub enable_experimental_optimizations: bool,
341
342    /// Enable bounds check optimizations (requires formal proofs)
343    /// Default: true (if production feature enabled)
344    #[serde(default = "default_true")]
345    pub enable_bounds_check_optimizations: bool,
346
347    /// Enable reference implementation checks (slower but safer)
348    /// Default: false
349    #[serde(default = "default_false")]
350    pub enable_reference_checks: bool,
351
352    /// Enable aggressive caching (may use more memory)
353    /// Default: true
354    #[serde(default = "default_true")]
355    pub enable_aggressive_caching: bool,
356
357    /// Enable batch transaction ID computation (faster but uses more memory)
358    /// Default: true
359    #[serde(default = "default_true")]
360    pub enable_batch_tx_id_computation: bool,
361
362    /// Enable SIMD hash operations (faster on supported CPUs)
363    /// Default: true
364    #[serde(default = "default_true")]
365    pub enable_simd_hash_operations: bool,
366}
367
368impl Default for FeatureFlagsConfig {
369    fn default() -> Self {
370        Self {
371            enable_experimental_optimizations: false,
372            enable_bounds_check_optimizations: true,
373            enable_reference_checks: false,
374            enable_aggressive_caching: true,
375            enable_batch_tx_id_computation: true,
376            enable_simd_hash_operations: true,
377        }
378    }
379}
380
381/// Advanced configuration options
382///
383/// Advanced settings for power users and specific use cases.
384/// These options provide fine-grained control over behavior.
385#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
386pub struct AdvancedConfig {
387    /// Custom checkpoint heights (additional to assume-valid)
388    /// Format: comma-separated list of block heights
389    /// Example: "100000,200000,300000"
390    /// Default: empty (no custom checkpoints)
391    #[serde(default)]
392    pub custom_checkpoints: Vec<u64>,
393
394    /// Maximum depth for chain reorganization (safety limit)
395    /// Prevents extremely deep reorganizations that could be DoS attacks
396    /// Default: 100 blocks
397    #[serde(default = "default_max_reorg_depth")]
398    pub max_reorg_depth: u64,
399
400    /// Enable strict mode (reject any non-standard transactions)
401    /// Default: false (accept standard transactions)
402    #[serde(default = "default_false")]
403    pub strict_mode: bool,
404
405    /// Maximum block size to accept (override consensus limit for testing)
406    /// Default: 0 (use consensus limit)
407    /// WARNING: Setting this may cause consensus divergence
408    #[serde(default)]
409    pub max_block_size_override: usize,
410
411    /// Enable transaction replacement (RBF) by default
412    /// Default: true
413    #[serde(default = "default_true")]
414    pub enable_rbf: bool,
415}
416
417fn default_max_reorg_depth() -> u64 {
418    100
419}
420
421impl Default for AdvancedConfig {
422    fn default() -> Self {
423        Self {
424            custom_checkpoints: Vec::new(),
425            max_reorg_depth: 100,
426            strict_mode: false,
427            max_block_size_override: 0,
428            enable_rbf: true,
429        }
430    }
431}
432
433fn default_true() -> bool {
434    true
435}
436
437/// Complete consensus configuration
438#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
439pub struct ConsensusConfig {
440    /// Network message size limits
441    #[serde(default)]
442    pub network_limits: NetworkMessageLimits,
443
444    /// Block validation configuration
445    #[serde(default)]
446    pub block_validation: BlockValidationConfig,
447
448    /// Mempool configuration
449    #[serde(default)]
450    pub mempool: MempoolConfig,
451
452    /// UTXO commitment set configuration
453    #[serde(default)]
454    pub utxo_commitment: UtxoCommitmentConfig,
455
456    /// Performance and optimization configuration
457    #[serde(default)]
458    pub performance: PerformanceConfig,
459
460    /// Debug and development configuration
461    #[serde(default)]
462    pub debug: DebugConfig,
463
464    /// Feature flags configuration
465    #[serde(default)]
466    pub features: FeatureFlagsConfig,
467
468    /// Advanced configuration options
469    #[serde(default)]
470    pub advanced: AdvancedConfig,
471}
472
473impl ConsensusConfig {
474    /// Load configuration from environment variables
475    ///
476    /// Environment variables. Short names (e.g. BLVM_ASSUME_VALID_HEIGHT) preferred.
477    pub fn from_env() -> Self {
478        let mut config = Self::default();
479
480        // Block validation
481        if let Ok(val) = std::env::var("BLVM_ASSUME_VALID_HEIGHT") {
482            if let Ok(height) = val.parse::<u64>() {
483                config.block_validation.assume_valid_height = height;
484            }
485        }
486
487        if let Ok(val) = std::env::var("BLVM_MTP_HEADERS") {
488            if let Ok(count) = val.parse::<usize>() {
489                config.block_validation.median_time_past_headers = count;
490            }
491        }
492        if let Ok(val) = std::env::var("BLVM_PARALLEL_VALIDATION") {
493            if let Ok(enabled) = val.parse::<bool>() {
494                config.block_validation.enable_parallel_validation = enabled;
495            }
496        }
497        if let Ok(val) = std::env::var("BLVM_COINBASE_MATURITY") {
498            if let Ok(maturity) = val.parse::<u64>() {
499                config.block_validation.coinbase_maturity_override = maturity;
500            }
501        }
502        if let Ok(val) = std::env::var("BLVM_MAX_SIGOPS_COST") {
503            if let Ok(cost) = val.parse::<u64>() {
504                config.block_validation.max_block_sigops_cost_override = cost;
505            }
506        }
507
508        if let Ok(val) = std::env::var("BLVM_MAX_ADDR_ADDRESSES") {
509            if let Ok(limit) = val.parse::<usize>() {
510                config.network_limits.max_addr_addresses = limit;
511            }
512        }
513
514        if let Ok(val) = std::env::var("BLVM_MAX_INV_ITEMS") {
515            if let Ok(limit) = val.parse::<usize>() {
516                config.network_limits.max_inv_items = limit;
517            }
518        }
519
520        if let Ok(val) = std::env::var("BLVM_MAX_HEADERS") {
521            if let Ok(limit) = val.parse::<usize>() {
522                config.network_limits.max_headers = limit;
523            }
524        }
525
526        if let Ok(val) = std::env::var("BLVM_MAX_USER_AGENT_LENGTH") {
527            if let Ok(limit) = val.parse::<usize>() {
528                config.network_limits.max_user_agent_length = limit;
529            }
530        }
531
532        // Load mempool configuration
533        if let Ok(val) = std::env::var("BLVM_MEMPOOL_MB") {
534            if let Ok(mb) = val.parse::<u64>() {
535                config.mempool.max_mempool_mb = mb;
536            }
537        }
538        if let Ok(val) = std::env::var("BLVM_MEMPOOL_TXS") {
539            if let Ok(count) = val.parse::<usize>() {
540                config.mempool.max_mempool_txs = count;
541            }
542        }
543        if let Ok(val) = std::env::var("BLVM_MEMPOOL_EXPIRY_HOURS") {
544            if let Ok(hours) = val.parse::<u64>() {
545                config.mempool.mempool_expiry_hours = hours;
546            }
547        }
548        if let Ok(val) = std::env::var("BLVM_MEMPOOL_MIN_RELAY_FEE") {
549            if let Ok(rate) = val.parse::<u64>() {
550                config.mempool.min_relay_fee_rate = rate;
551            }
552        }
553        if let Ok(val) = std::env::var("BLVM_MEMPOOL_MIN_TX_FEE") {
554            if let Ok(fee) = val.parse::<i64>() {
555                config.mempool.min_tx_fee = fee;
556            }
557        }
558        if let Ok(val) = std::env::var("BLVM_MEMPOOL_RBF_FEE_INCREMENT") {
559            if let Ok(increment) = val.parse::<i64>() {
560                config.mempool.rbf_fee_increment = increment;
561            }
562        }
563
564        // Load UTXO commitment configuration
565        if let Ok(val) = std::env::var("BLVM_UTXO_COMMITMENT_MAX_SET_MB") {
566            if let Ok(mb) = val.parse::<u64>() {
567                config.utxo_commitment.max_utxo_commitment_set_mb = mb;
568            }
569        }
570        if let Ok(val) = std::env::var("BLVM_UTXO_COMMITMENT_MAX_UTXO_COUNT") {
571            if let Ok(count) = val.parse::<u64>() {
572                config.utxo_commitment.max_utxo_count = count;
573            }
574        }
575        if let Ok(val) = std::env::var("BLVM_UTXO_COMMITMENT_MAX_HISTORICAL") {
576            if let Ok(count) = val.parse::<usize>() {
577                config.utxo_commitment.max_historical_commitments = count;
578            }
579        }
580        if let Ok(val) = std::env::var("BLVM_UTXO_COMMITMENT_INCREMENTAL") {
581            if let Ok(enabled) = val.parse::<bool>() {
582                config.utxo_commitment.enable_incremental_updates = enabled;
583            }
584        }
585
586        // Load performance configuration
587        if let Ok(val) = std::env::var("BLVM_SCRIPT_THREADS") {
588            if let Ok(threads) = val.parse::<usize>() {
589                config.performance.script_verification_threads = threads;
590            }
591        }
592        if let Ok(val) = std::env::var("BLVM_PARALLEL_BATCH_SIZE") {
593            if let Ok(size) = val.parse::<usize>() {
594                config.performance.parallel_batch_size = size;
595            }
596        }
597        if let Ok(val) = std::env::var("BLVM_SIMD") {
598            if let Ok(enabled) = val.parse::<bool>() {
599                config.performance.enable_simd_optimizations = enabled;
600            }
601        }
602        if let Ok(val) = std::env::var("BLVM_CACHE_OPTIMIZATIONS") {
603            if let Ok(enabled) = val.parse::<bool>() {
604                config.performance.enable_cache_optimizations = enabled;
605            }
606        }
607        if let Ok(val) = std::env::var("BLVM_BATCH_UTXO_LOOKUPS") {
608            if let Ok(enabled) = val.parse::<bool>() {
609                config.performance.enable_batch_utxo_lookups = enabled;
610            }
611        }
612        if let Ok(val) = std::env::var("BLVM_IBD_CHUNK_THRESHOLD") {
613            if let Ok(n) = val.parse::<usize>() {
614                config.performance.ibd_chunk_threshold = Some(n);
615            }
616        }
617        if let Ok(val) = std::env::var("BLVM_IBD_MIN_CHUNK_SIZE") {
618            if let Ok(n) = val.parse::<usize>() {
619                config.performance.ibd_min_chunk_size = Some(n);
620            }
621        }
622
623        // Load debug configuration: BLVM_CONSENSUS_DEBUG=assertions,invariants,verbose,profile,rejections or =full
624        if let Ok(val) = std::env::var("BLVM_CONSENSUS_DEBUG") {
625            let parts: Vec<&str> = val.split(',').map(|s| s.trim()).collect();
626            for p in &parts {
627                match *p {
628                    "full" => {
629                        config.debug.enable_runtime_assertions = true;
630                        config.debug.enable_runtime_invariants = true;
631                        config.debug.enable_verbose_logging = true;
632                        config.debug.enable_performance_profiling = true;
633                        config.debug.log_rejections = true;
634                    }
635                    "assertions" => config.debug.enable_runtime_assertions = true,
636                    "invariants" => config.debug.enable_runtime_invariants = true,
637                    "verbose" => config.debug.enable_verbose_logging = true,
638                    "profile" => config.debug.enable_performance_profiling = true,
639                    "rejections" => config.debug.log_rejections = true,
640                    _ => {}
641                }
642            }
643        }
644
645        // Load feature flags: BLVM_CONSENSUS_FEATURES=experimental,bounds_check,reference_checks,aggressive_cache,batch_txid,simd_hash or =full
646        if let Ok(val) = std::env::var("BLVM_CONSENSUS_FEATURES") {
647            let parts: Vec<&str> = val.split(',').map(|s| s.trim()).collect();
648            for p in &parts {
649                match *p {
650                    "full" => {
651                        config.features.enable_experimental_optimizations = true;
652                        config.features.enable_bounds_check_optimizations = true;
653                        config.features.enable_reference_checks = true;
654                        config.features.enable_aggressive_caching = true;
655                        config.features.enable_batch_tx_id_computation = true;
656                        config.features.enable_simd_hash_operations = true;
657                    }
658                    "experimental" => config.features.enable_experimental_optimizations = true,
659                    "bounds_check" => config.features.enable_bounds_check_optimizations = true,
660                    "reference_checks" => config.features.enable_reference_checks = true,
661                    "aggressive_cache" => config.features.enable_aggressive_caching = true,
662                    "batch_txid" => config.features.enable_batch_tx_id_computation = true,
663                    "simd_hash" => config.features.enable_simd_hash_operations = true,
664                    _ => {}
665                }
666            }
667        }
668
669        // Load advanced configuration
670        if let Ok(val) = std::env::var("BLVM_CUSTOM_CHECKPOINTS") {
671            // Parse comma-separated list of heights
672            config.advanced.custom_checkpoints = val
673                .split(',')
674                .filter_map(|s| s.trim().parse::<u64>().ok())
675                .collect();
676        }
677        if let Ok(val) = std::env::var("BLVM_MAX_REORG_DEPTH") {
678            if let Ok(depth) = val.parse::<u64>() {
679                config.advanced.max_reorg_depth = depth;
680            }
681        }
682        if let Ok(val) = std::env::var("BLVM_STRICT_MODE") {
683            if let Ok(enabled) = val.parse::<bool>() {
684                config.advanced.strict_mode = enabled;
685            }
686        }
687        if let Ok(val) = std::env::var("BLVM_MAX_BLOCK_SIZE") {
688            if let Ok(size) = val.parse::<usize>() {
689                config.advanced.max_block_size_override = size;
690            }
691        }
692        if let Ok(val) = std::env::var("BLVM_RBF") {
693            if let Ok(enabled) = val.parse::<bool>() {
694                config.advanced.enable_rbf = enabled;
695            }
696        }
697
698        config
699    }
700
701    /// Get assume-valid height (respects benchmarking override if enabled)
702    #[cfg(feature = "production")]
703    pub fn get_assume_valid_height(&self) -> u64 {
704        // Check for benchmarking override first
705        #[cfg(feature = "benchmarking")]
706        {
707            use std::sync::atomic::{AtomicU64, Ordering};
708            static OVERRIDE: AtomicU64 = AtomicU64::new(u64::MAX);
709            let override_val = OVERRIDE.load(Ordering::Relaxed);
710            if override_val != u64::MAX {
711                return override_val;
712            }
713        }
714
715        self.block_validation.assume_valid_height
716    }
717
718    /// Get assume-valid height (non-production version)
719    #[cfg(not(feature = "production"))]
720    pub fn get_assume_valid_height(&self) -> u64 {
721        self.block_validation.assume_valid_height
722    }
723}
724
725/// Global consensus configuration (cached at first use).
726///
727/// Uses a single OnceLock — from_env() runs once, then we clone. No init_consensus_config;
728/// the node can extend from_env (e.g. config file path in env) later if needed.
729/// CRITICAL: Was re-running 50+ std::env::var() per block before caching.
730static GLOBAL_CONSENSUS_CONFIG: std::sync::OnceLock<ConsensusConfig> = std::sync::OnceLock::new();
731
732/// Initialize global consensus configuration (optional, for tests or future node use).
733///
734/// If called before any get_consensus_config(), overrides the default from-env config.
735#[allow(dead_code)] // Reserved for when node loads config from file
736pub fn init_consensus_config(config: ConsensusConfig) {
737    let _ = GLOBAL_CONSENSUS_CONFIG.set(config);
738}
739
740/// Get global consensus configuration by reference (cached; no clone).
741///
742/// Prefer this over [`get_consensus_config`] in hot paths to avoid cloning.
743pub fn get_consensus_config_ref() -> &'static ConsensusConfig {
744    GLOBAL_CONSENSUS_CONFIG.get_or_init(ConsensusConfig::from_env)
745}
746
747/// Get global consensus configuration (cached; clone for compatibility).
748pub fn get_consensus_config() -> ConsensusConfig {
749    get_consensus_config_ref().clone()
750}
751
752/// Assume-valid height (cached). Used by block and script hot paths.
753/// Runtime override for assume-valid height, shared between get/set.
754/// u64::MAX means "no override" (fall through to config).
755#[cfg(all(feature = "production", feature = "benchmarking"))]
756static ASSUME_VALID_OVERRIDE: std::sync::atomic::AtomicU64 =
757    std::sync::atomic::AtomicU64::new(u64::MAX);
758
759/// Benchmarking: set_assume_valid_height() overrides when feature enabled.
760pub fn get_assume_valid_height() -> u64 {
761    #[cfg(all(feature = "production", feature = "benchmarking"))]
762    {
763        use std::sync::atomic::Ordering;
764        let v = ASSUME_VALID_OVERRIDE.load(Ordering::Relaxed);
765        if v != u64::MAX {
766            return v;
767        }
768    }
769    get_consensus_config_ref()
770        .block_validation
771        .assume_valid_height
772}
773
774/// Assume-valid block hash. When set, block at assume_valid_height must match.
775pub fn get_assume_valid_hash() -> Option<[u8; 32]> {
776    get_consensus_config_ref()
777        .block_validation
778        .assume_valid_hash
779}
780
781/// Minimum chain work. Skip only when best_header_chainwork >= this.
782pub fn get_n_minimum_chain_work() -> u128 {
783    get_consensus_config_ref()
784        .block_validation
785        .n_minimum_chain_work
786}
787
788/// Set assume-valid height for benchmarking (overrides config).
789#[cfg(all(feature = "production", feature = "benchmarking"))]
790pub fn set_assume_valid_height(height: u64) {
791    use std::sync::atomic::Ordering;
792    ASSUME_VALID_OVERRIDE.store(height, Ordering::Relaxed);
793}
794
795/// Reset assume-valid override (benchmarking).
796#[cfg(all(feature = "production", feature = "benchmarking"))]
797pub fn reset_assume_valid_height() {
798    set_assume_valid_height(u64::MAX);
799}
800
801/// Use overlay delta for UTXO merge instead of sync_block_to_batch.
802/// Always enabled. connect_block_ibd returns UtxoDelta for the node to apply to pending_writes
803/// without re-walking the block.
804pub fn use_overlay_delta() -> bool {
805    true
806}
807
808/// Initialize Rayon thread pool for script verification.
809///
810/// Call this at node startup before any block validation.
811/// - When `script_verification_threads` > 0: use that value explicitly.
812/// - When 0: let Rayon use its default (respects RAYON_NUM_THREADS env; typically num_cpus).
813///   IBD scripts set RAYON_NUM_THREADS=nproc-1 for par-1 workers.
814///
815/// Only takes effect once per process.
816#[cfg(all(feature = "production", feature = "rayon"))]
817pub fn init_rayon_for_script_verification() {
818    use std::sync::Once;
819    static INIT: Once = Once::new();
820    INIT.call_once(|| {
821        let config = get_consensus_config_ref();
822        let n = config.performance.script_verification_threads;
823        if n > 0 {
824            if let Err(e) = rayon::ThreadPoolBuilder::new()
825                .num_threads(n)
826                .build_global()
827            {
828                eprintln!(
829                    "Warning: Failed to set Rayon script verification pool to {n} threads: {e}. Using default."
830                );
831            }
832        }
833        // n==0: Rayon uses default pool (reads RAYON_NUM_THREADS if set)
834    });
835}