github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/committees/consensus_committee_test.go (about)

     1  package committees
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    12  	"github.com/koko1123/flow-go-1/model/flow"
    13  	"github.com/koko1123/flow-go-1/state/protocol"
    14  	protocolmock "github.com/koko1123/flow-go-1/state/protocol/mock"
    15  	"github.com/koko1123/flow-go-1/state/protocol/seed"
    16  	"github.com/koko1123/flow-go-1/utils/unittest"
    17  	"github.com/koko1123/flow-go-1/utils/unittest/mocks"
    18  )
    19  
    20  // TestConsensus_InvalidSigner tests that the appropriate sentinel error is
    21  // returned by the hotstuff.Committee implementation for non-existent or
    22  // non-committee identities.
    23  func TestConsensus_InvalidSigner(t *testing.T) {
    24  
    25  	realIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleConsensus))
    26  	zeroWeightConsensusIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleConsensus), unittest.WithWeight(0))
    27  	ejectedConsensusIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleConsensus), unittest.WithEjected(true))
    28  	validNonConsensusIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification))
    29  	fakeID := unittest.IdentifierFixture()
    30  	blockID := unittest.IdentifierFixture()
    31  
    32  	state := new(protocolmock.State)
    33  	snapshot := new(protocolmock.Snapshot)
    34  
    35  	// create a mock epoch for leader selection setup in constructor
    36  	currEpoch := newMockEpoch(
    37  		1,
    38  		unittest.IdentityListFixture(10),
    39  		1,
    40  		100,
    41  		unittest.SeedFixture(seed.RandomSourceLength),
    42  	)
    43  	epochs := mocks.NewEpochQuery(t, 1, currEpoch)
    44  	snapshot.On("Epochs").Return(epochs)
    45  
    46  	state.On("Final").Return(snapshot)
    47  	state.On("AtBlockID", blockID).Return(snapshot)
    48  
    49  	snapshot.On("Identity", realIdentity.NodeID).Return(realIdentity, nil)
    50  	snapshot.On("Identity", zeroWeightConsensusIdentity.NodeID).Return(zeroWeightConsensusIdentity, nil)
    51  	snapshot.On("Identity", ejectedConsensusIdentity.NodeID).Return(ejectedConsensusIdentity, nil)
    52  	snapshot.On("Identity", validNonConsensusIdentity.NodeID).Return(validNonConsensusIdentity, nil)
    53  	snapshot.On("Identity", fakeID).Return(nil, protocol.IdentityNotFoundError{})
    54  
    55  	com, err := NewConsensusCommittee(state, unittest.IdentifierFixture())
    56  	require.NoError(t, err)
    57  
    58  	t.Run("non-existent identity should return InvalidSignerError", func(t *testing.T) {
    59  		_, err := com.Identity(blockID, fakeID)
    60  		require.True(t, model.IsInvalidSignerError(err))
    61  	})
    62  
    63  	t.Run("existent but non-committee-member identity should return InvalidSignerError", func(t *testing.T) {
    64  		t.Run("zero-weight consensus node", func(t *testing.T) {
    65  			_, err := com.Identity(blockID, zeroWeightConsensusIdentity.NodeID)
    66  			require.True(t, model.IsInvalidSignerError(err))
    67  		})
    68  
    69  		t.Run("ejected consensus node", func(t *testing.T) {
    70  			_, err := com.Identity(blockID, ejectedConsensusIdentity.NodeID)
    71  			require.True(t, model.IsInvalidSignerError(err))
    72  		})
    73  
    74  		t.Run("otherwise valid non-consensus node", func(t *testing.T) {
    75  			_, err := com.Identity(blockID, validNonConsensusIdentity.NodeID)
    76  			require.True(t, model.IsInvalidSignerError(err))
    77  		})
    78  	})
    79  
    80  	t.Run("should be able to retrieve real identity", func(t *testing.T) {
    81  		actual, err := com.Identity(blockID, realIdentity.NodeID)
    82  		require.NoError(t, err)
    83  		require.Equal(t, realIdentity, actual)
    84  	})
    85  }
    86  
    87  // test that LeaderForView returns a valid leader for the previous and current
    88  // epoch and that it returns the appropriate sentinel for the next epoch if it
    89  // is not yet ready
    90  func TestConsensus_LeaderForView(t *testing.T) {
    91  
    92  	identities := unittest.IdentityListFixture(10)
    93  	me := identities[0].NodeID
    94  
    95  	// the counter for the current epoch
    96  	epochCounter := uint64(2)
    97  
    98  	// create mocks
    99  	state := new(protocolmock.State)
   100  	snapshot := new(protocolmock.Snapshot)
   101  
   102  	prevEpoch := newMockEpoch(
   103  		epochCounter-1,
   104  		identities,
   105  		1,
   106  		100,
   107  		unittest.SeedFixture(seed.RandomSourceLength),
   108  	)
   109  	currEpoch := newMockEpoch(
   110  		epochCounter,
   111  		identities,
   112  		101,
   113  		200,
   114  		unittest.SeedFixture(32),
   115  	)
   116  
   117  	state.On("Final").Return(snapshot)
   118  	epochs := mocks.NewEpochQuery(t, epochCounter, prevEpoch, currEpoch)
   119  	snapshot.On("Epochs").Return(epochs)
   120  
   121  	committee, err := NewConsensusCommittee(state, me)
   122  	require.Nil(t, err)
   123  
   124  	t.Run("next epoch not ready", func(t *testing.T) {
   125  		t.Run("previous epoch", func(t *testing.T) {
   126  			// get leader for view in previous epoch
   127  			leaderID, err := committee.LeaderForView(50)
   128  			require.Nil(t, err)
   129  			_, exists := identities.ByNodeID(leaderID)
   130  			assert.True(t, exists)
   131  		})
   132  
   133  		t.Run("current epoch", func(t *testing.T) {
   134  			// get leader for view in current epoch
   135  			leaderID, err := committee.LeaderForView(150)
   136  			require.Nil(t, err)
   137  			_, exists := identities.ByNodeID(leaderID)
   138  			assert.True(t, exists)
   139  		})
   140  
   141  		t.Run("after current epoch", func(t *testing.T) {
   142  			unittest.SkipUnless(t, unittest.TEST_TODO, "disabled as the current implementation uses a temporary fallback measure in this case (triggers EECC), rather than returning an error")
   143  			// REASON FOR SKIPPING TEST:
   144  			// We have a temporary fallback to continue with the current consensus committee, if the
   145  			// setup for the next epoch failed (aka emergency epoch chain continuation -- EECC).
   146  			// This test covers with behaviour _without_ EECC and is therefore skipped.
   147  			// The behaviour _with EECC_ is covered by the following test:
   148  			// "after current epoch - with emergency epoch chain continuation"
   149  			// TODO: for the mature implementation, remove EECC, enable this test, and remove the following test
   150  
   151  			// get leader for view in next epoch when it is not set up yet
   152  			_, err := committee.LeaderForView(250)
   153  			assert.Error(t, err)
   154  			assert.True(t, errors.Is(err, protocol.ErrNextEpochNotSetup))
   155  		})
   156  
   157  		t.Run("after current epoch - with emergency epoch chain continuation", func(t *testing.T) {
   158  			// This test covers the TEMPORARY emergency epoch chain continuation (EECC) fallback
   159  			// TODO: for the mature implementation, remove this test,
   160  			//       enable the previous test "after current epoch"
   161  
   162  			// get leader for view in next epoch when it is not set up yet
   163  			_, err := committee.LeaderForView(250)
   164  			// emergency epoch chain continuation should kick in and return a valid leader
   165  			assert.NoError(t, err)
   166  		})
   167  	})
   168  
   169  	// now, add a valid next epoch
   170  	nextEpoch := newMockEpoch(
   171  		epochCounter+1,
   172  		identities,
   173  		201,
   174  		300,
   175  		unittest.SeedFixture(seed.RandomSourceLength),
   176  	)
   177  	epochs.Add(nextEpoch)
   178  
   179  	t.Run("next epoch ready", func(t *testing.T) {
   180  		t.Run("previous epoch", func(t *testing.T) {
   181  			// get leader for view in previous epoch
   182  			leaderID, err := committee.LeaderForView(50)
   183  			require.Nil(t, err)
   184  			_, exists := identities.ByNodeID(leaderID)
   185  			assert.True(t, exists)
   186  		})
   187  
   188  		t.Run("current epoch", func(t *testing.T) {
   189  			// get leader for view in current epoch
   190  			leaderID, err := committee.LeaderForView(150)
   191  			require.Nil(t, err)
   192  			_, exists := identities.ByNodeID(leaderID)
   193  			assert.True(t, exists)
   194  		})
   195  
   196  		t.Run("next epoch", func(t *testing.T) {
   197  			// get leader for view in next epoch after it has been set up
   198  			leaderID, err := committee.LeaderForView(250)
   199  			require.Nil(t, err)
   200  			_, exists := identities.ByNodeID(leaderID)
   201  			assert.True(t, exists)
   202  		})
   203  	})
   204  }
   205  
   206  func TestRemoveOldEpochs(t *testing.T) {
   207  
   208  	identities := unittest.IdentityListFixture(10)
   209  	me := identities[0].NodeID
   210  
   211  	// keep track of epoch counter and views
   212  	firstEpochCounter := uint64(1)
   213  	currentEpochCounter := firstEpochCounter
   214  	epochFinalView := uint64(100)
   215  
   216  	epoch1 := newMockEpoch(currentEpochCounter, identities, 1, epochFinalView, unittest.SeedFixture(seed.RandomSourceLength))
   217  
   218  	// create mocks
   219  	state := new(protocolmock.State)
   220  	snapshot := new(protocolmock.Snapshot)
   221  
   222  	state.On("Final").Return(snapshot)
   223  	epochQuery := mocks.NewEpochQuery(t, currentEpochCounter, epoch1)
   224  	snapshot.On("Epochs").Return(epochQuery)
   225  
   226  	committee, err := NewConsensusCommittee(state, me)
   227  	require.Nil(t, err)
   228  
   229  	// we should start with only current epoch (epoch 1) pre-computed
   230  	// since there is no previous epoch
   231  	assert.Equal(t, 1, len(committee.leaders))
   232  
   233  	// test for 10 epochs
   234  	for currentEpochCounter < 10 {
   235  
   236  		// add another epoch
   237  		firstView := epochFinalView + 1
   238  		epochFinalView = epochFinalView + 100
   239  		currentEpochCounter++
   240  		nextEpoch := newMockEpoch(currentEpochCounter, identities, firstView, epochFinalView, unittest.SeedFixture(seed.RandomSourceLength))
   241  		epochQuery.Add(nextEpoch)
   242  
   243  		// query a view from the new epoch
   244  		_, err = committee.LeaderForView(firstView)
   245  		require.NoError(t, err)
   246  		// transition to the next epoch
   247  		epochQuery.Transition()
   248  
   249  		t.Run(fmt.Sprintf("epoch %d", currentEpochCounter), func(t *testing.T) {
   250  			// check we have the right number of epochs stored
   251  			if currentEpochCounter <= 3 {
   252  				assert.Equal(t, int(currentEpochCounter), len(committee.leaders))
   253  			} else {
   254  				assert.Equal(t, 3, len(committee.leaders))
   255  			}
   256  
   257  			// check we have the correct epochs stored
   258  			for i := uint64(0); i < 3; i++ {
   259  				counter := currentEpochCounter - i
   260  				if counter < firstEpochCounter {
   261  					break
   262  				}
   263  				_, exists := committee.leaders[counter]
   264  				assert.True(t, exists, "missing epoch with counter %d max counter is %d", counter, currentEpochCounter)
   265  			}
   266  		})
   267  	}
   268  }
   269  
   270  func newMockEpoch(
   271  	counter uint64,
   272  	identities flow.IdentityList,
   273  	firstView uint64,
   274  	finalView uint64,
   275  	seed []byte,
   276  ) *protocolmock.Epoch {
   277  
   278  	epoch := new(protocolmock.Epoch)
   279  	epoch.On("Counter").Return(counter, nil)
   280  	epoch.On("InitialIdentities").Return(identities, nil)
   281  	epoch.On("FirstView").Return(firstView, nil)
   282  	epoch.On("FinalView").Return(finalView, nil)
   283  	// return nil error to indicate the epoch is committed
   284  	epoch.On("DKG").Return(nil, nil)
   285  
   286  	epoch.On("RandomSource").Return(seed, nil)
   287  	return epoch
   288  }