blvm-consensus 0.1.10

Bitcoin Commons BLVM: Direct mathematical implementation of Bitcoin consensus rules from the Orange Paper
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
//! Soft fork activation tests (BIP9 version bits)
//!
//! Comprehensive tests for soft fork activation using BIP9 version bits.
//! Tests version bits state transitions, lock-in periods, activation heights,
//! and multiple concurrent soft forks.
//!
//! BIP9: Version bits with timeout and delay
//! https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki

use blvm_consensus::types::BlockHeader;
use blvm_consensus::{SEGWIT_ACTIVATION_MAINNET, TAPROOT_ACTIVATION_MAINNET};

/// BIP9 version bits constants
///
/// Version bits are bits 0-28 in the block version field.
/// Bit 29 is used for testnet, bit 30 is used for CSV, bit 31 is used for SegWit.
pub const VERSIONBITS_TOP_BITS: u32 = 0xE0000000;
pub const VERSIONBITS_TOP_MASK: u32 = 0xE0000000;

/// BIP9 deployment states
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Bip9State {
    /// Not defined (deployment not started)
    Defined,
    /// Started (deployment period started)
    Started,
    /// Locked in (threshold reached, waiting for activation)
    LockedIn,
    /// Active (soft fork is active)
    Active,
    /// Failed (timeout reached without activation)
    Failed,
}

/// BIP9 deployment parameters
#[derive(Debug, Clone)]
pub struct Bip9Deployment {
    /// Bit position (0-28)
    pub bit: u8,
    /// Start time (Unix timestamp)
    pub start_time: u64,
    /// Timeout (Unix timestamp)
    pub timeout: u64,
    /// Lock-in period (number of blocks)
    pub lock_in_period: u32,
    /// Activation threshold (number of blocks with bit set)
    pub threshold: u32,
}

/// Calculate BIP9 state from block headers
///
/// Given a deployment and block headers, calculates the current state
/// of the soft fork deployment.
pub fn calculate_bip9_state(
    deployment: &Bip9Deployment,
    headers: &[BlockHeader],
    current_time: u64,
    current_height: u64,
) -> Bip9State {
    // Check if deployment has started
    if current_time < deployment.start_time {
        return Bip9State::Defined;
    }

    // Check if deployment has timed out
    if current_time >= deployment.timeout {
        return Bip9State::Failed;
    }

    // Count blocks with version bit set in the recent period
    let mut bit_set_count = 0;
    let check_period = deployment.lock_in_period.min(headers.len() as u32);

    for header in headers.iter().take(check_period as usize) {
        let version_bit = (header.version >> deployment.bit) & 1;
        if version_bit == 1 {
            bit_set_count += 1;
        }
    }

    // Check if locked in
    if bit_set_count >= deployment.threshold {
        // Lock-in occurs when threshold is met during a lock_in_period
        // If we've checked a full period (check_period == lock_in_period), lock-in was detected
        // at the end of that period. Activation happens lock_in_period blocks after lock-in.

        // Calculate when lock-in was detected
        // Key insight: If we have exactly lock_in_period headers showing threshold met,
        // lock-in was detected at the end of the period those headers represent.
        // Once lock-in is detected at a specific height, it stays at that height.
        let lock_in_detected_at = if check_period == deployment.lock_in_period
            && headers.len() >= deployment.lock_in_period as usize
        {
            // Headers represent a complete period where threshold was met
            // Lock-in was detected at the end of that period
            // If we have exactly lock_in_period headers, they represent the period ending at lock_in_period
            // This is the first period where lock-in could occur
            deployment.lock_in_period as u64
        } else {
            // Partial period, but threshold met - assume lock-in will be at end of current period
            ((current_height / deployment.lock_in_period as u64) + 1)
                * deployment.lock_in_period as u64
        };

        let activation_height = lock_in_detected_at + deployment.lock_in_period as u64;

        if current_height >= activation_height {
            return Bip9State::Active;
        }
        return Bip9State::LockedIn;
    }

    // Still in started period
    Bip9State::Started
}

