Skip to main content

blvm_consensus/
activation.rs

1//! Unified fork activation: table and trait for "is fork X active at height H?".
2//!
3//! Used by block validation to query activation from a single precomputed table
4//! (built by the node from constants, version-bits, and config overrides)
5//! without passing per-BIP parameters.
6
7use crate::constants::*;
8use crate::types::{ForkId, Network};
9
10/// Trait for querying fork activation at a given height.
11///
12/// Implemented by `ForkActivationTable` and by `BlockValidationContext` (delegating to its table).
13/// Allows validation code to ask "is fork F active?" without depending on a concrete context type.
14pub trait IsForkActive {
15    /// Returns true if the fork is active at the given block height.
16    fn is_fork_active(&self, fork: ForkId, height: u64) -> bool;
17}
18
19/// Precomputed activation heights for all built-in forks.
20///
21/// Fixed-size storage: one field per fork (no HashMap). Built by the node from
22/// chain params, version-bits (e.g. BIP54), and config overrides. Consensus only reads.
23#[derive(Debug, Clone)]
24pub struct ForkActivationTable {
25    /// BIP30: active when height <= this (deactivation fork).
26    pub bip30_deactivation: u64,
27    /// Activation heights (active when height >= value; u64::MAX = never active).
28    pub bip16: u64,
29    pub bip34: u64,
30    pub bip66: u64,
31    pub bip65: u64,
32    pub bip112: u64,
33    pub bip147: u64,
34    pub segwit: u64,
35    pub taproot: u64,
36    pub ctv: u64,
37    pub csfs: u64,
38    pub bip54: u64,
39}
40
41impl IsForkActive for ForkActivationTable {
42    #[inline]
43    fn is_fork_active(&self, fork: ForkId, height: u64) -> bool {
44        match fork {
45            ForkId::Bip30 => height <= self.bip30_deactivation,
46            ForkId::Bip16 => height >= self.bip16,
47            ForkId::Bip34 => height >= self.bip34,
48            ForkId::Bip66 => height >= self.bip66,
49            ForkId::Bip65 => height >= self.bip65,
50            ForkId::Bip112 => height >= self.bip112,
51            ForkId::Bip147 => height >= self.bip147,
52            ForkId::SegWit => height >= self.segwit,
53            ForkId::Taproot => height >= self.taproot,
54            ForkId::Ctv => self.ctv != u64::MAX && height >= self.ctv,
55            ForkId::Csfs => self.csfs != u64::MAX && height >= self.csfs,
56            ForkId::Bip54 => height >= self.bip54,
57        }
58    }
59}
60
61impl ForkActivationTable {
62    /// Build table from network and constants. BIP54 uses per-network constant (u64::MAX by default).
63    pub fn from_network(network: Network) -> Self {
64        Self::from_network_and_bip54_override(network, None)
65    }
66
67    /// Build table from network and optional BIP54 activation override (e.g. from version bits).
68    pub fn from_network_and_bip54_override(
69        network: Network,
70        bip54_activation_override: Option<u64>,
71    ) -> Self {
72        let (
73            bip30_deactivation,
74            bip16,
75            bip34,
76            bip66,
77            bip65,
78            bip112,
79            bip147,
80            segwit,
81            taproot,
82            ctv,
83            csfs,
84        ) = match network {
85            Network::Mainnet => (
86                BIP30_DEACTIVATION_MAINNET,
87                BIP16_P2SH_ACTIVATION_MAINNET,
88                BIP34_ACTIVATION_MAINNET,
89                BIP66_ACTIVATION_MAINNET,
90                BIP65_ACTIVATION_MAINNET,
91                BIP112_CSV_ACTIVATION_MAINNET,
92                BIP147_ACTIVATION_MAINNET,
93                SEGWIT_ACTIVATION_MAINNET,
94                TAPROOT_ACTIVATION_MAINNET,
95                if CTV_ACTIVATION_MAINNET == 0 {
96                    u64::MAX
97                } else {
98                    CTV_ACTIVATION_MAINNET
99                },
100                if CSFS_ACTIVATION_MAINNET == 0 {
101                    u64::MAX
102                } else {
103                    CSFS_ACTIVATION_MAINNET
104                },
105            ),
106            Network::Testnet => (
107                BIP30_DEACTIVATION_TESTNET,
108                BIP16_P2SH_ACTIVATION_TESTNET,
109                BIP34_ACTIVATION_TESTNET,
110                BIP66_ACTIVATION_TESTNET,
111                BIP65_ACTIVATION_TESTNET,
112                BIP112_CSV_ACTIVATION_TESTNET,
113                BIP147_ACTIVATION_TESTNET,
114                SEGWIT_ACTIVATION_TESTNET,
115                TAPROOT_ACTIVATION_TESTNET,
116                if CTV_ACTIVATION_TESTNET == 0 {
117                    u64::MAX
118                } else {
119                    CTV_ACTIVATION_TESTNET
120                },
121                if CSFS_ACTIVATION_TESTNET == 0 {
122                    u64::MAX
123                } else {
124                    CSFS_ACTIVATION_TESTNET
125                },
126            ),
127            Network::Regtest => (
128                BIP30_DEACTIVATION_REGTEST,
129                BIP16_P2SH_ACTIVATION_REGTEST,
130                BIP34_ACTIVATION_REGTEST,
131                BIP66_ACTIVATION_REGTEST,
132                0,
133                BIP112_CSV_ACTIVATION_REGTEST,
134                0,
135                0,
136                0,
137                CTV_ACTIVATION_REGTEST,
138                CSFS_ACTIVATION_REGTEST,
139            ),
140        };
141
142        let bip54 = bip54_activation_override.unwrap_or(match network {
143            Network::Mainnet => BIP54_ACTIVATION_MAINNET,
144            Network::Testnet => BIP54_ACTIVATION_TESTNET,
145            Network::Regtest => BIP54_ACTIVATION_REGTEST,
146        });
147
148        Self {
149            bip30_deactivation,
150            bip16,
151            bip34,
152            bip66,
153            bip65,
154            bip112,
155            bip147,
156            segwit,
157            taproot,
158            ctv,
159            csfs,
160            bip54,
161        }
162    }
163}
164
165/// Taproot (BIP341) activation height for `network` (Core `chainparams` mainnet vs testnet3).
166#[inline]
167pub fn taproot_activation_height(network: Network) -> u64 {
168    match network {
169        Network::Mainnet => TAPROOT_ACTIVATION_MAINNET,
170        Network::Testnet => TAPROOT_ACTIVATION_TESTNET,
171        Network::Regtest => 0,
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use crate::types::Network;
179
180    #[test]
181    fn fork_table_testnet_matches_primitives() {
182        let t = ForkActivationTable::from_network(Network::Testnet);
183        assert_eq!(t.bip65, BIP65_ACTIVATION_TESTNET);
184        assert_eq!(t.bip112, BIP112_CSV_ACTIVATION_TESTNET);
185        assert_eq!(t.bip147, BIP147_ACTIVATION_TESTNET);
186        assert_eq!(t.segwit, SEGWIT_ACTIVATION_TESTNET);
187        assert_eq!(t.taproot, TAPROOT_ACTIVATION_TESTNET);
188    }
189
190    #[test]
191    fn taproot_activation_height_matches_table() {
192        for net in [Network::Mainnet, Network::Testnet, Network::Regtest] {
193            let h = taproot_activation_height(net);
194            let t = ForkActivationTable::from_network(net).taproot;
195            assert_eq!(h, t, "{net:?}");
196        }
197    }
198}