Skip to content

Commit 8fd6f14

Browse files
committed
[FAB-10857] Extract discovery endorsement filtering
This change set extracts the logic that deals with discovery endorsement peer filtering into a separate function, to be used by the discovery service core for filtering peers according to collections. As a result, the code that pre-filters principal sets according to MSP IDs is removed and the filtering is done implicitly at the time of layout construction (if no peers with the chaincode are installed for the given principal, the layout is filtered out in the isLayoutSatisfied() function). This removes the customly crafted MSPOfPrincipal method from existence. Change-Id: I89a3b5142a6893d84a8faf0b65e2fed36829bb7d Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent 9c5a8bc commit 8fd6f14

File tree

5 files changed

+153
-197
lines changed

5 files changed

+153
-197
lines changed

discovery/client/client_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -671,12 +671,6 @@ func (mdf *ccMetadataFetcher) Metadata(channel string, cc string, _ bool) *chain
671671
type principalEvaluator struct {
672672
}
673673

674-
func (pe *principalEvaluator) MSPOfPrincipal(principal *msp.MSPPrincipal) string {
675-
sID := &msp.SerializedIdentity{}
676-
proto.Unmarshal(principal.Principal, sID)
677-
return sID.Mspid
678-
}
679-
680674
func (pe *principalEvaluator) SatisfiesPrincipal(channel string, identity []byte, principal *msp.MSPPrincipal) error {
681675
sID := &msp.SerializedIdentity{}
682676
proto.Unmarshal(identity, sID)

discovery/endorsement/endorsement.go

Lines changed: 28 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ type principalEvaluator interface {
3030
// SatisfiesPrincipal returns whether a given peer identity satisfies a certain principal
3131
// on a given channel
3232
SatisfiesPrincipal(channel string, identity []byte, principal *msp.MSPPrincipal) error
33-
34-
// MSPOfPrincipal returns the MSP ID of the given principal
35-
MSPOfPrincipal(principal *msp.MSPPrincipal) string
3633
}
3734

3835
type chaincodeMetadataFetcher interface {
@@ -80,32 +77,17 @@ type peerPrincipalEvaluator func(member NetworkMember, principal *msp.MSPPrincip
8077

8178
// PeersForEndorsement returns an EndorsementDescriptor for a given set of peers, channel, and chaincode
8279
func (ea *endorsementAnalyzer) PeersForEndorsement(chainID common.ChainID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) {
83-
identities := ea.IdentityInfo()
84-
identitiesByID := identities.ByID()
85-
metadataAndCollectionFilters, err := loadMetadataAndFilters(metadataAndFilterContext{
86-
identityInfoByID: identitiesByID,
87-
interest: interest,
88-
chainID: chainID,
89-
evaluator: ea,
90-
fetch: ea,
91-
})
80+
chanMembership, err := ea.PeersAuthorizedByCriteria(chainID, interest)
9281
if err != nil {
9382
return nil, errors.WithStack(err)
9483
}
95-
metadata := metadataAndCollectionFilters.md
96-
// Filter out peers that don't have the chaincode installed on them
97-
chanMembership := ea.PeersOfChannel(chainID).Filter(peersWithChaincode(metadata...))
98-
// Filter out peers that aren't authorized by the collection configs of the chaincode invocation chain
99-
chanMembership = chanMembership.Filter(metadataAndCollectionFilters.isMemberAuthorized)
10084
channelMembersById := chanMembership.ByID()
10185
// Choose only the alive messages of those that have joined the channel
10286
aliveMembership := ea.Peers().Intersect(chanMembership)
10387
membersById := aliveMembership.ByID()
10488
// Compute a mapping between the PKI-IDs of members to their identities
105-
106-
identitiesOfMembers := computeIdentitiesOfMembers(identities, membersById)
107-
filter := ea.excludeIfCCNotInstalled(membersById, identitiesByID)
108-
principalsSets, err := ea.computePrincipalSets(chainID, interest, filter)
89+
identitiesOfMembers := computeIdentitiesOfMembers(ea.IdentityInfo(), membersById)
90+
principalsSets, err := ea.computePrincipalSets(chainID, interest)
10991
if err != nil {
11092
logger.Warningf("Principal set computation failed: %v", err)
11193
return nil, errors.WithStack(err)
@@ -121,6 +103,30 @@ func (ea *endorsementAnalyzer) PeersForEndorsement(chainID common.ChainID, inter
121103
})
122104
}
123105

106+
func (ea *endorsementAnalyzer) PeersAuthorizedByCriteria(chainID common.ChainID, interest *discovery.ChaincodeInterest) (Members, error) {
107+
peersOfChannel := ea.PeersOfChannel(chainID)
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: chainID,
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+
124130
type context struct {
125131
chaincode string
126132
channel string
@@ -161,23 +167,7 @@ func (ea *endorsementAnalyzer) computeEndorsementResponse(ctx *context) (*discov
161167
}, nil
162168
}
163169

164-
type principalFilter func(policies.PrincipalSet) bool
165-
166-
func (ea *endorsementAnalyzer) excludeIfCCNotInstalled(membersById map[string]NetworkMember, identitiesByID map[string]api.PeerIdentityInfo) principalFilter {
167-
// Obtain the MSP IDs of the members of the channel that are alive
168-
mspIDsOfChannelPeers := mspIDsOfMembers(membersById, identitiesByID)
169-
// Create an exclusion filter for MSP Principals which their peers don't have the chaincode installed
170-
excludeMSPsWithoutChaincodeInstalled := func(principal *msp.MSPPrincipal) bool {
171-
mspID := ea.MSPOfPrincipal(principal)
172-
_, exists := mspIDsOfChannelPeers[mspID]
173-
return mspID != "" && exists
174-
}
175-
return func(principalsSet policies.PrincipalSet) bool {
176-
return principalsSet.ContainingOnly(excludeMSPsWithoutChaincodeInstalled)
177-
}
178-
}
179-
180-
func (ea *endorsementAnalyzer) computePrincipalSets(chainID common.ChainID, interest *discovery.ChaincodeInterest, filter principalFilter) (policies.PrincipalSets, error) {
170+
func (ea *endorsementAnalyzer) computePrincipalSets(chainID common.ChainID, interest *discovery.ChaincodeInterest) (policies.PrincipalSets, error) {
181171
var inquireablePolicies []policies.InquireablePolicy
182172
for _, chaincode := range interest.Chaincodes {
183173
pol := ea.PolicyByChaincode(string(chainID), chaincode.Name)
@@ -193,10 +183,6 @@ func (ea *endorsementAnalyzer) computePrincipalSets(chainID common.ChainID, inte
193183
for _, policy := range inquireablePolicies {
194184
var cmpsets inquire.ComparablePrincipalSets
195185
for _, ps := range policy.SatisfiedBy() {
196-
if !filter(ps) {
197-
logger.Debug(ps, "filtered out due to chaincodes not being installed on the corresponding organizations")
198-
continue
199-
}
200186
cps := inquire.NewComparablePrincipalSet(ps)
201187
if cps == nil {
202188
return nil, errors.New("failed creating a comparable principal set")
@@ -549,16 +535,6 @@ func peersWithChaincode(metadata ...*chaincode.Metadata) func(member NetworkMemb
549535
}
550536
}
551537

552-
func mspIDsOfMembers(membersById map[string]NetworkMember, identitiesByID map[string]api.PeerIdentityInfo) map[string]struct{} {
553-
res := make(map[string]struct{})
554-
for pkiID := range membersById {
555-
if identity, exists := identitiesByID[string(pkiID)]; exists {
556-
res[string(identity.Organization)] = struct{}{}
557-
}
558-
}
559-
return res
560-
}
561-
562538
func mergePrincipalSets(cpss []inquire.ComparablePrincipalSets) (inquire.ComparablePrincipalSets, error) {
563539
// Obtain the first ComparablePrincipalSet first
564540
var cps inquire.ComparablePrincipalSets

discovery/endorsement/endorsement_test.go

Lines changed: 125 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,12 @@ var pkiID2MSPID = map[string]string{
4040
"p10": "Org10MSP",
4141
"p11": "Org11MSP",
4242
"p12": "Org12MSP",
43+
"p13": "Org13MSP",
44+
"p14": "Org14MSP",
45+
"p15": "Org15MSP",
4346
}
4447

4548
func TestPeersForEndorsement(t *testing.T) {
46-
peerRole := func(pkiID string) *msp.MSPPrincipal {
47-
return &msp.MSPPrincipal{
48-
PrincipalClassification: msp.MSPPrincipal_ROLE,
49-
Principal: utils.MarshalOrPanic(&msp.MSPRole{
50-
MspIdentifier: pkiID2MSPID[pkiID],
51-
Role: msp.MSPRole_PEER,
52-
}),
53-
}
54-
}
5549
extractPeers := func(desc *discoveryprotos.EndorsementDescriptor) map[string]struct{} {
5650
res := make(map[string]struct{})
5751
for _, endorsers := range desc.EndorsersByGroups {
@@ -183,7 +177,7 @@ func TestPeersForEndorsement(t *testing.T) {
183177
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
184178
desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{Chaincodes: []*discoveryprotos.ChaincodeCall{{Name: cc}}})
185179
assert.Nil(t, desc)
186-
assert.Equal(t, err.Error(), "chaincode isn't installed on sufficient organizations required by the endorsement policy")
180+
assert.Equal(t, "cannot satisfy any principal combination", err.Error())
187181

188182
// Scenario VI: Policy is found, there are enough peers to satisfy policy combinations,
189183
// but some peers have the wrong chaincode version, and some don't even have it installed.
@@ -203,22 +197,22 @@ func TestPeersForEndorsement(t *testing.T) {
203197
}).Once()
204198
desc, err = analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{Chaincodes: []*discoveryprotos.ChaincodeCall{{Name: cc}}})
205199
assert.Nil(t, desc)
206-
assert.Equal(t, err.Error(), "chaincode isn't installed on sufficient organizations required by the endorsement policy")
200+
assert.Equal(t, "cannot satisfy any principal combination", err.Error())
207201
})
208202

209203
t.Run("NoChaincodeMetadataFromLedger", func(t *testing.T) {
210204
// Scenario VII: Policy is found, there are enough peers to satisfy the policy,
211205
// but the chaincode metadata cannot be fetched from the ledger.
212-
//g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
213206
pb := principalBuilder{}
214207
policy := pb.newSet().addPrincipal(peerRole("p0")).addPrincipal(peerRole("p6")).
215208
newSet().addPrincipal(peerRole("p12")).buildPolicy()
209+
g.On("PeersOfChannel").Return(chanPeers.toMembers()).Once()
216210
pf.On("PolicyByChaincode", cc).Return(policy).Once()
217211
mf.On("Metadata").Return(nil).Once()
218212
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
219213
desc, err := analyzer.PeersForEndorsement(channel, &discoveryprotos.ChaincodeInterest{Chaincodes: []*discoveryprotos.ChaincodeCall{{Name: cc}}})
220214
assert.Nil(t, desc)
221-
assert.Equal(t, err.Error(), "No metadata was found for chaincode chaincode in channel test")
215+
assert.Equal(t, "No metadata was found for chaincode chaincode in channel test", err.Error())
222216
})
223217

224218
t.Run("Collections", func(t *testing.T) {
@@ -328,6 +322,113 @@ func TestPeersForEndorsement(t *testing.T) {
328322
})
329323
}
330324

325+
func TestPeersAuthorizedByCriteria(t *testing.T) {
326+
cc1 := "cc1"
327+
cc2 := "cc2"
328+
members := peerSet{
329+
newPeer(0).withChaincode(cc1, "1.0"),
330+
newPeer(3).withChaincode(cc1, "1.0"),
331+
newPeer(6).withChaincode(cc1, "1.0"),
332+
newPeer(9).withChaincode(cc1, "1.0"),
333+
newPeer(12).withChaincode(cc1, "1.0"),
334+
}.toMembers()
335+
336+
members2 := append(discovery.Members{}, members...)
337+
members2 = append(members2, peerSet{newPeer(13).withChaincode(cc1, "1.1").withChaincode(cc2, "1.0")}.toMembers()...)
338+
members2 = append(members2, peerSet{newPeer(14).withChaincode(cc1, "1.1")}.toMembers()...)
339+
members2 = append(members2, peerSet{newPeer(15).withChaincode(cc2, "1.0")}.toMembers()...)
340+
341+
alivePeers := peerSet{
342+
newPeer(0),
343+
newPeer(2),
344+
newPeer(4),
345+
newPeer(6),
346+
newPeer(8),
347+
newPeer(10),
348+
newPeer(11),
349+
newPeer(12),
350+
newPeer(13),
351+
newPeer(14),
352+
newPeer(15),
353+
}.toMembers()
354+
355+
identities := identitySet(pkiID2MSPID)
356+
357+
for _, tst := range []struct {
358+
name string
359+
arguments *discoveryprotos.ChaincodeInterest
360+
totalExistingMembers discovery.Members
361+
metadata []*chaincode.Metadata
362+
expected discovery.Members
363+
}{
364+
{
365+
name: "Nil interest",
366+
arguments: nil,
367+
totalExistingMembers: members,
368+
expected: members,
369+
},
370+
{
371+
name: "Empty interest invocation chain",
372+
arguments: &discoveryprotos.ChaincodeInterest{},
373+
totalExistingMembers: members,
374+
expected: members,
375+
},
376+
{
377+
name: "Chaincodes only installed on some peers",
378+
arguments: &discoveryprotos.ChaincodeInterest{
379+
Chaincodes: []*discoveryprotos.ChaincodeCall{
380+
{Name: cc1}, {Name: cc2},
381+
},
382+
},
383+
totalExistingMembers: members2,
384+
metadata: []*chaincode.Metadata{{
385+
Name: "cc1", Version: "1.1",
386+
}, {
387+
Name: "cc2", Version: "1.0",
388+
}},
389+
expected: peerSet{newPeer(13).withChaincode(cc1, "1.1").withChaincode(cc2, "1.0")}.toMembers(),
390+
},
391+
{
392+
name: "Only some peers authorized by collection",
393+
arguments: &discoveryprotos.ChaincodeInterest{
394+
Chaincodes: []*discoveryprotos.ChaincodeCall{
395+
{Name: cc1, CollectionNames: []string{"collection"}},
396+
},
397+
},
398+
totalExistingMembers: members,
399+
metadata: []*chaincode.Metadata{{
400+
Name: cc1, Version: "1.0",
401+
CollectionsConfig: buildCollectionConfig(map[string][]*msp.MSPPrincipal{
402+
"collection": {
403+
peerRole("p0"),
404+
peerRole("p12"),
405+
},
406+
}),
407+
}},
408+
expected: peerSet{
409+
newPeer(0).withChaincode(cc1, "1.0"),
410+
newPeer(12).withChaincode(cc1, "1.0")}.toMembers(),
411+
},
412+
} {
413+
t.Run(tst.name, func(t *testing.T) {
414+
g := &gossipMock{}
415+
pf := &policyFetcherMock{}
416+
mf := &metadataFetcher{}
417+
g.On("Peers").Return(alivePeers)
418+
g.On("IdentityInfo").Return(identities)
419+
g.On("PeersOfChannel").Return(tst.totalExistingMembers).Once()
420+
for _, md := range tst.metadata {
421+
mf.On("Metadata").Return(md).Once()
422+
}
423+
424+
analyzer := NewEndorsementAnalyzer(g, pf, &principalEvaluatorMock{}, mf)
425+
actualMembers, err := analyzer.PeersAuthorizedByCriteria(common.ChainID("mychannel"), tst.arguments)
426+
assert.NoError(t, err)
427+
assert.Equal(t, tst.expected, actualMembers)
428+
})
429+
}
430+
}
431+
331432
func TestPop(t *testing.T) {
332433
slice := []inquire.ComparablePrincipalSets{{}, {}}
333434
assert.Len(t, slice, 2)
@@ -354,10 +455,7 @@ func TestComputePrincipalSetsNoPolicies(t *testing.T) {
354455
Chaincodes: []*discoveryprotos.ChaincodeCall{},
355456
}
356457
ea := &endorsementAnalyzer{}
357-
acceptAll := func(policies.PrincipalSet) bool {
358-
return true
359-
}
360-
_, err := ea.computePrincipalSets(common.ChainID("mychannel"), interest, acceptAll)
458+
_, err := ea.computePrincipalSets(common.ChainID("mychannel"), interest)
361459
assert.Error(t, err)
362460
assert.Contains(t, err.Error(), "no principal sets remained after filtering")
363461
}
@@ -482,6 +580,16 @@ func newPeer(i int) *peerInfo {
482580
}
483581
}
484582

583+
func peerRole(pkiID string) *msp.MSPPrincipal {
584+
return &msp.MSPPrincipal{
585+
PrincipalClassification: msp.MSPPrincipal_ROLE,
586+
Principal: utils.MarshalOrPanic(&msp.MSPRole{
587+
MspIdentifier: pkiID2MSPID[pkiID],
588+
Role: msp.MSPRole_PEER,
589+
}),
590+
}
591+
}
592+
485593
func (pi *peerInfo) withChaincode(name, version string) *peerInfo {
486594
if pi.Properties == nil {
487595
pi.Properties = &gossip.Properties{}
@@ -553,12 +661,6 @@ func (ip inquireablePolicy) SatisfiedBy() []policies.PrincipalSet {
553661
type principalEvaluatorMock struct {
554662
}
555663

556-
func (pe *principalEvaluatorMock) MSPOfPrincipal(principal *msp.MSPPrincipal) string {
557-
role := &msp.MSPRole{}
558-
proto.Unmarshal(principal.Principal, role)
559-
return role.MspIdentifier
560-
}
561-
562664
func (pe *principalEvaluatorMock) SatisfiesPrincipal(channel string, identity []byte, principal *msp.MSPPrincipal) error {
563665
peerRole := &msp.MSPRole{}
564666
if err := proto.Unmarshal(principal.Principal, peerRole); err != nil {

0 commit comments

Comments
 (0)