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 }