github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/pacemaker/timeout/controller_test.go (about) 1 package timeout 2 3 import ( 4 "math" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 ) 11 12 const ( 13 startRepTimeout float64 = 120 // Milliseconds 14 minRepTimeout float64 = 100 // Milliseconds 15 voteTimeoutFraction float64 = 0.5 // multiplicative factor 16 multiplicativeIncrease float64 = 1.5 // multiplicative factor 17 multiplicativeDecrease float64 = 0.85 // multiplicative factor 18 ) 19 20 func initTimeoutController(t *testing.T) *Controller { 21 tc, err := NewConfig( 22 time.Duration(startRepTimeout*1e6), 23 time.Duration(minRepTimeout*1e6), 24 voteTimeoutFraction, 25 multiplicativeIncrease, 26 multiplicativeDecrease, 27 0) 28 if err != nil { 29 t.Fail() 30 } 31 return NewController(tc) 32 } 33 34 // Test_TimeoutInitialization timeouts are initialized ands reported properly 35 func Test_TimeoutInitialization(t *testing.T) { 36 tc := initTimeoutController(t) 37 assert.Equal(t, tc.ReplicaTimeout().Milliseconds(), int64(startRepTimeout)) 38 assert.Equal(t, tc.VoteCollectionTimeout().Milliseconds(), int64(startRepTimeout*voteTimeoutFraction)) 39 40 // verify that returned timeout channel 41 select { 42 case <-tc.Channel(): 43 break 44 default: 45 assert.Fail(t, "timeout channel did not return") 46 } 47 assert.True(t, tc.TimerInfo() == nil) 48 tc.Channel() 49 } 50 51 // Test_TimeoutIncrease verifies that timeout increases exponentially 52 func Test_TimeoutIncrease(t *testing.T) { 53 tc := initTimeoutController(t) 54 55 for i := 1; i < 10; i += 1 { 56 tc.OnTimeout() 57 assert.Equal(t, 58 tc.ReplicaTimeout().Milliseconds(), 59 int64(startRepTimeout*math.Pow(multiplicativeIncrease, float64(i))), 60 ) 61 assert.Equal(t, 62 tc.VoteCollectionTimeout().Milliseconds(), 63 int64(startRepTimeout*voteTimeoutFraction*math.Pow(multiplicativeIncrease, float64(i))), 64 ) 65 } 66 } 67 68 // Test_TimeoutDecrease verifies that timeout decreases exponentially 69 func Test_TimeoutDecrease(t *testing.T) { 70 tc := initTimeoutController(t) 71 tc.OnTimeout() 72 tc.OnTimeout() 73 tc.OnTimeout() 74 75 repTimeout := startRepTimeout * math.Pow(multiplicativeIncrease, 3.0) 76 for i := 1; i <= 6; i += 1 { 77 tc.OnProgressBeforeTimeout() 78 assert.Equal(t, 79 tc.ReplicaTimeout().Milliseconds(), 80 int64(repTimeout*math.Pow(multiplicativeDecrease, float64(i))), 81 ) 82 assert.Equal(t, 83 tc.VoteCollectionTimeout().Milliseconds(), 84 int64((repTimeout*math.Pow(multiplicativeDecrease, float64(i)))*voteTimeoutFraction), 85 ) 86 } 87 } 88 89 // Test_MinCutoff verifies that timeout does not decrease below minRepTimeout 90 func Test_MinCutoff(t *testing.T) { 91 tc := initTimeoutController(t) 92 93 tc.OnTimeout() // replica timeout increases 120 -> 1.5 * 120 = 180 94 tc.OnProgressBeforeTimeout() // replica timeout decreases 180 -> 180 * 0.85 = 153 95 tc.OnProgressBeforeTimeout() // replica timeout decreases 153 -> 153 * 0.85 = 130.05 96 tc.OnProgressBeforeTimeout() // replica timeout decreases 130.05 -> 130.05 * 0.85 = 110.5425 97 tc.OnProgressBeforeTimeout() // replica timeout decreases 110.5425 -> max(110.5425 * 0.85, 100) = 100 98 99 tc.OnProgressBeforeTimeout() 100 assert.Equal(t, tc.ReplicaTimeout().Milliseconds(), int64(minRepTimeout)) 101 assert.Equal(t, tc.VoteCollectionTimeout().Milliseconds(), int64(minRepTimeout*voteTimeoutFraction)) 102 } 103 104 // Test_MinCutoff verifies that timeout does not increase beyond timeout cap 105 func Test_MaxCutoff(t *testing.T) { 106 // here we use a different timeout controller with a larger timeoutIncrease to avoid too many iterations 107 c, err := NewConfig( 108 time.Duration(200*float64(time.Millisecond)), 109 time.Duration(minRepTimeout*float64(time.Millisecond)), 110 voteTimeoutFraction, 111 10, 112 multiplicativeDecrease, 113 0) 114 if err != nil { 115 t.Fail() 116 } 117 tc := NewController(c) 118 119 for i := 1; i <= 50; i += 1 { 120 tc.OnTimeout() // after already 7 iterations we should have reached the max value 121 assert.True(t, float64(tc.ReplicaTimeout().Milliseconds()) <= timeoutCap) 122 assert.True(t, float64(tc.VoteCollectionTimeout().Milliseconds()) <= timeoutCap*voteTimeoutFraction) 123 } 124 } 125 126 // Test_CombinedIncreaseDecreaseDynamics verifies that timeout increases and decreases 127 // work as expected in combination 128 func Test_CombinedIncreaseDecreaseDynamics(t *testing.T) { 129 increase, decrease := true, false 130 testDynamicSequence := func(seq []bool) { 131 tc := initTimeoutController(t) 132 var numberIncreases int = 0 133 var numberDecreases int = 0 134 for _, increase := range seq { 135 if increase { 136 numberIncreases += 1 137 tc.OnTimeout() 138 } else { 139 numberDecreases += 1 140 tc.OnProgressBeforeTimeout() 141 } 142 } 143 144 expectedRepTimeout := startRepTimeout * math.Pow(multiplicativeIncrease, float64(numberIncreases)) * math.Pow(multiplicativeDecrease, float64(numberDecreases)) 145 numericalError := math.Abs(expectedRepTimeout - float64(tc.ReplicaTimeout().Milliseconds())) 146 require.True(t, numericalError <= 1.0) // at most one millisecond numerical error 147 numericalError = math.Abs(expectedRepTimeout*voteTimeoutFraction - float64(tc.VoteCollectionTimeout().Milliseconds())) 148 require.True(t, numericalError <= 1.0) // at most one millisecond numerical error 149 } 150 151 testDynamicSequence([]bool{increase, increase, increase, decrease, decrease, decrease}) 152 testDynamicSequence([]bool{increase, decrease, increase, decrease, increase, decrease}) 153 } 154 155 func Test_BlockRateDelay(t *testing.T) { 156 // here we use a different timeout controller with a larger timeoutIncrease to avoid too many iterations 157 c, err := NewConfig( 158 time.Duration(200*float64(time.Millisecond)), 159 time.Duration(minRepTimeout*float64(time.Millisecond)), 160 voteTimeoutFraction, 161 10, 162 multiplicativeDecrease, 163 time.Second) 164 if err != nil { 165 t.Fail() 166 } 167 tc := NewController(c) 168 assert.Equal(t, time.Second, tc.BlockRateDelay()) 169 }