1 package blockchain
2 3 import (
4 "strconv"
5 "testing"
6 "time"
7 )
8 9 // TestMedianTime tests the medianTime implementation.
10 func TestMedianTime(t *testing.T) {
11 tests := []struct {
12 in []int64
13 wantOffset int64
14 useDupID bool
15 }{
16 // Not enough samples must result in an offset of 0.
17 {in: []int64{1}, wantOffset: 0},
18 {in: []int64{1, 2}, wantOffset: 0},
19 {in: []int64{1, 2, 3}, wantOffset: 0},
20 {in: []int64{1, 2, 3, 4}, wantOffset: 0},
21 // Various number of entries. The expected offset is only updated on odd number of elements.
22 {in: []int64{-13, 57, -4, -23, -12}, wantOffset: -12},
23 {in: []int64{55, -13, 61, -52, 39, 55}, wantOffset: 39},
24 {in: []int64{-62, -58, -30, -62, 51, -30, 15}, wantOffset: -30},
25 {in: []int64{29, -47, 39, 54, 42, 41, 8, -33}, wantOffset: 39},
26 {in: []int64{37, 54, 9, -21, -56, -36, 5, -11, -39}, wantOffset: -11},
27 {in: []int64{57, -28, 25, -39, 9, 63, -16, 19, -60, 25}, wantOffset: 9},
28 {in: []int64{-5, -4, -3, -2, -1}, wantOffset: -3, useDupID: true},
29 // The offset stops being updated once the max number of entries has been reached. This is actually a bug from
30 // Bitcoin Core, but since the time is ultimately used as a part of the consensus rules, it must be mirrored.
31 {in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52}, wantOffset: 17},
32 {in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45}, wantOffset: 17},
33 {in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45, 4}, wantOffset: 17},
34 // Offsets that are too far away from the local time should be ignored.
35 {in: []int64{-4201, 4202, -4203, 4204, -4205}, wantOffset: 0},
36 // Exercise the condition where the median offset is greater than the max allowed adjustment, but there is at
37 // least one sample that is close enough to the current time to avoid triggering a warning about an invalid
38 // local clock.
39 {in: []int64{4201, 4202, 4203, 4204, -299}, wantOffset: 0},
40 }
41 // Modify the max number of allowed median time entries for these tests.
42 maxMedianTimeEntries = 10
43 defer func() {
44 maxMedianTimeEntries = 200
45 }()
46 for i, test := range tests {
47 filter := NewMedianTime()
48 for j, offset := range test.in {
49 id := strconv.Itoa(j)
50 now := time.Unix(time.Now().Unix(), 0)
51 tOffset := now.Add(time.Duration(offset) * time.Second)
52 filter.AddTimeSample(id, tOffset)
53 // Ensure the duplicate IDs are ignored.
54 if test.useDupID {
55 // Modify the offsets to ensure the final median would be different if the duplicate is added.
56 tOffset = tOffset.Add(time.Duration(offset) *
57 time.Second,
58 )
59 filter.AddTimeSample(id, tOffset)
60 }
61 }
62 // Since it is possible that the time.Now call in AddTimeSample and the time.Now calls here in the tests will be
63 // off by one second, allow a fudge factor to compensate.
64 gotOffset := filter.Offset()
65 wantOffset := time.Duration(test.wantOffset) * time.Second
66 wantOffset2 := time.Duration(test.wantOffset-1) * time.Second
67 if gotOffset != wantOffset && gotOffset != wantOffset2 {
68 t.Errorf("Offset #%d: unexpected offset -- got %v, "+
69 "want %v or %v", i, gotOffset, wantOffset,
70 wantOffset2,
71 )
72 continue
73 }
74 // Since it is possible that the time.Now call in AdjustedTime and the time.Now call here in the tests will be
75 // off by one second, allow a fudge factor to compensate.
76 adjustedTime := filter.AdjustedTime()
77 now := time.Unix(time.Now().Unix(), 0)
78 wantTime := now.Add(filter.Offset())
79 wantTime2 := now.Add(filter.Offset() - time.Second)
80 if !adjustedTime.Equal(wantTime) && !adjustedTime.Equal(wantTime2) {
81 t.Errorf("AdjustedTime #%d: unexpected result -- got %v, "+
82 "want %v or %v", i, adjustedTime, wantTime,
83 wantTime2,
84 )
85 continue
86 }
87 }
88 }
89