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 }