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