github.com/defanghe/fabric@v2.1.1+incompatible/discovery/client/client_test.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 "crypto/tls" 12 "crypto/x509" 13 "fmt" 14 "io/ioutil" 15 "net" 16 "path/filepath" 17 "strconv" 18 "testing" 19 "time" 20 21 "github.com/golang/protobuf/proto" 22 "github.com/hyperledger/fabric-protos-go/common" 23 "github.com/hyperledger/fabric-protos-go/discovery" 24 "github.com/hyperledger/fabric-protos-go/gossip" 25 "github.com/hyperledger/fabric-protos-go/msp" 26 "github.com/hyperledger/fabric-protos-go/peer" 27 "github.com/hyperledger/fabric/common/chaincode" 28 "github.com/hyperledger/fabric/common/policies" 29 "github.com/hyperledger/fabric/common/policydsl" 30 "github.com/hyperledger/fabric/common/util" 31 fabricdisc "github.com/hyperledger/fabric/discovery" 32 "github.com/hyperledger/fabric/discovery/endorsement" 33 "github.com/hyperledger/fabric/gossip/api" 34 gossipcommon "github.com/hyperledger/fabric/gossip/common" 35 gdisc "github.com/hyperledger/fabric/gossip/discovery" 36 "github.com/hyperledger/fabric/gossip/protoext" 37 "github.com/hyperledger/fabric/internal/pkg/comm" 38 "github.com/hyperledger/fabric/protoutil" 39 "github.com/pkg/errors" 40 "github.com/stretchr/testify/assert" 41 "github.com/stretchr/testify/mock" 42 "google.golang.org/grpc" 43 "google.golang.org/grpc/credentials" 44 ) 45 46 const ( 47 signerCacheSize uint = 1 48 ) 49 50 var ( 51 ctx = context.Background() 52 53 orgCombinationsThatSatisfyPolicy = [][]string{ 54 {"A", "B"}, {"C"}, {"A", "D"}, 55 } 56 57 orgCombinationsThatSatisfyPolicy2 = [][]string{ 58 {"B", "D"}, 59 } 60 61 expectedOrgCombinations = []map[string]struct{}{ 62 { 63 "A": {}, 64 "B": {}, 65 }, 66 { 67 "C": {}, 68 }, 69 { 70 "A": {}, 71 "D": {}, 72 }, 73 } 74 75 expectedOrgCombinations2 = []map[string]struct{}{ 76 { 77 "B": {}, 78 "C": {}, 79 "D": {}, 80 }, 81 } 82 83 cc = &gossip.Chaincode{ 84 Name: "mycc", 85 Version: "1.0", 86 } 87 88 cc2 = &gossip.Chaincode{ 89 Name: "mycc2", 90 Version: "1.0", 91 } 92 93 cc3 = &gossip.Chaincode{ 94 Name: "mycc3", 95 Version: "1.0", 96 } 97 98 propertiesWithChaincodes = &gossip.Properties{ 99 Chaincodes: []*gossip.Chaincode{cc, cc2, cc3}, 100 } 101 102 expectedConf = &discovery.ConfigResult{ 103 Msps: map[string]*msp.FabricMSPConfig{ 104 "A": {}, 105 "B": {}, 106 "C": {}, 107 "D": {}, 108 }, 109 Orderers: map[string]*discovery.Endpoints{ 110 "A": {}, 111 "B": {}, 112 }, 113 } 114 115 channelPeersWithChaincodes = gdisc.Members{ 116 newPeer(0, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember, 117 newPeer(1, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember, 118 newPeer(2, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember, 119 newPeer(3, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember, 120 newPeer(4, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember, 121 newPeer(5, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember, 122 newPeer(6, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember, 123 newPeer(7, stateInfoMessage(cc, cc2), propertiesWithChaincodes).NetworkMember, 124 } 125 126 channelPeersWithoutChaincodes = gdisc.Members{ 127 newPeer(0, stateInfoMessage(), nil).NetworkMember, 128 newPeer(1, stateInfoMessage(), nil).NetworkMember, 129 newPeer(2, stateInfoMessage(), nil).NetworkMember, 130 newPeer(3, stateInfoMessage(), nil).NetworkMember, 131 newPeer(4, stateInfoMessage(), nil).NetworkMember, 132 newPeer(5, stateInfoMessage(), nil).NetworkMember, 133 newPeer(6, stateInfoMessage(), nil).NetworkMember, 134 newPeer(7, stateInfoMessage(), nil).NetworkMember, 135 } 136 137 channelPeersWithDifferentLedgerHeights = gdisc.Members{ 138 newPeer(0, stateInfoMessageWithHeight(100, cc3), propertiesWithChaincodes).NetworkMember, 139 newPeer(1, stateInfoMessageWithHeight(106, cc3), propertiesWithChaincodes).NetworkMember, 140 newPeer(2, stateInfoMessageWithHeight(107, cc3), propertiesWithChaincodes).NetworkMember, 141 newPeer(3, stateInfoMessageWithHeight(108, cc3), propertiesWithChaincodes).NetworkMember, 142 newPeer(4, stateInfoMessageWithHeight(101, cc3), propertiesWithChaincodes).NetworkMember, 143 newPeer(5, stateInfoMessageWithHeight(108, cc3), propertiesWithChaincodes).NetworkMember, 144 newPeer(6, stateInfoMessageWithHeight(110, cc3), propertiesWithChaincodes).NetworkMember, 145 newPeer(7, stateInfoMessageWithHeight(110, cc3), propertiesWithChaincodes).NetworkMember, 146 newPeer(8, stateInfoMessageWithHeight(100, cc3), propertiesWithChaincodes).NetworkMember, 147 newPeer(9, stateInfoMessageWithHeight(107, cc3), propertiesWithChaincodes).NetworkMember, 148 newPeer(10, stateInfoMessageWithHeight(110, cc3), propertiesWithChaincodes).NetworkMember, 149 newPeer(11, stateInfoMessageWithHeight(111, cc3), propertiesWithChaincodes).NetworkMember, 150 newPeer(12, stateInfoMessageWithHeight(105, cc3), propertiesWithChaincodes).NetworkMember, 151 newPeer(13, stateInfoMessageWithHeight(103, cc3), propertiesWithChaincodes).NetworkMember, 152 newPeer(14, stateInfoMessageWithHeight(109, cc3), propertiesWithChaincodes).NetworkMember, 153 newPeer(15, stateInfoMessageWithHeight(111, cc3), propertiesWithChaincodes).NetworkMember, 154 } 155 156 membershipPeers = gdisc.Members{ 157 newPeer(0, aliveMessage(0), nil).NetworkMember, 158 newPeer(1, aliveMessage(1), nil).NetworkMember, 159 newPeer(2, aliveMessage(2), nil).NetworkMember, 160 newPeer(3, aliveMessage(3), nil).NetworkMember, 161 newPeer(4, aliveMessage(4), nil).NetworkMember, 162 newPeer(5, aliveMessage(5), nil).NetworkMember, 163 newPeer(6, aliveMessage(6), nil).NetworkMember, 164 newPeer(7, aliveMessage(7), nil).NetworkMember, 165 newPeer(8, aliveMessage(8), nil).NetworkMember, 166 newPeer(9, aliveMessage(9), nil).NetworkMember, 167 newPeer(10, aliveMessage(10), nil).NetworkMember, 168 newPeer(11, aliveMessage(11), nil).NetworkMember, 169 newPeer(12, aliveMessage(12), nil).NetworkMember, 170 newPeer(13, aliveMessage(13), nil).NetworkMember, 171 newPeer(14, aliveMessage(14), nil).NetworkMember, 172 newPeer(15, aliveMessage(15), nil).NetworkMember, 173 } 174 175 peerIdentities = api.PeerIdentitySet{ 176 peerIdentity("A", 0), 177 peerIdentity("A", 1), 178 peerIdentity("B", 2), 179 peerIdentity("B", 3), 180 peerIdentity("C", 4), 181 peerIdentity("C", 5), 182 peerIdentity("D", 6), 183 peerIdentity("D", 7), 184 peerIdentity("A", 8), 185 peerIdentity("A", 9), 186 peerIdentity("B", 10), 187 peerIdentity("B", 11), 188 peerIdentity("C", 12), 189 peerIdentity("C", 13), 190 peerIdentity("D", 14), 191 peerIdentity("D", 15), 192 } 193 194 resultsWithoutEnvelopes = &discovery.QueryResult_CcQueryRes{ 195 CcQueryRes: &discovery.ChaincodeQueryResult{ 196 Content: []*discovery.EndorsementDescriptor{ 197 { 198 Chaincode: "mycc", 199 EndorsersByGroups: map[string]*discovery.Peers{ 200 "A": { 201 Peers: []*discovery.Peer{ 202 {}, 203 }, 204 }, 205 }, 206 Layouts: []*discovery.Layout{ 207 { 208 QuantitiesByGroup: map[string]uint32{}, 209 }, 210 }, 211 }, 212 }, 213 }, 214 } 215 216 resultsWithEnvelopesButWithInsufficientPeers = &discovery.QueryResult_CcQueryRes{ 217 CcQueryRes: &discovery.ChaincodeQueryResult{ 218 Content: []*discovery.EndorsementDescriptor{ 219 { 220 Chaincode: "mycc", 221 EndorsersByGroups: map[string]*discovery.Peers{ 222 "A": { 223 Peers: []*discovery.Peer{ 224 { 225 StateInfo: stateInfoMessage(), 226 MembershipInfo: aliveMessage(0), 227 Identity: peerIdentity("A", 0).Identity, 228 }, 229 }, 230 }, 231 }, 232 Layouts: []*discovery.Layout{ 233 { 234 QuantitiesByGroup: map[string]uint32{ 235 "A": 2, 236 }, 237 }, 238 }, 239 }, 240 }, 241 }, 242 } 243 244 resultsWithEnvelopesButWithMismatchedLayout = &discovery.QueryResult_CcQueryRes{ 245 CcQueryRes: &discovery.ChaincodeQueryResult{ 246 Content: []*discovery.EndorsementDescriptor{ 247 { 248 Chaincode: "mycc", 249 EndorsersByGroups: map[string]*discovery.Peers{ 250 "A": { 251 Peers: []*discovery.Peer{ 252 { 253 StateInfo: stateInfoMessage(), 254 MembershipInfo: aliveMessage(0), 255 Identity: peerIdentity("A", 0).Identity, 256 }, 257 }, 258 }, 259 }, 260 Layouts: []*discovery.Layout{ 261 { 262 QuantitiesByGroup: map[string]uint32{ 263 "B": 2, 264 }, 265 }, 266 }, 267 }, 268 }, 269 }, 270 } 271 ) 272 273 func loadFileOrPanic(file string) []byte { 274 b, err := ioutil.ReadFile(file) 275 if err != nil { 276 panic(err) 277 } 278 return b 279 } 280 281 func createGRPCServer(t *testing.T) *comm.GRPCServer { 282 serverCert := loadFileOrPanic(filepath.Join("testdata", "server", "cert.pem")) 283 serverKey := loadFileOrPanic(filepath.Join("testdata", "server", "key.pem")) 284 s, err := comm.NewGRPCServer("localhost:0", comm.ServerConfig{ 285 SecOpts: comm.SecureOptions{ 286 UseTLS: true, 287 Certificate: serverCert, 288 Key: serverKey, 289 }, 290 }) 291 assert.NoError(t, err) 292 return s 293 } 294 295 func createConnector(t *testing.T, certificate tls.Certificate, targetPort int) func() (*grpc.ClientConn, error) { 296 caCert := loadFileOrPanic(filepath.Join("testdata", "server", "ca.pem")) 297 tlsConf := &tls.Config{ 298 RootCAs: x509.NewCertPool(), 299 Certificates: []tls.Certificate{certificate}, 300 } 301 tlsConf.RootCAs.AppendCertsFromPEM(caCert) 302 303 addr := fmt.Sprintf("localhost:%d", targetPort) 304 return func() (*grpc.ClientConn, error) { 305 conn, err := grpc.Dial(addr, grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(tlsConf))) 306 assert.NoError(t, err) 307 if err != nil { 308 panic(err) 309 } 310 return conn, nil 311 } 312 } 313 314 func createDiscoveryService(sup *mockSupport) discovery.DiscoveryServer { 315 conf := fabricdisc.Config{TLS: true} 316 mdf := &ccMetadataFetcher{} 317 pe := &principalEvaluator{} 318 pf := &policyFetcher{} 319 320 sigPol, _ := policydsl.FromString("OR(AND('A.member', 'B.member'), 'C.member', AND('A.member', 'D.member'))") 321 polBytes, _ := proto.Marshal(sigPol) 322 mdf.On("Metadata", "mycc").Return(&chaincode.Metadata{ 323 Policy: polBytes, 324 Name: "mycc", 325 Version: "1.0", 326 Id: []byte{1, 2, 3}, 327 }) 328 329 pf.On("PoliciesByChaincode", "mycc").Return(&inquireablePolicy{ 330 orgCombinations: orgCombinationsThatSatisfyPolicy, 331 }) 332 333 sigPol, _ = policydsl.FromString("AND('B.member', 'C.member')") 334 polBytes, _ = proto.Marshal(sigPol) 335 mdf.On("Metadata", "mycc2").Return(&chaincode.Metadata{ 336 Policy: polBytes, 337 Name: "mycc2", 338 Version: "1.0", 339 Id: []byte{1, 2, 3}, 340 CollectionsConfig: buildCollectionConfig(map[string][]*msp.MSPPrincipal{ 341 "col": {memberPrincipal("B"), memberPrincipal("C"), memberPrincipal("D")}, 342 }), 343 }) 344 345 pf.On("PoliciesByChaincode", "mycc2").Return(&inquireablePolicy{ 346 orgCombinations: orgCombinationsThatSatisfyPolicy2, 347 }) 348 349 sigPol, _ = policydsl.FromString("AND('A.member', 'B.member', 'C.member', 'D.member')") 350 polBytes, _ = proto.Marshal(sigPol) 351 mdf.On("Metadata", "mycc3").Return(&chaincode.Metadata{ 352 Policy: polBytes, 353 Name: "mycc3", 354 Version: "1.0", 355 Id: []byte{1, 2, 3}, 356 }) 357 358 pf.On("PoliciesByChaincode", "mycc3").Return(&inquireablePolicy{ 359 orgCombinations: [][]string{{"A", "B", "C", "D"}}, 360 }) 361 362 sup.On("Config", "mychannel").Return(expectedConf) 363 sup.On("Peers").Return(membershipPeers) 364 sup.endorsementAnalyzer = endorsement.NewEndorsementAnalyzer(sup, pf, pe, mdf) 365 sup.On("IdentityInfo").Return(peerIdentities) 366 return fabricdisc.NewService(conf, sup) 367 } 368 369 func TestClient(t *testing.T) { 370 clientCert := loadFileOrPanic(filepath.Join("testdata", "client", "cert.pem")) 371 clientKey := loadFileOrPanic(filepath.Join("testdata", "client", "key.pem")) 372 clientTLSCert, err := tls.X509KeyPair(clientCert, clientKey) 373 assert.NoError(t, err) 374 server := createGRPCServer(t) 375 sup := &mockSupport{} 376 service := createDiscoveryService(sup) 377 discovery.RegisterDiscoveryServer(server.Server(), service) 378 go server.Start() 379 380 _, portStr, _ := net.SplitHostPort(server.Address()) 381 port, _ := strconv.ParseInt(portStr, 10, 64) 382 connect := createConnector(t, clientTLSCert, int(port)) 383 384 signer := func(msg []byte) ([]byte, error) { 385 return msg, nil 386 } 387 authInfo := &discovery.AuthInfo{ 388 ClientIdentity: []byte{1, 2, 3}, 389 ClientTlsCertHash: util.ComputeSHA256(clientTLSCert.Certificate[0]), 390 } 391 cl := NewClient(connect, signer, signerCacheSize) 392 393 sup.On("PeersOfChannel").Return(channelPeersWithoutChaincodes).Times(2) 394 req := NewRequest() 395 req.OfChannel("mychannel").AddPeersQuery().AddConfigQuery().AddLocalPeersQuery().AddEndorsersQuery(interest("mycc")) 396 r, err := cl.Send(ctx, req, authInfo) 397 assert.NoError(t, err) 398 399 t.Run("Channel mismatch", func(t *testing.T) { 400 // Check behavior for channels that we didn't query for. 401 fakeChannel := r.ForChannel("fakeChannel") 402 peers, err := fakeChannel.Peers() 403 assert.Equal(t, ErrNotFound, err) 404 assert.Nil(t, peers) 405 406 endorsers, err := fakeChannel.Endorsers(ccCall("mycc"), NoFilter) 407 assert.Equal(t, ErrNotFound, err) 408 assert.Nil(t, endorsers) 409 410 conf, err := fakeChannel.Config() 411 assert.Equal(t, ErrNotFound, err) 412 assert.Nil(t, conf) 413 }) 414 415 t.Run("Peer membership query", func(t *testing.T) { 416 // Check response for the correct channel 417 mychannel := r.ForChannel("mychannel") 418 conf, err := mychannel.Config() 419 assert.NoError(t, err) 420 assert.Equal(t, expectedConf.Msps, conf.Msps) 421 assert.Equal(t, expectedConf.Orderers, conf.Orderers) 422 peers, err := mychannel.Peers() 423 assert.NoError(t, err) 424 // We should see all peers as provided above 425 assert.Len(t, peers, 8) 426 // Check response for peers when doing a local query 427 peers, err = r.ForLocal().Peers() 428 assert.NoError(t, err) 429 assert.Len(t, peers, len(peerIdentities)) 430 }) 431 432 t.Run("Endorser query without chaincode installed", func(t *testing.T) { 433 mychannel := r.ForChannel("mychannel") 434 endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoFilter) 435 // However, since we didn't provide any chaincodes to these peers - the server shouldn't 436 // be able to construct the descriptor. 437 // Just check that the appropriate error is returned, and nothing crashes. 438 assert.Contains(t, err.Error(), "failed constructing descriptor for chaincode") 439 assert.Nil(t, endorsers) 440 }) 441 442 t.Run("Endorser query with chaincodes installed", func(t *testing.T) { 443 // Next, we check the case when the peers publish chaincode for themselves. 444 sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Times(2) 445 req = NewRequest() 446 req.OfChannel("mychannel").AddPeersQuery().AddEndorsersQuery(interest("mycc")) 447 r, err = cl.Send(ctx, req, authInfo) 448 assert.NoError(t, err) 449 450 mychannel := r.ForChannel("mychannel") 451 peers, err := mychannel.Peers() 452 assert.NoError(t, err) 453 assert.Len(t, peers, 8) 454 455 // We should get a valid endorsement descriptor from the service 456 endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoFilter) 457 assert.NoError(t, err) 458 // The combinations of endorsers should be in the expected combinations 459 assert.Contains(t, expectedOrgCombinations, getMSPs(endorsers)) 460 }) 461 462 t.Run("Endorser query with cc2cc and collections", func(t *testing.T) { 463 sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Twice() 464 req = NewRequest() 465 myccOnly := ccCall("mycc") 466 myccAndmycc2 := ccCall("mycc", "mycc2") 467 myccAndmycc2[1].CollectionNames = append(myccAndmycc2[1].CollectionNames, "col") 468 req.OfChannel("mychannel").AddEndorsersQuery(cc2ccInterests(myccAndmycc2, myccOnly)...) 469 r, err = cl.Send(ctx, req, authInfo) 470 assert.NoError(t, err) 471 mychannel := r.ForChannel("mychannel") 472 473 // Check the endorsers for the non cc2cc call 474 endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoFilter) 475 assert.NoError(t, err) 476 assert.Contains(t, expectedOrgCombinations, getMSPs(endorsers)) 477 // Check the endorsers for the cc2cc call with collections 478 call := ccCall("mycc", "mycc2") 479 call[1].CollectionNames = append(call[1].CollectionNames, "col") 480 endorsers, err = mychannel.Endorsers(call, NoFilter) 481 assert.NoError(t, err) 482 assert.Contains(t, expectedOrgCombinations2, getMSPs(endorsers)) 483 }) 484 485 t.Run("Peer membership query with collections and chaincodes", func(t *testing.T) { 486 sup.On("PeersOfChannel").Return(channelPeersWithChaincodes).Once() 487 interest := ccCall("mycc2") 488 interest[0].CollectionNames = append(interest[0].CollectionNames, "col") 489 req = NewRequest().OfChannel("mychannel").AddPeersQuery(interest...) 490 r, err = cl.Send(ctx, req, authInfo) 491 assert.NoError(t, err) 492 mychannel := r.ForChannel("mychannel") 493 peers, err := mychannel.Peers(interest...) 494 assert.NoError(t, err) 495 // We should see all peers that aren't in ORG A since it's not part of the collection 496 for _, p := range peers { 497 assert.NotEqual(t, "A", p.MSPID) 498 } 499 assert.Len(t, peers, 6) 500 }) 501 502 t.Run("Endorser query with PrioritiesByHeight selector", func(t *testing.T) { 503 sup.On("PeersOfChannel").Return(channelPeersWithDifferentLedgerHeights).Twice() 504 req = NewRequest() 505 req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc3")) 506 r, err = cl.Send(ctx, req, authInfo) 507 assert.NoError(t, err) 508 mychannel := r.ForChannel("mychannel") 509 510 // acceptablePeers are the ones at the highest ledger height for each org 511 acceptablePeers := []string{"p5", "p9", "p11", "p15"} 512 used := make(map[string]struct{}) 513 endorsers, err := mychannel.Endorsers(ccCall("mycc3"), NewFilter(PrioritiesByHeight, NoExclusion)) 514 assert.NoError(t, err) 515 names := getNames(endorsers) 516 assert.Subset(t, acceptablePeers, names) 517 for _, name := range names { 518 used[name] = struct{}{} 519 } 520 assert.Equalf(t, len(acceptablePeers), len(used), "expecting each endorser to be returned at least once") 521 }) 522 523 t.Run("Endorser query with custom filter", func(t *testing.T) { 524 sup.On("PeersOfChannel").Return(channelPeersWithDifferentLedgerHeights).Twice() 525 req = NewRequest() 526 req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc3")) 527 r, err = cl.Send(ctx, req, authInfo) 528 assert.NoError(t, err) 529 mychannel := r.ForChannel("mychannel") 530 531 threshold := uint64(3) // Use peers within 3 of the max height of the org peers 532 acceptablePeers := []string{"p1", "p9", "p3", "p5", "p6", "p7", "p10", "p11", "p12", "p14", "p15"} 533 used := make(map[string]struct{}) 534 535 for i := 0; i < 90; i++ { 536 endorsers, err := mychannel.Endorsers(ccCall("mycc3"), &ledgerHeightFilter{threshold: threshold}) 537 assert.NoError(t, err) 538 names := getNames(endorsers) 539 assert.Subset(t, acceptablePeers, names) 540 for _, name := range names { 541 used[name] = struct{}{} 542 } 543 } 544 assert.Equalf(t, len(acceptablePeers), len(used), "expecting each endorser to be returned at least once") 545 546 threshold = 0 // only use the peers at the highest ledger height (same as using the PrioritiesByHeight selector) 547 acceptablePeers = []string{"p5", "p9", "p11", "p15"} 548 used = make(map[string]struct{}) 549 endorsers, err := mychannel.Endorsers(ccCall("mycc3"), &ledgerHeightFilter{threshold: threshold}) 550 assert.NoError(t, err) 551 names := getNames(endorsers) 552 assert.Subset(t, acceptablePeers, names) 553 for _, name := range names { 554 used[name] = struct{}{} 555 } 556 t.Logf("Used peers: %#v\n", used) 557 assert.Equalf(t, len(acceptablePeers), len(used), "expecting each endorser to be returned at least once") 558 }) 559 } 560 561 func TestUnableToSign(t *testing.T) { 562 signer := func(msg []byte) ([]byte, error) { 563 return nil, errors.New("not enough entropy") 564 } 565 failToConnect := func() (*grpc.ClientConn, error) { 566 return nil, nil 567 } 568 authInfo := &discovery.AuthInfo{ 569 ClientIdentity: []byte{1, 2, 3}, 570 } 571 cl := NewClient(failToConnect, signer, signerCacheSize) 572 req := NewRequest() 573 req = req.OfChannel("mychannel") 574 resp, err := cl.Send(ctx, req, authInfo) 575 assert.Nil(t, resp) 576 assert.Contains(t, err.Error(), "not enough entropy") 577 } 578 579 func TestUnableToConnect(t *testing.T) { 580 signer := func(msg []byte) ([]byte, error) { 581 return msg, nil 582 } 583 failToConnect := func() (*grpc.ClientConn, error) { 584 return nil, errors.New("unable to connect") 585 } 586 auth := &discovery.AuthInfo{ 587 ClientIdentity: []byte{1, 2, 3}, 588 } 589 cl := NewClient(failToConnect, signer, signerCacheSize) 590 req := NewRequest() 591 req = req.OfChannel("mychannel") 592 resp, err := cl.Send(ctx, req, auth) 593 assert.Nil(t, resp) 594 assert.Contains(t, err.Error(), "unable to connect") 595 } 596 597 func TestBadResponses(t *testing.T) { 598 signer := func(msg []byte) ([]byte, error) { 599 return msg, nil 600 } 601 svc := newMockDiscoveryService() 602 t.Logf("Started mock discovery service on port %d", svc.port) 603 defer svc.shutdown() 604 605 connect := func() (*grpc.ClientConn, error) { 606 return grpc.Dial(fmt.Sprintf("localhost:%d", svc.port), grpc.WithInsecure()) 607 } 608 609 auth := &discovery.AuthInfo{ 610 ClientIdentity: []byte{1, 2, 3}, 611 } 612 cl := NewClient(connect, signer, signerCacheSize) 613 614 // Scenario I: discovery service sends back an error 615 svc.On("Discover").Return(nil, errors.New("foo")).Once() 616 req := NewRequest() 617 req.OfChannel("mychannel").AddPeersQuery().AddConfigQuery().AddEndorsersQuery(interest("mycc")) 618 r, err := cl.Send(ctx, req, auth) 619 assert.Contains(t, err.Error(), "foo") 620 assert.Nil(t, r) 621 622 // Scenario II: discovery service sends back an empty response 623 svc.On("Discover").Return(&discovery.Response{}, nil).Once() 624 req = NewRequest() 625 req.OfChannel("mychannel").AddPeersQuery().AddConfigQuery().AddEndorsersQuery(interest("mycc")) 626 r, err = cl.Send(ctx, req, auth) 627 assert.Equal(t, "Sent 3 queries but received 0 responses back", err.Error()) 628 assert.Nil(t, r) 629 630 // Scenario III: discovery service sends back a layout for the wrong chaincode 631 svc.On("Discover").Return(&discovery.Response{ 632 Results: []*discovery.QueryResult{ 633 { 634 Result: &discovery.QueryResult_CcQueryRes{ 635 CcQueryRes: &discovery.ChaincodeQueryResult{ 636 Content: []*discovery.EndorsementDescriptor{ 637 { 638 Chaincode: "notmycc", 639 }, 640 }, 641 }, 642 }, 643 }, 644 }, 645 }, nil).Once() 646 req = NewRequest() 647 req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc")) 648 r, err = cl.Send(ctx, req, auth) 649 assert.Nil(t, r) 650 assert.Contains(t, err.Error(), "expected chaincode mycc but got endorsement descriptor for notmycc") 651 652 // Scenario IV: discovery service sends back a layout that has empty envelopes 653 svc.On("Discover").Return(&discovery.Response{ 654 Results: []*discovery.QueryResult{ 655 { 656 Result: resultsWithoutEnvelopes, 657 }, 658 }, 659 }, nil).Once() 660 req = NewRequest() 661 req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc")) 662 r, err = cl.Send(ctx, req, auth) 663 assert.Contains(t, err.Error(), "received empty envelope(s) for endorsers for chaincode mycc") 664 assert.Nil(t, r) 665 666 // Scenario V: discovery service sends back a layout that has a group that requires more 667 // members than are present. 668 svc.On("Discover").Return(&discovery.Response{ 669 Results: []*discovery.QueryResult{ 670 { 671 Result: resultsWithEnvelopesButWithInsufficientPeers, 672 }, 673 }, 674 }, nil).Once() 675 req = NewRequest() 676 req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc")) 677 r, err = cl.Send(ctx, req, auth) 678 assert.NoError(t, err) 679 mychannel := r.ForChannel("mychannel") 680 endorsers, err := mychannel.Endorsers(ccCall("mycc"), NoFilter) 681 assert.Nil(t, endorsers) 682 assert.Contains(t, err.Error(), "no endorsement combination can be satisfied") 683 684 // Scenario VI: discovery service sends back a layout that has a group that doesn't have a matching peer set 685 svc.On("Discover").Return(&discovery.Response{ 686 Results: []*discovery.QueryResult{ 687 { 688 Result: resultsWithEnvelopesButWithMismatchedLayout, 689 }, 690 }, 691 }, nil).Once() 692 req = NewRequest() 693 req.OfChannel("mychannel").AddEndorsersQuery(interest("mycc")) 694 r, err = cl.Send(ctx, req, auth) 695 assert.Contains(t, err.Error(), "group B isn't mapped to endorsers, but exists in a layout") 696 assert.Empty(t, r) 697 } 698 699 func TestAddEndorsersQueryInvalidInput(t *testing.T) { 700 _, err := NewRequest().AddEndorsersQuery() 701 assert.Contains(t, err.Error(), "no chaincode interests given") 702 703 _, err = NewRequest().AddEndorsersQuery(nil) 704 assert.Contains(t, err.Error(), "chaincode interest is nil") 705 706 _, err = NewRequest().AddEndorsersQuery(&discovery.ChaincodeInterest{}) 707 assert.Contains(t, err.Error(), "invocation chain should not be empty") 708 709 _, err = NewRequest().AddEndorsersQuery(&discovery.ChaincodeInterest{ 710 Chaincodes: []*discovery.ChaincodeCall{{}}, 711 }) 712 assert.Contains(t, err.Error(), "chaincode name should not be empty") 713 } 714 715 func TestValidateAliveMessage(t *testing.T) { 716 am := aliveMessage(1) 717 msg, _ := protoext.EnvelopeToGossipMessage(am) 718 719 // Scenario I: Valid alive message 720 assert.NoError(t, validateAliveMessage(msg)) 721 722 // Scenario II: Nullify timestamp 723 msg.GetAliveMsg().Timestamp = nil 724 err := validateAliveMessage(msg) 725 assert.Equal(t, "timestamp is nil", err.Error()) 726 727 // Scenario III: Nullify membership 728 msg.GetAliveMsg().Membership = nil 729 err = validateAliveMessage(msg) 730 assert.Equal(t, "membership is empty", err.Error()) 731 732 // Scenario IV: Nullify the entire alive message part 733 msg.Content = nil 734 err = validateAliveMessage(msg) 735 assert.Equal(t, "message isn't an alive message", err.Error()) 736 } 737 738 func TestValidateStateInfoMessage(t *testing.T) { 739 si := stateInfoWithHeight(100) 740 741 // Scenario I: Valid state info message 742 assert.NoError(t, validateStateInfoMessage(si)) 743 744 // Scenario II: Nullify properties 745 si.GetStateInfo().Properties = nil 746 err := validateStateInfoMessage(si) 747 assert.Equal(t, "properties is nil", err.Error()) 748 749 // Scenario III: Nullify timestamp 750 si.GetStateInfo().Timestamp = nil 751 err = validateStateInfoMessage(si) 752 assert.Equal(t, "timestamp is nil", err.Error()) 753 754 // Scenario IV: Nullify the state info message part 755 si.Content = nil 756 err = validateStateInfoMessage(si) 757 assert.Equal(t, "message isn't a stateInfo message", err.Error()) 758 } 759 760 func TestString(t *testing.T) { 761 var ic InvocationChain 762 ic = append(ic, &discovery.ChaincodeCall{ 763 Name: "foo", 764 CollectionNames: []string{"c1", "c2"}, 765 }) 766 ic = append(ic, &discovery.ChaincodeCall{ 767 Name: "bar", 768 CollectionNames: []string{"c3", "c4"}, 769 }) 770 expected := `[{"name":"foo","collection_names":["c1","c2"]},{"name":"bar","collection_names":["c3","c4"]}]` 771 assert.Equal(t, expected, ic.String()) 772 } 773 774 func getMSP(peer *Peer) string { 775 endpoint := peer.AliveMessage.GetAliveMsg().Membership.Endpoint 776 id, _ := strconv.ParseInt(endpoint[1:], 10, 64) 777 switch id / 2 { 778 case 0, 4: 779 return "A" 780 case 1, 5: 781 return "B" 782 case 2, 6: 783 return "C" 784 default: 785 return "D" 786 } 787 } 788 789 func getMSPs(endorsers []*Peer) map[string]struct{} { 790 m := make(map[string]struct{}) 791 for _, endorser := range endorsers { 792 m[getMSP(endorser)] = struct{}{} 793 } 794 return m 795 } 796 797 type ccMetadataFetcher struct { 798 mock.Mock 799 } 800 801 func (mdf *ccMetadataFetcher) Metadata(channel string, cc string, _ ...string) *chaincode.Metadata { 802 return mdf.Called(cc).Get(0).(*chaincode.Metadata) 803 } 804 805 type principalEvaluator struct { 806 } 807 808 func (pe *principalEvaluator) SatisfiesPrincipal(channel string, identity []byte, principal *msp.MSPPrincipal) error { 809 sID := &msp.SerializedIdentity{} 810 proto.Unmarshal(identity, sID) 811 p := &msp.MSPRole{} 812 proto.Unmarshal(principal.Principal, p) 813 if sID.Mspid == p.MspIdentifier { 814 return nil 815 } 816 return errors.Errorf("peer %s has MSP %s but should have MSP %s", string(sID.IdBytes), sID.Mspid, p.MspIdentifier) 817 } 818 819 type policyFetcher struct { 820 mock.Mock 821 } 822 823 func (pf *policyFetcher) PoliciesByChaincode(channel string, cc string, collections ...string) []policies.InquireablePolicy { 824 return []policies.InquireablePolicy{pf.Called(cc).Get(0).(policies.InquireablePolicy)} 825 } 826 827 type endorsementAnalyzer interface { 828 PeersForEndorsement(chainID gossipcommon.ChannelID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) 829 830 PeersAuthorizedByCriteria(chainID gossipcommon.ChannelID, interest *discovery.ChaincodeInterest) (gdisc.Members, error) 831 } 832 833 type inquireablePolicy struct { 834 principals []*msp.MSPPrincipal 835 orgCombinations [][]string 836 } 837 838 func (ip *inquireablePolicy) appendPrincipal(orgName string) { 839 ip.principals = append(ip.principals, &msp.MSPPrincipal{ 840 PrincipalClassification: msp.MSPPrincipal_ROLE, 841 Principal: protoutil.MarshalOrPanic(&msp.MSPRole{Role: msp.MSPRole_MEMBER, MspIdentifier: orgName})}) 842 } 843 844 func (ip *inquireablePolicy) SatisfiedBy() []policies.PrincipalSet { 845 var res []policies.PrincipalSet 846 for _, orgs := range ip.orgCombinations { 847 for _, org := range orgs { 848 ip.appendPrincipal(org) 849 } 850 res = append(res, ip.principals) 851 ip.principals = nil 852 } 853 return res 854 } 855 856 func peerIdentity(mspID string, i int) api.PeerIdentityInfo { 857 p := []byte(fmt.Sprintf("p%d", i)) 858 sID := &msp.SerializedIdentity{ 859 Mspid: mspID, 860 IdBytes: p, 861 } 862 b, _ := proto.Marshal(sID) 863 return api.PeerIdentityInfo{ 864 Identity: api.PeerIdentityType(b), 865 PKIId: gossipcommon.PKIidType(p), 866 Organization: api.OrgIdentityType(mspID), 867 } 868 } 869 870 type peerInfo struct { 871 identity api.PeerIdentityType 872 pkiID gossipcommon.PKIidType 873 gdisc.NetworkMember 874 } 875 876 func aliveMessage(id int) *gossip.Envelope { 877 g := &gossip.GossipMessage{ 878 Content: &gossip.GossipMessage_AliveMsg{ 879 AliveMsg: &gossip.AliveMessage{ 880 Timestamp: &gossip.PeerTime{ 881 SeqNum: uint64(id), 882 IncNum: uint64(time.Now().UnixNano()), 883 }, 884 Membership: &gossip.Member{ 885 Endpoint: fmt.Sprintf("p%d", id), 886 }, 887 }, 888 }, 889 } 890 sMsg, _ := protoext.NoopSign(g) 891 return sMsg.Envelope 892 } 893 894 func stateInfoMessage(chaincodes ...*gossip.Chaincode) *gossip.Envelope { 895 return stateInfoMessageWithHeight(0, chaincodes...) 896 } 897 898 func stateInfoMessageWithHeight(ledgerHeight uint64, chaincodes ...*gossip.Chaincode) *gossip.Envelope { 899 g := &gossip.GossipMessage{ 900 Content: &gossip.GossipMessage_StateInfo{ 901 StateInfo: &gossip.StateInfo{ 902 Timestamp: &gossip.PeerTime{ 903 SeqNum: 5, 904 IncNum: uint64(time.Now().UnixNano()), 905 }, 906 Properties: &gossip.Properties{ 907 Chaincodes: chaincodes, 908 LedgerHeight: ledgerHeight, 909 }, 910 }, 911 }, 912 } 913 sMsg, _ := protoext.NoopSign(g) 914 return sMsg.Envelope 915 } 916 917 func newPeer(i int, env *gossip.Envelope, properties *gossip.Properties) *peerInfo { 918 p := fmt.Sprintf("p%d", i) 919 return &peerInfo{ 920 pkiID: gossipcommon.PKIidType(p), 921 identity: api.PeerIdentityType(p), 922 NetworkMember: gdisc.NetworkMember{ 923 PKIid: gossipcommon.PKIidType(p), 924 Endpoint: p, 925 InternalEndpoint: p, 926 Envelope: env, 927 Properties: properties, 928 }, 929 } 930 } 931 932 type mockSupport struct { 933 seq uint64 934 mock.Mock 935 endorsementAnalyzer 936 } 937 938 func (ms *mockSupport) ConfigSequence(channel string) uint64 { 939 // Ensure cache is bypassed 940 ms.seq++ 941 return ms.seq 942 } 943 944 func (ms *mockSupport) IdentityInfo() api.PeerIdentitySet { 945 return ms.Called().Get(0).(api.PeerIdentitySet) 946 } 947 948 func (*mockSupport) ChannelExists(channel string) bool { 949 return true 950 } 951 952 func (ms *mockSupport) PeersOfChannel(gossipcommon.ChannelID) gdisc.Members { 953 return ms.Called().Get(0).(gdisc.Members) 954 } 955 956 func (ms *mockSupport) Peers() gdisc.Members { 957 return ms.Called().Get(0).(gdisc.Members) 958 } 959 960 func (ms *mockSupport) PeersForEndorsement(channel gossipcommon.ChannelID, interest *discovery.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) { 961 return ms.endorsementAnalyzer.PeersForEndorsement(channel, interest) 962 } 963 964 func (ms *mockSupport) PeersAuthorizedByCriteria(channel gossipcommon.ChannelID, interest *discovery.ChaincodeInterest) (gdisc.Members, error) { 965 return ms.endorsementAnalyzer.PeersAuthorizedByCriteria(channel, interest) 966 } 967 968 func (*mockSupport) EligibleForService(channel string, data protoutil.SignedData) error { 969 return nil 970 } 971 972 func (ms *mockSupport) Config(channel string) (*discovery.ConfigResult, error) { 973 return ms.Called(channel).Get(0).(*discovery.ConfigResult), nil 974 } 975 976 type mockDiscoveryServer struct { 977 mock.Mock 978 *grpc.Server 979 port int64 980 } 981 982 func newMockDiscoveryService() *mockDiscoveryServer { 983 l, err := net.Listen("tcp", "localhost:0") 984 if err != nil { 985 panic(err) 986 } 987 s := grpc.NewServer() 988 d := &mockDiscoveryServer{ 989 Server: s, 990 } 991 discovery.RegisterDiscoveryServer(s, d) 992 go s.Serve(l) 993 _, portStr, _ := net.SplitHostPort(l.Addr().String()) 994 d.port, _ = strconv.ParseInt(portStr, 10, 64) 995 return d 996 } 997 998 func (ds *mockDiscoveryServer) shutdown() { 999 ds.Server.Stop() 1000 } 1001 1002 func (ds *mockDiscoveryServer) Discover(context.Context, *discovery.SignedRequest) (*discovery.Response, error) { 1003 args := ds.Called() 1004 if args.Get(0) == nil { 1005 return nil, args.Error(1) 1006 } 1007 return args.Get(0).(*discovery.Response), nil 1008 } 1009 1010 func ccCall(ccNames ...string) []*discovery.ChaincodeCall { 1011 var call []*discovery.ChaincodeCall 1012 for _, ccName := range ccNames { 1013 call = append(call, &discovery.ChaincodeCall{ 1014 Name: ccName, 1015 }) 1016 } 1017 return call 1018 } 1019 1020 func cc2ccInterests(invocationsChains ...[]*discovery.ChaincodeCall) []*discovery.ChaincodeInterest { 1021 var interests []*discovery.ChaincodeInterest 1022 for _, invocationChain := range invocationsChains { 1023 interests = append(interests, &discovery.ChaincodeInterest{ 1024 Chaincodes: invocationChain, 1025 }) 1026 } 1027 return interests 1028 } 1029 1030 func interest(ccNames ...string) *discovery.ChaincodeInterest { 1031 interest := &discovery.ChaincodeInterest{ 1032 Chaincodes: []*discovery.ChaincodeCall{}, 1033 } 1034 for _, cc := range ccNames { 1035 interest.Chaincodes = append(interest.Chaincodes, &discovery.ChaincodeCall{ 1036 Name: cc, 1037 }) 1038 } 1039 return interest 1040 } 1041 1042 func buildCollectionConfig(col2principals map[string][]*msp.MSPPrincipal) *peer.CollectionConfigPackage { 1043 collections := &peer.CollectionConfigPackage{} 1044 for col, principals := range col2principals { 1045 collections.Config = append(collections.Config, &peer.CollectionConfig{ 1046 Payload: &peer.CollectionConfig_StaticCollectionConfig{ 1047 StaticCollectionConfig: &peer.StaticCollectionConfig{ 1048 Name: col, 1049 MemberOrgsPolicy: &peer.CollectionPolicyConfig{ 1050 Payload: &peer.CollectionPolicyConfig_SignaturePolicy{ 1051 SignaturePolicy: &common.SignaturePolicyEnvelope{ 1052 Identities: principals, 1053 }, 1054 }, 1055 }, 1056 }, 1057 }, 1058 }) 1059 } 1060 return collections 1061 } 1062 1063 func memberPrincipal(mspID string) *msp.MSPPrincipal { 1064 return &msp.MSPPrincipal{ 1065 PrincipalClassification: msp.MSPPrincipal_ROLE, 1066 Principal: protoutil.MarshalOrPanic(&msp.MSPRole{ 1067 MspIdentifier: mspID, 1068 Role: msp.MSPRole_MEMBER, 1069 }), 1070 } 1071 } 1072 1073 // ledgerHeightFilter is a filter that uses ledger height to prioritize endorsers, although it provides more 1074 // even balancing than simply prioritizing by highest ledger height. Certain peers tend to always be at a slightly 1075 // higher ledger height than others (such as leaders) but we shouldn't always be selecting leaders. 1076 // This filter treats endorsers that are within a certain block height threshold equally and sorts them randomly. 1077 type ledgerHeightFilter struct { 1078 threshold uint64 1079 } 1080 1081 // Filter returns a random set of endorsers that are above the configured ledger height threshold. 1082 func (f *ledgerHeightFilter) Filter(endorsers Endorsers) Endorsers { 1083 if len(endorsers) <= 1 { 1084 return endorsers 1085 } 1086 1087 if f.threshold < 0 { 1088 return endorsers.Shuffle() 1089 } 1090 1091 maxHeight := getMaxLedgerHeight(endorsers) 1092 1093 if maxHeight <= f.threshold { 1094 return endorsers.Shuffle() 1095 } 1096 1097 cutoffHeight := maxHeight - f.threshold 1098 1099 var filteredEndorsers Endorsers 1100 for _, p := range endorsers { 1101 ledgerHeight := getLedgerHeight(p) 1102 if ledgerHeight >= cutoffHeight { 1103 filteredEndorsers = append(filteredEndorsers, p) 1104 } 1105 } 1106 return filteredEndorsers.Shuffle() 1107 } 1108 1109 func getLedgerHeight(endorser *Peer) uint64 { 1110 return endorser.StateInfoMessage.GetStateInfo().GetProperties().LedgerHeight 1111 } 1112 1113 func getMaxLedgerHeight(endorsers Endorsers) uint64 { 1114 var maxHeight uint64 1115 for _, peer := range endorsers { 1116 height := getLedgerHeight(peer) 1117 if height > maxHeight { 1118 maxHeight = height 1119 } 1120 } 1121 return maxHeight 1122 } 1123 1124 func getNames(endorsers Endorsers) []string { 1125 var names []string 1126 for _, p := range endorsers { 1127 names = append(names, p.AliveMessage.GetAliveMsg().Membership.Endpoint) 1128 } 1129 return names 1130 }