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  }