1use crate::ProtocolVersion;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum ActivationMethod {
13 BIP9,
15 HeightBased,
17 Timestamp,
19 HardFork,
21 AlwaysActive,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct FeatureActivation {
28 pub feature_name: String,
30 pub activation_height: Option<u64>,
32 pub activation_timestamp: Option<u64>,
34 pub activation_method: ActivationMethod,
36 pub bip_number: Option<u32>,
38}
39
40impl FeatureActivation {
41 pub fn is_active_at(&self, height: u64, timestamp: u64) -> bool {
43 match self.activation_method {
44 ActivationMethod::AlwaysActive => true,
45 ActivationMethod::HardFork => {
46 true
48 }
49 ActivationMethod::HeightBased => {
50 if let Some(activation_height) = self.activation_height {
51 height >= activation_height
52 } else {
53 false
54 }
55 }
56 ActivationMethod::Timestamp => {
57 if let Some(activation_timestamp) = self.activation_timestamp {
58 timestamp >= activation_timestamp
59 } else {
60 false
61 }
62 }
63 ActivationMethod::BIP9 => {
64 if self.activation_height.is_some() {
73 self.activation_height.is_some_and(|h| height >= h)
74 } else {
75 self.activation_timestamp.is_some_and(|t| timestamp >= t)
76 }
77 }
78 }
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct FeatureRegistry {
85 pub protocol_version: ProtocolVersion,
87 pub features: Vec<FeatureActivation>,
89}
90
91impl FeatureRegistry {
92 pub fn for_protocol(version: ProtocolVersion) -> Self {
94 match version {
95 ProtocolVersion::BitcoinV1 => Self::mainnet(),
96 ProtocolVersion::Testnet3 => Self::testnet(),
97 ProtocolVersion::Regtest => Self::regtest(),
98 ProtocolVersion::Signet => Self::signet(),
99 ProtocolVersion::Testnet4 => Self::testnet4(),
100 }
101 }
102
103 pub fn mainnet() -> Self {
105 Self {
106 protocol_version: ProtocolVersion::BitcoinV1,
107 features: vec![
108 FeatureActivation {
110 feature_name: "segwit".to_string(),
111 activation_height: Some(481_824),
112 activation_timestamp: Some(1503539857), activation_method: ActivationMethod::BIP9,
114 bip_number: Some(141),
115 },
116 FeatureActivation {
118 feature_name: "taproot".to_string(),
119 activation_height: Some(709_632),
120 activation_timestamp: Some(1636934400), activation_method: ActivationMethod::BIP9,
122 bip_number: Some(341),
123 },
124 FeatureActivation {
126 feature_name: "rbf".to_string(),
127 activation_height: Some(0),
128 activation_timestamp: None,
129 activation_method: ActivationMethod::AlwaysActive,
130 bip_number: Some(125),
131 },
132 FeatureActivation {
134 feature_name: "ctv".to_string(),
135 activation_height: None,
136 activation_timestamp: None,
137 activation_method: ActivationMethod::BIP9,
138 bip_number: Some(119),
139 },
140 FeatureActivation {
141 feature_name: "csv".to_string(),
142 activation_height: Some(blvm_consensus::BIP112_CSV_ACTIVATION_MAINNET),
143 activation_timestamp: None,
144 activation_method: ActivationMethod::HeightBased,
145 bip_number: Some(112),
146 },
147 FeatureActivation {
148 feature_name: "cltv".to_string(),
149 activation_height: Some(blvm_consensus::BIP65_ACTIVATION_MAINNET),
150 activation_timestamp: None,
151 activation_method: ActivationMethod::HeightBased,
152 bip_number: Some(65),
153 },
154 ],
155 }
156 }
157
158 pub fn testnet() -> Self {
160 Self {
161 protocol_version: ProtocolVersion::Testnet3,
162 features: vec![
163 FeatureActivation {
164 feature_name: "segwit".to_string(),
165 activation_height: Some(blvm_consensus::SEGWIT_ACTIVATION_TESTNET),
166 activation_timestamp: Some(1493596800),
167 activation_method: ActivationMethod::BIP9,
168 bip_number: Some(141),
169 },
170 FeatureActivation {
171 feature_name: "taproot".to_string(),
172 activation_height: Some(blvm_consensus::TAPROOT_ACTIVATION_TESTNET),
173 activation_timestamp: Some(1628640000),
174 activation_method: ActivationMethod::BIP9,
175 bip_number: Some(341),
176 },
177 FeatureActivation {
179 feature_name: "rbf".to_string(),
180 activation_height: Some(0),
181 activation_timestamp: None,
182 activation_method: ActivationMethod::AlwaysActive,
183 bip_number: Some(125),
184 },
185 FeatureActivation {
186 feature_name: "csv".to_string(),
187 activation_height: Some(blvm_consensus::BIP112_CSV_ACTIVATION_TESTNET),
188 activation_timestamp: None,
189 activation_method: ActivationMethod::HeightBased,
190 bip_number: Some(112),
191 },
192 FeatureActivation {
193 feature_name: "cltv".to_string(),
194 activation_height: Some(blvm_consensus::BIP65_ACTIVATION_TESTNET),
195 activation_timestamp: None,
196 activation_method: ActivationMethod::HeightBased,
197 bip_number: Some(65),
198 },
199 ],
200 }
201 }
202
203 pub fn regtest() -> Self {
205 Self {
206 protocol_version: ProtocolVersion::Regtest,
207 features: vec![
208 FeatureActivation {
210 feature_name: "segwit".to_string(),
211 activation_height: Some(0),
212 activation_timestamp: None,
213 activation_method: ActivationMethod::AlwaysActive,
214 bip_number: Some(141),
215 },
216 FeatureActivation {
217 feature_name: "taproot".to_string(),
218 activation_height: Some(0),
219 activation_timestamp: None,
220 activation_method: ActivationMethod::AlwaysActive,
221 bip_number: Some(341),
222 },
223 FeatureActivation {
224 feature_name: "rbf".to_string(),
225 activation_height: Some(0),
226 activation_timestamp: None,
227 activation_method: ActivationMethod::AlwaysActive,
228 bip_number: Some(125),
229 },
230 FeatureActivation {
231 feature_name: "csv".to_string(),
232 activation_height: Some(0),
233 activation_timestamp: None,
234 activation_method: ActivationMethod::AlwaysActive,
235 bip_number: Some(112),
236 },
237 FeatureActivation {
238 feature_name: "cltv".to_string(),
239 activation_height: Some(0),
240 activation_timestamp: None,
241 activation_method: ActivationMethod::AlwaysActive,
242 bip_number: Some(65),
243 },
244 FeatureActivation {
245 feature_name: "fast_mining".to_string(),
246 activation_height: Some(0),
247 activation_timestamp: None,
248 activation_method: ActivationMethod::AlwaysActive,
249 bip_number: None,
250 },
251 ],
252 }
253 }
254
255 pub fn signet() -> Self {
257 Self {
258 protocol_version: ProtocolVersion::Signet,
259 features: vec![
260 FeatureActivation {
261 feature_name: "segwit".to_string(),
262 activation_height: Some(1),
263 activation_timestamp: None,
264 activation_method: ActivationMethod::HeightBased,
265 bip_number: Some(141),
266 },
267 FeatureActivation {
268 feature_name: "taproot".to_string(),
269 activation_height: Some(1),
270 activation_timestamp: None,
271 activation_method: ActivationMethod::HeightBased,
272 bip_number: Some(341),
273 },
274 FeatureActivation {
275 feature_name: "rbf".to_string(),
276 activation_height: Some(0),
277 activation_timestamp: None,
278 activation_method: ActivationMethod::AlwaysActive,
279 bip_number: Some(125),
280 },
281 FeatureActivation {
282 feature_name: "csv".to_string(),
283 activation_height: Some(1),
284 activation_timestamp: None,
285 activation_method: ActivationMethod::HeightBased,
286 bip_number: Some(112),
287 },
288 FeatureActivation {
289 feature_name: "cltv".to_string(),
290 activation_height: Some(1),
291 activation_timestamp: None,
292 activation_method: ActivationMethod::HeightBased,
293 bip_number: Some(65),
294 },
295 FeatureActivation {
296 feature_name: "signet".to_string(),
297 activation_height: Some(0),
298 activation_timestamp: None,
299 activation_method: ActivationMethod::AlwaysActive,
300 bip_number: Some(325),
301 },
302 ],
303 }
304 }
305
306 pub fn testnet4() -> Self {
308 Self {
309 protocol_version: ProtocolVersion::Testnet4,
310 features: vec![
311 FeatureActivation {
312 feature_name: "segwit".to_string(),
313 activation_height: Some(1),
314 activation_timestamp: None,
315 activation_method: ActivationMethod::HeightBased,
316 bip_number: Some(141),
317 },
318 FeatureActivation {
319 feature_name: "taproot".to_string(),
320 activation_height: Some(1),
321 activation_timestamp: None,
322 activation_method: ActivationMethod::HeightBased,
323 bip_number: Some(341),
324 },
325 FeatureActivation {
326 feature_name: "rbf".to_string(),
327 activation_height: Some(0),
328 activation_timestamp: None,
329 activation_method: ActivationMethod::AlwaysActive,
330 bip_number: Some(125),
331 },
332 FeatureActivation {
333 feature_name: "csv".to_string(),
334 activation_height: Some(1),
335 activation_timestamp: None,
336 activation_method: ActivationMethod::HeightBased,
337 bip_number: Some(112),
338 },
339 FeatureActivation {
340 feature_name: "cltv".to_string(),
341 activation_height: Some(1),
342 activation_timestamp: None,
343 activation_method: ActivationMethod::HeightBased,
344 bip_number: Some(65),
345 },
346 ],
347 }
348 }
349
350 pub fn is_feature_active(&self, feature_name: &str, height: u64, timestamp: u64) -> bool {
352 self.features
353 .iter()
354 .find(|f| f.feature_name == feature_name)
355 .map(|f| f.is_active_at(height, timestamp))
356 .unwrap_or(false)
357 }
358
359 pub fn get_feature(&self, feature_name: &str) -> Option<&FeatureActivation> {
361 self.features
362 .iter()
363 .find(|f| f.feature_name == feature_name)
364 }
365
366 pub fn list_features(&self) -> Vec<String> {
368 self.features
369 .iter()
370 .map(|f| f.feature_name.clone())
371 .collect()
372 }
373
374 pub fn create_context(&self, height: u64, timestamp: u64) -> FeatureContext {
377 FeatureContext {
378 segwit: self.is_feature_active("segwit", height, timestamp),
379 taproot: self.is_feature_active("taproot", height, timestamp),
380 csv: self.is_feature_active("csv", height, timestamp),
381 cltv: self.is_feature_active("cltv", height, timestamp),
382 rbf: self.is_feature_active("rbf", height, timestamp),
383 ctv: self.is_feature_active("ctv", height, timestamp),
384 height,
385 timestamp,
386 }
387 }
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
393pub struct FeatureContext {
394 pub segwit: bool,
396 pub taproot: bool,
398 pub csv: bool,
400 pub cltv: bool,
402 pub rbf: bool,
404 pub ctv: bool,
406 pub height: u64,
408 pub timestamp: u64,
410}
411
412impl FeatureContext {
413 pub fn from_registry(registry: &FeatureRegistry, height: u64, timestamp: u64) -> Self {
415 registry.create_context(height, timestamp)
416 }
417
418 pub fn is_active(&self, feature: &str) -> bool {
420 match feature {
421 "segwit" => self.segwit,
422 "taproot" => self.taproot,
423 "csv" => self.csv,
424 "cltv" => self.cltv,
425 "rbf" => self.rbf,
426 "ctv" => self.ctv,
427 _ => false,
428 }
429 }
430
431 pub fn active_features(&self) -> Vec<&'static str> {
433 let mut features = Vec::new();
434 if self.segwit {
435 features.push("segwit");
436 }
437 if self.taproot {
438 features.push("taproot");
439 }
440 if self.csv {
441 features.push("csv");
442 }
443 if self.cltv {
444 features.push("cltv");
445 }
446 if self.rbf {
447 features.push("rbf");
448 }
449 if self.ctv {
450 features.push("ctv");
451 }
452 features
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn test_segwit_activation_mainnet() {
462 let registry = FeatureRegistry::mainnet();
463
464 assert!(!registry.is_feature_active("segwit", 481_823, 1503539000));
466
467 assert!(registry.is_feature_active("segwit", 481_824, 1503539857));
469
470 assert!(registry.is_feature_active("segwit", 500_000, 1504000000));
472 }
473
474 #[test]
475 fn test_taproot_activation_mainnet() {
476 let registry = FeatureRegistry::mainnet();
477
478 assert!(!registry.is_feature_active("taproot", 709_631, 1636934000));
480
481 assert!(registry.is_feature_active("taproot", 709_632, 1636934400));
483
484 assert!(registry.is_feature_active("taproot", 800_000, 1640000000));
486 }
487
488 #[test]
489 fn test_always_active_features() {
490 let registry = FeatureRegistry::mainnet();
491
492 assert!(registry.is_feature_active("rbf", 0, 1231006505));
493 assert!(!registry.is_feature_active("csv", 0, 1231006505));
494 assert!(registry.is_feature_active(
495 "csv",
496 blvm_consensus::BIP112_CSV_ACTIVATION_MAINNET,
497 1231006505
498 ));
499 assert!(!registry.is_feature_active("cltv", 0, 1231006505));
500 assert!(registry.is_feature_active(
501 "cltv",
502 blvm_consensus::BIP65_ACTIVATION_MAINNET,
503 1231006505
504 ));
505 assert!(registry.is_feature_active("rbf", 1_000_000, 2000000000));
506 }
507
508 #[test]
509 fn test_regtest_all_features_active() {
510 let registry = FeatureRegistry::regtest();
511
512 assert!(registry.is_feature_active("segwit", 0, 1231006505));
514 assert!(registry.is_feature_active("taproot", 0, 1231006505));
515 assert!(registry.is_feature_active("rbf", 0, 1231006505));
516 assert!(registry.is_feature_active("fast_mining", 0, 1231006505));
517 }
518
519 #[test]
520 fn test_testnet_earlier_activations() {
521 let registry = FeatureRegistry::testnet();
522
523 assert!(!registry.is_feature_active("segwit", 500_000, 1500000000));
524 assert!(registry.is_feature_active(
525 "segwit",
526 blvm_consensus::SEGWIT_ACTIVATION_TESTNET,
527 1500000000
528 ));
529 assert!(!registry.is_feature_active(
530 "segwit",
531 blvm_consensus::SEGWIT_ACTIVATION_TESTNET - 1,
532 1500000000
533 ));
534 }
535
536 #[test]
537 fn test_feature_not_found() {
538 let registry = FeatureRegistry::mainnet();
539
540 assert!(!registry.is_feature_active("nonexistent", 1_000_000, 2000000000));
542 }
543
544 #[test]
545 fn test_get_feature() {
546 let registry = FeatureRegistry::mainnet();
547
548 let segwit = registry.get_feature("segwit").unwrap();
549 assert_eq!(segwit.feature_name, "segwit");
550 assert_eq!(segwit.bip_number, Some(141));
551 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
552
553 assert!(registry.get_feature("nonexistent").is_none());
554 }
555
556 #[test]
557 fn test_list_features() {
558 let mainnet = FeatureRegistry::mainnet();
559 let features = mainnet.list_features();
560
561 assert!(features.contains(&"segwit".to_string()));
562 assert!(features.contains(&"taproot".to_string()));
563 assert!(features.contains(&"rbf".to_string()));
564 assert!(features.contains(&"csv".to_string()));
565 assert!(features.contains(&"cltv".to_string()));
566 }
567
568 #[test]
569 fn test_activation_methods() {
570 let mainnet = FeatureRegistry::mainnet();
571
572 let segwit = mainnet.get_feature("segwit").unwrap();
573 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
574
575 let rbf = mainnet.get_feature("rbf").unwrap();
576 assert_eq!(rbf.activation_method, ActivationMethod::AlwaysActive);
577 }
578
579 #[test]
580 fn test_bip9_height_and_timestamp() {
581 let registry = FeatureRegistry::mainnet();
582
583 assert!(registry.is_feature_active("segwit", 481_824, 1500000000));
586
587 assert!(!registry.is_feature_active("segwit", 481_000, 1503539857));
591
592 assert!(registry.is_feature_active("segwit", 481_824, 1503539857));
594 }
595
596 #[test]
597 fn test_feature_context_creation() {
598 let registry = FeatureRegistry::mainnet();
599
600 let ctx_before = registry.create_context(481_823, 1503539000);
602 assert!(!ctx_before.segwit);
603 assert!(!ctx_before.taproot);
604 assert!(ctx_before.csv); assert!(ctx_before.cltv); assert!(ctx_before.rbf); let ctx_at_segwit = registry.create_context(481_824, 1503539857);
610 assert!(ctx_at_segwit.segwit);
611 assert!(!ctx_at_segwit.taproot);
612
613 let ctx_at_taproot = registry.create_context(709_632, 1636934400);
615 assert!(ctx_at_taproot.segwit);
616 assert!(ctx_at_taproot.taproot);
617
618 let ctx_after = registry.create_context(800_000, 1640000000);
620 assert!(ctx_after.segwit);
621 assert!(ctx_after.taproot);
622 }
623
624 #[test]
625 fn test_feature_context_is_active() {
626 let registry = FeatureRegistry::mainnet();
627 let ctx = registry.create_context(800_000, 1640000000);
628
629 assert!(ctx.is_active("segwit"));
630 assert!(ctx.is_active("taproot"));
631 assert!(ctx.is_active("csv"));
632 assert!(ctx.is_active("cltv"));
633 assert!(ctx.is_active("rbf"));
634 assert!(!ctx.is_active("ctv")); assert!(!ctx.is_active("nonexistent"));
636 }
637
638 #[test]
639 fn test_feature_context_active_features() {
640 let registry = FeatureRegistry::mainnet();
641
642 let ctx_before = registry.create_context(0, 1231006505);
644 let active = ctx_before.active_features();
645 assert!(!active.contains(&"csv"));
646 assert!(!active.contains(&"cltv"));
647 assert!(active.contains(&"rbf"));
648 assert!(!active.contains(&"segwit"));
649 assert!(!active.contains(&"taproot"));
650
651 let ctx_after = registry.create_context(800_000, 1640000000);
653 let active = ctx_after.active_features();
654 assert!(active.contains(&"segwit"));
655 assert!(active.contains(&"taproot"));
656 assert!(active.contains(&"csv"));
657 assert!(active.contains(&"cltv"));
658 assert!(active.contains(&"rbf"));
659 }
660
661 #[test]
662 fn test_feature_context_regtest() {
663 let registry = FeatureRegistry::regtest();
664 let ctx = registry.create_context(0, 1231006505);
665
666 assert!(ctx.segwit);
668 assert!(ctx.taproot);
669 assert!(ctx.csv);
670 assert!(ctx.cltv);
671 assert!(ctx.rbf);
672 }
673
674 #[test]
675 fn test_feature_context_from_registry() {
676 let registry = FeatureRegistry::mainnet();
677 let ctx = FeatureContext::from_registry(®istry, 800_000, 1640000000);
678
679 assert!(ctx.segwit);
680 assert!(ctx.taproot);
681 assert_eq!(ctx.height, 800_000);
682 assert_eq!(ctx.timestamp, 1640000000);
683 }
684}