github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/integration/integration_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/onflow/flow-go/consensus/hotstuff/pacemaker/timeout"
    13  	"github.com/onflow/flow-go/utils/unittest"
    14  )
    15  
    16  // a pacemaker timeout to wait for proposals. Usually 10 ms is enough,
    17  // but for slow environment like CI, a longer one is needed.
    18  const safeTimeout = 2 * time.Second
    19  
    20  // number of failed rounds before first timeout increase
    21  const happyPathMaxRoundFailures = 6
    22  
    23  func TestSingleInstance(t *testing.T) {
    24  
    25  	// set up a single instance to run
    26  	// NOTE: currently, the HotStuff logic will infinitely call back on itself
    27  	// with a single instance, leading to a boundlessly growing call stack,
    28  	// which slows down the mocks significantly due to splitting the callstack
    29  	// to find the calling function name; we thus keep it low for now
    30  	finalView := uint64(10)
    31  	in := NewInstance(t,
    32  		WithStopCondition(ViewFinalized(finalView)),
    33  	)
    34  
    35  	// run the event handler until we reach a stop condition
    36  	err := in.Run()
    37  	require.ErrorIs(t, err, errStopCondition, "should run until stop condition")
    38  
    39  	// check if forks and pacemaker are in expected view state
    40  	assert.Equal(t, finalView, in.forks.FinalizedView(), "finalized view should be three lower than current view")
    41  }
    42  
    43  func TestThreeInstances(t *testing.T) {
    44  	// test parameters
    45  	// NOTE: block finalization seems to be rather slow on CI at the moment,
    46  	// needing around 1 minute on Travis for 1000 blocks and 10 minutes on
    47  	// TeamCity for 1000 blocks; in order to avoid test timeouts, we keep the
    48  	// number low here
    49  	num := 3
    50  	finalView := uint64(100)
    51  
    52  	// generate three hotstuff participants
    53  	participants := unittest.IdentityListFixture(num)
    54  	root := DefaultRoot()
    55  	timeouts, err := timeout.NewConfig(safeTimeout, safeTimeout, 1.5, happyPathMaxRoundFailures, safeTimeout)
    56  	require.NoError(t, err)
    57  
    58  	// set up three instances that are exactly the same
    59  	// since we don't block any messages we should have enough data to advance in happy path
    60  	// for that reason we will block all TO related communication.
    61  	instances := make([]*Instance, 0, num)
    62  	for n := 0; n < num; n++ {
    63  		in := NewInstance(t,
    64  			WithRoot(root),
    65  			WithParticipants(participants),
    66  			WithLocalID(participants[n].NodeID),
    67  			WithTimeouts(timeouts),
    68  			WithStopCondition(ViewFinalized(finalView)),
    69  			WithIncomingTimeoutObjects(BlockAllTimeoutObjects),
    70  		)
    71  		instances = append(instances, in)
    72  	}
    73  
    74  	// connect the communicators of the instances together
    75  	Connect(t, instances)
    76  
    77  	// start the instances and wait for them to finish
    78  	var wg sync.WaitGroup
    79  	for _, in := range instances {
    80  		wg.Add(1)
    81  		go func(in *Instance) {
    82  			err := in.Run()
    83  			require.True(t, errors.Is(err, errStopCondition), "should run until stop condition")
    84  			wg.Done()
    85  		}(in)
    86  	}
    87  	wg.Wait()
    88  
    89  	// check that all instances have the same finalized block
    90  	in1 := instances[0]
    91  	in2 := instances[1]
    92  	in3 := instances[2]
    93  	// verify progress has been made
    94  	assert.GreaterOrEqual(t, in1.forks.FinalizedBlock().View, finalView, "the first instance 's finalized view should be four lower than current view")
    95  	// verify same progresses have been made
    96  	assert.Equal(t, in1.forks.FinalizedBlock(), in2.forks.FinalizedBlock(), "second instance should have same finalized block as first instance")
    97  	assert.Equal(t, in1.forks.FinalizedBlock(), in3.forks.FinalizedBlock(), "third instance should have same finalized block as first instance")
    98  	assert.Equal(t, FinalizedViews(in1), FinalizedViews(in2))
    99  	assert.Equal(t, FinalizedViews(in1), FinalizedViews(in3))
   100  }
   101  
   102  func TestSevenInstances(t *testing.T) {
   103  	// test parameters
   104  	// NOTE: block finalization seems to be rather slow on CI at the moment,
   105  	// needing around 1 minute on Travis for 1000 blocks and 10 minutes on
   106  	// TeamCity for 1000 blocks; in order to avoid test timeouts, we keep the
   107  	// number low here
   108  	numPass := 5
   109  	numFail := 2
   110  
   111  	// When using 100 as finalView, I often saw this tests fail on CI, because it only made to around 64-86
   112  	// so using 30 will still check that it's making progress and give enough buffer.
   113  	finalView := uint64(30)
   114  
   115  	// generate the seven hotstuff participants
   116  	participants := unittest.IdentityListFixture(numPass + numFail)
   117  	instances := make([]*Instance, 0, numPass+numFail)
   118  	root := DefaultRoot()
   119  	timeouts, err := timeout.NewConfig(safeTimeout, safeTimeout, 1.5, happyPathMaxRoundFailures, safeTimeout)
   120  	require.NoError(t, err)
   121  
   122  	// set up five instances that work fully
   123  	for n := 0; n < numPass; n++ {
   124  		in := NewInstance(t,
   125  			WithRoot(root),
   126  			WithParticipants(participants),
   127  			WithLocalID(participants[n].NodeID),
   128  			WithTimeouts(timeouts),
   129  			WithStopCondition(ViewFinalized(finalView)),
   130  		)
   131  		instances = append(instances, in)
   132  	}
   133  
   134  	// set up two instances which can't vote
   135  	for n := numPass; n < numPass+numFail; n++ {
   136  		in := NewInstance(t,
   137  			WithRoot(root),
   138  			WithParticipants(participants),
   139  			WithLocalID(participants[n].NodeID),
   140  			WithTimeouts(timeouts),
   141  			WithStopCondition(ViewFinalized(finalView)),
   142  			WithOutgoingVotes(BlockAllVotes),
   143  		)
   144  		instances = append(instances, in)
   145  	}
   146  
   147  	// connect the communicators of the instances together
   148  	Connect(t, instances)
   149  
   150  	// start all seven instances and wait for them to wrap up
   151  	var wg sync.WaitGroup
   152  	for _, in := range instances {
   153  		wg.Add(1)
   154  		go func(in *Instance) {
   155  			err := in.Run()
   156  			require.True(t, errors.Is(err, errStopCondition), "should run until stop condition")
   157  			wg.Done()
   158  		}(in)
   159  	}
   160  	wg.Wait()
   161  
   162  	// check that all instances have the same finalized block
   163  	ref := instances[0]
   164  	assert.Less(t, finalView-uint64(2*numPass+numFail), ref.forks.FinalizedBlock().View, "expect instance 0 should made enough progress, but didn't")
   165  	finalizedViews := FinalizedViews(ref)
   166  	for i := 1; i < numPass; i++ {
   167  		assert.Equal(t, ref.forks.FinalizedBlock(), instances[i].forks.FinalizedBlock(), "instance %d should have same finalized block as first instance")
   168  		assert.Equal(t, finalizedViews, FinalizedViews(instances[i]), "instance %d should have same finalized view as first instance")
   169  	}
   170  }