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
7#[cfg(feature = "spec-lock")]
8use blvm_spec_lock::spec_locked;
9
10use crate::constants::*;
11use crate::types::{ForkId, Network};
12
13/// Trait for querying fork activation at a given height.
14///
15/// Implemented by `ForkActivationTable` and by `BlockValidationContext` (delegating to its table).
16/// Allows validation code to ask "is fork F active?" without depending on a concrete context type.
17pub trait IsForkActive {
18    /// Returns true if the fork is active at the given block height.
19    fn is_fork_active(&self, fork: ForkId, height: u64) -> bool;
20}
21
22/// Precomputed activation heights for all built-in forks.
23///
24/// Fixed-size storage: one field per fork (no HashMap). Built by the node from
25/// chain params, version-bits (e.g. BIP54), and config overrides. Consensus only reads.
26#[derive(Debug, Clone)]
27pub struct ForkActivationTable {
28    /// BIP30: active when `height <=` this. Production networks use `u64::MAX`
29    /// (always active). The two historical duplicates are exempt by block hash.
30    pub bip30_deactivation: u64,
31    /// Activation heights (active when height >= value; u64::MAX = never active).
32    pub bip16: u64,
33    pub bip34: u64,
34    pub bip66: u64,
35    pub bip65: u64,
36    pub bip112: u64,
37    pub bip147: u64,
38    pub segwit: u64,
39    pub taproot: u64,
40    pub ctv: u64,
41    pub csfs: u64,
42    pub bip54: u64,
43}
44
45impl IsForkActive for ForkActivationTable {
46    #[inline]
47    fn is_fork_active(&self, fork: ForkId, height: u64) -> bool {
48        match fork {
49            // No deactivation height. The two historical duplicates are exempt by
50            // block hash inside `check_bip30`, not by turning this fork off.
51            ForkId::Bip30 => true,
52            ForkId::Bip16 => height >= self.bip16,
53            ForkId::Bip34 => height >= self.bip34,
54            ForkId::Bip66 => height >= self.bip66,
55            ForkId::Bip65 => height >= self.bip65,
56            ForkId::Bip112 => height >= self.bip112,
57            ForkId::Bip147 => height >= self.bip147,
58            ForkId::SegWit => height >= self.segwit,
59            ForkId::Taproot => height >= self.taproot,
60            ForkId::Ctv => self.ctv != u64::MAX && height >= self.ctv,
61            ForkId::Csfs => self.csfs != u64::MAX && height >= self.csfs,
62            ForkId::Bip54 => self.bip54 != u64::MAX && height >= self.bip54,
63        }
64    }
65}
66
67impl ForkActivationTable {
68    /// Build table from network and constants. BIP54 uses per-network constant (u64::MAX by default).
69    pub fn from_network(network: Network) -> Self {
70        Self::from_network_and_bip54_override(network, None)
71    }
72
73    /// Build table from network and optional BIP54 activation override (e.g. from version bits).
74    pub fn from_network_and_bip54_override(
75        network: Network,
76        bip54_activation_override: Option<u64>,
77    ) -> Self {
78        let (
79            bip30_deactivation,
80            bip16,
81            bip34,
82            bip66,
83            bip65,
84            bip112,
85            bip147,
86            segwit,
87            taproot,
88            ctv,
89            csfs,
90        ) = match network {
91            Network::Mainnet => (
92                BIP30_DEACTIVATION_MAINNET,
93                BIP16_P2SH_ACTIVATION_MAINNET,
94                BIP34_ACTIVATION_MAINNET,
95                BIP66_ACTIVATION_MAINNET,
96                BIP65_ACTIVATION_MAINNET,
97                BIP112_CSV_ACTIVATION_MAINNET,
98                BIP147_ACTIVATION_MAINNET,
99                SEGWIT_ACTIVATION_MAINNET,
100                TAPROOT_ACTIVATION_MAINNET,
101                if CTV_ACTIVATION_MAINNET == 0 {
102                    u64::MAX
103                } else {
104                    CTV_ACTIVATION_MAINNET
105                },
106                if CSFS_ACTIVATION_MAINNET == 0 {
107                    u64::MAX
108                } else {
109                    CSFS_ACTIVATION_MAINNET
110                },
111            ),
112            Network::Testnet => (
113                BIP30_DEACTIVATION_TESTNET,
114                BIP16_P2SH_ACTIVATION_TESTNET,
115                BIP34_ACTIVATION_TESTNET,
116                BIP66_ACTIVATION_TESTNET,
117                BIP65_ACTIVATION_TESTNET,
118                BIP112_CSV_ACTIVATION_TESTNET,
119                BIP147_ACTIVATION_TESTNET,
120                SEGWIT_ACTIVATION_TESTNET,
121                TAPROOT_ACTIVATION_TESTNET,
122                if CTV_ACTIVATION_TESTNET == 0 {
123                    u64::MAX
124                } else {
125                    CTV_ACTIVATION_TESTNET
126                },
127                if CSFS_ACTIVATION_TESTNET == 0 {
128                    u64::MAX
129                } else {
130                    CSFS_ACTIVATION_TESTNET
131                },
132            ),
133            Network::Regtest => (
134                BIP30_DEACTIVATION_REGTEST,
135                BIP16_P2SH_ACTIVATION_REGTEST,
136                BIP34_ACTIVATION_REGTEST,
137                BIP66_ACTIVATION_REGTEST,
138                BIP65_ACTIVATION_REGTEST,
139                BIP112_CSV_ACTIVATION_REGTEST,
140                0,
141                0,
142                0,
143                CTV_ACTIVATION_REGTEST,
144                CSFS_ACTIVATION_REGTEST,
145            ),
146            Network::Signet | Network::Testnet4 => (
147                BIP30_DEACTIVATION_REGTEST,
148                BIP16_P2SH_ACTIVATION_REGTEST,
149                1,
150                1,
151                1,
152                1,
153                1,
154                1,
155                1,
156                u64::MAX,
157                u64::MAX,
158            ),
159        };
160
161        let bip54 = bip54_activation_override.unwrap_or(match network {
162            Network::Mainnet => BIP54_ACTIVATION_MAINNET,
163            Network::Testnet => BIP54_ACTIVATION_TESTNET,
164            Network::Regtest => BIP54_ACTIVATION_REGTEST,
165            Network::Signet | Network::Testnet4 => u64::MAX,
166        });
167
168        Self {
169            bip30_deactivation,
170            bip16,
171            bip34,
172            bip66,
173            bip65,
174            bip112,
175            bip147,
176            segwit,
177            taproot,
178            ctv,
179            csfs,
180            bip54,
181        }
182    }
183}
184
185/// Whether Taproot script verification applies at `block_height`.
186///
187/// When height is unknown (`None`), returns `true` so P2TR-shaped scriptPubKeys still
188/// run Taproot verification (callers that know height should pass `Some(h)`).
189pub fn taproot_active_at_height(block_height: Option<u64>, network: Network) -> bool {
190    block_height
191        .map(|h| h >= taproot_activation_height(network))
192        .unwrap_or(true)
193}
194
195/// Taproot (BIP341) activation height for `network` (mainnet vs testnet3).
196#[cfg_attr(feature = "spec-lock", spec_locked("11.2", "TaprootActivationHeight"))]
197#[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result == 0 || result == 1 || result == 709632 || result == 2011968))]
198#[inline]
199pub fn taproot_activation_height(network: Network) -> u64 {
200    // if/else (not enum match) so spec-lock Z3 can model control flow with Signet.
201    if network == Network::Mainnet {
202        TAPROOT_ACTIVATION_MAINNET
203    } else if network == Network::Testnet {
204        TAPROOT_ACTIVATION_TESTNET
205    } else if network == Network::Signet || network == Network::Testnet4 {
206        1
207    } else {
208        0
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215    use crate::types::{ForkId, Network};
216
217    #[test]
218    fn fork_table_testnet_matches_primitives() {
219        let t = ForkActivationTable::from_network(Network::Testnet);
220        assert_eq!(t.bip65, BIP65_ACTIVATION_TESTNET);
221        assert_eq!(t.bip112, BIP112_CSV_ACTIVATION_TESTNET);
222        assert_eq!(t.bip147, BIP147_ACTIVATION_TESTNET);
223        assert_eq!(t.segwit, SEGWIT_ACTIVATION_TESTNET);
224        assert_eq!(t.taproot, TAPROOT_ACTIVATION_TESTNET);
225    }
226
227    #[test]
228    fn taproot_activation_height_matches_table() {
229        for net in [
230            Network::Mainnet,
231            Network::Testnet,
232            Network::Regtest,
233            Network::Signet,
234        ] {
235            let h = taproot_activation_height(net);
236            let t = ForkActivationTable::from_network(net).taproot;
237            assert_eq!(h, t, "{net:?}");
238        }
239    }
240
241    #[test]
242    fn regtest_buried_deployments_start_at_height_one() {
243        let table = ForkActivationTable::from_network(Network::Regtest);
244        assert_eq!(table.bip34, 1);
245        assert_eq!(table.bip66, 1);
246        assert_eq!(table.bip65, 1);
247        assert_eq!(table.bip112, 1);
248        for fork in [ForkId::Bip34, ForkId::Bip66, ForkId::Bip65, ForkId::Bip112] {
249            assert!(
250                !table.is_fork_active(fork, 0),
251                "{fork:?} must not apply to genesis"
252            );
253            assert!(table.is_fork_active(fork, 1), "{fork:?} starts at height 1");
254        }
255    }
256}