Skip to content

Commit

Permalink
Update base (#22)
Browse files Browse the repository at this point in the history
* [FAB-17819] Discovery returns user friendly errors

Currently, the discovery service filters out peers that don't have the chaincode installed
early on in the computation, and as a result - the service cannot distinguish from a case
where there are not enough alive peers to satisfy the endorsement policy, or that there are
enough peers but the chaincode is not installed on enough of them.

This change set defers the chaincode filtering to the end of the computation, so
the layouts and peer group mapping is creating without taking into account if the peers
have the chaincode installed on them, and if there is no layout that can be satisfied
without taking into account the chaincodes - the error that is returned now
is "no peer combination can satisfy the endorsement policy",
instead of "cannot satisfy any principal combination".

Afterwards, the layouts are being inspected once again, and then the layouts
that cannot be satisfied are filtered out, when the error returned
when no layout can be satisfied is now: "required chaincodes are not installed on sufficient peers".

Change-Id: I74eb29b30aec1a87842d220414c73872cdbc8304
Signed-off-by: yacovm <yacovm@il.ibm.com>

* Fix docker network leak from RAFT integration test (hyperledger#1203)

Signed-off-by: Matthew Sykes <matthew.sykes@gmail.com>

Co-authored-by: yacovm <yacovm@il.ibm.com>
Co-authored-by: Matthew Sykes <sykesmat@us.ibm.com>
  • Loading branch information
3 people authored May 1, 2020
1 parent 8903381 commit abad5c1
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 25 deletions.
103 changes: 81 additions & 22 deletions discovery/endorsement/endorsement.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ type gossipSupport interface {
Peers() Members
}

type membersChaincodeMapping struct {
members Members
chaincodeMapping map[string]NetworkMember
}

type endorsementAnalyzer struct {
gossipSupport
principalEvaluator
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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 {
Expand All @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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.
Expand All @@ -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
}
Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions discovery/endorsement/endorsement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions integration/raft/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit abad5c1

Please sign in to comment.