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 }
99 }
100
101 pub fn mainnet() -> Self {
103 Self {
104 protocol_version: ProtocolVersion::BitcoinV1,
105 features: vec![
106 FeatureActivation {
108 feature_name: "segwit".to_string(),
109 activation_height: Some(481_824),
110 activation_timestamp: Some(1503539857), activation_method: ActivationMethod::BIP9,
112 bip_number: Some(141),
113 },
114 FeatureActivation {
116 feature_name: "taproot".to_string(),
117 activation_height: Some(709_632),
118 activation_timestamp: Some(1636934400), activation_method: ActivationMethod::BIP9,
120 bip_number: Some(341),
121 },
122 FeatureActivation {
124 feature_name: "rbf".to_string(),
125 activation_height: Some(0),
126 activation_timestamp: None,
127 activation_method: ActivationMethod::AlwaysActive,
128 bip_number: Some(125),
129 },
130 FeatureActivation {
132 feature_name: "ctv".to_string(),
133 activation_height: None,
134 activation_timestamp: None,
135 activation_method: ActivationMethod::BIP9,
136 bip_number: Some(119),
137 },
138 FeatureActivation {
140 feature_name: "csv".to_string(),
141 activation_height: Some(0),
142 activation_timestamp: None,
143 activation_method: ActivationMethod::AlwaysActive,
144 bip_number: Some(112),
145 },
146 FeatureActivation {
148 feature_name: "cltv".to_string(),
149 activation_height: Some(0),
150 activation_timestamp: None,
151 activation_method: ActivationMethod::AlwaysActive,
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 {
165 feature_name: "segwit".to_string(),
166 activation_height: Some(465_600), activation_timestamp: Some(1493596800), activation_method: ActivationMethod::BIP9,
169 bip_number: Some(141),
170 },
171 FeatureActivation {
173 feature_name: "taproot".to_string(),
174 activation_height: Some(2_016_000), activation_timestamp: Some(1628640000), activation_method: ActivationMethod::BIP9,
177 bip_number: Some(341),
178 },
179 FeatureActivation {
181 feature_name: "rbf".to_string(),
182 activation_height: Some(0),
183 activation_timestamp: None,
184 activation_method: ActivationMethod::AlwaysActive,
185 bip_number: Some(125),
186 },
187 FeatureActivation {
189 feature_name: "csv".to_string(),
190 activation_height: Some(0),
191 activation_timestamp: None,
192 activation_method: ActivationMethod::AlwaysActive,
193 bip_number: Some(112),
194 },
195 FeatureActivation {
197 feature_name: "cltv".to_string(),
198 activation_height: Some(0),
199 activation_timestamp: None,
200 activation_method: ActivationMethod::AlwaysActive,
201 bip_number: Some(65),
202 },
203 ],
204 }
205 }
206
207 pub fn regtest() -> Self {
209 Self {
210 protocol_version: ProtocolVersion::Regtest,
211 features: vec![
212 FeatureActivation {
214 feature_name: "segwit".to_string(),
215 activation_height: Some(0),
216 activation_timestamp: None,
217 activation_method: ActivationMethod::AlwaysActive,
218 bip_number: Some(141),
219 },
220 FeatureActivation {
221 feature_name: "taproot".to_string(),
222 activation_height: Some(0),
223 activation_timestamp: None,
224 activation_method: ActivationMethod::AlwaysActive,
225 bip_number: Some(341),
226 },
227 FeatureActivation {
228 feature_name: "rbf".to_string(),
229 activation_height: Some(0),
230 activation_timestamp: None,
231 activation_method: ActivationMethod::AlwaysActive,
232 bip_number: Some(125),
233 },
234 FeatureActivation {
235 feature_name: "csv".to_string(),
236 activation_height: Some(0),
237 activation_timestamp: None,
238 activation_method: ActivationMethod::AlwaysActive,
239 bip_number: Some(112),
240 },
241 FeatureActivation {
242 feature_name: "cltv".to_string(),
243 activation_height: Some(0),
244 activation_timestamp: None,
245 activation_method: ActivationMethod::AlwaysActive,
246 bip_number: Some(65),
247 },
248 FeatureActivation {
249 feature_name: "fast_mining".to_string(),
250 activation_height: Some(0),
251 activation_timestamp: None,
252 activation_method: ActivationMethod::AlwaysActive,
253 bip_number: None,
254 },
255 ],
256 }
257 }
258
259 pub fn is_feature_active(&self, feature_name: &str, height: u64, timestamp: u64) -> bool {
261 self.features
262 .iter()
263 .find(|f| f.feature_name == feature_name)
264 .map(|f| f.is_active_at(height, timestamp))
265 .unwrap_or(false)
266 }
267
268 pub fn get_feature(&self, feature_name: &str) -> Option<&FeatureActivation> {
270 self.features
271 .iter()
272 .find(|f| f.feature_name == feature_name)
273 }
274
275 pub fn list_features(&self) -> Vec<String> {
277 self.features
278 .iter()
279 .map(|f| f.feature_name.clone())
280 .collect()
281 }
282
283 pub fn create_context(&self, height: u64, timestamp: u64) -> FeatureContext {
286 FeatureContext {
287 segwit: self.is_feature_active("segwit", height, timestamp),
288 taproot: self.is_feature_active("taproot", height, timestamp),
289 csv: self.is_feature_active("csv", height, timestamp),
290 cltv: self.is_feature_active("cltv", height, timestamp),
291 rbf: self.is_feature_active("rbf", height, timestamp),
292 ctv: self.is_feature_active("ctv", height, timestamp),
293 height,
294 timestamp,
295 }
296 }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
302pub struct FeatureContext {
303 pub segwit: bool,
305 pub taproot: bool,
307 pub csv: bool,
309 pub cltv: bool,
311 pub rbf: bool,
313 pub ctv: bool,
315 pub height: u64,
317 pub timestamp: u64,
319}
320
321impl FeatureContext {
322 pub fn from_registry(registry: &FeatureRegistry, height: u64, timestamp: u64) -> Self {
324 registry.create_context(height, timestamp)
325 }
326
327 pub fn is_active(&self, feature: &str) -> bool {
329 match feature {
330 "segwit" => self.segwit,
331 "taproot" => self.taproot,
332 "csv" => self.csv,
333 "cltv" => self.cltv,
334 "rbf" => self.rbf,
335 "ctv" => self.ctv,
336 _ => false,
337 }
338 }
339
340 pub fn active_features(&self) -> Vec<&'static str> {
342 let mut features = Vec::new();
343 if self.segwit {
344 features.push("segwit");
345 }
346 if self.taproot {
347 features.push("taproot");
348 }
349 if self.csv {
350 features.push("csv");
351 }
352 if self.cltv {
353 features.push("cltv");
354 }
355 if self.rbf {
356 features.push("rbf");
357 }
358 if self.ctv {
359 features.push("ctv");
360 }
361 features
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn test_segwit_activation_mainnet() {
371 let registry = FeatureRegistry::mainnet();
372
373 assert!(!registry.is_feature_active("segwit", 481_823, 1503539000));
375
376 assert!(registry.is_feature_active("segwit", 481_824, 1503539857));
378
379 assert!(registry.is_feature_active("segwit", 500_000, 1504000000));
381 }
382
383 #[test]
384 fn test_taproot_activation_mainnet() {
385 let registry = FeatureRegistry::mainnet();
386
387 assert!(!registry.is_feature_active("taproot", 709_631, 1636934000));
389
390 assert!(registry.is_feature_active("taproot", 709_632, 1636934400));
392
393 assert!(registry.is_feature_active("taproot", 800_000, 1640000000));
395 }
396
397 #[test]
398 fn test_always_active_features() {
399 let registry = FeatureRegistry::mainnet();
400
401 assert!(registry.is_feature_active("rbf", 0, 1231006505));
403 assert!(registry.is_feature_active("csv", 0, 1231006505));
404 assert!(registry.is_feature_active("cltv", 0, 1231006505));
405 assert!(registry.is_feature_active("rbf", 1_000_000, 2000000000));
406 }
407
408 #[test]
409 fn test_regtest_all_features_active() {
410 let registry = FeatureRegistry::regtest();
411
412 assert!(registry.is_feature_active("segwit", 0, 1231006505));
414 assert!(registry.is_feature_active("taproot", 0, 1231006505));
415 assert!(registry.is_feature_active("rbf", 0, 1231006505));
416 assert!(registry.is_feature_active("fast_mining", 0, 1231006505));
417 }
418
419 #[test]
420 fn test_testnet_earlier_activations() {
421 let registry = FeatureRegistry::testnet();
422
423 assert!(!registry.is_feature_active("segwit", 465_599, 1493596000));
425 assert!(registry.is_feature_active("segwit", 465_600, 1493596800));
426 assert!(registry.is_feature_active("segwit", 500_000, 1500000000));
427 }
428
429 #[test]
430 fn test_feature_not_found() {
431 let registry = FeatureRegistry::mainnet();
432
433 assert!(!registry.is_feature_active("nonexistent", 1_000_000, 2000000000));
435 }
436
437 #[test]
438 fn test_get_feature() {
439 let registry = FeatureRegistry::mainnet();
440
441 let segwit = registry.get_feature("segwit").unwrap();
442 assert_eq!(segwit.feature_name, "segwit");
443 assert_eq!(segwit.bip_number, Some(141));
444 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
445
446 assert!(registry.get_feature("nonexistent").is_none());
447 }
448
449 #[test]
450 fn test_list_features() {
451 let mainnet = FeatureRegistry::mainnet();
452 let features = mainnet.list_features();
453
454 assert!(features.contains(&"segwit".to_string()));
455 assert!(features.contains(&"taproot".to_string()));
456 assert!(features.contains(&"rbf".to_string()));
457 assert!(features.contains(&"csv".to_string()));
458 assert!(features.contains(&"cltv".to_string()));
459 }
460
461 #[test]
462 fn test_activation_methods() {
463 let mainnet = FeatureRegistry::mainnet();
464
465 let segwit = mainnet.get_feature("segwit").unwrap();
466 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
467
468 let rbf = mainnet.get_feature("rbf").unwrap();
469 assert_eq!(rbf.activation_method, ActivationMethod::AlwaysActive);
470 }
471
472 #[test]
473 fn test_bip9_height_and_timestamp() {
474 let registry = FeatureRegistry::mainnet();
475
476 assert!(registry.is_feature_active("segwit", 481_824, 1500000000));
479
480 assert!(!registry.is_feature_active("segwit", 481_000, 1503539857));
484
485 assert!(registry.is_feature_active("segwit", 481_824, 1503539857));
487 }
488
489 #[test]
490 fn test_feature_context_creation() {
491 let registry = FeatureRegistry::mainnet();
492
493 let ctx_before = registry.create_context(481_823, 1503539000);
495 assert!(!ctx_before.segwit);
496 assert!(!ctx_before.taproot);
497 assert!(ctx_before.csv); assert!(ctx_before.cltv); assert!(ctx_before.rbf); let ctx_at_segwit = registry.create_context(481_824, 1503539857);
503 assert!(ctx_at_segwit.segwit);
504 assert!(!ctx_at_segwit.taproot);
505
506 let ctx_at_taproot = registry.create_context(709_632, 1636934400);
508 assert!(ctx_at_taproot.segwit);
509 assert!(ctx_at_taproot.taproot);
510
511 let ctx_after = registry.create_context(800_000, 1640000000);
513 assert!(ctx_after.segwit);
514 assert!(ctx_after.taproot);
515 }
516
517 #[test]
518 fn test_feature_context_is_active() {
519 let registry = FeatureRegistry::mainnet();
520 let ctx = registry.create_context(800_000, 1640000000);
521
522 assert!(ctx.is_active("segwit"));
523 assert!(ctx.is_active("taproot"));
524 assert!(ctx.is_active("csv"));
525 assert!(ctx.is_active("cltv"));
526 assert!(ctx.is_active("rbf"));
527 assert!(!ctx.is_active("ctv")); assert!(!ctx.is_active("nonexistent"));
529 }
530
531 #[test]
532 fn test_feature_context_active_features() {
533 let registry = FeatureRegistry::mainnet();
534
535 let ctx_before = registry.create_context(0, 1231006505);
537 let active = ctx_before.active_features();
538 assert!(active.contains(&"csv"));
539 assert!(active.contains(&"cltv"));
540 assert!(active.contains(&"rbf"));
541 assert!(!active.contains(&"segwit"));
542 assert!(!active.contains(&"taproot"));
543
544 let ctx_after = registry.create_context(800_000, 1640000000);
546 let active = ctx_after.active_features();
547 assert!(active.contains(&"segwit"));
548 assert!(active.contains(&"taproot"));
549 assert!(active.contains(&"csv"));
550 assert!(active.contains(&"cltv"));
551 assert!(active.contains(&"rbf"));
552 }
553
554 #[test]
555 fn test_feature_context_regtest() {
556 let registry = FeatureRegistry::regtest();
557 let ctx = registry.create_context(0, 1231006505);
558
559 assert!(ctx.segwit);
561 assert!(ctx.taproot);
562 assert!(ctx.csv);
563 assert!(ctx.cltv);
564 assert!(ctx.rbf);
565 }
566
567 #[test]
568 fn test_feature_context_from_registry() {
569 let registry = FeatureRegistry::mainnet();
570 let ctx = FeatureContext::from_registry(®istry, 800_000, 1640000000);
571
572 assert!(ctx.segwit);
573 assert!(ctx.taproot);
574 assert_eq!(ctx.height, 800_000);
575 assert_eq!(ctx.timestamp, 1640000000);
576 }
577}