diff --git a/discovery/endorsement/endorsement.go b/discovery/endorsement/endorsement.go index a8d77dc451b..529bc3fbc89 100644 --- a/discovery/endorsement/endorsement.go +++ b/discovery/endorsement/endorsement.go @@ -56,6 +56,11 @@ type gossipSupport interface { Peers() Members } +type membersChaincodeMapping struct { + members Members + chaincodeMapping map[string]NetworkMember +} + type endorsementAnalyzer struct { gossipSupport principalEvaluator @@ -77,13 +82,13 @@ type peerPrincipalEvaluator func(member NetworkMember, principal *msp.MSPPrincip // PeersForEndorsement returns an EndorsementDescriptor for a given set of peers, channel, and chaincode func (ea *endorsementAnalyzer) PeersForEndorsement(channelID common.ChannelID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) { - chanMembership, err := ea.PeersAuthorizedByCriteria(channelID, interest) + membersAndCC, err := ea.peersByCriteria(channelID, interest, false) if err != nil { return nil, errors.WithStack(err) } - channelMembersById := chanMembership.ByID() + channelMembersById := membersAndCC.members.ByID() // Choose only the alive messages of those that have joined the channel - aliveMembership := ea.Peers().Intersect(chanMembership) + aliveMembership := ea.Peers().Intersect(membersAndCC.members) membersById := aliveMembership.ByID() // Compute a mapping between the PKI-IDs of members to their identities identitiesOfMembers := computeIdentitiesOfMembers(ea.IdentityInfo(), membersById) @@ -100,13 +105,19 @@ func (ea *endorsementAnalyzer) PeersForEndorsement(channelID common.ChannelID, i channelMembersById: channelMembersById, aliveMembership: aliveMembership, identitiesOfMembers: identitiesOfMembers, + chaincodeMapping: membersAndCC.chaincodeMapping, }) } func (ea *endorsementAnalyzer) PeersAuthorizedByCriteria(channelID common.ChannelID, interest *discovery.ChaincodeInterest) (Members, error) { + res, err := ea.peersByCriteria(channelID, interest, true) + return res.members, err +} + +func (ea *endorsementAnalyzer) peersByCriteria(channelID common.ChannelID, interest *discovery.ChaincodeInterest, excludePeersWithoutChaincode bool) (membersChaincodeMapping, error) { peersOfChannel := ea.PeersOfChannel(channelID) if interest == nil || len(interest.Chaincodes) == 0 { - return peersOfChannel, nil + return membersChaincodeMapping{members: peersOfChannel}, nil } identities := ea.IdentityInfo() identitiesByID := identities.ByID() @@ -118,13 +129,22 @@ func (ea *endorsementAnalyzer) PeersAuthorizedByCriteria(channelID common.Channe fetch: ea, }) if err != nil { - return nil, errors.WithStack(err) + return membersChaincodeMapping{}, errors.WithStack(err) } metadata := metadataAndCollectionFilters.md - // Filter out peers that don't have the chaincode installed on them - chanMembership := peersOfChannel.Filter(peersWithChaincode(metadata...)) + // Filter out peers that don't have the chaincode installed on them if required + peersWithChaincode := peersOfChannel.Filter(peersWithChaincode(metadata...)) + chanMembership := peersOfChannel + if excludePeersWithoutChaincode { + chanMembership = peersWithChaincode + } + // Filter out peers that aren't authorized by the collection configs of the chaincode invocation chain - return chanMembership.Filter(metadataAndCollectionFilters.isMemberAuthorized), nil + members := chanMembership.Filter(metadataAndCollectionFilters.isMemberAuthorized) + return membersChaincodeMapping{ + members: members, + chaincodeMapping: peersWithChaincode.ByID(), + }, nil } type context struct { @@ -134,6 +154,7 @@ type context struct { principalsSets []policies.PrincipalSet channelMembersById map[string]NetworkMember identitiesOfMembers memberIdentities + chaincodeMapping map[string]NetworkMember } func (ea *endorsementAnalyzer) computeEndorsementResponse(ctx *context) (*discovery.EndorsementDescriptor, error) { @@ -150,23 +171,51 @@ func (ea *endorsementAnalyzer) computeEndorsementResponse(ctx *context) (*discov layouts := computeLayouts(ctx.principalsSets, principalGroups, satGraph) if len(layouts) == 0 { - return nil, errors.New("cannot satisfy any principal combination") + return nil, errors.New("no peer combination can satisfy the endorsement policy") } criteria := &peerMembershipCriteria{ - possibleLayouts: layouts, - satGraph: satGraph, - chanMemberById: ctx.channelMembersById, - idOfMembers: ctx.identitiesOfMembers, + possibleLayouts: layouts, + satGraph: satGraph, + chanMemberById: ctx.channelMembersById, + idOfMembers: ctx.identitiesOfMembers, + chaincodeMapping: ctx.chaincodeMapping, + } + + groupToEndorserListMapping := endorsersByGroup(criteria) + layouts = filterOutUnsatisfiedLayouts(groupToEndorserListMapping, layouts) + + if len(layouts) == 0 { + return nil, errors.New("required chaincodes are not installed on sufficient peers") } return &discovery.EndorsementDescriptor{ Chaincode: ctx.chaincode, Layouts: layouts, - EndorsersByGroups: endorsersByGroup(criteria), + EndorsersByGroups: groupToEndorserListMapping, }, nil } +func filterOutUnsatisfiedLayouts(endorsersByGroup map[string]*discovery.Peers, layouts []*discovery.Layout) []*discovery.Layout { + // Iterate once again over all layouts and ensure every layout has enough peers in the EndorsersByGroups + // as required by the quantity in the layout. + filteredLayouts := make([]*discovery.Layout, 0, len(layouts)) + for _, layout := range layouts { + var layoutInvalid bool + for group, quantity := range layout.QuantitiesByGroup { + peerList := endorsersByGroup[group] + if peerList == nil || len(peerList.Peers) < int(quantity) { + layoutInvalid = true + } + } + if layoutInvalid { + continue + } + filteredLayouts = append(filteredLayouts, layout) + } + return filteredLayouts +} + func (ea *endorsementAnalyzer) computePrincipalSets(channelID common.ChannelID, interest *discovery.ChaincodeInterest) (policies.PrincipalSets, error) { sessionLogger := logger.With("channel", string(channelID)) var inquireablePolicies []policies.InquireablePolicy @@ -194,7 +243,7 @@ func (ea *endorsementAnalyzer) computePrincipalSets(channelID common.ChannelID, cmpsets = append(cmpsets, cps) } if len(cmpsets) == 0 { - return nil, errors.New("chaincode isn't installed on sufficient organizations required by the endorsement policy") + return nil, errors.New("endorsement policy cannot be satisfied") } cpss = append(cpss, cmpsets) } @@ -323,10 +372,11 @@ func (ea *endorsementAnalyzer) satisfiesPrincipal(channel string, identitiesOfMe } type peerMembershipCriteria struct { - satGraph *principalPeerGraph - idOfMembers memberIdentities - chanMemberById map[string]NetworkMember - possibleLayouts layouts + satGraph *principalPeerGraph + idOfMembers memberIdentities + chanMemberById map[string]NetworkMember + possibleLayouts layouts + chaincodeMapping map[string]NetworkMember } // endorsersByGroup computes a map from groups to peers. @@ -351,15 +401,24 @@ func endorsersByGroup(criteria *peerMembershipCriteria) map[string]*discovery.Pe continue } peerList := &discovery.Peers{} - res[grp] = peerList for _, peerVertex := range principalVertex.Neighbors() { member := peerVertex.Data.(NetworkMember) + // Check if this peer has the chaincode installed + stateInfo := chanMemberById[string(member.PKIid)] + _, hasChaincodeInstalled := criteria.chaincodeMapping[string(stateInfo.PKIid)] + if !hasChaincodeInstalled { + continue + } peerList.Peers = append(peerList.Peers, &discovery.Peer{ Identity: idOfMembers.identityByPKIID(member.PKIid), - StateInfo: chanMemberById[string(member.PKIid)].Envelope, + StateInfo: stateInfo.Envelope, MembershipInfo: member.Envelope, }) } + + if len(peerList.Peers) > 0 { + res[grp] = peerList + } } return res } @@ -369,7 +428,7 @@ func endorsersByGroup(criteria *peerMembershipCriteria) map[string]*discovery.Pe // of available peers that maps each peer to a principal it satisfies. // Each such a combination is called a layout, because it maps // a group (alias for a principal) to a threshold of peers that need to endorse, -// and that satisfy the corresponding principal. +// and that satisfy the corresponding principal func computeLayouts(principalsSets []policies.PrincipalSet, principalGroups principalGroupMapper, satGraph *principalPeerGraph) []*discovery.Layout { var layouts []*discovery.Layout // principalsSets is a collection of combinations of principals, diff --git a/discovery/endorsement/endorsement_test.go b/discovery/endorsement/endorsement_test.go index 3f01905555c..ad46f16e70a 100644 --- a/discovery/endorsement/endorsement_test.go +++ b/discovery/endorsement/endorsement_test.go @@ -129,7 +129,7 @@ func TestPeersForEndorsement(t *testing.T) { }, }) assert.Nil(t, desc) - assert.Equal(t, err.Error(), "cannot satisfy any principal combination") + assert.Equal(t, err.Error(), "no peer combination can satisfy the endorsement policy") }) t.Run("DisjointViews", func(t *testing.T) { @@ -222,7 +222,7 @@ func TestPeersForEndorsement(t *testing.T) { }, }) assert.Nil(t, desc) - assert.Equal(t, "cannot satisfy any principal combination", err.Error()) + assert.Equal(t, "required chaincodes are not installed on sufficient peers", err.Error()) // Scenario VI: Policy is found, there are enough peers to satisfy policy combinations, // but some peers have the wrong chaincode version, and some don't even have it installed. @@ -247,7 +247,7 @@ func TestPeersForEndorsement(t *testing.T) { }, }) assert.Nil(t, desc) - assert.Equal(t, "cannot satisfy any principal combination", err.Error()) + assert.Equal(t, "required chaincodes are not installed on sufficient peers", err.Error()) }) t.Run("NoChaincodeMetadataFromLedger", func(t *testing.T) { diff --git a/integration/raft/config_test.go b/integration/raft/config_test.go index 1f4cb4c6fcb..b824a595c69 100644 --- a/integration/raft/config_test.go +++ b/integration/raft/config_test.go @@ -161,6 +161,7 @@ var _ = Describe("EndToEnd reconfiguration and onboarding", func() { ordererRunner := network.OrdererGroupRunner() process := ifrit.Invoke(ordererRunner) Eventually(process.Wait, network.EventuallyTimeout).Should(Receive()) // orderer process should exit + network.Cleanup() os.RemoveAll(testDir) By("Starting orderer with correct genesis block")