Skip to content

Commit ae9237f

Browse files
committed
[FAB-10963] Discovery: filtering in membership queries
This change set adds support to the discovery service to filter channel membership queries according to endorsement capabilities. Now the client can pass invocation chains to the query, and the discovery service would only return peers that satisfy the invocation chain: 1) Peers that have chaincodes installed on them 2) Peers that are authorized by a given collection configuration This change set also enhances the golang client implementation with an ability to pass in the Peers() method - an invocation chain. To preserve backward compatibility as much as possible, the invocation chain is passed as a variadic parameter. Change-Id: I28b4bcd88d76a40e5dfcdeba467e1269dbdd0101 Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent 8fd6f14 commit ae9237f

16 files changed

+267
-151
lines changed

discovery/api.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/hyperledger/fabric/gossip/common"
1212
"github.com/hyperledger/fabric/gossip/discovery"
1313
common2 "github.com/hyperledger/fabric/protos/common"
14-
discovery2 "github.com/hyperledger/fabric/protos/discovery"
14+
discprotos "github.com/hyperledger/fabric/protos/discovery"
1515
)
1616

1717
// AccessControlSupport checks if clients are eligible of being serviced
@@ -50,13 +50,19 @@ type GossipSupport interface {
5050
// for chaincodes
5151
type EndorsementSupport interface {
5252
// PeersForEndorsement returns an EndorsementDescriptor for a given set of peers, channel, and chaincode
53-
PeersForEndorsement(channel common.ChainID, interest *discovery2.ChaincodeInterest) (*discovery2.EndorsementDescriptor, error)
53+
PeersForEndorsement(channel common.ChainID, interest *discprotos.ChaincodeInterest) (*discprotos.EndorsementDescriptor, error)
54+
55+
// PeersAuthorizedByCriteria returns the peers of the channel that are authorized by the given chaincode interest
56+
// That is - taking in account if the chaincode(s) in the interest are installed on the peers, and also
57+
// taking in account whether the peers are part of the collections of the chaincodes.
58+
// If a nil interest, or an empty interest is passed - no filtering is done.
59+
PeersAuthorizedByCriteria(chainID common.ChainID, interest *discprotos.ChaincodeInterest) (discovery.Members, error)
5460
}
5561

5662
// ConfigSupport provides access to channel configuration
5763
type ConfigSupport interface {
5864
// Config returns the channel's configuration
59-
Config(channel string) (*discovery2.ConfigResult, error)
65+
Config(channel string) (*discprotos.ConfigResult, error)
6066
}
6167

6268
// Support defines an interface that allows the discovery service

discovery/client/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type ChannelResponse interface {
4040
Config() (*discovery.ConfigResult, error)
4141

4242
// Peers returns a response for a peer membership query, or error if something went wrong
43-
Peers() ([]*Peer, error)
43+
Peers(invocationChain ...*discovery.ChaincodeCall) ([]*Peer, error)
4444

4545
// Endorsers returns the response for an endorser query for a given
4646
// chaincode in a given channel context, or error if something went wrong.

discovery/client/client.go

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"bytes"
1111
"context"
1212
"encoding/json"
13+
"fmt"
1314
"math/rand"
1415
"time"
1516

@@ -105,24 +106,38 @@ func (req *Request) AddLocalPeersQuery() *Request {
105106
req.Queries = append(req.Queries, &discovery.Query{
106107
Query: q,
107108
})
108-
req.addQueryMapping(discovery.LocalMembershipQueryType, "")
109+
var ic InvocationChain
110+
req.addQueryMapping(discovery.LocalMembershipQueryType, channnelAndInvocationChain("", ic))
109111
return req
110112
}
111113

112114
// AddPeersQuery adds to the request a peer query
113-
func (req *Request) AddPeersQuery() *Request {
115+
func (req *Request) AddPeersQuery(invocationChain ...*discovery.ChaincodeCall) *Request {
114116
ch := req.lastChannel
115117
q := &discovery.Query_PeerQuery{
116-
PeerQuery: &discovery.PeerMembershipQuery{},
118+
PeerQuery: &discovery.PeerMembershipQuery{
119+
Filter: &discovery.ChaincodeInterest{
120+
Chaincodes: invocationChain,
121+
},
122+
},
117123
}
118124
req.Queries = append(req.Queries, &discovery.Query{
119125
Channel: ch,
120126
Query: q,
121127
})
122-
req.addQueryMapping(discovery.PeerMembershipQueryType, ch)
128+
var ic InvocationChain
129+
if len(invocationChain) > 0 {
130+
ic = InvocationChain(invocationChain)
131+
}
132+
req.addChaincodeQueryMapping([]InvocationChain{ic})
133+
req.addQueryMapping(discovery.PeerMembershipQueryType, channnelAndInvocationChain(ch, ic))
123134
return req
124135
}
125136

137+
func channnelAndInvocationChain(ch string, ic InvocationChain) string {
138+
return fmt.Sprintf("%s %s", ch, ic.String())
139+
}
140+
126141
// OfChannel sets the next queries added to be in the given channel's context
127142
func (req *Request) OfChannel(ch string) *Request {
128143
req.lastChannel = ch
@@ -204,7 +219,7 @@ type channelResponse struct {
204219
func (cr *channelResponse) Config() (*discovery.ConfigResult, error) {
205220
res, exists := cr.response[key{
206221
queryType: discovery.ConfigQueryType,
207-
channel: cr.channel,
222+
k: cr.channel,
208223
}]
209224

210225
if !exists {
@@ -218,11 +233,12 @@ func (cr *channelResponse) Config() (*discovery.ConfigResult, error) {
218233
return nil, res.(error)
219234
}
220235

221-
func parsePeers(queryType discovery.QueryType, r response, channel string) ([]*Peer, error) {
222-
res, exists := r[key{
236+
func parsePeers(queryType discovery.QueryType, r response, channel string, invocationChain ...*discovery.ChaincodeCall) ([]*Peer, error) {
237+
peerKeys := key{
223238
queryType: queryType,
224-
channel: channel,
225-
}]
239+
k: fmt.Sprintf("%s %s", channel, InvocationChain(invocationChain).String()),
240+
}
241+
res, exists := r[peerKeys]
226242

227243
if !exists {
228244
return nil, ErrNotFound
@@ -235,24 +251,24 @@ func parsePeers(queryType discovery.QueryType, r response, channel string) ([]*P
235251
return nil, res.(error)
236252
}
237253

238-
func (cr *channelResponse) Peers() ([]*Peer, error) {
239-
return parsePeers(discovery.PeerMembershipQueryType, cr.response, cr.channel)
254+
func (cr *channelResponse) Peers(invocationChain ...*discovery.ChaincodeCall) ([]*Peer, error) {
255+
return parsePeers(discovery.PeerMembershipQueryType, cr.response, cr.channel, invocationChain...)
240256
}
241257

242258
func (cr *channelResponse) Endorsers(invocationChain InvocationChain, ps PrioritySelector, ef ExclusionFilter) (Endorsers, error) {
243259
// If we have a key that has no chaincode field,
244260
// it means it's an error returned from the service
245261
if err, exists := cr.response[key{
246262
queryType: discovery.ChaincodeQueryType,
247-
channel: cr.channel,
263+
k: cr.channel,
248264
}]; exists {
249265
return nil, err.(error)
250266
}
251267

252268
// Else, the service returned a response that isn't an error
253269
res, exists := cr.response[key{
254270
queryType: discovery.ChaincodeQueryType,
255-
channel: cr.channel,
271+
k: cr.channel,
256272
invocationChain: invocationChain.String(),
257273
}]
258274

@@ -306,7 +322,7 @@ func (resp response) ForChannel(ch string) ChannelResponse {
306322

307323
type key struct {
308324
queryType discovery.QueryType
309-
channel string
325+
k string
310326
invocationChain string
311327
}
312328

@@ -318,7 +334,7 @@ func (req *Request) computeResponse(r *discovery.Response) (response, error) {
318334
case discovery.ConfigQueryType:
319335
err = resp.mapConfig(channel2index, r)
320336
case discovery.ChaincodeQueryType:
321-
err = resp.mapEndorsers(channel2index, r, req.queryMapping, req.invocationChainMapping)
337+
err = resp.mapEndorsers(channel2index, r, req.invocationChainMapping)
322338
case discovery.PeerMembershipQueryType:
323339
err = resp.mapPeerMembership(channel2index, r, discovery.PeerMembershipQueryType)
324340
case discovery.LocalMembershipQueryType:
@@ -340,7 +356,7 @@ func (resp response) mapConfig(channel2index map[string]int, r *discovery.Respon
340356
}
341357
key := key{
342358
queryType: discovery.ConfigQueryType,
343-
channel: ch,
359+
k: ch,
344360
}
345361

346362
if err != nil {
@@ -353,15 +369,16 @@ func (resp response) mapConfig(channel2index map[string]int, r *discovery.Respon
353369
return nil
354370
}
355371

356-
func (resp response) mapPeerMembership(channel2index map[string]int, r *discovery.Response, qt discovery.QueryType) error {
357-
for ch, index := range channel2index {
372+
func (resp response) mapPeerMembership(key2Index map[string]int, r *discovery.Response, qt discovery.QueryType) error {
373+
for k, index := range key2Index {
358374
membersRes, err := r.MembershipAt(index)
359375
if membersRes == nil && err == nil {
360376
return errors.Errorf("expected QueryResult of either PeerMembershipResult or Error but got %v instead", r.Results[index])
361377
}
378+
362379
key := key{
363380
queryType: qt,
364-
channel: ch,
381+
k: k,
365382
}
366383

367384
if err != nil {
@@ -418,7 +435,6 @@ func isStateInfoExpected(qt discovery.QueryType) bool {
418435
func (resp response) mapEndorsers(
419436
channel2index map[string]int,
420437
r *discovery.Response,
421-
queryMapping map[discovery.QueryType]map[string]int,
422438
chaincodeQueryMapping map[int][]InvocationChain) error {
423439
for ch, index := range channel2index {
424440
ccQueryRes, err := r.EndorsersAt(index)
@@ -429,7 +445,7 @@ func (resp response) mapEndorsers(
429445
if err != nil {
430446
key := key{
431447
queryType: discovery.ChaincodeQueryType,
432-
channel: ch,
448+
k: ch,
433449
}
434450
resp[key] = errors.New(err.Content)
435451
continue
@@ -453,7 +469,7 @@ func (resp response) mapEndorsersOfChannel(ccRs *discovery.ChaincodeQueryResult,
453469
}
454470
key := key{
455471
queryType: discovery.ChaincodeQueryType,
456-
channel: channel,
472+
k: channel,
457473
invocationChain: invocationChain[i].String(),
458474
}
459475

discovery/client/client_test.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
"github.com/hyperledger/fabric/discovery/endorsement"
2828
"github.com/hyperledger/fabric/gossip/api"
2929
gossipcommon "github.com/hyperledger/fabric/gossip/common"
30-
discovery3 "github.com/hyperledger/fabric/gossip/discovery"
30+
gdisc "github.com/hyperledger/fabric/gossip/discovery"
3131
"github.com/hyperledger/fabric/protos/common"
3232
"github.com/hyperledger/fabric/protos/discovery"
3333
"github.com/hyperledger/fabric/protos/gossip"
@@ -101,7 +101,7 @@ var (
101101
},
102102
}
103103

104-
channelPeersWithChaincodes = discovery3.Members{
104+
channelPeersWithChaincodes = gdisc.Members{
105105
newPeer(0, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
106106
newPeer(1, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
107107
newPeer(2, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
@@ -112,7 +112,7 @@ var (
112112
newPeer(7, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember,
113113
}
114114

115-
channelPeersWithoutChaincodes = discovery3.Members{
115+
channelPeersWithoutChaincodes = gdisc.Members{
116116
newPeer(0, stateInfoMessage(), nil).NetworkMember,
117117
newPeer(1, stateInfoMessage(), nil).NetworkMember,
118118
newPeer(2, stateInfoMessage(), nil).NetworkMember,
@@ -123,7 +123,7 @@ var (
123123
newPeer(7, stateInfoMessage(), nil).NetworkMember,
124124
}
125125

126-
membershipPeers = discovery3.Members{
126+
membershipPeers = gdisc.Members{
127127
newPeer(0, aliveMessage(0), nil).NetworkMember,
128128
newPeer(1, aliveMessage(1), nil).NetworkMember,
129129
newPeer(2, aliveMessage(2), nil).NetworkMember,
@@ -422,6 +422,22 @@ func TestClient(t *testing.T) {
422422
assert.Contains(t, expectedOrgCombinations2, getMSPs(endorsers))
423423
})
424424

425+
t.Run("Peer membership query with collections and chaincodes", func(t *testing.T) {
426+
sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Once()
427+
interest := ccCall("mycc2")
428+
interest[0].CollectionNames = append(interest[0].CollectionNames, "col")
429+
req = NewRequest().OfChannel("mychannel").AddPeersQuery(interest...)
430+
r, err = cl.Send(ctx, req, authInfo)
431+
assert.NoError(t, err)
432+
mychannel := r.ForChannel("mychannel")
433+
peers, err := mychannel.Peers(interest...)
434+
assert.NoError(t, err)
435+
// We should see all peers that aren't in ORG A since it's not part of the collection
436+
for _, p := range peers {
437+
assert.NotEqual(t, "A", p.MSPID)
438+
}
439+
assert.Len(t, peers, 6)
440+
})
425441
}
426442

427443
func TestUnableToSign(t *testing.T) {
@@ -692,6 +708,8 @@ func (pf *policyFetcher) PolicyByChaincode(channel string, cc string) policies.I
692708

693709
type endorsementAnalyzer interface {
694710
PeersForEndorsement(chainID gossipcommon.ChainID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error)
711+
712+
PeersAuthorizedByCriteria(chainID gossipcommon.ChainID, interest *discovery.ChaincodeInterest) (gdisc.Members, error)
695713
}
696714

697715
type inquireablePolicy struct {
@@ -734,7 +752,7 @@ func peerIdentity(mspID string, i int) api.PeerIdentityInfo {
734752
type peerInfo struct {
735753
identity api.PeerIdentityType
736754
pkiID gossipcommon.PKIidType
737-
discovery3.NetworkMember
755+
gdisc.NetworkMember
738756
}
739757

740758
func aliveMessage(id int) *gossip.Envelope {
@@ -778,7 +796,7 @@ func newPeer(i int, env *gossip.Envelope, properties *gossip.Properties) *peerIn
778796
return &peerInfo{
779797
pkiID: gossipcommon.PKIidType(p),
780798
identity: api.PeerIdentityType(p),
781-
NetworkMember: discovery3.NetworkMember{
799+
NetworkMember: gdisc.NetworkMember{
782800
PKIid: gossipcommon.PKIidType(p),
783801
Endpoint: p,
784802
InternalEndpoint: p,
@@ -808,18 +826,22 @@ func (*mockSupport) ChannelExists(channel string) bool {
808826
return true
809827
}
810828

811-
func (ms *mockSupport) PeersOfChannel(gossipcommon.ChainID) discovery3.Members {
812-
return ms.Called().Get(0).(discovery3.Members)
829+
func (ms *mockSupport) PeersOfChannel(gossipcommon.ChainID) gdisc.Members {
830+
return ms.Called().Get(0).(gdisc.Members)
813831
}
814832

815-
func (ms *mockSupport) Peers() discovery3.Members {
816-
return ms.Called().Get(0).(discovery3.Members)
833+
func (ms *mockSupport) Peers() gdisc.Members {
834+
return ms.Called().Get(0).(gdisc.Members)
817835
}
818836

819837
func (ms *mockSupport) PeersForEndorsement(channel gossipcommon.ChainID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) {
820838
return ms.endorsementAnalyzer.PeersForEndorsement(channel, interest)
821839
}
822840

841+
func (ms *mockSupport) PeersAuthorizedByCriteria(channel gossipcommon.ChainID, interest *discovery.ChaincodeInterest) (gdisc.Members, error) {
842+
return ms.endorsementAnalyzer.PeersAuthorizedByCriteria(channel, interest)
843+
}
844+
823845
func (*mockSupport) EligibleForService(channel string, data common.SignedData) error {
824846
return nil
825847
}

discovery/cmd/mocks/channel_response.go

Lines changed: 14 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

discovery/cmd/mocks/command_registrar.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

discovery/cmd/mocks/local_response.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

discovery/cmd/mocks/response_parser.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

discovery/cmd/mocks/service_response.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)