github.com/yimialmonte/fabric@v2.1.1+incompatible/discovery/endorsement/endorsement.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package endorsement
     8  
     9  import (
    10  	"fmt"
    11  
    12  	"github.com/hyperledger/fabric-protos-go/discovery"
    13  	"github.com/hyperledger/fabric-protos-go/msp"
    14  	"github.com/hyperledger/fabric/common/chaincode"
    15  	"github.com/hyperledger/fabric/common/flogging"
    16  	"github.com/hyperledger/fabric/common/graph"
    17  	"github.com/hyperledger/fabric/common/policies"
    18  	"github.com/hyperledger/fabric/common/policies/inquire"
    19  	"github.com/hyperledger/fabric/gossip/api"
    20  	"github.com/hyperledger/fabric/gossip/common"
    21  	. "github.com/hyperledger/fabric/gossip/discovery"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  var (
    26  	logger = flogging.MustGetLogger("discovery.endorsement")
    27  )
    28  
    29  type principalEvaluator interface {
    30  	// SatisfiesPrincipal returns whether a given peer identity satisfies a certain principal
    31  	// on a given channel
    32  	SatisfiesPrincipal(channel string, identity []byte, principal *msp.MSPPrincipal) error
    33  }
    34  
    35  type chaincodeMetadataFetcher interface {
    36  	// ChaincodeMetadata returns the metadata of the chaincode as appears in the ledger,
    37  	// or nil if the channel doesn't exist, or the chaincode isn't found in the ledger
    38  	Metadata(channel string, cc string, collections ...string) *chaincode.Metadata
    39  }
    40  
    41  type policyFetcher interface {
    42  	// PoliciesByChaincode returns the chaincode policy or existing collection level policies that can be
    43  	// inquired for which identities satisfy them
    44  	PoliciesByChaincode(channel string, cc string, collections ...string) []policies.InquireablePolicy
    45  }
    46  
    47  type gossipSupport interface {
    48  	// IdentityInfo returns identity information about peers
    49  	IdentityInfo() api.PeerIdentitySet
    50  
    51  	// PeersOfChannel returns the NetworkMembers considered alive
    52  	// and also subscribed to the channel given
    53  	PeersOfChannel(common.ChannelID) Members
    54  
    55  	// Peers returns the NetworkMembers considered alive
    56  	Peers() Members
    57  }
    58  
    59  type endorsementAnalyzer struct {
    60  	gossipSupport
    61  	principalEvaluator
    62  	policyFetcher
    63  	chaincodeMetadataFetcher
    64  }
    65  
    66  // NewEndorsementAnalyzer constructs an NewEndorsementAnalyzer out of the given support
    67  func NewEndorsementAnalyzer(gs gossipSupport, pf policyFetcher, pe principalEvaluator, mf chaincodeMetadataFetcher) *endorsementAnalyzer {
    68  	return &endorsementAnalyzer{
    69  		gossipSupport:            gs,
    70  		policyFetcher:            pf,
    71  		principalEvaluator:       pe,
    72  		chaincodeMetadataFetcher: mf,
    73  	}
    74  }
    75  
    76  type peerPrincipalEvaluator func(member NetworkMember, principal *msp.MSPPrincipal) bool
    77  
    78  // PeersForEndorsement returns an EndorsementDescriptor for a given set of peers, channel, and chaincode
    79  func (ea *endorsementAnalyzer) PeersForEndorsement(channelID common.ChannelID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) {
    80  	chanMembership, err := ea.PeersAuthorizedByCriteria(channelID, interest)
    81  	if err != nil {
    82  		return nil, errors.WithStack(err)
    83  	}
    84  	channelMembersById := chanMembership.ByID()
    85  	// Choose only the alive messages of those that have joined the channel
    86  	aliveMembership := ea.Peers().Intersect(chanMembership)
    87  	membersById := aliveMembership.ByID()
    88  	// Compute a mapping between the PKI-IDs of members to their identities
    89  	identitiesOfMembers := computeIdentitiesOfMembers(ea.IdentityInfo(), membersById)
    90  	principalsSets, err := ea.computePrincipalSets(channelID, interest)
    91  	if err != nil {
    92  		logger.Warningf("Principal set computation failed: %v", err)
    93  		return nil, errors.WithStack(err)
    94  	}
    95  
    96  	return ea.computeEndorsementResponse(&context{
    97  		chaincode:           interest.Chaincodes[0].Name,
    98  		channel:             string(channelID),
    99  		principalsSets:      principalsSets,
   100  		channelMembersById:  channelMembersById,
   101  		aliveMembership:     aliveMembership,
   102  		identitiesOfMembers: identitiesOfMembers,
   103  	})
   104  }
   105  
   106  func (ea *endorsementAnalyzer) PeersAuthorizedByCriteria(channelID common.ChannelID, interest *discovery.ChaincodeInterest) (Members, error) {
   107  	peersOfChannel := ea.PeersOfChannel(channelID)
   108  	if interest == nil || len(interest.Chaincodes) == 0 {
   109  		return peersOfChannel, nil
   110  	}
   111  	identities := ea.IdentityInfo()
   112  	identitiesByID := identities.ByID()
   113  	metadataAndCollectionFilters, err := loadMetadataAndFilters(metadataAndFilterContext{
   114  		identityInfoByID: identitiesByID,
   115  		interest:         interest,
   116  		chainID:          channelID,
   117  		evaluator:        ea,
   118  		fetch:            ea,
   119  	})
   120  	if err != nil {
   121  		return nil, errors.WithStack(err)
   122  	}
   123  	metadata := metadataAndCollectionFilters.md
   124  	// Filter out peers that don't have the chaincode installed on them
   125  	chanMembership := peersOfChannel.Filter(peersWithChaincode(metadata...))
   126  	// Filter out peers that aren't authorized by the collection configs of the chaincode invocation chain
   127  	return chanMembership.Filter(metadataAndCollectionFilters.isMemberAuthorized), nil
   128  }
   129  
   130  type context struct {
   131  	chaincode           string
   132  	channel             string
   133  	aliveMembership     Members
   134  	principalsSets      []policies.PrincipalSet
   135  	channelMembersById  map[string]NetworkMember
   136  	identitiesOfMembers memberIdentities
   137  }
   138  
   139  func (ea *endorsementAnalyzer) computeEndorsementResponse(ctx *context) (*discovery.EndorsementDescriptor, error) {
   140  	// mapPrincipalsToGroups returns a mapping from principals to their corresponding groups.
   141  	// groups are just human readable representations that mask the principals behind them
   142  	principalGroups := mapPrincipalsToGroups(ctx.principalsSets)
   143  	// principalsToPeersGraph computes a bipartite graph (V1 U V2 , E)
   144  	// such that V1 is the peers, V2 are the principals,
   145  	// and each e=(peer,principal) is in E if the peer satisfies the principal
   146  	satGraph := principalsToPeersGraph(principalAndPeerData{
   147  		members: ctx.aliveMembership,
   148  		pGrps:   principalGroups,
   149  	}, ea.satisfiesPrincipal(ctx.channel, ctx.identitiesOfMembers))
   150  
   151  	layouts := computeLayouts(ctx.principalsSets, principalGroups, satGraph)
   152  	if len(layouts) == 0 {
   153  		return nil, errors.New("cannot satisfy any principal combination")
   154  	}
   155  
   156  	criteria := &peerMembershipCriteria{
   157  		possibleLayouts: layouts,
   158  		satGraph:        satGraph,
   159  		chanMemberById:  ctx.channelMembersById,
   160  		idOfMembers:     ctx.identitiesOfMembers,
   161  	}
   162  
   163  	return &discovery.EndorsementDescriptor{
   164  		Chaincode:         ctx.chaincode,
   165  		Layouts:           layouts,
   166  		EndorsersByGroups: endorsersByGroup(criteria),
   167  	}, nil
   168  }
   169  
   170  func (ea *endorsementAnalyzer) computePrincipalSets(channelID common.ChannelID, interest *discovery.ChaincodeInterest) (policies.PrincipalSets, error) {
   171  	sessionLogger := logger.With("channel", string(channelID))
   172  	var inquireablePolicies []policies.InquireablePolicy
   173  	for _, chaincode := range interest.Chaincodes {
   174  		policies := ea.PoliciesByChaincode(string(channelID), chaincode.Name, chaincode.CollectionNames...)
   175  		if len(policies) == 0 {
   176  			sessionLogger.Debug("Policy for chaincode '", chaincode, "'doesn't exist")
   177  			return nil, errors.New("policy not found")
   178  		}
   179  
   180  		for _, pol := range policies {
   181  			inquireablePolicies = append(inquireablePolicies, pol)
   182  		}
   183  	}
   184  
   185  	var cpss []inquire.ComparablePrincipalSets
   186  
   187  	for _, policy := range inquireablePolicies {
   188  		var cmpsets inquire.ComparablePrincipalSets
   189  		for _, ps := range policy.SatisfiedBy() {
   190  			cps := inquire.NewComparablePrincipalSet(ps)
   191  			if cps == nil {
   192  				return nil, errors.New("failed creating a comparable principal set")
   193  			}
   194  			cmpsets = append(cmpsets, cps)
   195  		}
   196  		if len(cmpsets) == 0 {
   197  			return nil, errors.New("chaincode isn't installed on sufficient organizations required by the endorsement policy")
   198  		}
   199  		cpss = append(cpss, cmpsets)
   200  	}
   201  
   202  	cps, err := mergePrincipalSets(cpss)
   203  	if err != nil {
   204  		return nil, errors.WithStack(err)
   205  	}
   206  
   207  	return cps.ToPrincipalSets(), nil
   208  }
   209  
   210  type metadataAndFilterContext struct {
   211  	chainID          common.ChannelID
   212  	interest         *discovery.ChaincodeInterest
   213  	fetch            chaincodeMetadataFetcher
   214  	identityInfoByID map[string]api.PeerIdentityInfo
   215  	evaluator        principalEvaluator
   216  }
   217  
   218  // metadataAndColFilter holds metadata and member filters
   219  type metadataAndColFilter struct {
   220  	md                 []*chaincode.Metadata
   221  	isMemberAuthorized memberFilter
   222  }
   223  
   224  func loadMetadataAndFilters(ctx metadataAndFilterContext) (*metadataAndColFilter, error) {
   225  	sessionLogger := logger.With("channel", string(ctx.chainID))
   226  	var metadata []*chaincode.Metadata
   227  	var filters []identityFilter
   228  
   229  	for _, chaincode := range ctx.interest.Chaincodes {
   230  		ccMD := ctx.fetch.Metadata(string(ctx.chainID), chaincode.Name, chaincode.CollectionNames...)
   231  		if ccMD == nil {
   232  			return nil, errors.Errorf("No metadata was found for chaincode %s in channel %s", chaincode.Name, string(ctx.chainID))
   233  		}
   234  		metadata = append(metadata, ccMD)
   235  		if len(chaincode.CollectionNames) == 0 {
   236  			sessionLogger.Debugf("No collections for %s, skipping", chaincode.Name)
   237  			continue
   238  		}
   239  		if chaincode.NoPrivateReads {
   240  			sessionLogger.Debugf("No private reads, skipping")
   241  			continue
   242  		}
   243  		principalSetByCollections, err := principalsFromCollectionConfig(ccMD.CollectionsConfig)
   244  		if err != nil {
   245  			sessionLogger.Warningf("Failed initializing collection filter for chaincode %s: %v", chaincode.Name, err)
   246  			return nil, errors.WithStack(err)
   247  		}
   248  		filter, err := principalSetByCollections.toIdentityFilter(string(ctx.chainID), ctx.evaluator, chaincode)
   249  		if err != nil {
   250  			sessionLogger.Warningf("Failed computing collection principal sets for chaincode %s due to %v", chaincode.Name, err)
   251  			return nil, errors.WithStack(err)
   252  		}
   253  		filters = append(filters, filter)
   254  	}
   255  
   256  	return computeFiltersWithMetadata(filters, metadata, ctx.identityInfoByID), nil
   257  }
   258  
   259  func computeFiltersWithMetadata(filters identityFilters, metadata []*chaincode.Metadata, identityInfoByID map[string]api.PeerIdentityInfo) *metadataAndColFilter {
   260  	if len(filters) == 0 {
   261  		return &metadataAndColFilter{
   262  			md:                 metadata,
   263  			isMemberAuthorized: noopMemberFilter,
   264  		}
   265  	}
   266  	filter := filters.combine().toMemberFilter(identityInfoByID)
   267  	return &metadataAndColFilter{
   268  		isMemberAuthorized: filter,
   269  		md:                 metadata,
   270  	}
   271  }
   272  
   273  // identityFilter accepts or rejects peer identities
   274  type identityFilter func(api.PeerIdentityType) bool
   275  
   276  // identityFilters aggregates multiple identityFilters
   277  type identityFilters []identityFilter
   278  
   279  // memberFilter accepts or rejects NetworkMembers
   280  type memberFilter func(member NetworkMember) bool
   281  
   282  // noopMemberFilter accepts every NetworkMember
   283  func noopMemberFilter(_ NetworkMember) bool {
   284  	return true
   285  }
   286  
   287  // combine combines all identityFilters into a single identityFilter which only accepts identities
   288  // which all the original filters accept
   289  func (filters identityFilters) combine() identityFilter {
   290  	return func(identity api.PeerIdentityType) bool {
   291  		for _, f := range filters {
   292  			if !f(identity) {
   293  				return false
   294  			}
   295  		}
   296  		return true
   297  	}
   298  }
   299  
   300  // toMemberFilter converts this identityFilter to a memberFilter based on the given mapping
   301  // from PKI-ID as strings, to PeerIdentityInfo which holds the peer identities
   302  func (idf identityFilter) toMemberFilter(identityInfoByID map[string]api.PeerIdentityInfo) memberFilter {
   303  	return func(member NetworkMember) bool {
   304  		identity, exists := identityInfoByID[string(member.PKIid)]
   305  		if !exists {
   306  			return false
   307  		}
   308  		return idf(identity.Identity)
   309  	}
   310  }
   311  
   312  func (ea *endorsementAnalyzer) satisfiesPrincipal(channel string, identitiesOfMembers memberIdentities) peerPrincipalEvaluator {
   313  	return func(member NetworkMember, principal *msp.MSPPrincipal) bool {
   314  		err := ea.SatisfiesPrincipal(channel, identitiesOfMembers.identityByPKIID(member.PKIid), principal)
   315  		if err == nil {
   316  			// TODO: log the principals in a human readable form
   317  			logger.Debug(member, "satisfies principal", principal)
   318  			return true
   319  		}
   320  		logger.Debug(member, "doesn't satisfy principal", principal, ":", err)
   321  		return false
   322  	}
   323  }
   324  
   325  type peerMembershipCriteria struct {
   326  	satGraph        *principalPeerGraph
   327  	idOfMembers     memberIdentities
   328  	chanMemberById  map[string]NetworkMember
   329  	possibleLayouts layouts
   330  }
   331  
   332  // endorsersByGroup computes a map from groups to peers.
   333  // Each group included, is found in some layout, which means
   334  // that there is some principal combination that includes the corresponding
   335  // group.
   336  // This means that if a group isn't included in the result, there is no
   337  // principal combination (that includes the principal corresponding to the group),
   338  // such that there are enough peers to satisfy the principal combination.
   339  func endorsersByGroup(criteria *peerMembershipCriteria) map[string]*discovery.Peers {
   340  	satGraph := criteria.satGraph
   341  	idOfMembers := criteria.idOfMembers
   342  	chanMemberById := criteria.chanMemberById
   343  	includedGroups := criteria.possibleLayouts.groupsSet()
   344  
   345  	res := make(map[string]*discovery.Peers)
   346  	// Map endorsers to their corresponding groups.
   347  	// Iterate the principals, and put the peers into each group that corresponds with a principal vertex
   348  	for grp, principalVertex := range satGraph.principalVertices {
   349  		if _, exists := includedGroups[grp]; !exists {
   350  			// If the current group is not found in any layout, skip the corresponding principal
   351  			continue
   352  		}
   353  		peerList := &discovery.Peers{}
   354  		res[grp] = peerList
   355  		for _, peerVertex := range principalVertex.Neighbors() {
   356  			member := peerVertex.Data.(NetworkMember)
   357  			peerList.Peers = append(peerList.Peers, &discovery.Peer{
   358  				Identity:       idOfMembers.identityByPKIID(member.PKIid),
   359  				StateInfo:      chanMemberById[string(member.PKIid)].Envelope,
   360  				MembershipInfo: member.Envelope,
   361  			})
   362  		}
   363  	}
   364  	return res
   365  }
   366  
   367  // computeLayouts computes all possible principal combinations
   368  // that can be used to satisfy the endorsement policy, given a graph
   369  // of available peers that maps each peer to a principal it satisfies.
   370  // Each such a combination is called a layout, because it maps
   371  // a group (alias for a principal) to a threshold of peers that need to endorse,
   372  // and that satisfy the corresponding principal.
   373  func computeLayouts(principalsSets []policies.PrincipalSet, principalGroups principalGroupMapper, satGraph *principalPeerGraph) []*discovery.Layout {
   374  	var layouts []*discovery.Layout
   375  	// principalsSets is a collection of combinations of principals,
   376  	// such that each combination (given enough peers) satisfies the endorsement policy.
   377  	for _, principalSet := range principalsSets {
   378  		layout := &discovery.Layout{
   379  			QuantitiesByGroup: make(map[string]uint32),
   380  		}
   381  		// Since principalsSet has repetitions, we first
   382  		// compute a mapping from the principal to repetitions in the set.
   383  		for principal, plurality := range principalSet.UniqueSet() {
   384  			key := principalKey{
   385  				cls:       int32(principal.PrincipalClassification),
   386  				principal: string(principal.Principal),
   387  			}
   388  			// We map the principal to a group, which is an alias for the principal.
   389  			layout.QuantitiesByGroup[principalGroups.group(key)] = uint32(plurality)
   390  		}
   391  		// Check that the layout can be satisfied with the current known peers
   392  		// This is done by iterating the current layout, and ensuring that
   393  		// each principal vertex is connected to at least <plurality> peer vertices.
   394  		if isLayoutSatisfied(layout.QuantitiesByGroup, satGraph) {
   395  			// If so, then add the layout to the layouts, since we have enough peers to satisfy the
   396  			// principal combination
   397  			layouts = append(layouts, layout)
   398  		}
   399  	}
   400  	return layouts
   401  }
   402  
   403  func isLayoutSatisfied(layout map[string]uint32, satGraph *principalPeerGraph) bool {
   404  	for grp, plurality := range layout {
   405  		// Do we have more than <plurality> peers connected to the principal?
   406  		if len(satGraph.principalVertices[grp].Neighbors()) < int(plurality) {
   407  			return false
   408  		}
   409  	}
   410  	return true
   411  }
   412  
   413  type principalPeerGraph struct {
   414  	peerVertices      []*graph.Vertex
   415  	principalVertices map[string]*graph.Vertex
   416  }
   417  
   418  type principalAndPeerData struct {
   419  	members Members
   420  	pGrps   principalGroupMapper
   421  }
   422  
   423  func principalsToPeersGraph(data principalAndPeerData, satisfiesPrincipal peerPrincipalEvaluator) *principalPeerGraph {
   424  	// Create the peer vertices
   425  	peerVertices := make([]*graph.Vertex, len(data.members))
   426  	for i, member := range data.members {
   427  		peerVertices[i] = graph.NewVertex(string(member.PKIid), member)
   428  	}
   429  
   430  	// Create the principal vertices
   431  	principalVertices := make(map[string]*graph.Vertex)
   432  	for pKey, grp := range data.pGrps {
   433  		principalVertices[grp] = graph.NewVertex(grp, pKey.toPrincipal())
   434  	}
   435  
   436  	// Connect principals and peers
   437  	for _, principalVertex := range principalVertices {
   438  		for _, peerVertex := range peerVertices {
   439  			// If the current peer satisfies the principal, connect their corresponding vertices with an edge
   440  			principal := principalVertex.Data.(*msp.MSPPrincipal)
   441  			member := peerVertex.Data.(NetworkMember)
   442  			if satisfiesPrincipal(member, principal) {
   443  				peerVertex.AddNeighbor(principalVertex)
   444  			}
   445  		}
   446  	}
   447  	return &principalPeerGraph{
   448  		peerVertices:      peerVertices,
   449  		principalVertices: principalVertices,
   450  	}
   451  }
   452  
   453  func mapPrincipalsToGroups(principalsSets []policies.PrincipalSet) principalGroupMapper {
   454  	groupMapper := make(principalGroupMapper)
   455  	totalPrincipals := make(map[principalKey]struct{})
   456  	for _, principalSet := range principalsSets {
   457  		for _, principal := range principalSet {
   458  			totalPrincipals[principalKey{
   459  				principal: string(principal.Principal),
   460  				cls:       int32(principal.PrincipalClassification),
   461  			}] = struct{}{}
   462  		}
   463  	}
   464  	for principal := range totalPrincipals {
   465  		groupMapper.group(principal)
   466  	}
   467  	return groupMapper
   468  }
   469  
   470  type memberIdentities map[string]api.PeerIdentityType
   471  
   472  func (m memberIdentities) identityByPKIID(id common.PKIidType) api.PeerIdentityType {
   473  	return m[string(id)]
   474  }
   475  
   476  func computeIdentitiesOfMembers(identitySet api.PeerIdentitySet, members map[string]NetworkMember) memberIdentities {
   477  	identitiesByPKIID := make(map[string]api.PeerIdentityType)
   478  	identitiesOfMembers := make(map[string]api.PeerIdentityType, len(members))
   479  	for _, identity := range identitySet {
   480  		identitiesByPKIID[string(identity.PKIId)] = identity.Identity
   481  	}
   482  	for _, member := range members {
   483  		if identity, exists := identitiesByPKIID[string(member.PKIid)]; exists {
   484  			identitiesOfMembers[string(member.PKIid)] = identity
   485  		}
   486  	}
   487  	return identitiesOfMembers
   488  }
   489  
   490  // principalGroupMapper maps principals to names of groups
   491  type principalGroupMapper map[principalKey]string
   492  
   493  func (mapper principalGroupMapper) group(principal principalKey) string {
   494  	if grp, exists := mapper[principal]; exists {
   495  		return grp
   496  	}
   497  	grp := fmt.Sprintf("G%d", len(mapper))
   498  	mapper[principal] = grp
   499  	return grp
   500  }
   501  
   502  type principalKey struct {
   503  	cls       int32
   504  	principal string
   505  }
   506  
   507  func (pk principalKey) toPrincipal() *msp.MSPPrincipal {
   508  	return &msp.MSPPrincipal{
   509  		PrincipalClassification: msp.MSPPrincipal_Classification(pk.cls),
   510  		Principal:               []byte(pk.principal),
   511  	}
   512  }
   513  
   514  // layouts is an aggregation of several layouts
   515  type layouts []*discovery.Layout
   516  
   517  // groupsSet returns a set of groups that the layouts contain
   518  func (l layouts) groupsSet() map[string]struct{} {
   519  	m := make(map[string]struct{})
   520  	for _, layout := range l {
   521  		for grp := range layout.QuantitiesByGroup {
   522  			m[grp] = struct{}{}
   523  		}
   524  	}
   525  	return m
   526  }
   527  
   528  func peersWithChaincode(metadata ...*chaincode.Metadata) func(member NetworkMember) bool {
   529  	return func(member NetworkMember) bool {
   530  		if member.Properties == nil {
   531  			return false
   532  		}
   533  		for _, ccMD := range metadata {
   534  			var found bool
   535  			for _, cc := range member.Properties.Chaincodes {
   536  				if cc.Name == ccMD.Name && cc.Version == ccMD.Version {
   537  					found = true
   538  				}
   539  			}
   540  			if !found {
   541  				return false
   542  			}
   543  		}
   544  		return true
   545  	}
   546  }
   547  
   548  func mergePrincipalSets(cpss []inquire.ComparablePrincipalSets) (inquire.ComparablePrincipalSets, error) {
   549  	// Obtain the first ComparablePrincipalSet first
   550  	var cps inquire.ComparablePrincipalSets
   551  	cps, cpss, err := popComparablePrincipalSets(cpss)
   552  	if err != nil {
   553  		return nil, errors.WithStack(err)
   554  	}
   555  
   556  	for _, cps2 := range cpss {
   557  		cps = inquire.Merge(cps, cps2)
   558  	}
   559  	return cps, nil
   560  }
   561  
   562  func popComparablePrincipalSets(sets []inquire.ComparablePrincipalSets) (inquire.ComparablePrincipalSets, []inquire.ComparablePrincipalSets, error) {
   563  	if len(sets) == 0 {
   564  		return nil, nil, errors.New("no principal sets remained after filtering")
   565  	}
   566  	cps, cpss := sets[0], sets[1:]
   567  	return cps, cpss, nil
   568  }