/// Test BIP9 version bits extraction
#[test]
fn test_bip9_version_bits_extraction() {
    // Test extracting version bits from block version
    let version = 0x20000001u32; // Bit 29 (0x20000000) + bit 0 set
                                 // Note: BIP9 version bits are bits 0-28, but SegWit uses bit 1 of the top 3 bits
                                 // Bit 29 (0x20000000) is SegWit-related, not bit 31

    // Extract bit 0
    let bit0 = version & 1;
    assert_eq!(bit0, 1);

    // Extract bit 29 (often associated with SegWit signaling)
    let bit29 = (version >> 29) & 1;
    assert_eq!(bit29, 1);

    // Extract bit 31 (should be 0 for 0x20000001)
    let bit31 = (version >> 31) & 1;
    assert_eq!(bit31, 0);
}

/// Test BIP9 state transitions
#[test]
fn test_bip9_state_transitions() {
    // Create a deployment
    let deployment = Bip9Deployment {
        bit: 0,
        start_time: 1000,
        timeout: 10000,
        lock_in_period: 2016, // 2 weeks
        threshold: 1916,      // 95% threshold
    };

    let current_time = 500;
    let current_height = 0;
    let headers = vec![];

    // Before start time - should be Defined
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::Defined);

    // After start time but before timeout - should be Started
    let current_time = 2000;
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::Started);

    // After timeout - should be Failed
    let current_time = 11000;
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::Failed);
}

/// Test BIP9 lock-in period
#[test]
fn test_bip9_lock_in_period() {
    let deployment = Bip9Deployment {
        bit: 0,
        start_time: 1000,
        timeout: 10000,
        lock_in_period: 2016,
        threshold: 1916, // 95% of 2016
    };

    // Create headers with bit set (above threshold)
    let mut headers = Vec::new();
    for i in 0..2016 {
        let version = if i < 1916 { 0x00000001 } else { 0x00000000 }; // Bit 0 set for first 1916 blocks
        headers.push(BlockHeader {
            version,
            prev_block_hash: [i as u8; 32],
            merkle_root: [0; 32],
            timestamp: 1000 + (i * 600),
            bits: 0x1d00ffff,
            nonce: 0,
        });
    }

    let current_time = 2000;
    let current_height = 2016;

    // Should be LockedIn (threshold reached)
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::LockedIn);

    // After lock-in period, should be Active
    let current_height = 4032; // 2016 blocks after lock-in
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::Active);
}

/// Test BIP9 activation height
#[test]
fn test_bip9_activation_height() {
    let deployment = Bip9Deployment {
        bit: 0,
        start_time: 1000,
        timeout: 10000,
        lock_in_period: 2016,
        threshold: 1916,
    };

    // Create headers with bit set
    let mut headers = Vec::new();
    for i in 0..2016 {
        let version = if i < 1916 { 0x00000001 } else { 0x00000000 };
        headers.push(BlockHeader {
            version,
            prev_block_hash: [i as u8; 32],
            merkle_root: [0; 32],
            timestamp: 1000 + (i * 600),
            bits: 0x1d00ffff,
            nonce: 0,
        });
    }

    let current_time = 2000;

    // At lock-in height - should be LockedIn
    let current_height = 2016;
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::LockedIn);

    // At activation height - should be Active
    let current_height = 4032; // lock_in_period blocks after lock-in
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::Active);
}

