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        };
143
144        let bip54 = bip54_activation_override.unwrap_or(match network {
145            Network::Mainnet => BIP54_ACTIVATION_MAINNET,
146            Network::Testnet => BIP54_ACTIVATION_TESTNET,
147            Network::Regtest => BIP54_ACTIVATION_REGTEST,
148        });
149
150        Self {
151            bip30_deactivation,
152            bip16,
153            bip34,
154            bip66,
155            bip65,
156            bip112,
157            bip147,
158            segwit,
159            taproot,
160            ctv,
161            csfs,
162            bip54,
163        }
164    }
165}
166
167/// Taproot (BIP341) activation height for `network` (Core `chainparams` mainnet vs testnet3).
168#[spec_locked("11.2", "TaprootActivationHeight")]
169#[blvm_spec_lock::ensures(result == 0 || result == 709632 || result == 2011968)]
170#[inline]
171pub fn taproot_activation_height(network: Network) -> u64 {
172    match network {
173        Network::Mainnet => TAPROOT_ACTIVATION_MAINNET,
174        Network::Testnet => TAPROOT_ACTIVATION_TESTNET,
175        Network::Regtest => 0,
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use crate::types::Network;
183
184    #[test]
185    fn fork_table_testnet_matches_primitives() {
186        let t = ForkActivationTable::from_network(Network::Testnet);
187        assert_eq!(t.bip65, BIP65_ACTIVATION_TESTNET);
188        assert_eq!(t.bip112, BIP112_CSV_ACTIVATION_TESTNET);
189        assert_eq!(t.bip147, BIP147_ACTIVATION_TESTNET);
190        assert_eq!(t.segwit, SEGWIT_ACTIVATION_TESTNET);
191        assert_eq!(t.taproot, TAPROOT_ACTIVATION_TESTNET);
192    }
193
194    #[test]
195    fn taproot_activation_height_matches_table() {
196        for net in [Network::Mainnet, Network::Testnet, Network::Regtest] {
197            let h = taproot_activation_height(net);
198            let t = ForkActivationTable::from_network(net).taproot;
199            assert_eq!(h, t, "{net:?}");
200        }
201    }
202}