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