github.com/kaituanwang/hyperledger@v2.0.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, loadCollections bool) *chaincode.Metadata 39 } 40 41 type policyFetcher interface { 42 // PolicyByChaincode returns a policy that can be inquired which identities 43 // satisfy it 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 var inquireablePolicies []policies.InquireablePolicy 172 for _, chaincode := range interest.Chaincodes { 173 policies := ea.PoliciesByChaincode(string(channelID), chaincode.Name, chaincode.CollectionNames...) 174 if len(policies) == 0 { 175 logger.Debug("Policy for chaincode '", chaincode, "'doesn't exist") 176 return nil, errors.New("policy not found") 177 } 178 179 for _, pol := range policies { 180 inquireablePolicies = append(inquireablePolicies, pol) 181 } 182 } 183 184 var cpss []inquire.ComparablePrincipalSets 185 186 for _, policy := range inquireablePolicies { 187 var cmpsets inquire.ComparablePrincipalSets 188 for _, ps := range policy.SatisfiedBy() { 189 cps := inquire.NewComparablePrincipalSet(ps) 190 if cps == nil { 191 return nil, errors.New("failed creating a comparable principal set") 192 } 193 cmpsets = append(cmpsets, cps) 194 } 195 if len(cmpsets) == 0 { 196 return nil, errors.New("chaincode isn't installed on sufficient organizations required by the endorsement policy") 197 } 198 cpss = append(cpss, cmpsets) 199 } 200 201 cps, err := mergePrincipalSets(cpss) 202 if err != nil { 203 return nil, errors.WithStack(err) 204 } 205 206 return cps.ToPrincipalSets(), nil 207 } 208 209 type metadataAndFilterContext struct { 210 chainID common.ChannelID 211 interest *discovery.ChaincodeInterest 212 fetch chaincodeMetadataFetcher 213 identityInfoByID map[string]api.PeerIdentityInfo 214 evaluator principalEvaluator 215 } 216 217 // metadataAndColFilter holds metadata and member filters 218 type metadataAndColFilter struct { 219 md []*chaincode.Metadata 220 isMemberAuthorized memberFilter 221 } 222 223 func loadMetadataAndFilters(ctx metadataAndFilterContext) (*metadataAndColFilter, error) { 224 var metadata []*chaincode.Metadata 225 var filters []identityFilter 226 227 for _, chaincode := range ctx.interest.Chaincodes { 228 ccMD := ctx.fetch.Metadata(string(ctx.chainID), chaincode.Name, len(chaincode.CollectionNames) > 0) 229 if ccMD == nil { 230 return nil, errors.Errorf("No metadata was found for chaincode %s in channel %s", chaincode.Name, string(ctx.chainID)) 231 } 232 metadata = append(metadata, ccMD) 233 if len(chaincode.CollectionNames) == 0 { 234 continue 235 } 236 principalSetByCollections, err := principalsFromCollectionConfig(ccMD.CollectionsConfig) 237 if err != nil { 238 logger.Warningf("Failed initializing collection filter for chaincode %s: %v", chaincode.Name, err) 239 return nil, errors.WithStack(err) 240 } 241 filter, err := principalSetByCollections.toIdentityFilter(string(ctx.chainID), ctx.evaluator, chaincode) 242 if err != nil { 243 logger.Warningf("Failed computing collection principal sets for chaincode %s due to %v", chaincode.Name, err) 244 return nil, errors.WithStack(err) 245 } 246 filters = append(filters, filter) 247 } 248 249 return computeFiltersWithMetadata(filters, metadata, ctx.identityInfoByID), nil 250 } 251 252 func computeFiltersWithMetadata(filters identityFilters, metadata []*chaincode.Metadata, identityInfoByID map[string]api.PeerIdentityInfo) *metadataAndColFilter { 253 if len(filters) == 0 { 254 return &metadataAndColFilter{ 255 md: metadata, 256 isMemberAuthorized: noopMemberFilter, 257 } 258 } 259 filter := filters.combine().toMemberFilter(identityInfoByID) 260 return &metadataAndColFilter{ 261 isMemberAuthorized: filter, 262 md: metadata, 263 } 264 } 265 266 // identityFilter accepts or rejects peer identities 267 type identityFilter func(api.PeerIdentityType) bool 268 269 // identityFilters aggregates multiple identityFilters 270 type identityFilters []identityFilter 271 272 // memberFilter accepts or rejects NetworkMembers 273 type memberFilter func(member NetworkMember) bool 274 275 // noopMemberFilter accepts every NetworkMember 276 func noopMemberFilter(_ NetworkMember) bool { 277 return true 278 } 279 280 // combine combines all identityFilters into a single identityFilter which only accepts identities 281 // which all the original filters accept 282 func (filters identityFilters) combine() identityFilter { 283 return func(identity api.PeerIdentityType) bool { 284 for _, f := range filters { 285 if !f(identity) { 286 return false 287 } 288 } 289 return true 290 } 291 } 292 293 // toMemberFilter converts this identityFilter to a memberFilter based on the given mapping 294 // from PKI-ID as strings, to PeerIdentityInfo which holds the peer identities 295 func (idf identityFilter) toMemberFilter(identityInfoByID map[string]api.PeerIdentityInfo) memberFilter { 296 return func(member NetworkMember) bool { 297 identity, exists := identityInfoByID[string(member.PKIid)] 298 if !exists { 299 return false 300 } 301 return idf(identity.Identity) 302 } 303 } 304 305 func (ea *endorsementAnalyzer) satisfiesPrincipal(channel string, identitiesOfMembers memberIdentities) peerPrincipalEvaluator { 306 return func(member NetworkMember, principal *msp.MSPPrincipal) bool { 307 err := ea.SatisfiesPrincipal(channel, identitiesOfMembers.identityByPKIID(member.PKIid), principal) 308 if err == nil { 309 // TODO: log the principals in a human readable form 310 logger.Debug(member, "satisfies principal", principal) 311 return true 312 } 313 logger.Debug(member, "doesn't satisfy principal", principal, ":", err) 314 return false 315 } 316 } 317 318 type peerMembershipCriteria struct { 319 satGraph *principalPeerGraph 320 idOfMembers memberIdentities 321 chanMemberById map[string]NetworkMember 322 possibleLayouts layouts 323 } 324 325 // endorsersByGroup computes a map from groups to peers. 326 // Each group included, is found in some layout, which means 327 // that there is some principal combination that includes the corresponding 328 // group. 329 // This means that if a group isn't included in the result, there is no 330 // principal combination (that includes the principal corresponding to the group), 331 // such that there are enough peers to satisfy the principal combination. 332 func endorsersByGroup(criteria *peerMembershipCriteria) map[string]*discovery.Peers { 333 satGraph := criteria.satGraph 334 idOfMembers := criteria.idOfMembers 335 chanMemberById := criteria.chanMemberById 336 includedGroups := criteria.possibleLayouts.groupsSet() 337 338 res := make(map[string]*discovery.Peers) 339 // Map endorsers to their corresponding groups. 340 // Iterate the principals, and put the peers into each group that corresponds with a principal vertex 341 for grp, principalVertex := range satGraph.principalVertices { 342 if _, exists := includedGroups[grp]; !exists { 343 // If the current group is not found in any layout, skip the corresponding principal 344 continue 345 } 346 peerList := &discovery.Peers{} 347 res[grp] = peerList 348 for _, peerVertex := range principalVertex.Neighbors() { 349 member := peerVertex.Data.(NetworkMember) 350 peerList.Peers = append(peerList.Peers, &discovery.Peer{ 351 Identity: idOfMembers.identityByPKIID(member.PKIid), 352 StateInfo: chanMemberById[string(member.PKIid)].Envelope, 353 MembershipInfo: member.Envelope, 354 }) 355 } 356 } 357 return res 358 } 359 360 // computeLayouts computes all possible principal combinations 361 // that can be used to satisfy the endorsement policy, given a graph 362 // of available peers that maps each peer to a principal it satisfies. 363 // Each such a combination is called a layout, because it maps 364 // a group (alias for a principal) to a threshold of peers that need to endorse, 365 // and that satisfy the corresponding principal. 366 func computeLayouts(principalsSets []policies.PrincipalSet, principalGroups principalGroupMapper, satGraph *principalPeerGraph) []*discovery.Layout { 367 var layouts []*discovery.Layout 368 // principalsSets is a collection of combinations of principals, 369 // such that each combination (given enough peers) satisfies the endorsement policy. 370 for _, principalSet := range principalsSets { 371 layout := &discovery.Layout{ 372 QuantitiesByGroup: make(map[string]uint32), 373 } 374 // Since principalsSet has repetitions, we first 375 // compute a mapping from the principal to repetitions in the set. 376 for principal, plurality := range principalSet.UniqueSet() { 377 key := principalKey{ 378 cls: int32(principal.PrincipalClassification), 379 principal: string(principal.Principal), 380 } 381 // We map the principal to a group, which is an alias for the principal. 382 layout.QuantitiesByGroup[principalGroups.group(key)] = uint32(plurality) 383 } 384 // Check that the layout can be satisfied with the current known peers 385 // This is done by iterating the current layout, and ensuring that 386 // each principal vertex is connected to at least <plurality> peer vertices. 387 if isLayoutSatisfied(layout.QuantitiesByGroup, satGraph) { 388 // If so, then add the layout to the layouts, since we have enough peers to satisfy the 389 // principal combination 390 layouts = append(layouts, layout) 391 } 392 } 393 return layouts 394 } 395 396 func isLayoutSatisfied(layout map[string]uint32, satGraph *principalPeerGraph) bool { 397 for grp, plurality := range layout { 398 // Do we have more than <plurality> peers connected to the principal? 399 if len(satGraph.principalVertices[grp].Neighbors()) < int(plurality) { 400 return false 401 } 402 } 403 return true 404 } 405 406 type principalPeerGraph struct { 407 peerVertices []*graph.Vertex 408 principalVertices map[string]*graph.Vertex 409 } 410 411 type principalAndPeerData struct { 412 members Members 413 pGrps principalGroupMapper 414 } 415 416 func principalsToPeersGraph(data principalAndPeerData, satisfiesPrincipal peerPrincipalEvaluator) *principalPeerGraph { 417 // Create the peer vertices 418 peerVertices := make([]*graph.Vertex, len(data.members)) 419 for i, member := range data.members { 420 peerVertices[i] = graph.NewVertex(string(member.PKIid), member) 421 } 422 423 // Create the principal vertices 424 principalVertices := make(map[string]*graph.Vertex) 425 for pKey, grp := range data.pGrps { 426 principalVertices[grp] = graph.NewVertex(grp, pKey.toPrincipal()) 427 } 428 429 // Connect principals and peers 430 for _, principalVertex := range principalVertices { 431 for _, peerVertex := range peerVertices { 432 // If the current peer satisfies the principal, connect their corresponding vertices with an edge 433 principal := principalVertex.Data.(*msp.MSPPrincipal) 434 member := peerVertex.Data.(NetworkMember) 435 if satisfiesPrincipal(member, principal) { 436 peerVertex.AddNeighbor(principalVertex) 437 } 438 } 439 } 440 return &principalPeerGraph{ 441 peerVertices: peerVertices, 442 principalVertices: principalVertices, 443 } 444 } 445 446 func mapPrincipalsToGroups(principalsSets []policies.PrincipalSet) principalGroupMapper { 447 groupMapper := make(principalGroupMapper) 448 totalPrincipals := make(map[principalKey]struct{}) 449 for _, principalSet := range principalsSets { 450 for _, principal := range principalSet { 451 totalPrincipals[principalKey{ 452 principal: string(principal.Principal), 453 cls: int32(principal.PrincipalClassification), 454 }] = struct{}{} 455 } 456 } 457 for principal := range totalPrincipals { 458 groupMapper.group(principal) 459 } 460 return groupMapper 461 } 462 463 type memberIdentities map[string]api.PeerIdentityType 464 465 func (m memberIdentities) identityByPKIID(id common.PKIidType) api.PeerIdentityType { 466 return m[string(id)] 467 } 468 469 func computeIdentitiesOfMembers(identitySet api.PeerIdentitySet, members map[string]NetworkMember) memberIdentities { 470 identitiesByPKIID := make(map[string]api.PeerIdentityType) 471 identitiesOfMembers := make(map[string]api.PeerIdentityType, len(members)) 472 for _, identity := range identitySet { 473 identitiesByPKIID[string(identity.PKIId)] = identity.Identity 474 } 475 for _, member := range members { 476 if identity, exists := identitiesByPKIID[string(member.PKIid)]; exists { 477 identitiesOfMembers[string(member.PKIid)] = identity 478 } 479 } 480 return identitiesOfMembers 481 } 482 483 // principalGroupMapper maps principals to names of groups 484 type principalGroupMapper map[principalKey]string 485 486 func (mapper principalGroupMapper) group(principal principalKey) string { 487 if grp, exists := mapper[principal]; exists { 488 return grp 489 } 490 grp := fmt.Sprintf("G%d", len(mapper)) 491 mapper[principal] = grp 492 return grp 493 } 494 495 type principalKey struct { 496 cls int32 497 principal string 498 } 499 500 func (pk principalKey) toPrincipal() *msp.MSPPrincipal { 501 return &msp.MSPPrincipal{ 502 PrincipalClassification: msp.MSPPrincipal_Classification(pk.cls), 503 Principal: []byte(pk.principal), 504 } 505 } 506 507 // layouts is an aggregation of several layouts 508 type layouts []*discovery.Layout 509 510 // groupsSet returns a set of groups that the layouts contain 511 func (l layouts) groupsSet() map[string]struct{} { 512 m := make(map[string]struct{}) 513 for _, layout := range l { 514 for grp := range layout.QuantitiesByGroup { 515 m[grp] = struct{}{} 516 } 517 } 518 return m 519 } 520 521 func peersWithChaincode(metadata ...*chaincode.Metadata) func(member NetworkMember) bool { 522 return func(member NetworkMember) bool { 523 if member.Properties == nil { 524 return false 525 } 526 for _, ccMD := range metadata { 527 var found bool 528 for _, cc := range member.Properties.Chaincodes { 529 if cc.Name == ccMD.Name && cc.Version == ccMD.Version { 530 found = true 531 } 532 } 533 if !found { 534 return false 535 } 536 } 537 return true 538 } 539 } 540 541 func mergePrincipalSets(cpss []inquire.ComparablePrincipalSets) (inquire.ComparablePrincipalSets, error) { 542 // Obtain the first ComparablePrincipalSet first 543 var cps inquire.ComparablePrincipalSets 544 cps, cpss, err := popComparablePrincipalSets(cpss) 545 if err != nil { 546 return nil, errors.WithStack(err) 547 } 548 549 for _, cps2 := range cpss { 550 cps = inquire.Merge(cps, cps2) 551 } 552 return cps, nil 553 } 554 555 func popComparablePrincipalSets(sets []inquire.ComparablePrincipalSets) (inquire.ComparablePrincipalSets, []inquire.ComparablePrincipalSets, error) { 556 if len(sets) == 0 { 557 return nil, nil, errors.New("no principal sets remained after filtering") 558 } 559 cps, cpss := sets[0], sets[1:] 560 return cps, cpss, nil 561 }