1#[cfg(feature = "spec-lock")]
7use blvm_spec_lock::spec_locked;
8
9use crate::types::BlockHeader;
10
11pub const LOCK_IN_PERIOD: u32 = 2016;
13
14pub const ACTIVATION_THRESHOLD: u32 = 1916;
16
17pub const VERSIONBITS_TOP_MASK: u32 = 0xE000_0000;
20pub const VERSIONBITS_TOP_BITS: u32 = 0x2000_0000;
21
22#[derive(Debug, Clone, Copy)]
24pub struct Bip9Deployment {
25 pub bit: u8,
27 pub start_time: u64,
29 pub timeout: u64,
31}
32
33#[cfg_attr(feature = "spec-lock", spec_locked("5.4.9", "Bip54DeploymentMainnet"))]
39#[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result.bit == 15))]
40pub fn bip54_deployment_mainnet() -> Bip9Deployment {
41 Bip9Deployment {
42 bit: 15,
43 start_time: 0,
44 timeout: u64::MAX,
45 }
46}
47
48#[cfg_attr(feature = "spec-lock", spec_locked("5.4.9", "Bip54DeploymentTestnet"))]
53#[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result.bit == 15))]
54pub fn bip54_deployment_testnet() -> Bip9Deployment {
55 Bip9Deployment {
56 bit: 15,
57 start_time: 0,
58 timeout: u64::MAX,
59 }
60}
61
62#[cfg_attr(feature = "spec-lock", spec_locked("5.4.9", "Bip54DeploymentRegtest"))]
67#[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result.bit == 15))]
68pub fn bip54_deployment_regtest() -> Bip9Deployment {
69 Bip9Deployment {
70 bit: 15,
71 start_time: 0,
72 timeout: u64::MAX,
73 }
74}
75
76#[cfg_attr(
81 feature = "spec-lock",
82 spec_locked("5.4.9", "Bip54DeploymentForNetwork")
83)]
84#[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result.bit == 15))]
85pub fn bip54_deployment_for_network(network: &crate::types::Network) -> Bip9Deployment {
86 if *network == crate::types::Network::Mainnet {
88 bip54_deployment_mainnet()
89 } else if *network == crate::types::Network::Testnet {
90 bip54_deployment_testnet()
91 } else {
92 bip54_deployment_regtest()
93 }
94}
95
96#[cfg_attr(
118 feature = "spec-lock",
119 spec_locked("5.4.9", "ActivationHeightFromVersionBits")
120)]
121pub fn activation_height_from_headers<H: AsRef<BlockHeader>>(
122 headers: &[H],
123 current_height: u64,
124 current_time: u64,
125 deployment: &Bip9Deployment,
126) -> Option<u64> {
127 if deployment.start_time >= deployment.timeout {
128 return None;
129 }
130 if current_time < deployment.start_time || current_time >= deployment.timeout {
131 return None;
132 }
133 if headers.len() < LOCK_IN_PERIOD as usize {
134 return None;
135 }
136
137 let mut count = 0u32;
138 for h in headers.iter().take(LOCK_IN_PERIOD as usize) {
139 let v = h.as_ref().version as u32;
142 if deployment.bit < 29
143 && (v & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS
144 && ((v >> deployment.bit) & 1) != 0
145 {
146 count += 1;
147 }
148 }
149 if count < ACTIVATION_THRESHOLD {
150 return None;
151 }
152
153 let period_end = current_height.saturating_sub(1);
158 let period_index = period_end / LOCK_IN_PERIOD as u64;
159 let activation_height = (period_index + 2).checked_mul(LOCK_IN_PERIOD as u64)?;
161
162 Some(activation_height)
163}
164
165#[inline]
168pub fn merge_bip54_activation_candidate(
169 previous: Option<u64>,
170 candidate: Option<u64>,
171) -> Option<u64> {
172 match (previous, candidate) {
173 (Some(a), Some(b)) => Some(a.min(b)),
174 (Some(a), None) => Some(a),
175 (None, Some(b)) => Some(b),
176 (None, None) => None,
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::types::BlockHeader;
184
185 fn header(version: i64) -> BlockHeader {
186 BlockHeader {
187 version,
188 prev_block_hash: [0u8; 32],
189 merkle_root: [0u8; 32],
190 timestamp: 0,
191 bits: 0x1d00ffff,
192 nonce: 0,
193 }
194 }
195
196 fn signaling_version(bit: u8) -> i64 {
198 i64::from(VERSIONBITS_TOP_BITS | (1u32 << bit))
199 }
200
201 #[test]
202 fn disabled_deployment_returns_none() {
203 let dep = Bip9Deployment {
204 bit: 0,
205 start_time: 100,
206 timeout: 100,
207 };
208 let headers: Vec<BlockHeader> = (0..2016).map(|_| header(1)).collect();
209 assert!(activation_height_from_headers(&headers, 4032, 150, &dep).is_none());
210 }
211
212 #[test]
213 fn active_after_lockin() {
214 let dep = Bip9Deployment {
215 bit: 0,
216 start_time: 0,
217 timeout: u64::MAX,
218 };
219 let headers: Vec<BlockHeader> = (0..2016).map(|_| header(signaling_version(0))).collect();
221 assert_eq!(
223 activation_height_from_headers(&headers, 4032, 1, &dep),
224 Some(6048)
225 );
226 assert_eq!(
228 activation_height_from_headers(&headers, 6048, 1, &dep),
229 Some(8064)
230 );
231 assert_eq!(
232 merge_bip54_activation_candidate(
233 activation_height_from_headers(&headers, 4032, 1, &dep),
234 activation_height_from_headers(&headers, 6048, 1, &dep),
235 ),
236 Some(6048)
237 );
238 }
239
240 #[test]
241 fn not_active_before_activation_height() {
242 let dep = Bip9Deployment {
243 bit: 0,
244 start_time: 0,
245 timeout: u64::MAX,
246 };
247 let headers: Vec<BlockHeader> = (0..2016).map(|_| header(signaling_version(0))).collect();
248 let act = activation_height_from_headers(&headers, 4031, 1, &dep);
249 assert_eq!(act, Some(6048));
250 assert!(
251 !crate::bip_validation::is_bip54_active_at(4031, crate::types::Network::Mainnet, act),
252 "override height must not activate BIP54 before that height"
253 );
254 }
255
256 #[test]
257 fn huge_current_height_does_not_panic() {
258 let dep = Bip9Deployment {
259 bit: 0,
260 start_time: 0,
261 timeout: u64::MAX,
262 };
263 let headers: Vec<BlockHeader> = (0..2016).map(|_| header(signaling_version(0))).collect();
264 assert!(activation_height_from_headers(&headers, u64::MAX, 1, &dep).is_none());
265 }
266
267 #[test]
268 fn version_one_does_not_signal() {
269 let dep = Bip9Deployment {
270 bit: 0,
271 start_time: 0,
272 timeout: u64::MAX,
273 };
274 let headers: Vec<BlockHeader> = (0..2016).map(|_| header(1)).collect();
276 assert!(activation_height_from_headers(&headers, 4032, 1, &dep).is_none());
277 }
278
279 #[test]
280 fn bip54_bit_without_prefix_does_not_lock_in() {
281 let dep = bip54_deployment_mainnet();
282 let headers: Vec<BlockHeader> = (0..2016).map(|_| header(1i64 << 15)).collect();
283 assert!(activation_height_from_headers(&headers, 4032, 1, &dep).is_none());
284 }
285
286 #[test]
287 fn bip54_prefix_and_bit_locks_in() {
288 let dep = bip54_deployment_mainnet();
289 let headers: Vec<BlockHeader> = (0..2016).map(|_| header(signaling_version(15))).collect();
290 assert_eq!(
291 activation_height_from_headers(&headers, 4032, 1, &dep),
292 Some(6048)
293 );
294 }
295}