Skip to content

Commit dd5ebf1

Browse files
committed
[FAB-10028] Prepare discovery for cc2cc queries
This change set refacors the endorsement logic in order to prepare a smoother landing for the cc2cc query support. It also adds stricter validation for chaincode queries. Change-Id: Id17e139fc5c7294ce2ec5fb54e8f1415a74960a3 Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent e321cb5 commit dd5ebf1

File tree

5 files changed

+264
-51
lines changed

5 files changed

+264
-51
lines changed

discovery/endorsement/collection.go

+14
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ import (
1515

1616
type filterPrincipalSets func(collectionName string, principalSets policies.PrincipalSets) (policies.PrincipalSets, error)
1717

18+
func (f filterPrincipalSets) forCollections(ccName string, collections ...string) filterFunc {
19+
return func(principalSets policies.PrincipalSets) (policies.PrincipalSets, error) {
20+
var err error
21+
for _, col := range collections {
22+
principalSets, err = f(col, principalSets)
23+
if err != nil {
24+
logger.Warningf("Failed filtering collection for chaincode %s, collection %s: %v", ccName, col, err)
25+
return nil, err
26+
}
27+
}
28+
return principalSets, nil
29+
}
30+
}
31+
1832
func newCollectionFilter(configBytes []byte) (filterPrincipalSets, error) {
1933
mapFilter := make(principalSetsByCollectionName)
2034
if len(configBytes) == 0 {

discovery/endorsement/collection_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,36 @@ import (
1313
"github.com/hyperledger/fabric/protos/common"
1414
"github.com/hyperledger/fabric/protos/msp"
1515
"github.com/hyperledger/fabric/protos/utils"
16+
"github.com/pkg/errors"
1617
"github.com/stretchr/testify/assert"
1718
)
1819

20+
func TestForCollections(t *testing.T) {
21+
foos := policies.PrincipalSets{{orgPrincipal("foo")}}
22+
bars := policies.PrincipalSets{{orgPrincipal("bar")}}
23+
f := filterPrincipalSets(func(collectionName string, principalSets policies.PrincipalSets) (policies.PrincipalSets, error) {
24+
switch collectionName {
25+
case "foo":
26+
return foos, nil
27+
case "bar":
28+
return bars, nil
29+
default:
30+
return nil, errors.Errorf("collection %s doesn't exist", collectionName)
31+
}
32+
})
33+
34+
res, err := f.forCollections("mycc", "foo")(nil)
35+
assert.NoError(t, err)
36+
assert.Equal(t, foos, res)
37+
38+
res, err = f.forCollections("mycc", "bar")(nil)
39+
assert.NoError(t, err)
40+
assert.Equal(t, bars, res)
41+
42+
res, err = f.forCollections("mycc", "baz")(nil)
43+
assert.Equal(t, "collection baz doesn't exist", err.Error())
44+
}
45+
1946
func TestCollectionFilter(t *testing.T) {
2047
org1AndOrg2 := []*msp.MSPPrincipal{orgPrincipal("Org1MSP"), orgPrincipal("Org2MSP")}
2148
org1AndOrg3 := []*msp.MSPPrincipal{orgPrincipal("Org1MSP"), orgPrincipal("Org3MSP")}

discovery/endorsement/endorsement.go

+162-41
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,18 @@ type peerPrincipalEvaluator func(member discovery2.NetworkMember, principal *msp
7979

8080
// PeersForEndorsement returns an EndorsementDescriptor for a given set of peers, channel, and chaincode
8181
func (ea *endorsementAnalyzer) PeersForEndorsement(chainID common.ChainID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) {
82-
chaincode := interest.Chaincodes[0]
83-
loadCollections := len(chaincode.CollectionNames) > 0
84-
ccMD := ea.Metadata(string(chainID), chaincode.Name, loadCollections)
85-
if ccMD == nil {
86-
return nil, errors.Errorf("No metadata was found for chaincode %s in channel %s", chaincode.Name, string(chainID))
82+
// For now we only support a single chaincode
83+
if len(interest.Chaincodes) != 1 {
84+
return nil, errors.New("only a single chaincode is supported")
85+
}
86+
interest.Chaincodes = []*discovery.ChaincodeCall{interest.Chaincodes[0]}
87+
88+
metadataAndCollectionFilters, err := loadMetadataAndFilters(chainID, interest, ea)
89+
if err != nil {
90+
return nil, errors.WithStack(err)
8791
}
8892
// Filter out peers that don't have the chaincode installed on them
89-
chanMembership := ea.PeersOfChannel(chainID).Filter(peersWithChaincode(ccMD))
93+
chanMembership := ea.PeersOfChannel(chainID).Filter(peersWithChaincode(metadataAndCollectionFilters.md...))
9094
channelMembersById := chanMembership.ByID()
9195
// Choose only the alive messages of those that have joined the channel
9296
aliveMembership := ea.Peers().Intersect(chanMembership)
@@ -95,68 +99,179 @@ func (ea *endorsementAnalyzer) PeersForEndorsement(chainID common.ChainID, inter
9599
identities := ea.IdentityInfo()
96100
identitiesOfMembers := computeIdentitiesOfMembers(identities, membersById)
97101

98-
// Retrieve the policy for the chaincode
99-
pol := ea.PolicyByChaincode(string(chainID), chaincode.Name)
100-
if pol == nil {
101-
logger.Debug("Policy for chaincode '", chaincode, "'doesn't exist")
102-
return nil, errors.New("policy not found")
102+
// Retrieve the policies for the chaincodes
103+
pols, err := ea.loadPolicies(chainID, interest)
104+
if err != nil {
105+
return nil, errors.WithStack(err)
103106
}
107+
// For now we take the first policy and ignore the rest
108+
pol := pols[0]
104109

105110
// Compute the combinations of principals (principal sets) that satisfy the endorsement policy
106111
principalsSets := policies.PrincipalSets(pol.SatisfiedBy())
107112

108-
// Obtain the MSP IDs of the members of the channel that are alive
109-
mspIDsOfChannelPeers := mspIDsOfMembers(membersById, identities.ByID())
110113
// Filter out principal sets that contain MSP IDs for peers that don't have the chaincode(s) installed
111-
principalsSets = principalsSets.ContainingOnly(func(principal *msp.MSPPrincipal) bool {
112-
mspID := ea.MSPOfPrincipal(principal)
113-
_, exists := mspIDsOfChannelPeers[mspID]
114-
return mspID != "" && exists
115-
})
114+
principalsSets = ea.excludeIfCCNotInstalled(principalsSets, membersById, identities.ByID())
116115

117-
if loadCollections {
118-
filterByCollections, err := newCollectionFilter(ccMD.CollectionsConfig)
119-
if err != nil {
120-
return nil, errors.Wrap(err, "failed creating collection filter")
121-
}
122-
for _, col := range chaincode.CollectionNames {
123-
principalsSets, err = filterByCollections(col, principalsSets)
124-
if err != nil {
125-
return nil, errors.Wrapf(err, "failed filtering collection %s for chaincode %s", col, chaincode.Name)
126-
}
127-
}
116+
// Filter the principal sets by the collections (if applicable)
117+
principalsSets, err = metadataAndCollectionFilters.filter(principalsSets)
118+
if err != nil {
119+
return nil, errors.WithStack(err)
128120
}
129121

122+
return ea.computeEndorsementResponse(&context{
123+
chaincode: interest.Chaincodes[0].Name,
124+
channel: string(chainID),
125+
principalsSets: principalsSets,
126+
channelMembersById: channelMembersById,
127+
aliveMembership: aliveMembership,
128+
identitiesOfMembers: identitiesOfMembers,
129+
})
130+
}
131+
132+
type context struct {
133+
chaincode string
134+
channel string
135+
aliveMembership discovery2.Members
136+
principalsSets []policies.PrincipalSet
137+
channelMembersById map[string]discovery2.NetworkMember
138+
identitiesOfMembers memberIdentities
139+
}
140+
141+
func (ea *endorsementAnalyzer) computeEndorsementResponse(ctx *context) (*discovery.EndorsementDescriptor, error) {
130142
// mapPrincipalsToGroups returns a mapping from principals to their corresponding groups.
131143
// groups are just human readable representations that mask the principals behind them
132-
principalGroups := mapPrincipalsToGroups(principalsSets)
144+
principalGroups := mapPrincipalsToGroups(ctx.principalsSets)
133145
// principalsToPeersGraph computes a bipartite graph (V1 U V2 , E)
134146
// such that V1 is the peers, V2 are the principals,
135147
// and each e=(peer,principal) is in E if the peer satisfies the principal
136148
satGraph := principalsToPeersGraph(principalAndPeerData{
137-
members: aliveMembership,
149+
members: ctx.aliveMembership,
138150
pGrps: principalGroups,
139-
}, ea.satisfiesPrincipal(string(chainID), identitiesOfMembers))
151+
}, ea.satisfiesPrincipal(ctx.channel, ctx.identitiesOfMembers))
140152

141-
layouts := computeLayouts(principalsSets, principalGroups, satGraph)
153+
layouts := computeLayouts(ctx.principalsSets, principalGroups, satGraph)
142154
if len(layouts) == 0 {
143155
return nil, errors.New("cannot satisfy any principal combination")
144156
}
145157

146158
criteria := &peerMembershipCriteria{
147159
possibleLayouts: layouts,
148160
satGraph: satGraph,
149-
chanMemberById: channelMembersById,
150-
idOfMembers: identitiesOfMembers,
161+
chanMemberById: ctx.channelMembersById,
162+
idOfMembers: ctx.identitiesOfMembers,
151163
}
152164

153165
return &discovery.EndorsementDescriptor{
154-
Chaincode: chaincode.Name,
166+
Chaincode: ctx.chaincode,
155167
Layouts: layouts,
156168
EndorsersByGroups: endorsersByGroup(criteria),
157169
}, nil
158170
}
159171

172+
func (ea *endorsementAnalyzer) excludeIfCCNotInstalled(principalsSets policies.PrincipalSets, membersById map[string]discovery2.NetworkMember, identitiesByID map[string]api.PeerIdentityInfo) policies.PrincipalSets {
173+
// Obtain the MSP IDs of the members of the channel that are alive
174+
mspIDsOfChannelPeers := mspIDsOfMembers(membersById, identitiesByID)
175+
principalsSets = ea.excludePrincipals(func(principal *msp.MSPPrincipal) bool {
176+
mspID := ea.MSPOfPrincipal(principal)
177+
_, exists := mspIDsOfChannelPeers[mspID]
178+
return mspID != "" && exists
179+
}, principalsSets)
180+
return principalsSets
181+
}
182+
183+
func (ea *endorsementAnalyzer) excludePrincipals(filter func(principal *msp.MSPPrincipal) bool, sets ...policies.PrincipalSets) policies.PrincipalSets {
184+
var res []policies.PrincipalSets
185+
for _, principalSets := range sets {
186+
principalSets = principalSets.ContainingOnly(filter)
187+
if len(principalSets) == 0 {
188+
continue
189+
}
190+
res = append(res, principalSets)
191+
}
192+
if len(res) == 0 {
193+
return nil
194+
}
195+
return res[0]
196+
}
197+
198+
func (ea *endorsementAnalyzer) loadPolicies(chainID common.ChainID, interest *discovery.ChaincodeInterest) ([]policies.InquireablePolicy, error) {
199+
var res []policies.InquireablePolicy
200+
for _, chaincode := range interest.Chaincodes {
201+
pol := ea.PolicyByChaincode(string(chainID), chaincode.Name)
202+
if pol == nil {
203+
logger.Debug("Policy for chaincode '", chaincode, "'doesn't exist")
204+
return nil, errors.New("policy not found")
205+
}
206+
res = append(res, pol)
207+
}
208+
return res, nil
209+
}
210+
211+
type filterFunc func(policies.PrincipalSets) (policies.PrincipalSets, error)
212+
213+
type filterFunctions []filterFunc
214+
215+
type metadataAndColFilter struct {
216+
md []*chaincode.Metadata
217+
filter filterFunc
218+
}
219+
220+
func loadMetadataAndFilters(chainID common.ChainID, interest *discovery.ChaincodeInterest, fetch chaincodeMetadataFetcher) (*metadataAndColFilter, error) {
221+
var metadata []*chaincode.Metadata
222+
var filters filterFunctions
223+
224+
for _, chaincode := range interest.Chaincodes {
225+
ccMD := fetch.Metadata(string(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(chainID))
228+
}
229+
metadata = append(metadata, ccMD)
230+
if len(chaincode.CollectionNames) == 0 {
231+
continue
232+
}
233+
f, err := newCollectionFilter(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+
filters = append(filters, f.forCollections(chaincode.Name, chaincode.CollectionNames...))
239+
}
240+
241+
return computeFiltersWithMetadata(filters, metadata), nil
242+
}
243+
244+
func computeFiltersWithMetadata(filters filterFunctions, metadata []*chaincode.Metadata) *metadataAndColFilter {
245+
if len(filters) == 0 {
246+
return &metadataAndColFilter{
247+
md: metadata,
248+
filter: noopFilter,
249+
}
250+
}
251+
252+
return &metadataAndColFilter{
253+
md: metadata,
254+
filter: filters.combine(),
255+
}
256+
}
257+
258+
func noopFilter(policies policies.PrincipalSets) (policies.PrincipalSets, error) {
259+
return policies, nil
260+
}
261+
262+
func (filters filterFunctions) combine() filterFunc {
263+
return func(principals policies.PrincipalSets) (policies.PrincipalSets, error) {
264+
var err error
265+
for _, filter := range filters {
266+
principals, err = filter(principals)
267+
if err != nil {
268+
return nil, err
269+
}
270+
}
271+
return principals, nil
272+
}
273+
}
274+
160275
func (ea *endorsementAnalyzer) satisfiesPrincipal(channel string, identitiesOfMembers memberIdentities) peerPrincipalEvaluator {
161276
return func(member discovery2.NetworkMember, principal *msp.MSPPrincipal) bool {
162277
err := ea.SatisfiesPrincipal(channel, identitiesOfMembers.identityByPKIID(member.PKIid), principal)
@@ -373,17 +488,23 @@ func (l layouts) groupsSet() map[string]struct{} {
373488
return m
374489
}
375490

376-
func peersWithChaincode(ccMD *chaincode.Metadata) func(member discovery2.NetworkMember) bool {
491+
func peersWithChaincode(metadata ...*chaincode.Metadata) func(member discovery2.NetworkMember) bool {
377492
return func(member discovery2.NetworkMember) bool {
378493
if member.Properties == nil {
379494
return false
380495
}
381-
for _, cc := range member.Properties.Chaincodes {
382-
if cc.Name == ccMD.Name && cc.Version == ccMD.Version {
383-
return true
496+
for _, ccMD := range metadata {
497+
var found bool
498+
for _, cc := range member.Properties.Chaincodes {
499+
if cc.Name == ccMD.Name && cc.Version == ccMD.Version {
500+
found = true
501+
}
502+
}
503+
if !found {
504+
return false
384505
}
385506
}
386-
return false
507+
return true
387508
}
388509
}
389510

discovery/service.go

+23-3
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,11 @@ func (s *service) dispatch(q *discovery.Query) *discovery.QueryResult {
119119
}
120120

121121
func (s *service) chaincodeQuery(q *discovery.Query) *discovery.QueryResult {
122+
if err := validateCCQuery(q.GetCcQuery()); err != nil {
123+
return wrapError(err)
124+
}
122125
var descriptors []*discovery.EndorsementDescriptor
123126
for _, interest := range q.GetCcQuery().Interests {
124-
if len(interest.Chaincodes) == 0 || interest.Chaincodes[0] == nil {
125-
return wrapError(errors.Errorf("must include at least one chaincode"))
126-
}
127127
desc, err := s.PeersForEndorsement(common2.ChainID(q.Channel), interest)
128128
if err != nil {
129129
logger.Errorf("Failed constructing descriptor for chaincode %s,: %v", interest, err)
@@ -247,6 +247,26 @@ func validateStructure(ctx context.Context, request *discovery.SignedRequest, ad
247247
return req, nil
248248
}
249249

250+
func validateCCQuery(ccQuery *discovery.ChaincodeQuery) error {
251+
if len(ccQuery.Interests) == 0 {
252+
return errors.New("chaincode query must have at least one chaincode interest")
253+
}
254+
for _, interest := range ccQuery.Interests {
255+
if interest == nil {
256+
return errors.New("chaincode interest is nil")
257+
}
258+
if len(interest.Chaincodes) == 0 {
259+
return errors.New("chaincode interest must contain at least one chaincode")
260+
}
261+
for _, cc := range interest.Chaincodes {
262+
if cc.Name == "" {
263+
return errors.New("chaincode name in interest cannot be empty")
264+
}
265+
}
266+
}
267+
return nil
268+
}
269+
250270
func wrapError(err error) *discovery.QueryResult {
251271
return &discovery.QueryResult{
252272
Result: &discovery.QueryResult_Error{

0 commit comments

Comments
 (0)