/// Test multiple concurrent soft forks
#[test]
fn test_multiple_concurrent_soft_forks() {
    // Test that multiple soft forks can be tracked simultaneously
    let deployment1 = Bip9Deployment {
        bit: 0,
        start_time: 1000,
        timeout: 10000,
        lock_in_period: 2016,
        threshold: 1916,
    };

    let deployment2 = Bip9Deployment {
        bit: 1,
        start_time: 2000,
        timeout: 11000,
        lock_in_period: 2016,
        threshold: 1916,
    };

    // Create headers with both bits set
    let mut headers = Vec::new();
    for i in 0..2016 {
        let version = 0x00000003; // Bits 0 and 1 set
        headers.push(BlockHeader {
            version,
            prev_block_hash: [i as u8; 32],
            merkle_root: [0; 32],
            timestamp: 1000 + (i * 600),
            bits: 0x1d00ffff,
            nonce: 0,
        });
    }

    let current_time = 3000;
    let current_height = 2016;

    // Both deployments should be in LockedIn state
    let state1 = calculate_bip9_state(&deployment1, &headers, current_time, current_height);
    let state2 = calculate_bip9_state(&deployment2, &headers, current_time, current_height);

    assert_eq!(state1, Bip9State::LockedIn);
    assert_eq!(state2, Bip9State::LockedIn);
}

/// Test blocks at exact activation heights
#[test]
fn test_blocks_at_exact_activation_heights() {
    // Test behavior at exact activation height
    let deployment = Bip9Deployment {
        bit: 0,
        start_time: 1000,
        timeout: 10000,
        lock_in_period: 2016,
        threshold: 1916,
    };

    // Create headers with bit set
    let mut headers = Vec::new();
    for i in 0..2016 {
        let version = if i < 1916 { 0x00000001 } else { 0x00000000 };
        headers.push(BlockHeader {
            version,
            prev_block_hash: [i as u8; 32],
            merkle_root: [0; 32],
            timestamp: 1000 + (i * 600),
            bits: 0x1d00ffff,
            nonce: 0,
        });
    }

    let current_time = 2000;

    // One block before activation - should be LockedIn
    let current_height = 4031;
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::LockedIn);

    // At exact activation height - should be Active
    let current_height = 4032;
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::Active);

    // After activation - should remain Active
    let current_height = 4033;
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::Active);
}

/// Test BIP9 deactivation scenarios
#[test]
fn test_bip9_deactivation() {
    let deployment = Bip9Deployment {
        bit: 0,
        start_time: 1000,
        timeout: 10000,
        lock_in_period: 2016,
        threshold: 1916,
    };

    // Create headers without bit set (below threshold)
    let mut headers = Vec::new();
    for i in 0..2016 {
        let version = 0x00000000; // Bit not set
        headers.push(BlockHeader {
            version,
            prev_block_hash: [i as u8; 32],
            merkle_root: [0; 32],
            timestamp: 1000 + (i * 600),
            bits: 0x1d00ffff,
            nonce: 0,
        });
    }

    let current_time = 2000;
    let current_height = 2016;

    // Should be Started (below threshold)
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::Started);

    // After timeout - should be Failed
    let current_time = 11000;
    let state = calculate_bip9_state(&deployment, &headers, current_time, current_height);
    assert_eq!(state, Bip9State::Failed);
}

/// Test historical SegWit activation
///
/// SegWit (BIP141) on mainnet: `SEGWIT_ACTIVATION_MAINNET`.
/// This test verifies the activation process.
#[test]
fn test_segwit_activation() {
    // SegWit uses bit 1 (which is bit 29 in full version field, 0x20000000)
    // Note: BIP9 version bits are 0-28, but SegWit uses bit 1 of the top 3 bits
    let segwit_deployment = Bip9Deployment {
        bit: 1,                 // BIP9 bit 1 (not bit 31 of full version)
        start_time: 1479168000, // Approximate start time
        timeout: 1510704000,    // Approximate timeout
        lock_in_period: 2016,
        threshold: 1916, // 95%
    };

    // Create headers with SegWit bit set
    let mut headers = Vec::new();
    for i in 0..2016 {
        let version = 0x20000002; // Bit 1 set (0x00000002) + top bit for SegWit
        headers.push(BlockHeader {
            version,
            prev_block_hash: [i as u8; 32],
            merkle_root: [0; 32],
            timestamp: 1479168000 + (i * 600),
            bits: 0x1d00ffff,
            nonce: 0,
        });
    }

    let current_time = 1500000000;
    let current_height = SEGWIT_ACTIVATION_MAINNET;

    // At activation height, should be Active
    let state = calculate_bip9_state(&segwit_deployment, &headers, current_time, current_height);
    // Note: This is a simplified test - actual SegWit activation was more complex
    assert!(state == Bip9State::Active || state == Bip9State::LockedIn);
}

