blvm_consensus/sequence_locks.rs
1//! Sequence lock calculation functions (BIP68)
2//!
3//! Implements consensus's sequence lock calculation for relative locktime.
4//! Sequence locks are used to enforce relative locktime constraints using
5//! transaction input sequence numbers.
6//!
7//! Reference: consensus `tx_verify.cpp` CalculateSequenceLocks and EvaluateSequenceLocks
8
9use crate::bip113::get_median_time_past;
10use crate::error::Result;
11use crate::locktime::{
12 extract_sequence_locktime_value, extract_sequence_type_flag, is_sequence_disabled,
13};
14use crate::types::*;
15use blvm_spec_lock::spec_locked;
16
17/// Sequence locktime disable flag (bit 31)
18/// When set, the sequence number is not treated as a relative locktime
19#[allow(dead_code)]
20const SEQUENCE_LOCKTIME_DISABLE_FLAG: u32 = 0x80000000;
21
22/// Sequence locktime type flag (bit 22)
23/// When set, locktime is time-based; otherwise block-based
24#[allow(dead_code)]
25const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 0x00400000;
26
27/// Sequence locktime mask (bits 0-15)
28/// Extracts the locktime value from sequence number
29#[allow(dead_code)]
30const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000ffff;
31
32/// Sequence locktime granularity (for time-based locks)
33/// Time-based locks are measured in 512-second intervals
34const SEQUENCE_LOCKTIME_GRANULARITY: u32 = 9; // 2^9 = 512 seconds
35
36/// Locktime verify sequence flag
37/// Must be set to enable BIP68 sequence lock enforcement
38const LOCKTIME_VERIFY_SEQUENCE: u32 = 0x01;
39
40/// Calculate sequence locks for a transaction (BIP68)
41///
42/// Computes the minimum block height and time that must be reached
43/// before the transaction can be included in a block.
44///
45/// Matches consensus's CalculateSequenceLocks() exactly.
46///
47/// # Arguments
48/// * `tx` - Transaction to calculate locks for
49/// * `flags` - Script verification flags (must include LOCKTIME_VERIFY_SEQUENCE)
50/// * `prev_heights` - Block heights at which each input confirmed
51/// * `recent_headers` - Recent block headers for median time-past calculation
52///
53/// # Returns
54/// Pair (min_height, min_time) that must be satisfied:
55/// - min_height: Minimum block height (or -1 if no height constraint)
56/// - min_time: Minimum block time (or -1 if no time constraint)
57#[spec_locked("5.5", "CalculateSequenceLocks")]
58#[blvm_spec_lock::axiom(result_0 >= -1)]
59#[blvm_spec_lock::axiom(result_1 >= -1)]
60#[blvm_spec_lock::ensures(result_0 >= -1)]
61#[blvm_spec_lock::ensures(result_1 >= -1)]
62pub fn calculate_sequence_locks(
63 tx: &Transaction,
64 flags: u32,
65 prev_heights: &[u64],
66 recent_headers: Option<&[BlockHeader]>,
67) -> Result<(i64, i64)> {
68 // Ensure prev_heights matches input count
69 if prev_heights.len() != tx.inputs.len() {
70 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
71 format!(
72 "prev_heights length {} does not match input count {}",
73 prev_heights.len(),
74 tx.inputs.len()
75 )
76 .into(),
77 ));
78 }
79
80 // Initialize to -1 (no constraint)
81 let mut min_height: i64 = -1;
82 let mut min_time: i64 = -1;
83
84 // BIP68 is only enforced for version 2+ transactions and when flag is set
85 let enforce_bip68 = tx.version >= 2 && (flags & LOCKTIME_VERIFY_SEQUENCE) != 0;
86
87 if !enforce_bip68 {
88 return Ok((min_height, min_time));
89 }
90
91 // Process each input
92 for (i, input) in tx.inputs.iter().enumerate() {
93 // Check if sequence is disabled (bit 31 set)
94 if is_sequence_disabled(input.sequence as u32) {
95 // This input doesn't contribute to sequence locks
96 continue;
97 }
98
99 // Must not use `as i64`: large u64 values wrap to negative i64 and break invariants.
100 let coin_height = i64::try_from(prev_heights[i]).map_err(|_| {
101 crate::error::ConsensusError::ConsensusRuleViolation(
102 "prev_height does not fit in i64 for sequence lock calculation".into(),
103 )
104 })?;
105
106 // Check locktime type (bit 22)
107 if extract_sequence_type_flag(input.sequence as u32) {
108 // Time-based relative locktime
109 // Need median time-past of the block prior to the coin's block
110 let coin_time = if let Some(headers) = recent_headers {
111 // Calculate median time-past for the block prior to coin_height
112 // For simplicity, we'll use the most recent header's median time-past
113 // In a full implementation, we'd need to look up the actual block
114 i64::try_from(get_median_time_past(headers)).map_err(|_| {
115 crate::error::ConsensusError::ConsensusRuleViolation(
116 "median time-past does not fit in i64 for sequence lock calculation".into(),
117 )
118 })?
119 } else {
120 // No headers available - can't calculate time-based lock
121 // This is acceptable for some contexts (e.g., mempool validation)
122 continue;
123 };
124
125 // Extract locktime value and multiply by granularity (512 seconds)
126 let locktime_value = extract_sequence_locktime_value(input.sequence as u32) as i64;
127
128 // Runtime assertion: Locktime value must be non-negative
129 debug_assert!(
130 locktime_value >= 0,
131 "Locktime value ({locktime_value}) must be non-negative"
132 );
133
134 let locktime_seconds = locktime_value << SEQUENCE_LOCKTIME_GRANULARITY;
135
136 // Runtime assertion: Shift operation must not overflow
137 // locktime_value is u16 (max 65535), so locktime_seconds = 65535 * 512 = 33,553,920
138 // This fits in i64, so no overflow possible
139 debug_assert!(
140 locktime_seconds >= 0,
141 "Locktime seconds ({locktime_seconds}) must be non-negative (locktime_value: {locktime_value}, granularity: {SEQUENCE_LOCKTIME_GRANULARITY})"
142 );
143
144 // Calculate minimum time: coin_time + locktime_seconds - 1
145 // The -1 is to maintain nLockTime semantics (last invalid time)
146 let required_time = coin_time
147 .checked_add(locktime_seconds)
148 .and_then(|sum| sum.checked_sub(1))
149 .ok_or_else(|| {
150 crate::error::ConsensusError::ConsensusRuleViolation(
151 "Sequence lock time calculation overflow".into(),
152 )
153 })?;
154
155 // Runtime assertion: Required time must be >= coin_time (or close to it after -1)
156 debug_assert!(
157 required_time >= coin_time.saturating_sub(1),
158 "Required time ({required_time}) must be >= coin_time - 1 ({coin_time})"
159 );
160
161 min_time = min_time.max(required_time);
162 } else {
163 // Block-based relative locktime
164 // Extract locktime value (number of blocks)
165 let locktime_value = extract_sequence_locktime_value(input.sequence as u32) as i64;
166
167 // Runtime assertion: Locktime value must be non-negative
168 debug_assert!(
169 locktime_value >= 0,
170 "Locktime value ({locktime_value}) must be non-negative"
171 );
172
173 // Calculate minimum height: coin_height + locktime_value - 1
174 // The -1 is to maintain nLockTime semantics (last invalid height)
175 let required_height = coin_height
176 .checked_add(locktime_value)
177 .and_then(|sum| sum.checked_sub(1))
178 .ok_or_else(|| {
179 crate::error::ConsensusError::ConsensusRuleViolation(
180 "Sequence lock height calculation overflow".into(),
181 )
182 })?;
183
184 // Runtime assertion: Required height must be >= coin_height - 1
185 debug_assert!(
186 required_height >= coin_height.saturating_sub(1),
187 "Required height ({required_height}) must be >= coin_height - 1 ({coin_height})"
188 );
189
190 min_height = min_height.max(required_height);
191 }
192 }
193
194 Ok((min_height, min_time))
195}
196
197/// Evaluate if sequence locks are satisfied
198///
199/// Checks if the current block height and time satisfy the sequence lock constraints.
200///
201/// Matches consensus's EvaluateSequenceLocks() exactly.
202///
203/// # Arguments
204/// * `block_height` - Current block height
205/// * `block_time` - Current block's median time-past
206/// * `lock_pair` - (min_height, min_time) from calculate_sequence_locks
207///
208/// # Returns
209/// true if locks are satisfied, false otherwise
210#[spec_locked("5.5", "EvaluateSequenceLocks")]
211pub fn evaluate_sequence_locks(block_height: u64, block_time: u64, lock_pair: (i64, i64)) -> bool {
212 let (min_height, min_time) = lock_pair;
213
214 // u64 is always non-negative - no assertion needed
215
216 // Check height constraint
217 if min_height >= 0 && block_height <= min_height as u64 {
218 // Runtime assertion: Cast must be valid (min_height >= 0)
219 debug_assert!(
220 min_height >= 0,
221 "min_height ({min_height}) must be non-negative for cast to u64"
222 );
223 return false;
224 }
225
226 // Check time constraint
227 if min_time >= 0 && block_time <= min_time as u64 {
228 // Runtime assertion: Cast must be valid (min_time >= 0)
229 debug_assert!(
230 min_time >= 0,
231 "min_time ({min_time}) must be non-negative for cast to u64"
232 );
233 return false;
234 }
235
236 true
237}
238
239/// Check if transaction sequence locks are satisfied
240///
241/// Convenience function that combines CalculateSequenceLocks and EvaluateSequenceLocks.
242///
243/// # Arguments
244/// * `tx` - Transaction to check
245/// * `flags` - Script verification flags
246/// * `prev_heights` - Block heights at which each input confirmed
247/// * `block_height` - Current block height
248/// * `block_time` - Current block's median time-past
249/// * `recent_headers` - Recent headers for median time-past calculation
250///
251/// # Returns
252/// true if sequence locks are satisfied, false otherwise
253pub fn sequence_locks(
254 tx: &Transaction,
255 flags: u32,
256 prev_heights: &[u64],
257 block_height: u64,
258 block_time: u64,
259 recent_headers: Option<&[BlockHeader]>,
260) -> Result<bool> {
261 let lock_pair = calculate_sequence_locks(tx, flags, prev_heights, recent_headers)?;
262 Ok(evaluate_sequence_locks(block_height, block_time, lock_pair))
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn test_calculate_sequence_locks_disabled() {
271 let tx = Transaction {
272 version: 2,
273 inputs: vec![TransactionInput {
274 prevout: OutPoint {
275 hash: [0; 32],
276 index: 0,
277 },
278 script_sig: vec![],
279 sequence: SEQUENCE_LOCKTIME_DISABLE_FLAG as u64, // Disabled
280 }]
281 .into(),
282 outputs: vec![].into(),
283 lock_time: 0,
284 };
285
286 let prev_heights = vec![100];
287 let result =
288 calculate_sequence_locks(&tx, LOCKTIME_VERIFY_SEQUENCE, &prev_heights, None).unwrap();
289
290 // Disabled sequence should not create locks
291 assert_eq!(result, (-1, -1));
292 }
293
294 #[test]
295 fn test_calculate_sequence_locks_block_based() {
296 let tx = Transaction {
297 version: 2,
298 inputs: vec![TransactionInput {
299 prevout: OutPoint {
300 hash: [0; 32],
301 index: 0,
302 },
303 script_sig: vec![],
304 sequence: 100, // 100 blocks relative locktime
305 }]
306 .into(),
307 outputs: vec![].into(),
308 lock_time: 0,
309 };
310
311 let prev_heights = vec![1000]; // Input confirmed at height 1000
312 let result =
313 calculate_sequence_locks(&tx, LOCKTIME_VERIFY_SEQUENCE, &prev_heights, None).unwrap();
314
315 // Should require height 1000 + 100 - 1 = 1099
316 assert_eq!(result.0, 1099);
317 assert_eq!(result.1, -1); // No time constraint
318 }
319
320 #[test]
321 fn test_prev_height_overflowing_i64_returns_err() {
322 let tx = Transaction {
323 version: 2,
324 inputs: vec![TransactionInput {
325 prevout: OutPoint {
326 hash: [0; 32],
327 index: 0,
328 },
329 script_sig: vec![],
330 sequence: 10,
331 }]
332 .into(),
333 outputs: vec![].into(),
334 lock_time: 0,
335 };
336 let prev_heights = vec![(i64::MAX as u64).saturating_add(1)];
337 let err = calculate_sequence_locks(&tx, LOCKTIME_VERIFY_SEQUENCE, &prev_heights, None)
338 .unwrap_err();
339 assert!(matches!(
340 err,
341 crate::error::ConsensusError::ConsensusRuleViolation(_)
342 ));
343 }
344
345 #[test]
346 fn test_evaluate_sequence_locks() {
347 // Lock requires height 1099
348 let lock_pair = (1099, -1);
349
350 // Block height 1100 satisfies the lock
351 assert!(evaluate_sequence_locks(1100, 0, lock_pair));
352
353 // Block height 1099 does not satisfy (must be > 1099)
354 assert!(!evaluate_sequence_locks(1099, 0, lock_pair));
355
356 // Block height 1098 does not satisfy
357 assert!(!evaluate_sequence_locks(1098, 0, lock_pair));
358 }
359}