github.com/lzy4123/fabric@v2.1.1+incompatible/discovery/client/client.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package discovery 8 9 import ( 10 "context" 11 "encoding/json" 12 "fmt" 13 "math/rand" 14 "time" 15 16 "github.com/golang/protobuf/proto" 17 "github.com/hyperledger/fabric-protos-go/discovery" 18 "github.com/hyperledger/fabric-protos-go/msp" 19 "github.com/hyperledger/fabric/discovery/protoext" 20 gprotoext "github.com/hyperledger/fabric/gossip/protoext" 21 "github.com/pkg/errors" 22 ) 23 24 var configTypes = []protoext.QueryType{ 25 protoext.ConfigQueryType, 26 protoext.PeerMembershipQueryType, 27 protoext.ChaincodeQueryType, 28 protoext.LocalMembershipQueryType, 29 } 30 31 // Client interacts with the discovery server 32 type Client struct { 33 createConnection Dialer 34 signRequest Signer 35 } 36 37 // NewRequest creates a new request 38 func NewRequest() *Request { 39 r := &Request{ 40 invocationChainMapping: make(map[int][]InvocationChain), 41 queryMapping: make(map[protoext.QueryType]map[string]int), 42 Request: &discovery.Request{}, 43 } 44 // pre-populate types 45 for _, queryType := range configTypes { 46 r.queryMapping[queryType] = make(map[string]int) 47 } 48 return r 49 } 50 51 // Request aggregates several queries inside it 52 type Request struct { 53 lastChannel string 54 lastIndex int 55 // map from query type to channel to expected index in response 56 queryMapping map[protoext.QueryType]map[string]int 57 // map from expected index in response to invocation chains 58 invocationChainMapping map[int][]InvocationChain 59 *discovery.Request 60 } 61 62 // AddConfigQuery adds to the request a config query 63 func (req *Request) AddConfigQuery() *Request { 64 ch := req.lastChannel 65 q := &discovery.Query_ConfigQuery{ 66 ConfigQuery: &discovery.ConfigQuery{}, 67 } 68 req.Queries = append(req.Queries, &discovery.Query{ 69 Channel: ch, 70 Query: q, 71 }) 72 req.addQueryMapping(protoext.ConfigQueryType, ch) 73 return req 74 } 75 76 // AddEndorsersQuery adds to the request a query for given chaincodes 77 // interests are the chaincode interests that the client wants to query for. 78 // All interests for a given channel should be supplied in an aggregated slice 79 func (req *Request) AddEndorsersQuery(interests ...*discovery.ChaincodeInterest) (*Request, error) { 80 if err := validateInterests(interests...); err != nil { 81 return nil, err 82 } 83 ch := req.lastChannel 84 q := &discovery.Query_CcQuery{ 85 CcQuery: &discovery.ChaincodeQuery{ 86 Interests: interests, 87 }, 88 } 89 req.Queries = append(req.Queries, &discovery.Query{ 90 Channel: ch, 91 Query: q, 92 }) 93 var invocationChains []InvocationChain 94 for _, interest := range interests { 95 invocationChains = append(invocationChains, interest.Chaincodes) 96 } 97 req.addChaincodeQueryMapping(invocationChains) 98 req.addQueryMapping(protoext.ChaincodeQueryType, ch) 99 return req, nil 100 } 101 102 // AddLocalPeersQuery adds to the request a local peer query 103 func (req *Request) AddLocalPeersQuery() *Request { 104 q := &discovery.Query_LocalPeers{ 105 LocalPeers: &discovery.LocalPeerQuery{}, 106 } 107 req.Queries = append(req.Queries, &discovery.Query{ 108 Query: q, 109 }) 110 var ic InvocationChain 111 req.addQueryMapping(protoext.LocalMembershipQueryType, channnelAndInvocationChain("", ic)) 112 return req 113 } 114 115 // AddPeersQuery adds to the request a peer query 116 func (req *Request) AddPeersQuery(invocationChain ...*discovery.ChaincodeCall) *Request { 117 ch := req.lastChannel 118 q := &discovery.Query_PeerQuery{ 119 PeerQuery: &discovery.PeerMembershipQuery{ 120 Filter: &discovery.ChaincodeInterest{ 121 Chaincodes: invocationChain, 122 }, 123 }, 124 } 125 req.Queries = append(req.Queries, &discovery.Query{ 126 Channel: ch, 127 Query: q, 128 }) 129 var ic InvocationChain 130 if len(invocationChain) > 0 { 131 ic = InvocationChain(invocationChain) 132 } 133 req.addChaincodeQueryMapping([]InvocationChain{ic}) 134 req.addQueryMapping(protoext.PeerMembershipQueryType, channnelAndInvocationChain(ch, ic)) 135 return req 136 } 137 138 func channnelAndInvocationChain(ch string, ic InvocationChain) string { 139 return fmt.Sprintf("%s %s", ch, ic.String()) 140 } 141 142 // OfChannel sets the next queries added to be in the given channel's context 143 func (req *Request) OfChannel(ch string) *Request { 144 req.lastChannel = ch 145 return req 146 } 147 148 func (req *Request) addChaincodeQueryMapping(invocationChains []InvocationChain) { 149 req.invocationChainMapping[req.lastIndex] = invocationChains 150 } 151 152 func (req *Request) addQueryMapping(queryType protoext.QueryType, key string) { 153 req.queryMapping[queryType][key] = req.lastIndex 154 req.lastIndex++ 155 } 156 157 // Send sends the request and returns the response, or error on failure 158 func (c *Client) Send(ctx context.Context, req *Request, auth *discovery.AuthInfo) (Response, error) { 159 reqToBeSent := *req.Request 160 reqToBeSent.Authentication = auth 161 payload, err := proto.Marshal(&reqToBeSent) 162 if err != nil { 163 return nil, errors.Wrap(err, "failed marshaling Request to bytes") 164 } 165 166 sig, err := c.signRequest(payload) 167 if err != nil { 168 return nil, errors.Wrap(err, "failed signing Request") 169 } 170 171 conn, err := c.createConnection() 172 if err != nil { 173 return nil, errors.Wrap(err, "failed connecting to discovery service") 174 } 175 176 cl := discovery.NewDiscoveryClient(conn) 177 resp, err := cl.Discover(ctx, &discovery.SignedRequest{ 178 Payload: payload, 179 Signature: sig, 180 }) 181 if err != nil { 182 return nil, errors.Wrap(err, "discovery service refused our Request") 183 } 184 if n := len(resp.Results); n != req.lastIndex { 185 return nil, errors.Errorf("Sent %d queries but received %d responses back", req.lastIndex, n) 186 } 187 return req.computeResponse(resp) 188 } 189 190 type resultOrError interface { 191 } 192 193 type response map[key]resultOrError 194 195 type localResponse struct { 196 response 197 } 198 199 func (cr *localResponse) Peers() ([]*Peer, error) { 200 return parsePeers(protoext.LocalMembershipQueryType, cr.response, "") 201 } 202 203 type channelResponse struct { 204 response 205 channel string 206 } 207 208 func (cr *channelResponse) Config() (*discovery.ConfigResult, error) { 209 res, exists := cr.response[key{ 210 queryType: protoext.ConfigQueryType, 211 k: cr.channel, 212 }] 213 214 if !exists { 215 return nil, ErrNotFound 216 } 217 218 if config, isConfig := res.(*discovery.ConfigResult); isConfig { 219 return config, nil 220 } 221 222 return nil, res.(error) 223 } 224 225 func parsePeers(queryType protoext.QueryType, r response, channel string, invocationChain ...*discovery.ChaincodeCall) ([]*Peer, error) { 226 peerKeys := key{ 227 queryType: queryType, 228 k: fmt.Sprintf("%s %s", channel, InvocationChain(invocationChain).String()), 229 } 230 res, exists := r[peerKeys] 231 232 if !exists { 233 return nil, ErrNotFound 234 } 235 236 if peers, isPeers := res.([]*Peer); isPeers { 237 return peers, nil 238 } 239 240 return nil, res.(error) 241 } 242 243 func (cr *channelResponse) Peers(invocationChain ...*discovery.ChaincodeCall) ([]*Peer, error) { 244 return parsePeers(protoext.PeerMembershipQueryType, cr.response, cr.channel, invocationChain...) 245 } 246 247 func (cr *channelResponse) Endorsers(invocationChain InvocationChain, f Filter) (Endorsers, error) { 248 // If we have a key that has no chaincode field, 249 // it means it's an error returned from the service 250 if err, exists := cr.response[key{ 251 queryType: protoext.ChaincodeQueryType, 252 k: cr.channel, 253 }]; exists { 254 return nil, err.(error) 255 } 256 257 // Else, the service returned a response that isn't an error 258 res, exists := cr.response[key{ 259 queryType: protoext.ChaincodeQueryType, 260 k: cr.channel, 261 invocationChain: invocationChain.String(), 262 }] 263 264 if !exists { 265 return nil, ErrNotFound 266 } 267 268 desc := res.(*endorsementDescriptor) 269 rand.Seed(time.Now().Unix()) 270 // We iterate over all layouts to find one that we have enough peers to select 271 for _, index := range rand.Perm(len(desc.layouts)) { 272 layout := desc.layouts[index] 273 endorsers, canLayoutBeSatisfied := selectPeersForLayout(desc.endorsersByGroups, layout, f) 274 if canLayoutBeSatisfied { 275 return endorsers, nil 276 } 277 } 278 return nil, errors.New("no endorsement combination can be satisfied") 279 } 280 281 type filter struct { 282 ef ExclusionFilter 283 ps PrioritySelector 284 } 285 286 // NewFilter returns an endorser filter that uses the given exclusion filter and priority selector 287 // to filter and sort the endorsers 288 func NewFilter(ps PrioritySelector, ef ExclusionFilter) Filter { 289 return &filter{ 290 ef: ef, 291 ps: ps, 292 } 293 } 294 295 // Filter returns a filtered and sorted list of endorsers 296 func (f *filter) Filter(endorsers Endorsers) Endorsers { 297 return endorsers.Shuffle().Filter(f.ef).Sort(f.ps) 298 } 299 300 // NoFilter returns a noop Filter 301 var NoFilter = NewFilter(NoPriorities, NoExclusion) 302 303 func selectPeersForLayout(endorsersByGroups map[string][]*Peer, layout map[string]int, f Filter) (Endorsers, bool) { 304 var endorsers []*Peer 305 for grp, count := range layout { 306 endorsersOfGrp := f.Filter(Endorsers(endorsersByGroups[grp])) 307 308 // We couldn't select enough peers for this layout because the current group 309 // requires more peers than we have available to be selected 310 if len(endorsersOfGrp) < count { 311 return nil, false 312 } 313 endorsersOfGrp = endorsersOfGrp[:count] 314 endorsers = append(endorsers, endorsersOfGrp...) 315 } 316 // The current (randomly chosen) layout can be satisfied, so return it 317 // instead of checking the next one. 318 return endorsers, true 319 } 320 321 func (resp response) ForLocal() LocalResponse { 322 return &localResponse{ 323 response: resp, 324 } 325 } 326 327 func (resp response) ForChannel(ch string) ChannelResponse { 328 return &channelResponse{ 329 channel: ch, 330 response: resp, 331 } 332 } 333 334 type key struct { 335 queryType protoext.QueryType 336 k string 337 invocationChain string 338 } 339 340 func (req *Request) computeResponse(r *discovery.Response) (response, error) { 341 var err error 342 resp := make(response) 343 for configType, channel2index := range req.queryMapping { 344 switch configType { 345 case protoext.ConfigQueryType: 346 err = resp.mapConfig(channel2index, r) 347 case protoext.ChaincodeQueryType: 348 err = resp.mapEndorsers(channel2index, r, req.invocationChainMapping) 349 case protoext.PeerMembershipQueryType: 350 err = resp.mapPeerMembership(channel2index, r, protoext.PeerMembershipQueryType) 351 case protoext.LocalMembershipQueryType: 352 err = resp.mapPeerMembership(channel2index, r, protoext.LocalMembershipQueryType) 353 } 354 if err != nil { 355 return nil, err 356 } 357 } 358 359 return resp, err 360 } 361 362 func (resp response) mapConfig(channel2index map[string]int, r *discovery.Response) error { 363 for ch, index := range channel2index { 364 config, err := protoext.ResponseConfigAt(r, index) 365 if config == nil && err == nil { 366 return errors.Errorf("expected QueryResult of either ConfigResult or Error but got %v instead", r.Results[index]) 367 } 368 key := key{ 369 queryType: protoext.ConfigQueryType, 370 k: ch, 371 } 372 373 if err != nil { 374 resp[key] = errors.New(err.Content) 375 continue 376 } 377 378 resp[key] = config 379 } 380 return nil 381 } 382 383 func (resp response) mapPeerMembership(key2Index map[string]int, r *discovery.Response, qt protoext.QueryType) error { 384 for k, index := range key2Index { 385 membersRes, err := protoext.ResponseMembershipAt(r, index) 386 if membersRes == nil && err == nil { 387 return errors.Errorf("expected QueryResult of either PeerMembershipResult or Error but got %v instead", r.Results[index]) 388 } 389 390 key := key{ 391 queryType: qt, 392 k: k, 393 } 394 395 if err != nil { 396 resp[key] = errors.New(err.Content) 397 continue 398 } 399 400 peers, err2 := peersForChannel(membersRes, qt) 401 if err2 != nil { 402 return errors.Wrap(err2, "failed constructing peer membership out of response") 403 } 404 405 resp[key] = peers 406 } 407 return nil 408 } 409 410 func peersForChannel(membersRes *discovery.PeerMembershipResult, qt protoext.QueryType) ([]*Peer, error) { 411 var peers []*Peer 412 for org, peersOfCurrentOrg := range membersRes.PeersByOrg { 413 for _, peer := range peersOfCurrentOrg.Peers { 414 aliveMsg, err := gprotoext.EnvelopeToGossipMessage(peer.MembershipInfo) 415 if err != nil { 416 return nil, errors.Wrap(err, "failed unmarshaling alive message") 417 } 418 var stateInfoMsg *gprotoext.SignedGossipMessage 419 if isStateInfoExpected(qt) { 420 stateInfoMsg, err = gprotoext.EnvelopeToGossipMessage(peer.StateInfo) 421 if err != nil { 422 return nil, errors.Wrap(err, "failed unmarshaling stateInfo message") 423 } 424 if err := validateStateInfoMessage(stateInfoMsg); err != nil { 425 return nil, errors.Wrap(err, "failed validating stateInfo message") 426 } 427 } 428 if err := validateAliveMessage(aliveMsg); err != nil { 429 return nil, errors.Wrap(err, "failed validating alive message") 430 } 431 peers = append(peers, &Peer{ 432 MSPID: org, 433 Identity: peer.Identity, 434 AliveMessage: aliveMsg, 435 StateInfoMessage: stateInfoMsg, 436 }) 437 } 438 } 439 return peers, nil 440 } 441 442 func isStateInfoExpected(qt protoext.QueryType) bool { 443 return qt != protoext.LocalMembershipQueryType 444 } 445 446 func (resp response) mapEndorsers( 447 channel2index map[string]int, 448 r *discovery.Response, 449 chaincodeQueryMapping map[int][]InvocationChain) error { 450 for ch, index := range channel2index { 451 ccQueryRes, err := protoext.ResponseEndorsersAt(r, index) 452 if ccQueryRes == nil && err == nil { 453 return errors.Errorf("expected QueryResult of either ChaincodeQueryResult or Error but got %v instead", r.Results[index]) 454 } 455 456 if err != nil { 457 key := key{ 458 queryType: protoext.ChaincodeQueryType, 459 k: ch, 460 } 461 resp[key] = errors.New(err.Content) 462 continue 463 } 464 465 if err := resp.mapEndorsersOfChannel(ccQueryRes, ch, chaincodeQueryMapping[index]); err != nil { 466 return errors.Wrapf(err, "failed assembling endorsers of channel %s", ch) 467 } 468 } 469 return nil 470 } 471 472 func (resp response) mapEndorsersOfChannel(ccRs *discovery.ChaincodeQueryResult, channel string, invocationChain []InvocationChain) error { 473 if len(ccRs.Content) < len(invocationChain) { 474 return errors.Errorf("expected %d endorsement descriptors but got only %d", len(invocationChain), len(ccRs.Content)) 475 } 476 for i, desc := range ccRs.Content { 477 expectedCCName := invocationChain[i][0].Name 478 if desc.Chaincode != expectedCCName { 479 return errors.Errorf("expected chaincode %s but got endorsement descriptor for %s", expectedCCName, desc.Chaincode) 480 } 481 key := key{ 482 queryType: protoext.ChaincodeQueryType, 483 k: channel, 484 invocationChain: invocationChain[i].String(), 485 } 486 487 descriptor, err := resp.createEndorsementDescriptor(desc, channel) 488 if err != nil { 489 return err 490 } 491 resp[key] = descriptor 492 } 493 494 return nil 495 } 496 497 func (resp response) createEndorsementDescriptor(desc *discovery.EndorsementDescriptor, channel string) (*endorsementDescriptor, error) { 498 descriptor := &endorsementDescriptor{ 499 layouts: []map[string]int{}, 500 endorsersByGroups: make(map[string][]*Peer), 501 } 502 for _, l := range desc.Layouts { 503 currentLayout := make(map[string]int) 504 descriptor.layouts = append(descriptor.layouts, currentLayout) 505 for grp, count := range l.QuantitiesByGroup { 506 if _, exists := desc.EndorsersByGroups[grp]; !exists { 507 return nil, errors.Errorf("group %s isn't mapped to endorsers, but exists in a layout", grp) 508 } 509 currentLayout[grp] = int(count) 510 } 511 } 512 513 for grp, peers := range desc.EndorsersByGroups { 514 var endorsers []*Peer 515 for _, p := range peers.Peers { 516 peer, err := endorser(p, desc.Chaincode, channel) 517 if err != nil { 518 return nil, errors.Wrap(err, "failed creating endorser object") 519 } 520 endorsers = append(endorsers, peer) 521 } 522 descriptor.endorsersByGroups[grp] = endorsers 523 } 524 525 return descriptor, nil 526 } 527 528 func endorser(peer *discovery.Peer, chaincode, channel string) (*Peer, error) { 529 if peer.MembershipInfo == nil || peer.StateInfo == nil { 530 return nil, errors.Errorf("received empty envelope(s) for endorsers for chaincode %s, channel %s", chaincode, channel) 531 } 532 aliveMsg, err := gprotoext.EnvelopeToGossipMessage(peer.MembershipInfo) 533 if err != nil { 534 return nil, errors.Wrap(err, "failed unmarshaling gossip envelope to alive message") 535 } 536 stateInfMsg, err := gprotoext.EnvelopeToGossipMessage(peer.StateInfo) 537 if err != nil { 538 return nil, errors.Wrap(err, "failed unmarshaling gossip envelope to state info message") 539 } 540 if err := validateAliveMessage(aliveMsg); err != nil { 541 return nil, errors.Wrap(err, "failed validating alive message") 542 } 543 if err := validateStateInfoMessage(stateInfMsg); err != nil { 544 return nil, errors.Wrap(err, "failed validating stateInfo message") 545 } 546 sID := &msp.SerializedIdentity{} 547 if err := proto.Unmarshal(peer.Identity, sID); err != nil { 548 return nil, errors.Wrap(err, "failed unmarshaling peer's identity") 549 } 550 return &Peer{ 551 Identity: peer.Identity, 552 StateInfoMessage: stateInfMsg, 553 AliveMessage: aliveMsg, 554 MSPID: sID.Mspid, 555 }, nil 556 } 557 558 type endorsementDescriptor struct { 559 endorsersByGroups map[string][]*Peer 560 layouts []map[string]int 561 } 562 563 // NewClient creates a new Client instance 564 func NewClient(createConnection Dialer, s Signer, signerCacheSize uint) *Client { 565 return &Client{ 566 createConnection: createConnection, 567 signRequest: NewMemoizeSigner(s, signerCacheSize).Sign, 568 } 569 } 570 571 func validateAliveMessage(message *gprotoext.SignedGossipMessage) error { 572 am := message.GetAliveMsg() 573 if am == nil { 574 return errors.New("message isn't an alive message") 575 } 576 m := am.Membership 577 if m == nil { 578 return errors.New("membership is empty") 579 } 580 if am.Timestamp == nil { 581 return errors.New("timestamp is nil") 582 } 583 return nil 584 } 585 586 func validateStateInfoMessage(message *gprotoext.SignedGossipMessage) error { 587 si := message.GetStateInfo() 588 if si == nil { 589 return errors.New("message isn't a stateInfo message") 590 } 591 if si.Timestamp == nil { 592 return errors.New("timestamp is nil") 593 } 594 if si.Properties == nil { 595 return errors.New("properties is nil") 596 } 597 return nil 598 } 599 600 func validateInterests(interests ...*discovery.ChaincodeInterest) error { 601 if len(interests) == 0 { 602 return errors.New("no chaincode interests given") 603 } 604 for _, interest := range interests { 605 if interest == nil { 606 return errors.New("chaincode interest is nil") 607 } 608 if err := InvocationChain(interest.Chaincodes).ValidateInvocationChain(); err != nil { 609 return err 610 } 611 } 612 return nil 613 } 614 615 // InvocationChain aggregates ChaincodeCalls 616 type InvocationChain []*discovery.ChaincodeCall 617 618 // String returns a string representation of this invocation chain 619 func (ic InvocationChain) String() string { 620 s, _ := json.Marshal(ic) 621 return string(s) 622 } 623 624 // ValidateInvocationChain validates the InvocationChain's structure 625 func (ic InvocationChain) ValidateInvocationChain() error { 626 if len(ic) == 0 { 627 return errors.New("invocation chain should not be empty") 628 } 629 for _, cc := range ic { 630 if cc.Name == "" { 631 return errors.New("chaincode name should not be empty") 632 } 633 } 634 return nil 635 }