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
//! Difficulty Adjustment Verification Tests
//!
//! Tests to verify BLLVM's difficulty adjustment matches consensus exactly,
//! including the known off-by-one bug for consensus compatibility.
//!
//! Consensus-critical: Difficulty differences = chain split
use blvm_consensus::constants::*;
use blvm_consensus::pow::get_next_work_required;
use blvm_consensus::types::*;
/// Test difficulty adjustment clamping: timespan < expected_time/4
///
/// Consensus clamps to expected_time/4, which should result in 4x difficulty increase
#[test]
fn test_difficulty_clamp_minimum_timespan() {
// Create 2016 blocks with very short timespan (all at same time)
// This should clamp to expected_time/4, resulting in 4x difficulty increase
let mut prev_headers = Vec::new();
let base_timestamp = 1231006505; // Genesis block timestamp
// Create exactly DIFFICULTY_ADJUSTMENT_INTERVAL blocks with minimal spacing
for i in 0..DIFFICULTY_ADJUSTMENT_INTERVAL {
prev_headers.push(BlockHeader {
version: 1,
prev_block_hash: [i as u8; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (i * 10), // Very short 10-second intervals
bits: 0x1d00ffff, // Genesis difficulty
nonce: 0,
});
}
let current_header = BlockHeader {
version: 1,
prev_block_hash: [0xff; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (DIFFICULTY_ADJUSTMENT_INTERVAL * 10),
bits: 0x1d00ffff,
nonce: 0,
};
let result = get_next_work_required(¤t_header, &prev_headers);
assert!(result.is_ok(), "Difficulty adjustment should succeed");
let new_bits = result.unwrap();
// With timespan clamped to expected_time/4, difficulty should increase 4x
// This means target should decrease 4x, so bits should decrease (bits are inverse of difficulty)
// Genesis bits = 0x1d00ffff, after 4x increase should be lower (harder)
// Note: bits encode target, lower target = higher difficulty = lower bits
assert!(
new_bits <= 0x1d00ffff,
"Difficulty should increase (bits should be <= genesis, as bits are inverse)"
);
}
/// Test difficulty adjustment clamping: timespan > expected_time*4
///
/// Consensus clamps to expected_time*4, which should result in 4x difficulty decrease
#[test]
fn test_difficulty_clamp_maximum_timespan() {
// Create 2016 blocks with very long timespan
// This should clamp to expected_time*4, resulting in 4x difficulty decrease
let mut prev_headers = Vec::new();
let base_timestamp = 1231006505;
// Create exactly DIFFICULTY_ADJUSTMENT_INTERVAL blocks with very long spacing
for i in 0..DIFFICULTY_ADJUSTMENT_INTERVAL {
prev_headers.push(BlockHeader {
version: 1,
prev_block_hash: [i as u8; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (i * TARGET_TIME_PER_BLOCK * 10), // 10x normal spacing
bits: 0x1d00ffff,
nonce: 0,
});
}
let current_header = BlockHeader {
version: 1,
prev_block_hash: [0xff; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (DIFFICULTY_ADJUSTMENT_INTERVAL * TARGET_TIME_PER_BLOCK * 10),
bits: 0x1d00ffff,
nonce: 0,
};
let result = get_next_work_required(¤t_header, &prev_headers);
assert!(result.is_ok(), "Difficulty adjustment should succeed");
let new_bits = result.unwrap();
// With timespan clamped to expected_time*4, difficulty should decrease 4x
// This means target should increase 4x, so bits should decrease
// Genesis bits = 0x1d00ffff, after 4x decrease should be lower (easier)
// Note: bits are inverse of difficulty (higher bits = easier)
assert!(
new_bits <= MAX_TARGET as u64,
"Bits should not exceed maximum target"
);
}
/// Test difficulty adjustment with perfect timing (exactly expected time)
///
/// With perfect 10-minute intervals, difficulty should stay approximately the same
#[test]
fn test_difficulty_perfect_timing() {
let mut prev_headers = Vec::new();
let base_timestamp = 1231006505;
// Create exactly DIFFICULTY_ADJUSTMENT_INTERVAL blocks with perfect 10-minute spacing
for i in 0..DIFFICULTY_ADJUSTMENT_INTERVAL {
prev_headers.push(BlockHeader {
version: 1,
prev_block_hash: [i as u8; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (i * TARGET_TIME_PER_BLOCK),
bits: 0x1d00ffff,
nonce: 0,
});
}
let current_header = BlockHeader {
version: 1,
prev_block_hash: [0xff; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (DIFFICULTY_ADJUSTMENT_INTERVAL * TARGET_TIME_PER_BLOCK),
bits: 0x1d00ffff,
nonce: 0,
};
let result = get_next_work_required(¤t_header, &prev_headers);
assert!(result.is_ok(), "Difficulty adjustment should succeed");
let new_bits = result.unwrap();
// With perfect timing, difficulty should stay approximately the same
// Due to the off-by-one bug, there will be a small adjustment
// But it should be close to the original bits
let original_bits = 0x1d00ffff;
let diff = new_bits.abs_diff(original_bits);
// Allow small difference due to off-by-one bug and rounding
assert!(
diff < 0x00010000,
"Difficulty should stay approximately the same with perfect timing"
);
}
/// Test difficulty adjustment off-by-one bug
///
/// Consensus measures (n-1) intervals but compares against n intervals
/// This causes a small adjustment even with perfect timing
#[test]
fn test_difficulty_off_by_one_bug() {
let mut prev_headers = Vec::new();
let base_timestamp = 1231006505;
// Create exactly DIFFICULTY_ADJUSTMENT_INTERVAL blocks with perfect spacing
for i in 0..DIFFICULTY_ADJUSTMENT_INTERVAL {
prev_headers.push(BlockHeader {
version: 1,
prev_block_hash: [i as u8; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (i * TARGET_TIME_PER_BLOCK),
bits: 0x1d00ffff,
nonce: 0,
});
}
let current_header = BlockHeader {
version: 1,
prev_block_hash: [0xff; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (DIFFICULTY_ADJUSTMENT_INTERVAL * TARGET_TIME_PER_BLOCK),
bits: 0x1d00ffff,
nonce: 0,
};
let result = get_next_work_required(¤t_header, &prev_headers);
assert!(result.is_ok(), "Difficulty adjustment should succeed");
let new_bits = result.unwrap();
let original_bits = 0x1d00ffff;
// Due to off-by-one bug:
// - We measure (DIFFICULTY_ADJUSTMENT_INTERVAL - 1) intervals = 2015 * 600 seconds
// - But compare against DIFFICULTY_ADJUSTMENT_INTERVAL intervals = 2016 * 600 seconds
// - Adjustment = (2015 * 600) / (2016 * 600) ≈ 0.9995
// - So difficulty increases slightly (target decreases, bits decrease)
// Note: bits are inverse of difficulty - lower bits = higher difficulty
// This matches The specification's buggy behavior exactly
assert!(new_bits <= original_bits, "Off-by-one bug should cause slight difficulty increase (bits decrease as difficulty increases)");
}
/// Test difficulty adjustment with insufficient headers
#[test]
fn test_difficulty_insufficient_headers() {
let prev_headers = vec![BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 1231006505,
bits: 0x1d00ffff,
nonce: 0,
}];
let current_header = BlockHeader {
version: 1,
prev_block_hash: [0xff; 32],
merkle_root: [0; 32],
timestamp: 1231006505 + 600,
bits: 0x1d00ffff,
nonce: 0,
};
let result = get_next_work_required(¤t_header, &prev_headers);
assert!(result.is_err(), "Should fail with insufficient headers");
}
/// Test difficulty adjustment clamping boundaries
///
/// Verify that clamping works correctly at the boundaries
#[test]
fn test_difficulty_clamping_boundaries() {
let mut prev_headers = Vec::new();
let base_timestamp = 1231006505;
// Test at exactly expected_time/4 boundary
let timespan_quarter = (DIFFICULTY_ADJUSTMENT_INTERVAL * TARGET_TIME_PER_BLOCK) / 4;
for i in 0..DIFFICULTY_ADJUSTMENT_INTERVAL {
prev_headers.push(BlockHeader {
version: 1,
prev_block_hash: [i as u8; 32],
merkle_root: [0; 32],
timestamp: base_timestamp
+ (i * timespan_quarter / (DIFFICULTY_ADJUSTMENT_INTERVAL - 1)),
bits: 0x1d00ffff,
nonce: 0,
});
}
let current_header = BlockHeader {
version: 1,
prev_block_hash: [0xff; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + timespan_quarter,
bits: 0x1d00ffff,
nonce: 0,
};
let result = get_next_work_required(¤t_header, &prev_headers);
assert!(
result.is_ok(),
"Difficulty adjustment should succeed at boundary"
);
}
/// Test difficulty adjustment with integer arithmetic
///
/// Verify no floating point is used (consensus-critical)
#[test]
fn test_difficulty_integer_arithmetic() {
// This test verifies that all calculations use integer arithmetic
// by checking that results are deterministic and don't have floating point artifacts
let mut prev_headers = Vec::new();
let base_timestamp = 1231006505;
for i in 0..DIFFICULTY_ADJUSTMENT_INTERVAL {
prev_headers.push(BlockHeader {
version: 1,
prev_block_hash: [i as u8; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (i * TARGET_TIME_PER_BLOCK),
bits: 0x1d00ffff,
nonce: 0,
});
}
let current_header = BlockHeader {
version: 1,
prev_block_hash: [0xff; 32],
merkle_root: [0; 32],
timestamp: base_timestamp + (DIFFICULTY_ADJUSTMENT_INTERVAL * TARGET_TIME_PER_BLOCK),
bits: 0x1d00ffff,
nonce: 0,
};
// Call twice - should get identical results (no floating point randomness)
let result1 = get_next_work_required(¤t_header, &prev_headers);
let result2 = get_next_work_required(¤t_header, &prev_headers);
assert_eq!(
result1, result2,
"Difficulty calculation must be deterministic (integer arithmetic)"
);
}
/// Mainnet height 112896 retarget — Blockstream `bits` for 112896 is `0x1b00dc31` (453041201).
#[test]
fn mainnet_retarget_height_112896_matches_observed_chain_bits() {
let interval = DIFFICULTY_ADJUSTMENT_INTERVAL as usize;
let mut prev_headers = Vec::with_capacity(interval);
// Only timestamps of block 110880 and 112895 and nBits of 112895 affect the formula.
let first_ts = 1298800760u64;
let last_ts = 1299683275u64;
let period_bits = 453062093u64; // 0x1b012dcd (mainnet blocks 110880..=112895)
for i in 0..interval {
let ts = match i {
0 => first_ts,
x if x == interval - 1 => last_ts,
_ => first_ts + (i as u64 * 60),
};
prev_headers.push(BlockHeader {
version: 1,
prev_block_hash: [i as u8; 32],
merkle_root: [0; 32],
timestamp: ts,
bits: period_bits,
nonce: 0,
});
}
let current = BlockHeader {
version: 1,
prev_block_hash: [0xee; 32],
merkle_root: [0; 32],
timestamp: 1299684355,
bits: 453041201,
nonce: 0,
};
let got = get_next_work_required(¤t, &prev_headers).unwrap();
assert_eq!(
got, 453041201,
"expected chain nBits 0x1b00dc31 for retarget at 112896"
);
}