github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/discovery/service_test.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package discovery 8 9 import ( 10 "context" 11 "errors" 12 "fmt" 13 "reflect" 14 "testing" 15 16 "github.com/hyperledger/fabric-protos-go/peer" 17 18 "github.com/golang/protobuf/proto" 19 "github.com/hechain20/hechain/gossip/api" 20 gcommon "github.com/hechain20/hechain/gossip/common" 21 gdisc "github.com/hechain20/hechain/gossip/discovery" 22 "github.com/hechain20/hechain/gossip/protoext" 23 "github.com/hechain20/hechain/protoutil" 24 "github.com/hyperledger/fabric-protos-go/discovery" 25 "github.com/hyperledger/fabric-protos-go/gossip" 26 "github.com/stretchr/testify/mock" 27 "github.com/stretchr/testify/require" 28 ) 29 30 func TestConfig(t *testing.T) { 31 for _, trueOfFalse := range []bool{true, false} { 32 conf := Config{ 33 AuthCacheEnabled: trueOfFalse, 34 AuthCachePurgeRetentionRatio: 0.5, 35 AuthCacheMaxSize: 42, 36 } 37 service := NewService(conf, &mockSupport{}) 38 require.Equal(t, trueOfFalse, service.auth.conf.enabled) 39 require.Equal(t, 42, service.auth.conf.maxCacheSize) 40 require.Equal(t, 0.5, service.auth.conf.purgeRetentionRatio) 41 } 42 } 43 44 func TestService(t *testing.T) { 45 conf := Config{ 46 AuthCacheEnabled: true, 47 } 48 ctx := context.Background() 49 req := &discovery.Request{ 50 Authentication: &discovery.AuthInfo{ 51 ClientIdentity: []byte{1, 2, 3}, 52 }, 53 Queries: []*discovery.Query{ 54 { 55 Channel: "noneExistentChannel", 56 }, 57 }, 58 } 59 mockSup := &mockSupport{} 60 mockSup.On("ChannelExists", "noneExistentChannel").Return(false) 61 mockSup.On("ChannelExists", "channelWithAccessDenied").Return(true) 62 mockSup.On("ChannelExists", "channelWithAccessGranted").Return(true) 63 mockSup.On("ChannelExists", "channelWithSomeProblem").Return(true) 64 mockSup.On("EligibleForService", "channelWithAccessDenied", mock.Anything).Return(errors.New("foo")) 65 mockSup.On("EligibleForService", "channelWithAccessGranted", mock.Anything).Return(nil) 66 mockSup.On("EligibleForService", "channelWithSomeProblem", mock.Anything).Return(nil) 67 ed1 := &discovery.EndorsementDescriptor{ 68 Chaincode: "cc1", 69 } 70 ed2 := &discovery.EndorsementDescriptor{ 71 Chaincode: "cc2", 72 } 73 ed3 := &discovery.EndorsementDescriptor{ 74 Chaincode: "cc3", 75 } 76 mockSup.On("PeersForEndorsement", "unknownCC").Return(nil, errors.New("unknown chaincode")) 77 mockSup.On("PeersForEndorsement", "cc1").Return(ed1, nil) 78 mockSup.On("PeersForEndorsement", "cc2").Return(ed2, nil) 79 mockSup.On("PeersForEndorsement", "cc3").Return(ed3, nil) 80 81 service := NewService(conf, mockSup) 82 83 // Scenario I: Channel does not exist 84 resp, err := service.Discover(ctx, toSignedRequest(req)) 85 require.NoError(t, err) 86 require.Equal(t, wrapResult(&discovery.Error{Content: "access denied"}), resp) 87 88 // Scenario II: Channel does not exist 89 req.Queries[0].Channel = "channelWithAccessDenied" 90 resp, err = service.Discover(ctx, toSignedRequest(req)) 91 require.NoError(t, err) 92 require.Equal(t, wrapResult(&discovery.Error{Content: "access denied"}), resp) 93 94 // Scenario III: Request with nil query 95 req.Queries[0].Channel = "channelWithAccessGranted" 96 req.Queries[0].Query = nil 97 resp, err = service.Discover(ctx, toSignedRequest(req)) 98 require.NoError(t, err) 99 require.Contains(t, resp.Results[0].GetError().Content, "unknown or missing request type") 100 101 // Scenario IV: Request payload is invalid 102 signedRequest := toSignedRequest(req) 103 // Corrupt the payload by appending a zero byte at its end 104 signedRequest.Payload = append(signedRequest.Payload, 0) 105 resp, err = service.Discover(ctx, signedRequest) 106 require.Nil(t, resp) 107 require.Contains(t, err.Error(), "failed parsing request") 108 109 // Scenario V: Request a CC query with no chaincodes at all 110 req.Queries[0].Query = &discovery.Query_CcQuery{ 111 CcQuery: &discovery.ChaincodeQuery{ 112 Interests: []*peer.ChaincodeInterest{ 113 {}, 114 }, 115 }, 116 } 117 resp, err = service.Discover(ctx, toSignedRequest(req)) 118 require.NoError(t, err) 119 require.Contains(t, resp.Results[0].GetError().Content, "chaincode interest must contain at least one chaincode") 120 121 // Scenario VI: Request a CC query with no interests at all 122 req.Queries[0].Query = &discovery.Query_CcQuery{ 123 CcQuery: &discovery.ChaincodeQuery{ 124 Interests: []*peer.ChaincodeInterest{}, 125 }, 126 } 127 resp, err = service.Discover(ctx, toSignedRequest(req)) 128 require.NoError(t, err) 129 require.Contains(t, resp.Results[0].GetError().Content, "chaincode query must have at least one chaincode interest") 130 131 // Scenario VII: Request a CC query with a chaincode name that is empty 132 req.Queries[0].Query = &discovery.Query_CcQuery{ 133 CcQuery: &discovery.ChaincodeQuery{ 134 Interests: []*peer.ChaincodeInterest{{ 135 Chaincodes: []*peer.ChaincodeCall{{ 136 Name: "", 137 }}, 138 }}, 139 }, 140 } 141 resp, err = service.Discover(ctx, toSignedRequest(req)) 142 require.NoError(t, err) 143 require.Contains(t, resp.Results[0].GetError().Content, "chaincode name in interest cannot be empty") 144 145 // Scenario VIII: Request with a CC query where one chaincode is unavailable 146 req.Queries[0].Query = &discovery.Query_CcQuery{ 147 CcQuery: &discovery.ChaincodeQuery{ 148 Interests: []*peer.ChaincodeInterest{ 149 { 150 Chaincodes: []*peer.ChaincodeCall{{Name: "unknownCC"}}, 151 }, 152 { 153 Chaincodes: []*peer.ChaincodeCall{{Name: "cc1"}}, 154 }, 155 }, 156 }, 157 } 158 159 resp, err = service.Discover(ctx, toSignedRequest(req)) 160 require.NoError(t, err) 161 require.Contains(t, resp.Results[0].GetError().Content, "failed constructing descriptor") 162 require.Contains(t, resp.Results[0].GetError().Content, "unknownCC") 163 164 // Scenario IX: Request with a CC query where all are available 165 req.Queries[0].Query = &discovery.Query_CcQuery{ 166 CcQuery: &discovery.ChaincodeQuery{ 167 Interests: []*peer.ChaincodeInterest{ 168 { 169 Chaincodes: []*peer.ChaincodeCall{{Name: "cc1"}}, 170 }, 171 { 172 Chaincodes: []*peer.ChaincodeCall{{Name: "cc2"}}, 173 }, 174 { 175 Chaincodes: []*peer.ChaincodeCall{{Name: "cc3"}}, 176 }, 177 }, 178 }, 179 } 180 resp, err = service.Discover(ctx, toSignedRequest(req)) 181 require.NoError(t, err) 182 expected := wrapResult(&discovery.ChaincodeQueryResult{ 183 Content: []*discovery.EndorsementDescriptor{ed1, ed2, ed3}, 184 }) 185 require.Equal(t, expected, resp) 186 187 // Scenario X: Request with a config query 188 mockSup.On("Config", mock.Anything).Return(nil, errors.New("failed fetching config")).Once() 189 req.Queries[0].Query = &discovery.Query_ConfigQuery{ 190 ConfigQuery: &discovery.ConfigQuery{}, 191 } 192 resp, err = service.Discover(ctx, toSignedRequest(req)) 193 require.NoError(t, err) 194 require.Contains(t, resp.Results[0].GetError().Content, "failed fetching config for channel channelWithAccessGranted") 195 196 // Scenario XI: Request with a config query 197 mockSup.On("Config", mock.Anything).Return(&discovery.ConfigResult{}, nil).Once() 198 req.Queries[0].Query = &discovery.Query_ConfigQuery{ 199 ConfigQuery: &discovery.ConfigQuery{}, 200 } 201 resp, err = service.Discover(ctx, toSignedRequest(req)) 202 require.NoError(t, err) 203 require.NotNil(t, resp.Results[0].GetConfigResult()) 204 205 // Scenario XII: Request with a membership query 206 // Peers in membership view: { p0, p1, p2, p3} 207 // Peers in channel view: {p1, p2, p4} 208 // So that means that the returned peers for the channel should be the intersection 209 // which is: {p1, p2}, but the returned peers for the local query should be 210 // simply the membership view. 211 peersInMembershipView := gdisc.Members{ 212 aliveMsg(0), aliveMsg(1), aliveMsg(2), aliveMsg(3), 213 } 214 peersInChannelView := gdisc.Members{ 215 stateInfoMsg(1), stateInfoMsg(2), stateInfoMsg(4), 216 } 217 // EligibleForService for an "empty" channel 218 mockSup.On("EligibleForService", "", mock.Anything).Return(nil).Once() 219 mockSup.On("PeersAuthorizedByCriteria", gcommon.ChannelID("channelWithAccessGranted")).Return(peersInChannelView, nil).Once() 220 mockSup.On("PeersAuthorizedByCriteria", gcommon.ChannelID("channelWithSomeProblem")).Return(nil, errors.New("an error occurred")).Once() 221 mockSup.On("Peers").Return(peersInMembershipView).Twice() 222 mockSup.On("IdentityInfo").Return(api.PeerIdentitySet{ 223 idInfo(0, "O2"), idInfo(1, "O2"), idInfo(2, "O3"), 224 idInfo(3, "O3"), idInfo(4, "O3"), 225 }).Twice() 226 227 req.Queries = []*discovery.Query{ 228 { 229 Channel: "channelWithAccessGranted", 230 Query: &discovery.Query_PeerQuery{ 231 PeerQuery: &discovery.PeerMembershipQuery{}, 232 }, 233 }, 234 { 235 Query: &discovery.Query_LocalPeers{ 236 LocalPeers: &discovery.LocalPeerQuery{}, 237 }, 238 }, 239 { 240 Channel: "channelWithSomeProblem", 241 Query: &discovery.Query_PeerQuery{ 242 PeerQuery: &discovery.PeerMembershipQuery{ 243 Filter: &peer.ChaincodeInterest{}, 244 }, 245 }, 246 }, 247 } 248 resp, err = service.Discover(ctx, toSignedRequest(req)) 249 require.NoError(t, err) 250 expectedChannelResponse := &discovery.PeerMembershipResult{ 251 PeersByOrg: map[string]*discovery.Peers{ 252 "O2": { 253 Peers: []*discovery.Peer{ 254 { 255 Identity: idInfo(1, "O2").Identity, 256 StateInfo: stateInfoMsg(1).Envelope, 257 MembershipInfo: aliveMsg(1).Envelope, 258 }, 259 }, 260 }, 261 "O3": { 262 Peers: []*discovery.Peer{ 263 { 264 Identity: idInfo(2, "O3").Identity, 265 StateInfo: stateInfoMsg(2).Envelope, 266 MembershipInfo: aliveMsg(2).Envelope, 267 }, 268 }, 269 }, 270 }, 271 } 272 expectedLocalResponse := &discovery.PeerMembershipResult{ 273 PeersByOrg: map[string]*discovery.Peers{ 274 "O2": { 275 Peers: []*discovery.Peer{ 276 { 277 Identity: idInfo(0, "O2").Identity, 278 MembershipInfo: aliveMsg(0).Envelope, 279 }, 280 { 281 Identity: idInfo(1, "O2").Identity, 282 MembershipInfo: aliveMsg(1).Envelope, 283 }, 284 }, 285 }, 286 "O3": { 287 Peers: []*discovery.Peer{ 288 { 289 Identity: idInfo(2, "O3").Identity, 290 MembershipInfo: aliveMsg(2).Envelope, 291 }, 292 { 293 Identity: idInfo(3, "O3").Identity, 294 MembershipInfo: aliveMsg(3).Envelope, 295 }, 296 }, 297 }, 298 }, 299 } 300 301 require.Len(t, resp.Results, 3) 302 require.Len(t, resp.Results[0].GetMembers().PeersByOrg, 2) 303 require.Len(t, resp.Results[1].GetMembers().PeersByOrg, 2) 304 require.Equal(t, "an error occurred", resp.Results[2].GetError().Content) 305 306 for org, responsePeers := range resp.Results[0].GetMembers().PeersByOrg { 307 err := peers(expectedChannelResponse.PeersByOrg[org].Peers).compare(peers(responsePeers.Peers)) 308 require.NoError(t, err) 309 } 310 for org, responsePeers := range resp.Results[1].GetMembers().PeersByOrg { 311 err := peers(expectedLocalResponse.PeersByOrg[org].Peers).compare(peers(responsePeers.Peers)) 312 require.NoError(t, err) 313 } 314 315 // Scenario XIII: The client is eligible for channel queries but not for channel-less 316 // since it's not an admin. It sends a query for a channel-less query but puts a channel in the query. 317 // It should fail because channel-less query types cannot have a channel configured in them. 318 req.Queries = []*discovery.Query{ 319 { 320 Channel: "channelWithAccessGranted", 321 Query: &discovery.Query_LocalPeers{ 322 LocalPeers: &discovery.LocalPeerQuery{}, 323 }, 324 }, 325 } 326 resp, err = service.Discover(ctx, toSignedRequest(req)) 327 require.NoError(t, err) 328 require.Contains(t, resp.Results[0].GetError().Content, "unknown or missing request type") 329 } 330 331 func TestValidateStructure(t *testing.T) { 332 extractHash := func(ctx context.Context) []byte { 333 return nil 334 } 335 // Scenarios I-V without TLS, scenarios VI onwards TLS 336 337 // Scenario I: Nil request 338 res, err := validateStructure(context.Background(), nil, false, extractHash) 339 require.Nil(t, res) 340 require.Equal(t, "nil request", err.Error()) 341 342 // Scenario II: Malformed envelope 343 res, err = validateStructure(context.Background(), &discovery.SignedRequest{ 344 Payload: []byte{1, 2, 3}, 345 }, false, extractHash) 346 require.Nil(t, res) 347 require.Contains(t, err.Error(), "failed parsing request") 348 349 // Scenario III: Empty request 350 res, err = validateStructure(context.Background(), &discovery.SignedRequest{}, false, extractHash) 351 require.Nil(t, res) 352 require.Equal(t, "access denied, no authentication info in request", err.Error()) 353 354 // Scenario IV: request without a client identity 355 req := &discovery.Request{ 356 Authentication: &discovery.AuthInfo{}, 357 } 358 b, _ := proto.Marshal(req) 359 res, err = validateStructure(context.Background(), &discovery.SignedRequest{ 360 Payload: b, 361 }, false, extractHash) 362 require.Nil(t, res) 363 require.Equal(t, "access denied, client identity wasn't supplied", err.Error()) 364 365 // Scenario V: request with a client identity, should succeed because no TLS is used 366 req = &discovery.Request{ 367 Authentication: &discovery.AuthInfo{ 368 ClientIdentity: []byte{1, 2, 3}, 369 }, 370 } 371 b, _ = proto.Marshal(req) 372 res, err = validateStructure(context.Background(), &discovery.SignedRequest{ 373 Payload: b, 374 }, false, extractHash) 375 require.NoError(t, err) 376 // Ensure returned request is as before serialization to bytes 377 require.True(t, proto.Equal(req, res)) 378 379 // Scenario VI: request with a client identity but with TLS enabled but client doesn't send a TLS cert 380 req = &discovery.Request{ 381 Authentication: &discovery.AuthInfo{ 382 ClientIdentity: []byte{1, 2, 3}, 383 }, 384 } 385 b, _ = proto.Marshal(req) 386 res, err = validateStructure(context.Background(), &discovery.SignedRequest{ 387 Payload: b, 388 }, true, extractHash) 389 require.Nil(t, res) 390 require.Equal(t, "client didn't send a TLS certificate", err.Error()) 391 392 // Scenario VII: request with a client identity and with TLS enabled but the TLS cert hash doesn't match 393 // the computed one 394 extractHash = func(ctx context.Context) []byte { 395 return []byte{1, 2} 396 } 397 req = &discovery.Request{ 398 Authentication: &discovery.AuthInfo{ 399 ClientIdentity: []byte{1, 2, 3}, 400 ClientTlsCertHash: []byte{1, 2, 3}, 401 }, 402 } 403 b, _ = proto.Marshal(req) 404 res, err = validateStructure(context.Background(), &discovery.SignedRequest{ 405 Payload: b, 406 }, true, extractHash) 407 require.Nil(t, res) 408 require.Equal(t, "client claimed TLS hash doesn't match computed TLS hash from gRPC stream", err.Error()) 409 410 // Scenario VIII: request with a client identity and with TLS enabled and the TLS cert hash doesn't match 411 // the computed one 412 extractHash = func(ctx context.Context) []byte { 413 return []byte{1, 2, 3} 414 } 415 req = &discovery.Request{ 416 Authentication: &discovery.AuthInfo{ 417 ClientIdentity: []byte{1, 2, 3}, 418 ClientTlsCertHash: []byte{1, 2, 3}, 419 }, 420 } 421 b, _ = proto.Marshal(req) 422 res, err = validateStructure(context.Background(), &discovery.SignedRequest{ 423 Payload: b, 424 }, true, extractHash) 425 require.NoError(t, err) 426 require.NotNil(t, res) 427 } 428 429 func TestValidateCCQuery(t *testing.T) { 430 err := validateCCQuery(&discovery.ChaincodeQuery{ 431 Interests: []*peer.ChaincodeInterest{ 432 nil, 433 }, 434 }) 435 require.Equal(t, "chaincode interest is nil", err.Error()) 436 } 437 438 func wrapResult(responses ...interface{}) *discovery.Response { 439 response := &discovery.Response{} 440 for _, res := range responses { 441 response.Results = append(response.Results, wrapQueryResult(res)) 442 } 443 return response 444 } 445 446 func wrapQueryResult(res interface{}) *discovery.QueryResult { 447 if err, isErr := res.(*discovery.Error); isErr { 448 return &discovery.QueryResult{ 449 Result: &discovery.QueryResult_Error{ 450 Error: err, 451 }, 452 } 453 } 454 if ccRes, isCCQuery := res.(*discovery.ChaincodeQueryResult); isCCQuery { 455 return &discovery.QueryResult{ 456 Result: &discovery.QueryResult_CcQueryRes{ 457 CcQueryRes: ccRes, 458 }, 459 } 460 } 461 if membRes, isMembershipQuery := res.(*discovery.PeerMembershipResult); isMembershipQuery { 462 return &discovery.QueryResult{ 463 Result: &discovery.QueryResult_Members{ 464 Members: membRes, 465 }, 466 } 467 } 468 if confRes, isConfQuery := res.(*discovery.ConfigResult); isConfQuery { 469 return &discovery.QueryResult{ 470 Result: &discovery.QueryResult_ConfigResult{ 471 ConfigResult: confRes, 472 }, 473 } 474 } 475 panic(fmt.Sprint("invalid type:", reflect.TypeOf(res))) 476 } 477 478 func toSignedRequest(req *discovery.Request) *discovery.SignedRequest { 479 b, _ := proto.Marshal(req) 480 return &discovery.SignedRequest{ 481 Payload: b, 482 } 483 } 484 485 type mockSupport struct { 486 mock.Mock 487 } 488 489 func (ms *mockSupport) ConfigSequence(channel string) uint64 { 490 return 0 491 } 492 493 func (ms *mockSupport) IdentityInfo() api.PeerIdentitySet { 494 return ms.Called().Get(0).(api.PeerIdentitySet) 495 } 496 497 func (ms *mockSupport) ChannelExists(channel string) bool { 498 return ms.Called(channel).Get(0).(bool) 499 } 500 501 func (ms *mockSupport) PeersOfChannel(channel gcommon.ChannelID) gdisc.Members { 502 panic("not implemented") 503 } 504 505 func (ms *mockSupport) Peers() gdisc.Members { 506 return ms.Called().Get(0).(gdisc.Members) 507 } 508 509 func (ms *mockSupport) PeersForEndorsement(channel gcommon.ChannelID, interest *peer.ChaincodeInterest) (*discovery.EndorsementDescriptor, error) { 510 cc := interest.Chaincodes[0].Name 511 args := ms.Called(cc) 512 if args.Get(0) == nil { 513 return nil, args.Error(1) 514 } 515 return args.Get(0).(*discovery.EndorsementDescriptor), args.Error(1) 516 } 517 518 func (ms *mockSupport) PeersAuthorizedByCriteria(chainID gcommon.ChannelID, interest *peer.ChaincodeInterest) (gdisc.Members, error) { 519 args := ms.Called(chainID) 520 if args.Error(1) != nil { 521 return nil, args.Error(1) 522 } 523 return args.Get(0).(gdisc.Members), args.Error(1) 524 } 525 526 func (*mockSupport) Chaincodes(id gcommon.ChannelID) []*gossip.Chaincode { 527 panic("implement me") 528 } 529 530 func (ms *mockSupport) EligibleForService(channel string, data protoutil.SignedData) error { 531 return ms.Called(channel, data).Error(0) 532 } 533 534 func (ms *mockSupport) Config(channel string) (*discovery.ConfigResult, error) { 535 args := ms.Called(channel) 536 if args.Get(0) == nil { 537 return nil, args.Error(1) 538 } 539 return args.Get(0).(*discovery.ConfigResult), args.Error(1) 540 } 541 542 func idInfo(id int, org string) api.PeerIdentityInfo { 543 endpoint := fmt.Sprintf("p%d", id) 544 return api.PeerIdentityInfo{ 545 PKIId: gcommon.PKIidType(endpoint), 546 Organization: api.OrgIdentityType(org), 547 Identity: api.PeerIdentityType(endpoint), 548 } 549 } 550 551 func stateInfoMsg(id int) gdisc.NetworkMember { 552 endpoint := fmt.Sprintf("p%d", id) 553 pkiID := gcommon.PKIidType(endpoint) 554 si := &gossip.StateInfo{ 555 PkiId: pkiID, 556 } 557 gm := &gossip.GossipMessage{ 558 Content: &gossip.GossipMessage_StateInfo{ 559 StateInfo: si, 560 }, 561 } 562 sm, _ := protoext.NoopSign(gm) 563 return gdisc.NetworkMember{ 564 PKIid: pkiID, 565 Envelope: sm.Envelope, 566 } 567 } 568 569 func aliveMsg(id int) gdisc.NetworkMember { 570 endpoint := fmt.Sprintf("p%d", id) 571 pkiID := gcommon.PKIidType(endpoint) 572 am := &gossip.AliveMessage{ 573 Membership: &gossip.Member{ 574 PkiId: pkiID, 575 Endpoint: endpoint, 576 }, 577 } 578 gm := &gossip.GossipMessage{ 579 Content: &gossip.GossipMessage_AliveMsg{ 580 AliveMsg: am, 581 }, 582 } 583 sm, _ := protoext.NoopSign(gm) 584 return gdisc.NetworkMember{ 585 PKIid: pkiID, 586 Endpoint: endpoint, 587 Envelope: sm.Envelope, 588 } 589 } 590 591 type peers []*discovery.Peer 592 593 func (ps peers) exists(p *discovery.Peer) error { 594 var found bool 595 for _, q := range ps { 596 if reflect.DeepEqual(*p, *q) { 597 found = true 598 break 599 } 600 } 601 if !found { 602 return fmt.Errorf("%v wasn't found in %v", ps, p) 603 } 604 return nil 605 } 606 607 func (ps peers) compare(otherPeers peers) error { 608 if len(ps) != len(otherPeers) { 609 return fmt.Errorf("size mismatch: %d, %d", len(ps), len(otherPeers)) 610 } 611 612 for _, p := range otherPeers { 613 if err := ps.exists(p); err != nil { 614 return err 615 } 616 } 617 618 for _, p := range ps { 619 if err := otherPeers.exists(p); err != nil { 620 return err 621 } 622 } 623 return nil 624 }