github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/committees/cluster_committee_test.go (about) 1 package committees 2 3 import ( 4 "math/rand" 5 "testing" 6 7 "github.com/stretchr/testify/suite" 8 9 "github.com/onflow/flow-go/consensus/hotstuff/model" 10 "github.com/onflow/flow-go/model/cluster" 11 "github.com/onflow/flow-go/model/flow" 12 clusterstate "github.com/onflow/flow-go/state/cluster" 13 "github.com/onflow/flow-go/state/protocol" 14 protocolmock "github.com/onflow/flow-go/state/protocol/mock" 15 "github.com/onflow/flow-go/state/protocol/prg" 16 storagemock "github.com/onflow/flow-go/storage/mock" 17 "github.com/onflow/flow-go/utils/unittest" 18 ) 19 20 // ClusterSuite defines a test suite for the cluster committee. 21 type ClusterSuite struct { 22 suite.Suite 23 24 state *protocolmock.State 25 snap *protocolmock.Snapshot 26 cluster *protocolmock.Cluster 27 epoch *protocolmock.Epoch 28 payloads *storagemock.ClusterPayloads 29 30 members flow.IdentityList 31 root *cluster.Block 32 me *flow.Identity 33 34 com *Cluster 35 } 36 37 func TestClusterCommittee(t *testing.T) { 38 suite.Run(t, new(ClusterSuite)) 39 } 40 41 func (suite *ClusterSuite) SetupTest() { 42 43 suite.state = new(protocolmock.State) 44 suite.snap = new(protocolmock.Snapshot) 45 suite.cluster = new(protocolmock.Cluster) 46 suite.epoch = new(protocolmock.Epoch) 47 suite.payloads = new(storagemock.ClusterPayloads) 48 49 suite.members = unittest.IdentityListFixture(5, unittest.WithRole(flow.RoleCollection)) 50 suite.me = suite.members[0] 51 counter := uint64(1) 52 suite.root = clusterstate.CanonicalRootBlock(counter, suite.members) 53 54 suite.cluster.On("EpochCounter").Return(counter) 55 suite.cluster.On("Index").Return(uint(1)) 56 suite.cluster.On("Members").Return(suite.members) 57 suite.cluster.On("RootBlock").Return(suite.root) 58 suite.epoch.On("Counter").Return(counter, nil) 59 suite.epoch.On("RandomSource").Return(unittest.SeedFixture(prg.RandomSourceLength), nil) 60 61 var err error 62 suite.com, err = NewClusterCommittee( 63 suite.state, 64 suite.payloads, 65 suite.cluster, 66 suite.epoch, 67 suite.me.NodeID, 68 ) 69 suite.Require().NoError(err) 70 } 71 72 // TestThresholds tests that the correct thresholds are returned. 73 func (suite *ClusterSuite) TestThresholds() { 74 threshold, err := suite.com.QuorumThresholdForView(rand.Uint64()) 75 suite.Require().NoError(err) 76 suite.Assert().Equal(WeightThresholdToBuildQC(suite.members.TotalWeight()), threshold) 77 78 threshold, err = suite.com.TimeoutThresholdForView(rand.Uint64()) 79 suite.Require().NoError(err) 80 suite.Assert().Equal(WeightThresholdToTimeout(suite.members.TotalWeight()), threshold) 81 } 82 83 // TestInvalidSigner tests that the InvalidSignerError sentinel is 84 // returned under the appropriate conditions. 85 func (suite *ClusterSuite) TestInvalidSigner() { 86 87 // hook up cluster->main chain connection for root and non-root cluster block 88 nonRootBlockID := unittest.IdentifierFixture() 89 rootBlockID := suite.root.ID() 90 91 refID := unittest.IdentifierFixture() // reference block on main chain 92 payload := cluster.EmptyPayload(refID) // payload referencing main chain 93 rootPayload := cluster.EmptyPayload(flow.ZeroID) // root cluster block payload 94 95 suite.payloads.On("ByBlockID", nonRootBlockID).Return(&payload, nil) 96 suite.payloads.On("ByBlockID", rootBlockID).Return(&rootPayload, nil) 97 98 // a real cluster member which continues to be a valid member 99 realClusterMember := suite.members[1] 100 // a real cluster member which loses all its weight between cluster initialization 101 // and the test's reference block 102 realNoWeightClusterMember := suite.members[2] 103 realNoWeightClusterMember.Weight = 0 104 // a real cluster member which is ejected between cluster initialization and 105 // the test's reference block 106 realEjectedClusterMember := suite.members[3] 107 realEjectedClusterMember.Ejected = true 108 realNonClusterMember := unittest.IdentityFixture(unittest.WithRole(flow.RoleCollection)) 109 fakeID := unittest.IdentifierFixture() 110 111 suite.state.On("AtBlockID", refID).Return(suite.snap) 112 suite.snap.On("Identity", realClusterMember.NodeID).Return(realClusterMember, nil) 113 suite.snap.On("Identity", realNoWeightClusterMember.NodeID).Return(realNoWeightClusterMember, nil) 114 suite.snap.On("Identity", realEjectedClusterMember.NodeID).Return(realEjectedClusterMember, nil) 115 suite.snap.On("Identity", realNonClusterMember.NodeID).Return(realNonClusterMember, nil) 116 suite.snap.On("Identity", fakeID).Return(nil, protocol.IdentityNotFoundError{}) 117 118 suite.Run("should return InvalidSignerError for non-existent signer", func() { 119 suite.Run("root block", func() { 120 _, err := suite.com.IdentityByBlock(rootBlockID, fakeID) 121 suite.Assert().True(model.IsInvalidSignerError(err)) 122 }) 123 suite.Run("non-root block", func() { 124 _, err := suite.com.IdentityByBlock(nonRootBlockID, fakeID) 125 suite.Assert().True(model.IsInvalidSignerError(err)) 126 }) 127 suite.Run("by epoch", func() { 128 _, err := suite.com.IdentityByEpoch(rand.Uint64(), fakeID) 129 suite.Assert().True(model.IsInvalidSignerError(err)) 130 }) 131 }) 132 133 suite.Run("should return InvalidSignerError for existent non-cluster-member", func() { 134 suite.Run("root block", func() { 135 _, err := suite.com.IdentityByBlock(rootBlockID, realNonClusterMember.NodeID) 136 suite.Assert().True(model.IsInvalidSignerError(err)) 137 }) 138 suite.Run("non-root block", func() { 139 _, err := suite.com.IdentityByBlock(nonRootBlockID, realNonClusterMember.NodeID) 140 suite.Assert().True(model.IsInvalidSignerError(err)) 141 }) 142 suite.Run("by epoch", func() { 143 _, err := suite.com.IdentityByEpoch(rand.Uint64(), realNonClusterMember.NodeID) 144 suite.Assert().True(model.IsInvalidSignerError(err)) 145 }) 146 }) 147 148 suite.Run("should return ErrInvalidSigner for existent but ejected cluster member", func() { 149 // at the root block, the cluster member is not ejected yet 150 suite.Run("root block", func() { 151 actual, err := suite.com.IdentityByBlock(rootBlockID, realEjectedClusterMember.NodeID) 152 suite.Require().NoError(err) 153 suite.Assert().Equal(realEjectedClusterMember, actual) 154 }) 155 suite.Run("non-root block", func() { 156 _, err := suite.com.IdentityByBlock(nonRootBlockID, realEjectedClusterMember.NodeID) 157 suite.Assert().True(model.IsInvalidSignerError(err)) 158 }) 159 suite.Run("by epoch", func() { 160 actual, err := suite.com.IdentityByEpoch(rand.Uint64(), realEjectedClusterMember.NodeID) 161 suite.Assert().NoError(err) 162 suite.Assert().Equal(realEjectedClusterMember, actual) 163 }) 164 }) 165 166 suite.Run("should return ErrInvalidSigner for existent but zero-weight cluster member", func() { 167 // at the root block, the cluster member has its initial weight 168 suite.Run("root block", func() { 169 actual, err := suite.com.IdentityByBlock(rootBlockID, realNoWeightClusterMember.NodeID) 170 suite.Require().NoError(err) 171 suite.Assert().Equal(realNoWeightClusterMember, actual) 172 }) 173 suite.Run("non-root block", func() { 174 _, err := suite.com.IdentityByBlock(nonRootBlockID, realNoWeightClusterMember.NodeID) 175 suite.Assert().True(model.IsInvalidSignerError(err)) 176 }) 177 suite.Run("by epoch", func() { 178 actual, err := suite.com.IdentityByEpoch(rand.Uint64(), realNoWeightClusterMember.NodeID) 179 suite.Require().NoError(err) 180 suite.Assert().Equal(realNoWeightClusterMember, actual) 181 }) 182 }) 183 184 suite.Run("should return identity for existent cluster member", func() { 185 suite.Run("root block", func() { 186 actual, err := suite.com.IdentityByBlock(rootBlockID, realClusterMember.NodeID) 187 suite.Require().NoError(err) 188 suite.Assert().Equal(realClusterMember, actual) 189 }) 190 suite.Run("non-root block", func() { 191 actual, err := suite.com.IdentityByBlock(nonRootBlockID, realClusterMember.NodeID) 192 suite.Require().NoError(err) 193 suite.Assert().Equal(realClusterMember, actual) 194 }) 195 suite.Run("by epoch", func() { 196 actual, err := suite.com.IdentityByEpoch(rand.Uint64(), realClusterMember.NodeID) 197 suite.Require().NoError(err) 198 suite.Assert().Equal(realClusterMember, actual) 199 }) 200 }) 201 }