/// Test historical Taproot activation
///
/// Taproot (BIP341) on mainnet: `TAPROOT_ACTIVATION_MAINNET`.
#[test]
fn test_taproot_activation() {
    // Taproot uses bit 2 (0x00000004) in version bits
    let taproot_deployment = Bip9Deployment {
        bit: 2,
        start_time: 1619222400, // Approximate start time
        timeout: 1628640000,    // Approximate timeout
        lock_in_period: 2016,
        threshold: 1815, // 90% threshold (changed from 95% for Taproot)
    };

    // Create headers with Taproot bit set
    let mut headers = Vec::new();
    for i in 0..2016 {
        let version = 0x00000004; // Taproot bit set
        headers.push(BlockHeader {
            version,
            prev_block_hash: [i as u8; 32],
            merkle_root: [0; 32],
            timestamp: 1619222400 + (i * 600),
            bits: 0x1d00ffff,
            nonce: 0,
        });
    }

    let current_time = 1625000000;
    let current_height = TAPROOT_ACTIVATION_MAINNET;

    // At activation height, should be Active
    let state = calculate_bip9_state(&taproot_deployment, &headers, current_time, current_height);
    // Note: This is a simplified test - actual Taproot activation was more complex
    assert!(state == Bip9State::Active || state == Bip9State::LockedIn);
}

/// Test version bits state machine correctness
///
/// Verifies that state transitions follow the correct state machine:
/// Defined -> Started -> LockedIn -> Active
/// or
/// Defined -> Started -> Failed
#[test]
fn test_version_bits_state_machine() {
    let deployment = Bip9Deployment {
        bit: 0,
        start_time: 1000,
        timeout: 10000,
        lock_in_period: 2016,
        threshold: 1916,
    };

    let headers = vec![];

    // Test state machine progression
    // Defined -> Started
    let mut current_time = 500;
    let mut state = calculate_bip9_state(&deployment, &headers, current_time, 0);
    assert_eq!(state, Bip9State::Defined);

    current_time = 2000;
    state = calculate_bip9_state(&deployment, &headers, current_time, 0);
    assert_eq!(state, Bip9State::Started);

    // Started -> Failed (timeout without lock-in)
    current_time = 11000;
    state = calculate_bip9_state(&deployment, &headers, current_time, 0);
    assert_eq!(state, Bip9State::Failed);
}

/// Test version bits with different thresholds
///
/// Tests that different activation thresholds work correctly.
#[test]
fn test_version_bits_thresholds() {
    let deployment_95 = Bip9Deployment {
        bit: 0,
        start_time: 1000,
        timeout: 10000,
        lock_in_period: 2016,
        threshold: 1916, // 95%
    };

    let deployment_90 = Bip9Deployment {
        bit: 1,
        start_time: 1000,
        timeout: 10000,
        lock_in_period: 2016,
        threshold: 1815, // 90% (Taproot threshold)
    };

    // Create headers with both bits set
    let mut headers = Vec::new();
    for i in 0..2016 {
        let version = 0x00000003; // Both bits set
        headers.push(BlockHeader {
            version,
            prev_block_hash: [i as u8; 32],
            merkle_root: [0; 32],
            timestamp: 1000 + (i * 600),
            bits: 0x1d00ffff,
            nonce: 0,
        });
    }

    let current_time = 2000;
    let current_height = 2016;

    // Both should be LockedIn (both thresholds met)
    let state_95 = calculate_bip9_state(&deployment_95, &headers, current_time, current_height);
    let state_90 = calculate_bip9_state(&deployment_90, &headers, current_time, current_height);

    assert_eq!(state_95, Bip9State::LockedIn);
    assert_eq!(state_90, Bip9State::LockedIn);
}