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 }
100 }
101
102 pub fn mainnet() -> Self {
104 Self {
105 protocol_version: ProtocolVersion::BitcoinV1,
106 features: vec![
107 FeatureActivation {
109 feature_name: "segwit".to_string(),
110 activation_height: Some(481_824),
111 activation_timestamp: Some(1503539857), activation_method: ActivationMethod::BIP9,
113 bip_number: Some(141),
114 },
115 FeatureActivation {
117 feature_name: "taproot".to_string(),
118 activation_height: Some(709_632),
119 activation_timestamp: Some(1636934400), activation_method: ActivationMethod::BIP9,
121 bip_number: Some(341),
122 },
123 FeatureActivation {
125 feature_name: "rbf".to_string(),
126 activation_height: Some(0),
127 activation_timestamp: None,
128 activation_method: ActivationMethod::AlwaysActive,
129 bip_number: Some(125),
130 },
131 FeatureActivation {
133 feature_name: "ctv".to_string(),
134 activation_height: None,
135 activation_timestamp: None,
136 activation_method: ActivationMethod::BIP9,
137 bip_number: Some(119),
138 },
139 FeatureActivation {
141 feature_name: "csv".to_string(),
142 activation_height: Some(0),
143 activation_timestamp: None,
144 activation_method: ActivationMethod::AlwaysActive,
145 bip_number: Some(112),
146 },
147 FeatureActivation {
149 feature_name: "cltv".to_string(),
150 activation_height: Some(0),
151 activation_timestamp: None,
152 activation_method: ActivationMethod::AlwaysActive,
153 bip_number: Some(65),
154 },
155 ],
156 }
157 }
158
159 pub fn testnet() -> Self {
161 Self {
162 protocol_version: ProtocolVersion::Testnet3,
163 features: vec![
164 FeatureActivation {
166 feature_name: "segwit".to_string(),
167 activation_height: Some(465_600), activation_timestamp: Some(1493596800), activation_method: ActivationMethod::BIP9,
170 bip_number: Some(141),
171 },
172 FeatureActivation {
174 feature_name: "taproot".to_string(),
175 activation_height: Some(2_016_000), activation_timestamp: Some(1628640000), activation_method: ActivationMethod::BIP9,
178 bip_number: Some(341),
179 },
180 FeatureActivation {
182 feature_name: "rbf".to_string(),
183 activation_height: Some(0),
184 activation_timestamp: None,
185 activation_method: ActivationMethod::AlwaysActive,
186 bip_number: Some(125),
187 },
188 FeatureActivation {
190 feature_name: "csv".to_string(),
191 activation_height: Some(0),
192 activation_timestamp: None,
193 activation_method: ActivationMethod::AlwaysActive,
194 bip_number: Some(112),
195 },
196 FeatureActivation {
198 feature_name: "cltv".to_string(),
199 activation_height: Some(0),
200 activation_timestamp: None,
201 activation_method: ActivationMethod::AlwaysActive,
202 bip_number: Some(65),
203 },
204 ],
205 }
206 }
207
208 pub fn regtest() -> Self {
210 Self {
211 protocol_version: ProtocolVersion::Regtest,
212 features: vec![
213 FeatureActivation {
215 feature_name: "segwit".to_string(),
216 activation_height: Some(0),
217 activation_timestamp: None,
218 activation_method: ActivationMethod::AlwaysActive,
219 bip_number: Some(141),
220 },
221 FeatureActivation {
222 feature_name: "taproot".to_string(),
223 activation_height: Some(0),
224 activation_timestamp: None,
225 activation_method: ActivationMethod::AlwaysActive,
226 bip_number: Some(341),
227 },
228 FeatureActivation {
229 feature_name: "rbf".to_string(),
230 activation_height: Some(0),
231 activation_timestamp: None,
232 activation_method: ActivationMethod::AlwaysActive,
233 bip_number: Some(125),
234 },
235 FeatureActivation {
236 feature_name: "csv".to_string(),
237 activation_height: Some(0),
238 activation_timestamp: None,
239 activation_method: ActivationMethod::AlwaysActive,
240 bip_number: Some(112),
241 },
242 FeatureActivation {
243 feature_name: "cltv".to_string(),
244 activation_height: Some(0),
245 activation_timestamp: None,
246 activation_method: ActivationMethod::AlwaysActive,
247 bip_number: Some(65),
248 },
249 FeatureActivation {
250 feature_name: "fast_mining".to_string(),
251 activation_height: Some(0),
252 activation_timestamp: None,
253 activation_method: ActivationMethod::AlwaysActive,
254 bip_number: None,
255 },
256 ],
257 }
258 }
259
260 pub fn signet() -> Self {
262 Self {
263 protocol_version: ProtocolVersion::Signet,
264 features: vec![
265 FeatureActivation {
266 feature_name: "segwit".to_string(),
267 activation_height: Some(1),
268 activation_timestamp: None,
269 activation_method: ActivationMethod::HeightBased,
270 bip_number: Some(141),
271 },
272 FeatureActivation {
273 feature_name: "taproot".to_string(),
274 activation_height: Some(1),
275 activation_timestamp: None,
276 activation_method: ActivationMethod::HeightBased,
277 bip_number: Some(341),
278 },
279 FeatureActivation {
280 feature_name: "rbf".to_string(),
281 activation_height: Some(0),
282 activation_timestamp: None,
283 activation_method: ActivationMethod::AlwaysActive,
284 bip_number: Some(125),
285 },
286 FeatureActivation {
287 feature_name: "csv".to_string(),
288 activation_height: Some(1),
289 activation_timestamp: None,
290 activation_method: ActivationMethod::HeightBased,
291 bip_number: Some(112),
292 },
293 FeatureActivation {
294 feature_name: "cltv".to_string(),
295 activation_height: Some(1),
296 activation_timestamp: None,
297 activation_method: ActivationMethod::HeightBased,
298 bip_number: Some(65),
299 },
300 FeatureActivation {
301 feature_name: "signet".to_string(),
302 activation_height: Some(0),
303 activation_timestamp: None,
304 activation_method: ActivationMethod::AlwaysActive,
305 bip_number: Some(325),
306 },
307 ],
308 }
309 }
310
311 pub fn is_feature_active(&self, feature_name: &str, height: u64, timestamp: u64) -> bool {
313 self.features
314 .iter()
315 .find(|f| f.feature_name == feature_name)
316 .map(|f| f.is_active_at(height, timestamp))
317 .unwrap_or(false)
318 }
319
320 pub fn get_feature(&self, feature_name: &str) -> Option<&FeatureActivation> {
322 self.features
323 .iter()
324 .find(|f| f.feature_name == feature_name)
325 }
326
327 pub fn list_features(&self) -> Vec<String> {
329 self.features
330 .iter()
331 .map(|f| f.feature_name.clone())
332 .collect()
333 }
334
335 pub fn create_context(&self, height: u64, timestamp: u64) -> FeatureContext {
338 FeatureContext {
339 segwit: self.is_feature_active("segwit", height, timestamp),
340 taproot: self.is_feature_active("taproot", height, timestamp),
341 csv: self.is_feature_active("csv", height, timestamp),
342 cltv: self.is_feature_active("cltv", height, timestamp),
343 rbf: self.is_feature_active("rbf", height, timestamp),
344 ctv: self.is_feature_active("ctv", height, timestamp),
345 height,
346 timestamp,
347 }
348 }
349}
350
351#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
354pub struct FeatureContext {
355 pub segwit: bool,
357 pub taproot: bool,
359 pub csv: bool,
361 pub cltv: bool,
363 pub rbf: bool,
365 pub ctv: bool,
367 pub height: u64,
369 pub timestamp: u64,
371}
372
373impl FeatureContext {
374 pub fn from_registry(registry: &FeatureRegistry, height: u64, timestamp: u64) -> Self {
376 registry.create_context(height, timestamp)
377 }
378
379 pub fn is_active(&self, feature: &str) -> bool {
381 match feature {
382 "segwit" => self.segwit,
383 "taproot" => self.taproot,
384 "csv" => self.csv,
385 "cltv" => self.cltv,
386 "rbf" => self.rbf,
387 "ctv" => self.ctv,
388 _ => false,
389 }
390 }
391
392 pub fn active_features(&self) -> Vec<&'static str> {
394 let mut features = Vec::new();
395 if self.segwit {
396 features.push("segwit");
397 }
398 if self.taproot {
399 features.push("taproot");
400 }
401 if self.csv {
402 features.push("csv");
403 }
404 if self.cltv {
405 features.push("cltv");
406 }
407 if self.rbf {
408 features.push("rbf");
409 }
410 if self.ctv {
411 features.push("ctv");
412 }
413 features
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_segwit_activation_mainnet() {
423 let registry = FeatureRegistry::mainnet();
424
425 assert!(!registry.is_feature_active("segwit", 481_823, 1503539000));
427
428 assert!(registry.is_feature_active("segwit", 481_824, 1503539857));
430
431 assert!(registry.is_feature_active("segwit", 500_000, 1504000000));
433 }
434
435 #[test]
436 fn test_taproot_activation_mainnet() {
437 let registry = FeatureRegistry::mainnet();
438
439 assert!(!registry.is_feature_active("taproot", 709_631, 1636934000));
441
442 assert!(registry.is_feature_active("taproot", 709_632, 1636934400));
444
445 assert!(registry.is_feature_active("taproot", 800_000, 1640000000));
447 }
448
449 #[test]
450 fn test_always_active_features() {
451 let registry = FeatureRegistry::mainnet();
452
453 assert!(registry.is_feature_active("rbf", 0, 1231006505));
455 assert!(registry.is_feature_active("csv", 0, 1231006505));
456 assert!(registry.is_feature_active("cltv", 0, 1231006505));
457 assert!(registry.is_feature_active("rbf", 1_000_000, 2000000000));
458 }
459
460 #[test]
461 fn test_regtest_all_features_active() {
462 let registry = FeatureRegistry::regtest();
463
464 assert!(registry.is_feature_active("segwit", 0, 1231006505));
466 assert!(registry.is_feature_active("taproot", 0, 1231006505));
467 assert!(registry.is_feature_active("rbf", 0, 1231006505));
468 assert!(registry.is_feature_active("fast_mining", 0, 1231006505));
469 }
470
471 #[test]
472 fn test_testnet_earlier_activations() {
473 let registry = FeatureRegistry::testnet();
474
475 assert!(!registry.is_feature_active("segwit", 465_599, 1493596000));
477 assert!(registry.is_feature_active("segwit", 465_600, 1493596800));
478 assert!(registry.is_feature_active("segwit", 500_000, 1500000000));
479 }
480
481 #[test]
482 fn test_feature_not_found() {
483 let registry = FeatureRegistry::mainnet();
484
485 assert!(!registry.is_feature_active("nonexistent", 1_000_000, 2000000000));
487 }
488
489 #[test]
490 fn test_get_feature() {
491 let registry = FeatureRegistry::mainnet();
492
493 let segwit = registry.get_feature("segwit").unwrap();
494 assert_eq!(segwit.feature_name, "segwit");
495 assert_eq!(segwit.bip_number, Some(141));
496 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
497
498 assert!(registry.get_feature("nonexistent").is_none());
499 }
500
501 #[test]
502 fn test_list_features() {
503 let mainnet = FeatureRegistry::mainnet();
504 let features = mainnet.list_features();
505
506 assert!(features.contains(&"segwit".to_string()));
507 assert!(features.contains(&"taproot".to_string()));
508 assert!(features.contains(&"rbf".to_string()));
509 assert!(features.contains(&"csv".to_string()));
510 assert!(features.contains(&"cltv".to_string()));
511 }
512
513 #[test]
514 fn test_activation_methods() {
515 let mainnet = FeatureRegistry::mainnet();
516
517 let segwit = mainnet.get_feature("segwit").unwrap();
518 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
519
520 let rbf = mainnet.get_feature("rbf").unwrap();
521 assert_eq!(rbf.activation_method, ActivationMethod::AlwaysActive);
522 }
523
524 #[test]
525 fn test_bip9_height_and_timestamp() {
526 let registry = FeatureRegistry::mainnet();
527
528 assert!(registry.is_feature_active("segwit", 481_824, 1500000000));
531
532 assert!(!registry.is_feature_active("segwit", 481_000, 1503539857));
536
537 assert!(registry.is_feature_active("segwit", 481_824, 1503539857));
539 }
540
541 #[test]
542 fn test_feature_context_creation() {
543 let registry = FeatureRegistry::mainnet();
544
545 let ctx_before = registry.create_context(481_823, 1503539000);
547 assert!(!ctx_before.segwit);
548 assert!(!ctx_before.taproot);
549 assert!(ctx_before.csv); assert!(ctx_before.cltv); assert!(ctx_before.rbf); let ctx_at_segwit = registry.create_context(481_824, 1503539857);
555 assert!(ctx_at_segwit.segwit);
556 assert!(!ctx_at_segwit.taproot);
557
558 let ctx_at_taproot = registry.create_context(709_632, 1636934400);
560 assert!(ctx_at_taproot.segwit);
561 assert!(ctx_at_taproot.taproot);
562
563 let ctx_after = registry.create_context(800_000, 1640000000);
565 assert!(ctx_after.segwit);
566 assert!(ctx_after.taproot);
567 }
568
569 #[test]
570 fn test_feature_context_is_active() {
571 let registry = FeatureRegistry::mainnet();
572 let ctx = registry.create_context(800_000, 1640000000);
573
574 assert!(ctx.is_active("segwit"));
575 assert!(ctx.is_active("taproot"));
576 assert!(ctx.is_active("csv"));
577 assert!(ctx.is_active("cltv"));
578 assert!(ctx.is_active("rbf"));
579 assert!(!ctx.is_active("ctv")); assert!(!ctx.is_active("nonexistent"));
581 }
582
583 #[test]
584 fn test_feature_context_active_features() {
585 let registry = FeatureRegistry::mainnet();
586
587 let ctx_before = registry.create_context(0, 1231006505);
589 let active = ctx_before.active_features();
590 assert!(active.contains(&"csv"));
591 assert!(active.contains(&"cltv"));
592 assert!(active.contains(&"rbf"));
593 assert!(!active.contains(&"segwit"));
594 assert!(!active.contains(&"taproot"));
595
596 let ctx_after = registry.create_context(800_000, 1640000000);
598 let active = ctx_after.active_features();
599 assert!(active.contains(&"segwit"));
600 assert!(active.contains(&"taproot"));
601 assert!(active.contains(&"csv"));
602 assert!(active.contains(&"cltv"));
603 assert!(active.contains(&"rbf"));
604 }
605
606 #[test]
607 fn test_feature_context_regtest() {
608 let registry = FeatureRegistry::regtest();
609 let ctx = registry.create_context(0, 1231006505);
610
611 assert!(ctx.segwit);
613 assert!(ctx.taproot);
614 assert!(ctx.csv);
615 assert!(ctx.cltv);
616 assert!(ctx.rbf);
617 }
618
619 #[test]
620 fn test_feature_context_from_registry() {
621 let registry = FeatureRegistry::mainnet();
622 let ctx = FeatureContext::from_registry(®istry, 800_000, 1640000000);
623
624 assert!(ctx.segwit);
625 assert!(ctx.taproot);
626 assert_eq!(ctx.height, 800_000);
627 assert_eq!(ctx.timestamp, 1640000000);
628 }
629}