github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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.ToSkeleton()) 53 54 suite.cluster.On("EpochCounter").Return(counter) 55 suite.cluster.On("Index").Return(uint(1)) 56 suite.cluster.On("Members").Return(suite.members.ToSkeleton()) 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.ToSkeleton().TotalWeight()), threshold) 77 78 threshold, err = suite.com.TimeoutThresholdForView(rand.Uint64()) 79 suite.Require().NoError(err) 80 suite.Assert().Equal(WeightThresholdToTimeout(suite.members.ToSkeleton().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 unstaked and is not active anymore 101 // and the test's reference block 102 realLeavingClusterMember := suite.members[2] 103 realLeavingClusterMember.EpochParticipationStatus = flow.EpochParticipationStatusLeaving 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.EpochParticipationStatus = flow.EpochParticipationStatusEjected 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", realLeavingClusterMember.NodeID).Return(realLeavingClusterMember, 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 ErrInvalidSigner for existent but not active cluster member", func() { 134 suite.Run("non-root block", func() { 135 _, err := suite.com.IdentityByBlock(nonRootBlockID, realLeavingClusterMember.NodeID) 136 suite.Assert().True(model.IsInvalidSignerError(err)) 137 }) 138 suite.Run("by epoch", func() { 139 actual, err := suite.com.IdentityByEpoch(rand.Uint64(), realLeavingClusterMember.NodeID) 140 suite.Require().NoError(err) 141 suite.Assert().Equal(realLeavingClusterMember.IdentitySkeleton, *actual) 142 }) 143 }) 144 145 suite.Run("should return InvalidSignerError for existent non-cluster-member", func() { 146 suite.Run("root block", func() { 147 _, err := suite.com.IdentityByBlock(rootBlockID, realNonClusterMember.NodeID) 148 suite.Assert().True(model.IsInvalidSignerError(err)) 149 }) 150 suite.Run("non-root block", func() { 151 _, err := suite.com.IdentityByBlock(nonRootBlockID, realNonClusterMember.NodeID) 152 suite.Assert().True(model.IsInvalidSignerError(err)) 153 }) 154 suite.Run("by epoch", func() { 155 _, err := suite.com.IdentityByEpoch(rand.Uint64(), realNonClusterMember.NodeID) 156 suite.Assert().True(model.IsInvalidSignerError(err)) 157 }) 158 }) 159 160 suite.Run("should return ErrInvalidSigner for existent but ejected cluster member", func() { 161 suite.Run("non-root block", func() { 162 _, err := suite.com.IdentityByBlock(nonRootBlockID, realEjectedClusterMember.NodeID) 163 suite.Assert().True(model.IsInvalidSignerError(err)) 164 }) 165 suite.Run("by epoch", func() { 166 actual, err := suite.com.IdentityByEpoch(rand.Uint64(), realEjectedClusterMember.NodeID) 167 suite.Assert().NoError(err) 168 suite.Assert().Equal(realEjectedClusterMember.IdentitySkeleton, *actual) 169 }) 170 }) 171 172 suite.Run("should return identity for existent cluster member", func() { 173 suite.Run("root block", func() { 174 actual, err := suite.com.IdentityByBlock(rootBlockID, realClusterMember.NodeID) 175 suite.Require().NoError(err) 176 suite.Assert().Equal(realClusterMember, actual) 177 }) 178 suite.Run("non-root block", func() { 179 actual, err := suite.com.IdentityByBlock(nonRootBlockID, realClusterMember.NodeID) 180 suite.Require().NoError(err) 181 suite.Assert().Equal(realClusterMember, actual) 182 }) 183 suite.Run("by epoch", func() { 184 actual, err := suite.com.IdentityByEpoch(rand.Uint64(), realClusterMember.NodeID) 185 suite.Require().NoError(err) 186 suite.Assert().Equal(realClusterMember.IdentitySkeleton, *actual) 187 }) 188 }) 189 }