mediantime_test.go raw

   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