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  }