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  }