1#[cfg(feature = "spec-lock")]
8use blvm_spec_lock::spec_locked;
9
10use crate::constants::*;
11use crate::types::{ForkId, Network};
12
13pub trait IsForkActive {
18 fn is_fork_active(&self, fork: ForkId, height: u64) -> bool;
20}
21
22#[derive(Debug, Clone)]
27pub struct ForkActivationTable {
28 pub bip30_deactivation: u64,
31 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 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 pub fn from_network(network: Network) -> Self {
70 Self::from_network_and_bip54_override(network, None)
71 }
72
73 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
185pub 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#[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 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}