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 {
142 feature_name: "csv".to_string(),
143 activation_height: Some(0),
144 activation_timestamp: None,
145 activation_method: ActivationMethod::AlwaysActive,
146 bip_number: Some(112),
147 },
148 FeatureActivation {
150 feature_name: "cltv".to_string(),
151 activation_height: Some(0),
152 activation_timestamp: None,
153 activation_method: ActivationMethod::AlwaysActive,
154 bip_number: Some(65),
155 },
156 ],
157 }
158 }
159
160 pub fn testnet() -> Self {
162 Self {
163 protocol_version: ProtocolVersion::Testnet3,
164 features: vec![
165 FeatureActivation {
167 feature_name: "segwit".to_string(),
168 activation_height: Some(465_600), activation_timestamp: Some(1493596800), activation_method: ActivationMethod::BIP9,
171 bip_number: Some(141),
172 },
173 FeatureActivation {
175 feature_name: "taproot".to_string(),
176 activation_height: Some(2_016_000), activation_timestamp: Some(1628640000), activation_method: ActivationMethod::BIP9,
179 bip_number: Some(341),
180 },
181 FeatureActivation {
183 feature_name: "rbf".to_string(),
184 activation_height: Some(0),
185 activation_timestamp: None,
186 activation_method: ActivationMethod::AlwaysActive,
187 bip_number: Some(125),
188 },
189 FeatureActivation {
191 feature_name: "csv".to_string(),
192 activation_height: Some(0),
193 activation_timestamp: None,
194 activation_method: ActivationMethod::AlwaysActive,
195 bip_number: Some(112),
196 },
197 FeatureActivation {
199 feature_name: "cltv".to_string(),
200 activation_height: Some(0),
201 activation_timestamp: None,
202 activation_method: ActivationMethod::AlwaysActive,
203 bip_number: Some(65),
204 },
205 ],
206 }
207 }
208
209 pub fn regtest() -> Self {
211 Self {
212 protocol_version: ProtocolVersion::Regtest,
213 features: vec![
214 FeatureActivation {
216 feature_name: "segwit".to_string(),
217 activation_height: Some(0),
218 activation_timestamp: None,
219 activation_method: ActivationMethod::AlwaysActive,
220 bip_number: Some(141),
221 },
222 FeatureActivation {
223 feature_name: "taproot".to_string(),
224 activation_height: Some(0),
225 activation_timestamp: None,
226 activation_method: ActivationMethod::AlwaysActive,
227 bip_number: Some(341),
228 },
229 FeatureActivation {
230 feature_name: "rbf".to_string(),
231 activation_height: Some(0),
232 activation_timestamp: None,
233 activation_method: ActivationMethod::AlwaysActive,
234 bip_number: Some(125),
235 },
236 FeatureActivation {
237 feature_name: "csv".to_string(),
238 activation_height: Some(0),
239 activation_timestamp: None,
240 activation_method: ActivationMethod::AlwaysActive,
241 bip_number: Some(112),
242 },
243 FeatureActivation {
244 feature_name: "cltv".to_string(),
245 activation_height: Some(0),
246 activation_timestamp: None,
247 activation_method: ActivationMethod::AlwaysActive,
248 bip_number: Some(65),
249 },
250 FeatureActivation {
251 feature_name: "fast_mining".to_string(),
252 activation_height: Some(0),
253 activation_timestamp: None,
254 activation_method: ActivationMethod::AlwaysActive,
255 bip_number: None,
256 },
257 ],
258 }
259 }
260
261 pub fn signet() -> Self {
263 Self {
264 protocol_version: ProtocolVersion::Signet,
265 features: vec![
266 FeatureActivation {
267 feature_name: "segwit".to_string(),
268 activation_height: Some(1),
269 activation_timestamp: None,
270 activation_method: ActivationMethod::HeightBased,
271 bip_number: Some(141),
272 },
273 FeatureActivation {
274 feature_name: "taproot".to_string(),
275 activation_height: Some(1),
276 activation_timestamp: None,
277 activation_method: ActivationMethod::HeightBased,
278 bip_number: Some(341),
279 },
280 FeatureActivation {
281 feature_name: "rbf".to_string(),
282 activation_height: Some(0),
283 activation_timestamp: None,
284 activation_method: ActivationMethod::AlwaysActive,
285 bip_number: Some(125),
286 },
287 FeatureActivation {
288 feature_name: "csv".to_string(),
289 activation_height: Some(1),
290 activation_timestamp: None,
291 activation_method: ActivationMethod::HeightBased,
292 bip_number: Some(112),
293 },
294 FeatureActivation {
295 feature_name: "cltv".to_string(),
296 activation_height: Some(1),
297 activation_timestamp: None,
298 activation_method: ActivationMethod::HeightBased,
299 bip_number: Some(65),
300 },
301 FeatureActivation {
302 feature_name: "signet".to_string(),
303 activation_height: Some(0),
304 activation_timestamp: None,
305 activation_method: ActivationMethod::AlwaysActive,
306 bip_number: Some(325),
307 },
308 ],
309 }
310 }
311
312 pub fn testnet4() -> Self {
314 Self {
315 protocol_version: ProtocolVersion::Testnet4,
316 features: vec![
317 FeatureActivation {
318 feature_name: "segwit".to_string(),
319 activation_height: Some(1),
320 activation_timestamp: None,
321 activation_method: ActivationMethod::HeightBased,
322 bip_number: Some(141),
323 },
324 FeatureActivation {
325 feature_name: "taproot".to_string(),
326 activation_height: Some(1),
327 activation_timestamp: None,
328 activation_method: ActivationMethod::HeightBased,
329 bip_number: Some(341),
330 },
331 FeatureActivation {
332 feature_name: "rbf".to_string(),
333 activation_height: Some(0),
334 activation_timestamp: None,
335 activation_method: ActivationMethod::AlwaysActive,
336 bip_number: Some(125),
337 },
338 FeatureActivation {
339 feature_name: "csv".to_string(),
340 activation_height: Some(1),
341 activation_timestamp: None,
342 activation_method: ActivationMethod::HeightBased,
343 bip_number: Some(112),
344 },
345 FeatureActivation {
346 feature_name: "cltv".to_string(),
347 activation_height: Some(1),
348 activation_timestamp: None,
349 activation_method: ActivationMethod::HeightBased,
350 bip_number: Some(65),
351 },
352 ],
353 }
354 }
355
356 pub fn is_feature_active(&self, feature_name: &str, height: u64, timestamp: u64) -> bool {
358 self.features
359 .iter()
360 .find(|f| f.feature_name == feature_name)
361 .map(|f| f.is_active_at(height, timestamp))
362 .unwrap_or(false)
363 }
364
365 pub fn get_feature(&self, feature_name: &str) -> Option<&FeatureActivation> {
367 self.features
368 .iter()
369 .find(|f| f.feature_name == feature_name)
370 }
371
372 pub fn list_features(&self) -> Vec<String> {
374 self.features
375 .iter()
376 .map(|f| f.feature_name.clone())
377 .collect()
378 }
379
380 pub fn create_context(&self, height: u64, timestamp: u64) -> FeatureContext {
383 FeatureContext {
384 segwit: self.is_feature_active("segwit", height, timestamp),
385 taproot: self.is_feature_active("taproot", height, timestamp),
386 csv: self.is_feature_active("csv", height, timestamp),
387 cltv: self.is_feature_active("cltv", height, timestamp),
388 rbf: self.is_feature_active("rbf", height, timestamp),
389 ctv: self.is_feature_active("ctv", height, timestamp),
390 height,
391 timestamp,
392 }
393 }
394}
395
396#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
399pub struct FeatureContext {
400 pub segwit: bool,
402 pub taproot: bool,
404 pub csv: bool,
406 pub cltv: bool,
408 pub rbf: bool,
410 pub ctv: bool,
412 pub height: u64,
414 pub timestamp: u64,
416}
417
418impl FeatureContext {
419 pub fn from_registry(registry: &FeatureRegistry, height: u64, timestamp: u64) -> Self {
421 registry.create_context(height, timestamp)
422 }
423
424 pub fn is_active(&self, feature: &str) -> bool {
426 match feature {
427 "segwit" => self.segwit,
428 "taproot" => self.taproot,
429 "csv" => self.csv,
430 "cltv" => self.cltv,
431 "rbf" => self.rbf,
432 "ctv" => self.ctv,
433 _ => false,
434 }
435 }
436
437 pub fn active_features(&self) -> Vec<&'static str> {
439 let mut features = Vec::new();
440 if self.segwit {
441 features.push("segwit");
442 }
443 if self.taproot {
444 features.push("taproot");
445 }
446 if self.csv {
447 features.push("csv");
448 }
449 if self.cltv {
450 features.push("cltv");
451 }
452 if self.rbf {
453 features.push("rbf");
454 }
455 if self.ctv {
456 features.push("ctv");
457 }
458 features
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 #[test]
467 fn test_segwit_activation_mainnet() {
468 let registry = FeatureRegistry::mainnet();
469
470 assert!(!registry.is_feature_active("segwit", 481_823, 1503539000));
472
473 assert!(registry.is_feature_active("segwit", 481_824, 1503539857));
475
476 assert!(registry.is_feature_active("segwit", 500_000, 1504000000));
478 }
479
480 #[test]
481 fn test_taproot_activation_mainnet() {
482 let registry = FeatureRegistry::mainnet();
483
484 assert!(!registry.is_feature_active("taproot", 709_631, 1636934000));
486
487 assert!(registry.is_feature_active("taproot", 709_632, 1636934400));
489
490 assert!(registry.is_feature_active("taproot", 800_000, 1640000000));
492 }
493
494 #[test]
495 fn test_always_active_features() {
496 let registry = FeatureRegistry::mainnet();
497
498 assert!(registry.is_feature_active("rbf", 0, 1231006505));
500 assert!(registry.is_feature_active("csv", 0, 1231006505));
501 assert!(registry.is_feature_active("cltv", 0, 1231006505));
502 assert!(registry.is_feature_active("rbf", 1_000_000, 2000000000));
503 }
504
505 #[test]
506 fn test_regtest_all_features_active() {
507 let registry = FeatureRegistry::regtest();
508
509 assert!(registry.is_feature_active("segwit", 0, 1231006505));
511 assert!(registry.is_feature_active("taproot", 0, 1231006505));
512 assert!(registry.is_feature_active("rbf", 0, 1231006505));
513 assert!(registry.is_feature_active("fast_mining", 0, 1231006505));
514 }
515
516 #[test]
517 fn test_testnet_earlier_activations() {
518 let registry = FeatureRegistry::testnet();
519
520 assert!(!registry.is_feature_active("segwit", 465_599, 1493596000));
522 assert!(registry.is_feature_active("segwit", 465_600, 1493596800));
523 assert!(registry.is_feature_active("segwit", 500_000, 1500000000));
524 }
525
526 #[test]
527 fn test_feature_not_found() {
528 let registry = FeatureRegistry::mainnet();
529
530 assert!(!registry.is_feature_active("nonexistent", 1_000_000, 2000000000));
532 }
533
534 #[test]
535 fn test_get_feature() {
536 let registry = FeatureRegistry::mainnet();
537
538 let segwit = registry.get_feature("segwit").unwrap();
539 assert_eq!(segwit.feature_name, "segwit");
540 assert_eq!(segwit.bip_number, Some(141));
541 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
542
543 assert!(registry.get_feature("nonexistent").is_none());
544 }
545
546 #[test]
547 fn test_list_features() {
548 let mainnet = FeatureRegistry::mainnet();
549 let features = mainnet.list_features();
550
551 assert!(features.contains(&"segwit".to_string()));
552 assert!(features.contains(&"taproot".to_string()));
553 assert!(features.contains(&"rbf".to_string()));
554 assert!(features.contains(&"csv".to_string()));
555 assert!(features.contains(&"cltv".to_string()));
556 }
557
558 #[test]
559 fn test_activation_methods() {
560 let mainnet = FeatureRegistry::mainnet();
561
562 let segwit = mainnet.get_feature("segwit").unwrap();
563 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
564
565 let rbf = mainnet.get_feature("rbf").unwrap();
566 assert_eq!(rbf.activation_method, ActivationMethod::AlwaysActive);
567 }
568
569 #[test]
570 fn test_bip9_height_and_timestamp() {
571 let registry = FeatureRegistry::mainnet();
572
573 assert!(registry.is_feature_active("segwit", 481_824, 1500000000));
576
577 assert!(!registry.is_feature_active("segwit", 481_000, 1503539857));
581
582 assert!(registry.is_feature_active("segwit", 481_824, 1503539857));
584 }
585
586 #[test]
587 fn test_feature_context_creation() {
588 let registry = FeatureRegistry::mainnet();
589
590 let ctx_before = registry.create_context(481_823, 1503539000);
592 assert!(!ctx_before.segwit);
593 assert!(!ctx_before.taproot);
594 assert!(ctx_before.csv); assert!(ctx_before.cltv); assert!(ctx_before.rbf); let ctx_at_segwit = registry.create_context(481_824, 1503539857);
600 assert!(ctx_at_segwit.segwit);
601 assert!(!ctx_at_segwit.taproot);
602
603 let ctx_at_taproot = registry.create_context(709_632, 1636934400);
605 assert!(ctx_at_taproot.segwit);
606 assert!(ctx_at_taproot.taproot);
607
608 let ctx_after = registry.create_context(800_000, 1640000000);
610 assert!(ctx_after.segwit);
611 assert!(ctx_after.taproot);
612 }
613
614 #[test]
615 fn test_feature_context_is_active() {
616 let registry = FeatureRegistry::mainnet();
617 let ctx = registry.create_context(800_000, 1640000000);
618
619 assert!(ctx.is_active("segwit"));
620 assert!(ctx.is_active("taproot"));
621 assert!(ctx.is_active("csv"));
622 assert!(ctx.is_active("cltv"));
623 assert!(ctx.is_active("rbf"));
624 assert!(!ctx.is_active("ctv")); assert!(!ctx.is_active("nonexistent"));
626 }
627
628 #[test]
629 fn test_feature_context_active_features() {
630 let registry = FeatureRegistry::mainnet();
631
632 let ctx_before = registry.create_context(0, 1231006505);
634 let active = ctx_before.active_features();
635 assert!(active.contains(&"csv"));
636 assert!(active.contains(&"cltv"));
637 assert!(active.contains(&"rbf"));
638 assert!(!active.contains(&"segwit"));
639 assert!(!active.contains(&"taproot"));
640
641 let ctx_after = registry.create_context(800_000, 1640000000);
643 let active = ctx_after.active_features();
644 assert!(active.contains(&"segwit"));
645 assert!(active.contains(&"taproot"));
646 assert!(active.contains(&"csv"));
647 assert!(active.contains(&"cltv"));
648 assert!(active.contains(&"rbf"));
649 }
650
651 #[test]
652 fn test_feature_context_regtest() {
653 let registry = FeatureRegistry::regtest();
654 let ctx = registry.create_context(0, 1231006505);
655
656 assert!(ctx.segwit);
658 assert!(ctx.taproot);
659 assert!(ctx.csv);
660 assert!(ctx.cltv);
661 assert!(ctx.rbf);
662 }
663
664 #[test]
665 fn test_feature_context_from_registry() {
666 let registry = FeatureRegistry::mainnet();
667 let ctx = FeatureContext::from_registry(®istry, 800_000, 1640000000);
668
669 assert!(ctx.segwit);
670 assert!(ctx.taproot);
671 assert_eq!(ctx.height, 800_000);
672 assert_eq!(ctx.timestamp, 1640000000);
673 }
674}