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