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 let height_active = self.activation_height.is_some_and(|h| height >= h);
67 let timestamp_active = self.activation_timestamp.is_some_and(|t| timestamp >= t);
68 height_active || timestamp_active
69 }
70 }
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub struct FeatureRegistry {
77 pub protocol_version: ProtocolVersion,
79 pub features: Vec<FeatureActivation>,
81}
82
83impl FeatureRegistry {
84 pub fn for_protocol(version: ProtocolVersion) -> Self {
86 match version {
87 ProtocolVersion::BitcoinV1 => Self::mainnet(),
88 ProtocolVersion::Testnet3 => Self::testnet(),
89 ProtocolVersion::Regtest => Self::regtest(),
90 }
91 }
92
93 pub fn mainnet() -> Self {
95 Self {
96 protocol_version: ProtocolVersion::BitcoinV1,
97 features: vec![
98 FeatureActivation {
100 feature_name: "segwit".to_string(),
101 activation_height: Some(481_824),
102 activation_timestamp: Some(1503539857), activation_method: ActivationMethod::BIP9,
104 bip_number: Some(141),
105 },
106 FeatureActivation {
108 feature_name: "taproot".to_string(),
109 activation_height: Some(709_632),
110 activation_timestamp: Some(1636934400), activation_method: ActivationMethod::BIP9,
112 bip_number: Some(341),
113 },
114 FeatureActivation {
116 feature_name: "rbf".to_string(),
117 activation_height: Some(0),
118 activation_timestamp: None,
119 activation_method: ActivationMethod::AlwaysActive,
120 bip_number: Some(125),
121 },
122 FeatureActivation {
124 feature_name: "ctv".to_string(),
125 activation_height: None,
126 activation_timestamp: None,
127 activation_method: ActivationMethod::BIP9,
128 bip_number: Some(119),
129 },
130 FeatureActivation {
132 feature_name: "csv".to_string(),
133 activation_height: Some(0),
134 activation_timestamp: None,
135 activation_method: ActivationMethod::AlwaysActive,
136 bip_number: Some(112),
137 },
138 FeatureActivation {
140 feature_name: "cltv".to_string(),
141 activation_height: Some(0),
142 activation_timestamp: None,
143 activation_method: ActivationMethod::AlwaysActive,
144 bip_number: Some(65),
145 },
146 ],
147 }
148 }
149
150 pub fn testnet() -> Self {
152 Self {
153 protocol_version: ProtocolVersion::Testnet3,
154 features: vec![
155 FeatureActivation {
157 feature_name: "segwit".to_string(),
158 activation_height: Some(465_600), activation_timestamp: Some(1493596800), activation_method: ActivationMethod::BIP9,
161 bip_number: Some(141),
162 },
163 FeatureActivation {
165 feature_name: "taproot".to_string(),
166 activation_height: Some(2_016_000), activation_timestamp: Some(1628640000), activation_method: ActivationMethod::BIP9,
169 bip_number: Some(341),
170 },
171 FeatureActivation {
173 feature_name: "rbf".to_string(),
174 activation_height: Some(0),
175 activation_timestamp: None,
176 activation_method: ActivationMethod::AlwaysActive,
177 bip_number: Some(125),
178 },
179 FeatureActivation {
181 feature_name: "csv".to_string(),
182 activation_height: Some(0),
183 activation_timestamp: None,
184 activation_method: ActivationMethod::AlwaysActive,
185 bip_number: Some(112),
186 },
187 FeatureActivation {
189 feature_name: "cltv".to_string(),
190 activation_height: Some(0),
191 activation_timestamp: None,
192 activation_method: ActivationMethod::AlwaysActive,
193 bip_number: Some(65),
194 },
195 ],
196 }
197 }
198
199 pub fn regtest() -> Self {
201 Self {
202 protocol_version: ProtocolVersion::Regtest,
203 features: vec![
204 FeatureActivation {
206 feature_name: "segwit".to_string(),
207 activation_height: Some(0),
208 activation_timestamp: None,
209 activation_method: ActivationMethod::AlwaysActive,
210 bip_number: Some(141),
211 },
212 FeatureActivation {
213 feature_name: "taproot".to_string(),
214 activation_height: Some(0),
215 activation_timestamp: None,
216 activation_method: ActivationMethod::AlwaysActive,
217 bip_number: Some(341),
218 },
219 FeatureActivation {
220 feature_name: "rbf".to_string(),
221 activation_height: Some(0),
222 activation_timestamp: None,
223 activation_method: ActivationMethod::AlwaysActive,
224 bip_number: Some(125),
225 },
226 FeatureActivation {
227 feature_name: "csv".to_string(),
228 activation_height: Some(0),
229 activation_timestamp: None,
230 activation_method: ActivationMethod::AlwaysActive,
231 bip_number: Some(112),
232 },
233 FeatureActivation {
234 feature_name: "cltv".to_string(),
235 activation_height: Some(0),
236 activation_timestamp: None,
237 activation_method: ActivationMethod::AlwaysActive,
238 bip_number: Some(65),
239 },
240 FeatureActivation {
241 feature_name: "fast_mining".to_string(),
242 activation_height: Some(0),
243 activation_timestamp: None,
244 activation_method: ActivationMethod::AlwaysActive,
245 bip_number: None,
246 },
247 ],
248 }
249 }
250
251 pub fn is_feature_active(&self, feature_name: &str, height: u64, timestamp: u64) -> bool {
253 self.features
254 .iter()
255 .find(|f| f.feature_name == feature_name)
256 .map(|f| f.is_active_at(height, timestamp))
257 .unwrap_or(false)
258 }
259
260 pub fn get_feature(&self, feature_name: &str) -> Option<&FeatureActivation> {
262 self.features
263 .iter()
264 .find(|f| f.feature_name == feature_name)
265 }
266
267 pub fn list_features(&self) -> Vec<String> {
269 self.features
270 .iter()
271 .map(|f| f.feature_name.clone())
272 .collect()
273 }
274
275 pub fn create_context(&self, height: u64, timestamp: u64) -> FeatureContext {
278 FeatureContext {
279 segwit: self.is_feature_active("segwit", height, timestamp),
280 taproot: self.is_feature_active("taproot", height, timestamp),
281 csv: self.is_feature_active("csv", height, timestamp),
282 cltv: self.is_feature_active("cltv", height, timestamp),
283 rbf: self.is_feature_active("rbf", height, timestamp),
284 ctv: self.is_feature_active("ctv", height, timestamp),
285 height,
286 timestamp,
287 }
288 }
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
294pub struct FeatureContext {
295 pub segwit: bool,
297 pub taproot: bool,
299 pub csv: bool,
301 pub cltv: bool,
303 pub rbf: bool,
305 pub ctv: bool,
307 pub height: u64,
309 pub timestamp: u64,
311}
312
313impl FeatureContext {
314 pub fn from_registry(registry: &FeatureRegistry, height: u64, timestamp: u64) -> Self {
316 registry.create_context(height, timestamp)
317 }
318
319 pub fn is_active(&self, feature: &str) -> bool {
321 match feature {
322 "segwit" => self.segwit,
323 "taproot" => self.taproot,
324 "csv" => self.csv,
325 "cltv" => self.cltv,
326 "rbf" => self.rbf,
327 "ctv" => self.ctv,
328 _ => false,
329 }
330 }
331
332 pub fn active_features(&self) -> Vec<&'static str> {
334 let mut features = Vec::new();
335 if self.segwit {
336 features.push("segwit");
337 }
338 if self.taproot {
339 features.push("taproot");
340 }
341 if self.csv {
342 features.push("csv");
343 }
344 if self.cltv {
345 features.push("cltv");
346 }
347 if self.rbf {
348 features.push("rbf");
349 }
350 if self.ctv {
351 features.push("ctv");
352 }
353 features
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn test_segwit_activation_mainnet() {
363 let registry = FeatureRegistry::mainnet();
364
365 assert!(!registry.is_feature_active("segwit", 481_823, 1503539000));
367
368 assert!(registry.is_feature_active("segwit", 481_824, 1503539857));
370
371 assert!(registry.is_feature_active("segwit", 500_000, 1504000000));
373 }
374
375 #[test]
376 fn test_taproot_activation_mainnet() {
377 let registry = FeatureRegistry::mainnet();
378
379 assert!(!registry.is_feature_active("taproot", 709_631, 1636934000));
381
382 assert!(registry.is_feature_active("taproot", 709_632, 1636934400));
384
385 assert!(registry.is_feature_active("taproot", 800_000, 1640000000));
387 }
388
389 #[test]
390 fn test_always_active_features() {
391 let registry = FeatureRegistry::mainnet();
392
393 assert!(registry.is_feature_active("rbf", 0, 1231006505));
395 assert!(registry.is_feature_active("csv", 0, 1231006505));
396 assert!(registry.is_feature_active("cltv", 0, 1231006505));
397 assert!(registry.is_feature_active("rbf", 1_000_000, 2000000000));
398 }
399
400 #[test]
401 fn test_regtest_all_features_active() {
402 let registry = FeatureRegistry::regtest();
403
404 assert!(registry.is_feature_active("segwit", 0, 1231006505));
406 assert!(registry.is_feature_active("taproot", 0, 1231006505));
407 assert!(registry.is_feature_active("rbf", 0, 1231006505));
408 assert!(registry.is_feature_active("fast_mining", 0, 1231006505));
409 }
410
411 #[test]
412 fn test_testnet_earlier_activations() {
413 let registry = FeatureRegistry::testnet();
414
415 assert!(!registry.is_feature_active("segwit", 465_599, 1493596000));
417 assert!(registry.is_feature_active("segwit", 465_600, 1493596800));
418 assert!(registry.is_feature_active("segwit", 500_000, 1500000000));
419 }
420
421 #[test]
422 fn test_feature_not_found() {
423 let registry = FeatureRegistry::mainnet();
424
425 assert!(!registry.is_feature_active("nonexistent", 1_000_000, 2000000000));
427 }
428
429 #[test]
430 fn test_get_feature() {
431 let registry = FeatureRegistry::mainnet();
432
433 let segwit = registry.get_feature("segwit").unwrap();
434 assert_eq!(segwit.feature_name, "segwit");
435 assert_eq!(segwit.bip_number, Some(141));
436 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
437
438 assert!(registry.get_feature("nonexistent").is_none());
439 }
440
441 #[test]
442 fn test_list_features() {
443 let mainnet = FeatureRegistry::mainnet();
444 let features = mainnet.list_features();
445
446 assert!(features.contains(&"segwit".to_string()));
447 assert!(features.contains(&"taproot".to_string()));
448 assert!(features.contains(&"rbf".to_string()));
449 assert!(features.contains(&"csv".to_string()));
450 assert!(features.contains(&"cltv".to_string()));
451 }
452
453 #[test]
454 fn test_activation_methods() {
455 let mainnet = FeatureRegistry::mainnet();
456
457 let segwit = mainnet.get_feature("segwit").unwrap();
458 assert_eq!(segwit.activation_method, ActivationMethod::BIP9);
459
460 let rbf = mainnet.get_feature("rbf").unwrap();
461 assert_eq!(rbf.activation_method, ActivationMethod::AlwaysActive);
462 }
463
464 #[test]
465 fn test_bip9_height_and_timestamp() {
466 let registry = FeatureRegistry::mainnet();
467
468 assert!(registry.is_feature_active("segwit", 481_824, 1500000000));
471
472 assert!(registry.is_feature_active("segwit", 481_000, 1503539857));
474 }
475
476 #[test]
477 fn test_feature_context_creation() {
478 let registry = FeatureRegistry::mainnet();
479
480 let ctx_before = registry.create_context(481_823, 1503539000);
482 assert!(!ctx_before.segwit);
483 assert!(!ctx_before.taproot);
484 assert!(ctx_before.csv); assert!(ctx_before.cltv); assert!(ctx_before.rbf); let ctx_at_segwit = registry.create_context(481_824, 1503539857);
490 assert!(ctx_at_segwit.segwit);
491 assert!(!ctx_at_segwit.taproot);
492
493 let ctx_at_taproot = registry.create_context(709_632, 1636934400);
495 assert!(ctx_at_taproot.segwit);
496 assert!(ctx_at_taproot.taproot);
497
498 let ctx_after = registry.create_context(800_000, 1640000000);
500 assert!(ctx_after.segwit);
501 assert!(ctx_after.taproot);
502 }
503
504 #[test]
505 fn test_feature_context_is_active() {
506 let registry = FeatureRegistry::mainnet();
507 let ctx = registry.create_context(800_000, 1640000000);
508
509 assert!(ctx.is_active("segwit"));
510 assert!(ctx.is_active("taproot"));
511 assert!(ctx.is_active("csv"));
512 assert!(ctx.is_active("cltv"));
513 assert!(ctx.is_active("rbf"));
514 assert!(!ctx.is_active("ctv")); assert!(!ctx.is_active("nonexistent"));
516 }
517
518 #[test]
519 fn test_feature_context_active_features() {
520 let registry = FeatureRegistry::mainnet();
521
522 let ctx_before = registry.create_context(0, 1231006505);
524 let active = ctx_before.active_features();
525 assert!(active.contains(&"csv"));
526 assert!(active.contains(&"cltv"));
527 assert!(active.contains(&"rbf"));
528 assert!(!active.contains(&"segwit"));
529 assert!(!active.contains(&"taproot"));
530
531 let ctx_after = registry.create_context(800_000, 1640000000);
533 let active = ctx_after.active_features();
534 assert!(active.contains(&"segwit"));
535 assert!(active.contains(&"taproot"));
536 assert!(active.contains(&"csv"));
537 assert!(active.contains(&"cltv"));
538 assert!(active.contains(&"rbf"));
539 }
540
541 #[test]
542 fn test_feature_context_regtest() {
543 let registry = FeatureRegistry::regtest();
544 let ctx = registry.create_context(0, 1231006505);
545
546 assert!(ctx.segwit);
548 assert!(ctx.taproot);
549 assert!(ctx.csv);
550 assert!(ctx.cltv);
551 assert!(ctx.rbf);
552 }
553
554 #[test]
555 fn test_feature_context_from_registry() {
556 let registry = FeatureRegistry::mainnet();
557 let ctx = FeatureContext::from_registry(®istry, 800_000, 1640000000);
558
559 assert!(ctx.segwit);
560 assert!(ctx.taproot);
561 assert_eq!(ctx.height, 800_000);
562 assert_eq!(ctx.timestamp, 1640000000);
563 }
564}