github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/internal/pkg/gateway/api_test.go (about) 1 /* 2 Copyright 2021 IBM All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package gateway 8 9 import ( 10 "context" 11 "fmt" 12 "io" 13 "testing" 14 "time" 15 16 "github.com/golang/protobuf/proto" 17 "github.com/golang/protobuf/ptypes/timestamp" 18 "github.com/hechain20/hechain/common/crypto/tlsgen" 19 commonledger "github.com/hechain20/hechain/common/ledger" 20 "github.com/hechain20/hechain/gossip/api" 21 "github.com/hechain20/hechain/gossip/common" 22 gdiscovery "github.com/hechain20/hechain/gossip/discovery" 23 "github.com/hechain20/hechain/internal/pkg/comm" 24 "github.com/hechain20/hechain/internal/pkg/gateway/commit" 25 "github.com/hechain20/hechain/internal/pkg/gateway/config" 26 "github.com/hechain20/hechain/internal/pkg/gateway/mocks" 27 idmocks "github.com/hechain20/hechain/internal/pkg/identity/mocks" 28 "github.com/hechain20/hechain/protoutil" 29 cp "github.com/hyperledger/fabric-protos-go/common" 30 dp "github.com/hyperledger/fabric-protos-go/discovery" 31 pb "github.com/hyperledger/fabric-protos-go/gateway" 32 "github.com/hyperledger/fabric-protos-go/gossip" 33 "github.com/hyperledger/fabric-protos-go/msp" 34 ab "github.com/hyperledger/fabric-protos-go/orderer" 35 "github.com/hyperledger/fabric-protos-go/peer" 36 "github.com/pkg/errors" 37 "github.com/spf13/viper" 38 "github.com/stretchr/testify/require" 39 "google.golang.org/grpc" 40 "google.golang.org/grpc/codes" 41 "google.golang.org/grpc/status" 42 ) 43 44 // The following private interfaces are here purely to prevent counterfeiter creating an import cycle in the unit test 45 //go:generate counterfeiter -o mocks/endorserclient.go --fake-name EndorserClient . endorserClient 46 type endorserClient interface { 47 peer.EndorserClient 48 } 49 50 //go:generate counterfeiter -o mocks/discovery.go --fake-name Discovery . discovery 51 type discovery interface { 52 Discovery 53 } 54 55 //go:generate counterfeiter -o mocks/abclient.go --fake-name ABClient . abClient 56 type abClient interface { 57 ab.AtomicBroadcastClient 58 } 59 60 //go:generate counterfeiter -o mocks/abbclient.go --fake-name ABBClient . abbClient 61 type abbClient interface { 62 ab.AtomicBroadcast_BroadcastClient 63 } 64 65 //go:generate counterfeiter -o mocks/commitfinder.go --fake-name CommitFinder . commitFinder 66 type commitFinder interface { 67 CommitFinder 68 } 69 70 //go:generate counterfeiter -o mocks/chaincodeeventsserver.go --fake-name ChaincodeEventsServer github.com/hyperledger/fabric-protos-go/gateway.Gateway_ChaincodeEventsServer 71 72 //go:generate counterfeiter -o mocks/aclchecker.go --fake-name ACLChecker . aclChecker 73 type aclChecker interface { 74 ACLChecker 75 } 76 77 //go:generate counterfeiter -o mocks/ledgerprovider.go --fake-name LedgerProvider . ledgerProvider 78 type ledgerProvider interface { 79 LedgerProvider 80 } 81 82 //go:generate counterfeiter -o mocks/ledger.go --fake-name Ledger . mockLedger 83 type mockLedger interface { 84 commonledger.Ledger 85 } 86 87 //go:generate counterfeiter -o mocks/resultsiterator.go --fake-name ResultsIterator . resultsIterator 88 type mockResultsIterator interface { 89 commonledger.ResultsIterator 90 } 91 92 type ( 93 endorsementPlan map[string][]endorserState 94 endorsementLayout map[string]uint32 95 ) 96 97 type networkMember struct { 98 id string 99 endpoint string 100 mspid string 101 height uint64 102 } 103 104 type endpointDef struct { 105 proposalResponseValue string 106 proposalResponseStatus int32 107 proposalResponseMessage string 108 proposalError error 109 ordererResponse string 110 ordererStatus int32 111 ordererBroadcastError error 112 ordererSendError error 113 ordererRecvError error 114 } 115 116 var defaultEndpointDef = &endpointDef{ 117 proposalResponseValue: "mock_response", 118 proposalResponseStatus: 200, 119 ordererResponse: "mock_orderer_response", 120 ordererStatus: 200, 121 } 122 123 const ( 124 testChannel = "test_channel" 125 testChaincode = "test_chaincode" 126 endorsementTimeout = -1 * time.Second 127 ) 128 129 type testDef struct { 130 name string 131 plan endorsementPlan 132 layouts []endorsementLayout 133 members []networkMember 134 config *dp.ConfigResult 135 identity []byte 136 localResponse string 137 errString string 138 errCode codes.Code 139 errDetails []*pb.ErrorDetail 140 endpointDefinition *endpointDef 141 endorsingOrgs []string 142 postSetup func(t *testing.T, def *preparedTest) 143 postTest func(t *testing.T, def *preparedTest) 144 expectedEndorsers []string 145 finderStatus *commit.Status 146 finderErr error 147 eventErr error 148 policyErr error 149 expectedResponse proto.Message 150 expectedResponses []proto.Message 151 transientData map[string][]byte 152 interest *peer.ChaincodeInterest 153 blocks []*cp.Block 154 startPosition *ab.SeekPosition 155 } 156 157 type preparedTest struct { 158 server *Server 159 ctx context.Context 160 signedProposal *peer.SignedProposal 161 localEndorser *mocks.EndorserClient 162 discovery *mocks.Discovery 163 dialer *mocks.Dialer 164 finder *mocks.CommitFinder 165 eventsServer *mocks.ChaincodeEventsServer 166 policy *mocks.ACLChecker 167 ledgerProvider *mocks.LedgerProvider 168 ledger *mocks.Ledger 169 blockIterator *mocks.ResultsIterator 170 } 171 172 type contextKey string 173 174 var ( 175 localhostMock = &endorser{endpointConfig: &endpointConfig{pkiid: []byte("0"), address: "localhost:7051", mspid: "msp1"}} 176 peer1Mock = &endorser{endpointConfig: &endpointConfig{pkiid: []byte("1"), address: "peer1:8051", mspid: "msp1"}} 177 peer2Mock = &endorser{endpointConfig: &endpointConfig{pkiid: []byte("2"), address: "peer2:9051", mspid: "msp2"}} 178 peer3Mock = &endorser{endpointConfig: &endpointConfig{pkiid: []byte("3"), address: "peer3:10051", mspid: "msp2"}} 179 peer4Mock = &endorser{endpointConfig: &endpointConfig{pkiid: []byte("4"), address: "peer4:11051", mspid: "msp3"}} 180 unavailable1Mock = &endorser{endpointConfig: &endpointConfig{pkiid: []byte("5"), address: "unavailable1:12051", mspid: "msp1"}} 181 unavailable2Mock = &endorser{endpointConfig: &endpointConfig{pkiid: []byte("6"), address: "unavailable2:13051", mspid: "msp1"}} 182 unavailable3Mock = &endorser{endpointConfig: &endpointConfig{pkiid: []byte("7"), address: "unavailable3:14051", mspid: "msp1"}} 183 endorsers = map[string]*endorser{ 184 localhostMock.address: localhostMock, 185 peer1Mock.address: peer1Mock, 186 peer2Mock.address: peer2Mock, 187 peer3Mock.address: peer3Mock, 188 peer4Mock.address: peer4Mock, 189 } 190 ) 191 192 func TestEvaluate(t *testing.T) { 193 tests := []testDef{ 194 { 195 name: "single endorser", 196 members: []networkMember{ 197 {"id1", "localhost:7051", "msp1", 5}, 198 }, 199 }, 200 { 201 name: "no endorsers", 202 plan: endorsementPlan{}, 203 members: []networkMember{}, 204 errCode: codes.FailedPrecondition, 205 errString: "no peers available to evaluate chaincode test_chaincode in channel test_channel", 206 }, 207 { 208 name: "five endorsers, prefer local org", 209 members: []networkMember{ 210 {"id1", "localhost:7051", "msp1", 5}, 211 {"id2", "peer1:8051", "msp1", 6}, 212 {"id3", "peer2:9051", "msp2", 6}, 213 {"id4", "peer3:10051", "msp2", 5}, 214 {"id5", "peer4:11051", "msp3", 6}, 215 }, 216 expectedEndorsers: []string{"peer1:8051"}, 217 }, 218 { 219 name: "five endorsers, prefer host peer", 220 members: []networkMember{ 221 {"id1", "localhost:7051", "msp1", 5}, 222 {"id2", "peer1:8051", "msp1", 5}, 223 {"id3", "peer2:9051", "msp2", 6}, 224 {"id4", "peer3:10051", "msp2", 5}, 225 {"id5", "peer4:11051", "msp3", 6}, 226 }, 227 expectedEndorsers: []string{"localhost:7051"}, 228 }, 229 { 230 name: "five endorsers, prefer host peer despite no endpoint", 231 members: []networkMember{ 232 {"id1", "", "msp1", 5}, 233 {"id2", "peer1:8051", "msp1", 5}, 234 {"id3", "peer2:9051", "msp2", 6}, 235 {"id4", "peer3:10051", "msp2", 5}, 236 {"id5", "peer4:11051", "msp3", 6}, 237 }, 238 expectedEndorsers: []string{"localhost:7051"}, 239 }, 240 { 241 name: "evaluate with targetOrganizations, prefer local org despite block height", 242 members: []networkMember{ 243 {"id1", "localhost:7051", "msp1", 5}, 244 {"id2", "peer1:8051", "msp1", 5}, 245 {"id3", "peer2:9051", "msp2", 6}, 246 {"id4", "peer3:10051", "msp2", 5}, 247 {"id5", "peer4:11051", "msp3", 6}, 248 }, 249 endorsingOrgs: []string{"msp3", "msp1"}, 250 expectedEndorsers: []string{"localhost:7051"}, 251 }, 252 { 253 name: "evaluate with targetOrganizations that doesn't include local org, prefer highest block height", 254 members: []networkMember{ 255 {"id1", "localhost:7051", "msp1", 5}, 256 {"id2", "peer1:8051", "msp1", 5}, 257 {"id3", "peer2:9051", "msp2", 6}, 258 {"id4", "peer3:10051", "msp2", 5}, 259 {"id5", "peer4:11051", "msp3", 7}, 260 }, 261 endorsingOrgs: []string{"msp2", "msp3"}, 262 expectedEndorsers: []string{"peer4:11051"}, 263 }, 264 { 265 name: "evaluate with transient data should select local org, highest block height", 266 members: []networkMember{ 267 {"id1", "localhost:7051", "msp1", 4}, 268 {"id2", "peer1:8051", "msp1", 5}, 269 {"id3", "peer2:9051", "msp2", 6}, 270 {"id4", "peer3:10051", "msp2", 5}, 271 {"id5", "peer4:11051", "msp3", 7}, 272 }, 273 transientData: map[string][]byte{"transient-key": []byte("transient-value")}, 274 expectedEndorsers: []string{"peer1:8051"}, 275 }, 276 { 277 name: "evaluate with transient data should fail if local org not available", 278 members: []networkMember{ 279 {"id3", "peer2:9051", "msp2", 6}, 280 {"id4", "peer3:10051", "msp2", 5}, 281 {"id5", "peer4:11051", "msp3", 7}, 282 }, 283 transientData: map[string][]byte{"transient-key": []byte("transient-value")}, 284 errCode: codes.FailedPrecondition, 285 errString: "no endorsers found in the gateway's organization; retry specifying target organization(s) to protect transient data: no peers available to evaluate chaincode test_chaincode in channel test_channel", 286 }, 287 { 288 name: "evaluate with transient data and target (non-local) orgs should select the highest block height peer", 289 members: []networkMember{ 290 {"id1", "localhost:7051", "msp1", 11}, 291 {"id2", "peer1:8051", "msp1", 5}, 292 {"id3", "peer2:9051", "msp2", 6}, 293 {"id4", "peer3:10051", "msp2", 9}, 294 {"id5", "peer4:11051", "msp3", 7}, 295 }, 296 transientData: map[string][]byte{"transient-key": []byte("transient-value")}, 297 endorsingOrgs: []string{"msp2", "msp3"}, 298 expectedEndorsers: []string{"peer3:10051"}, 299 }, 300 { 301 name: "process proposal fails", 302 members: []networkMember{ 303 {"id1", "localhost:7051", "msp1", 5}, 304 }, 305 endpointDefinition: &endpointDef{ 306 proposalError: status.Error(codes.Aborted, "wibble"), 307 }, 308 errCode: codes.Aborted, 309 errString: "failed to evaluate transaction, see attached details for more info", 310 errDetails: []*pb.ErrorDetail{{ 311 Address: "localhost:7051", 312 MspId: "msp1", 313 Message: "rpc error: code = Aborted desc = wibble", 314 }}, 315 }, 316 { 317 name: "process proposal chaincode error", 318 members: []networkMember{ 319 {"id2", "peer1:8051", "msp1", 5}, 320 }, 321 endpointDefinition: &endpointDef{ 322 proposalResponseStatus: 400, 323 proposalResponseMessage: "Mock chaincode error", 324 }, 325 errCode: codes.Unknown, 326 errString: "evaluate call to endorser returned error: chaincode response 400, Mock chaincode error", 327 errDetails: []*pb.ErrorDetail{{ 328 Address: "peer1:8051", 329 MspId: "msp1", 330 Message: "chaincode response 400, Mock chaincode error", 331 }}, 332 }, 333 { 334 name: "evaluate on local org fails - retry in other org", 335 members: []networkMember{ 336 {"id1", "localhost:7051", "msp1", 4}, 337 {"id2", "peer1:8051", "msp1", 4}, 338 {"id3", "peer2:9051", "msp2", 3}, 339 {"id4", "peer3:10051", "msp2", 4}, 340 {"id5", "peer4:11051", "msp3", 5}, 341 }, 342 plan: endorsementPlan{ 343 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 344 "g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 345 "g3": {{endorser: peer4Mock, height: 5}}, // msp3 346 }, 347 postSetup: func(t *testing.T, def *preparedTest) { 348 def.localEndorser.ProcessProposalReturns(createErrorResponse(t, 500, "bad local endorser", nil), nil) 349 peer1Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(createErrorResponse(t, 500, "bad peer1 endorser", nil), nil) 350 }, 351 expectedEndorsers: []string{"peer4:11051"}, 352 }, 353 { 354 name: "restrict to local org peers - which all fail", 355 members: []networkMember{ 356 {"id1", "localhost:7051", "msp1", 4}, 357 {"id2", "peer1:8051", "msp1", 4}, 358 {"id3", "peer2:9051", "msp2", 3}, 359 {"id4", "peer3:10051", "msp2", 4}, 360 {"id5", "peer4:11051", "msp3", 5}, 361 }, 362 plan: endorsementPlan{ 363 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 364 "g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 365 "g3": {{endorser: peer4Mock, height: 5}}, // msp3 366 }, 367 postSetup: func(t *testing.T, def *preparedTest) { 368 def.localEndorser.ProcessProposalReturns(createErrorResponse(t, 500, "bad local endorser", nil), nil) 369 peer1Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(createErrorResponse(t, 500, "bad peer1 endorser", nil), nil) 370 }, 371 endorsingOrgs: []string{"msp1"}, 372 errCode: codes.Aborted, 373 errString: "failed to evaluate transaction, see attached details for more info", 374 errDetails: []*pb.ErrorDetail{ 375 { 376 Address: "localhost:7051", 377 MspId: "msp1", 378 Message: "bad local endorser", 379 }, 380 { 381 Address: "peer1:8051", 382 MspId: "msp1", 383 Message: "bad peer1 endorser", 384 }, 385 }, 386 }, 387 { 388 name: "fails due to invalid signature (pre-process check) - does not retry", 389 members: []networkMember{ 390 {"id1", "localhost:7051", "msp1", 4}, 391 {"id2", "peer1:8051", "msp1", 4}, 392 {"id3", "peer2:9051", "msp2", 3}, 393 {"id4", "peer3:10051", "msp2", 4}, 394 {"id5", "peer4:11051", "msp3", 5}, 395 }, 396 plan: endorsementPlan{ 397 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 398 "g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 399 "g3": {{endorser: peer4Mock, height: 5}}, // msp3 400 }, 401 postSetup: func(t *testing.T, def *preparedTest) { 402 def.localEndorser.ProcessProposalReturns(createErrorResponse(t, 500, "invalid signature", nil), fmt.Errorf("invalid signature")) 403 }, 404 endorsingOrgs: []string{"msp1"}, 405 errCode: codes.FailedPrecondition, // Code path could fail for reasons other than authentication 406 errString: "evaluate call to endorser returned error: invalid signature", 407 errDetails: []*pb.ErrorDetail{ 408 { 409 Address: "localhost:7051", 410 MspId: "msp1", 411 Message: "invalid signature", 412 }, 413 }, 414 }, 415 { 416 name: "fails due to chaincode panic - retry on next peer", 417 members: []networkMember{ 418 {"id1", "localhost:7051", "msp1", 4}, 419 {"id2", "peer1:8051", "msp1", 4}, 420 {"id3", "peer2:9051", "msp2", 3}, 421 {"id4", "peer3:10051", "msp2", 4}, 422 {"id5", "peer4:11051", "msp3", 5}, 423 }, 424 plan: endorsementPlan{ 425 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 426 "g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 427 "g3": {{endorser: peer4Mock, height: 5}}, // msp3 428 }, 429 postSetup: func(t *testing.T, def *preparedTest) { 430 def.localEndorser.ProcessProposalReturns(createErrorResponse(t, 500, "error in simulation: chaincode stream terminated", nil), nil) 431 peer1Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(createErrorResponse(t, 500, "error in simulation: chaincode stream terminated", nil), nil) 432 }, 433 endorsingOrgs: []string{"msp1"}, 434 errCode: codes.Aborted, 435 errString: "failed to evaluate transaction, see attached details for more info", 436 errDetails: []*pb.ErrorDetail{ 437 { 438 Address: "localhost:7051", 439 MspId: "msp1", 440 Message: "error in simulation: chaincode stream terminated", 441 }, 442 { 443 Address: "peer1:8051", 444 MspId: "msp1", 445 Message: "error in simulation: chaincode stream terminated", 446 }, 447 }, 448 }, 449 { 450 name: "dialing endorser endpoint fails", 451 members: []networkMember{ 452 {"id3", "peer2:9051", "msp2", 5}, 453 }, 454 postSetup: func(t *testing.T, def *preparedTest) { 455 def.dialer.Calls(func(_ context.Context, target string, _ ...grpc.DialOption) (*grpc.ClientConn, error) { 456 if target == "peer2:9051" { 457 return nil, fmt.Errorf("endorser not answering") 458 } 459 return nil, nil 460 }) 461 }, 462 errCode: codes.Unavailable, 463 errString: "failed to create new connection: endorser not answering", 464 }, 465 { 466 name: "discovery returns incomplete information - no Properties", 467 postSetup: func(t *testing.T, def *preparedTest) { 468 def.discovery.PeersOfChannelReturns([]gdiscovery.NetworkMember{{ 469 Endpoint: "localhost:7051", 470 PKIid: []byte("ill-defined"), 471 }}) 472 }, 473 errCode: codes.FailedPrecondition, 474 errString: "no peers available to evaluate chaincode test_chaincode in channel test_channel", 475 }, 476 { 477 name: "context timeout during evaluate", 478 plan: endorsementPlan{ 479 "g1": {{endorser: localhostMock, height: 3}}, // msp1 480 }, 481 postSetup: func(t *testing.T, def *preparedTest) { 482 var cancel context.CancelFunc 483 def.ctx, cancel = context.WithTimeout(def.ctx, 100*time.Millisecond) 484 485 def.localEndorser.ProcessProposalStub = func(ctx context.Context, proposal *peer.SignedProposal, option ...grpc.CallOption) (*peer.ProposalResponse, error) { 486 cancel() 487 time.Sleep(200 * time.Millisecond) 488 return createProposalResponse(t, peer1Mock.address, "mock_response", 200, ""), nil 489 } 490 }, 491 errCode: codes.DeadlineExceeded, 492 errString: "evaluate timeout expired", 493 }, 494 } 495 for _, tt := range tests { 496 t.Run(tt.name, func(t *testing.T) { 497 test := prepareTest(t, &tt) 498 499 response, err := test.server.Evaluate(test.ctx, &pb.EvaluateRequest{ProposedTransaction: test.signedProposal, TargetOrganizations: tt.endorsingOrgs}) 500 501 if checkError(t, &tt, err) { 502 require.Nil(t, response, "response on error") 503 return 504 } 505 506 // test the assertions 507 508 require.NoError(t, err) 509 // assert the result is the payload from the proposal response returned by the local endorser 510 require.Equal(t, []byte("mock_response"), response.Result.Payload, "Incorrect result") 511 512 // check the correct endorsers (mock) were called with the right parameters 513 checkEndorsers(t, tt.expectedEndorsers, test) 514 515 // check the discovery service (mock) was invoked as expected 516 expectedChannel := common.ChannelID(testChannel) 517 require.Equal(t, 2, test.discovery.PeersOfChannelCallCount()) 518 channel := test.discovery.PeersOfChannelArgsForCall(0) 519 require.Equal(t, expectedChannel, channel) 520 channel = test.discovery.PeersOfChannelArgsForCall(1) 521 require.Equal(t, expectedChannel, channel) 522 523 require.Equal(t, 1, test.discovery.IdentityInfoCallCount()) 524 }) 525 } 526 } 527 528 func TestEndorse(t *testing.T) { 529 tests := []testDef{ 530 { 531 name: "two endorsers", 532 plan: endorsementPlan{ 533 "g1": {{endorser: localhostMock, height: 3}}, // msp1 534 "g2": {{endorser: peer2Mock, height: 3}}, // msp2 535 }, 536 expectedEndorsers: []string{"localhost:7051", "peer2:9051"}, 537 }, 538 { 539 name: "three endorsers, two groups", 540 plan: endorsementPlan{ 541 "g1": {{endorser: localhostMock, height: 4}}, // msp1 542 "g2": {{endorser: peer3Mock, height: 4}, {endorser: peer2Mock, height: 5}}, // msp2 543 }, 544 expectedEndorsers: []string{"localhost:7051", "peer2:9051"}, 545 }, 546 { 547 name: "multiple endorsers, two groups, prefer host peer", 548 plan: endorsementPlan{ 549 "g1": {{endorser: peer1Mock, height: 4}, {endorser: localhostMock, height: 4}, {endorser: unavailable1Mock, height: 4}}, // msp1 550 "g2": {{endorser: peer3Mock, height: 4}, {endorser: peer2Mock, height: 5}}, // msp2 551 }, 552 expectedEndorsers: []string{"localhost:7051", "peer2:9051"}, 553 }, 554 { 555 name: "endorse with specified orgs, despite block height", 556 endorsingOrgs: []string{"msp1", "msp3"}, 557 expectedEndorsers: []string{"localhost:7051", "peer4:11051"}, 558 }, 559 { 560 name: "endorse with specified orgs, doesn't include local peer", 561 endorsingOrgs: []string{"msp2", "msp3"}, 562 expectedEndorsers: []string{"peer2:9051", "peer4:11051"}, 563 }, 564 { 565 name: "endorse with specified orgs, but fails to satisfy one org", 566 endorsingOrgs: []string{"msp2", "msp4"}, 567 errCode: codes.Unavailable, 568 errString: "failed to find any endorsing peers for org(s): msp4", 569 }, 570 { 571 name: "endorse with specified orgs, but fails to satisfy two orgs", 572 endorsingOrgs: []string{"msp2", "msp4", "msp5"}, 573 errCode: codes.Unavailable, 574 errString: "failed to find any endorsing peers for org(s): msp4, msp5", 575 }, 576 { 577 name: "endorse retry - localhost and peer3 fail - retry on peer1 and peer2", 578 plan: endorsementPlan{ 579 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 580 "g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 581 "g3": {{endorser: peer4Mock, height: 5}}, // msp3 582 }, 583 layouts: []endorsementLayout{ 584 {"g1": 1, "g2": 1}, 585 }, 586 postSetup: func(t *testing.T, def *preparedTest) { 587 def.localEndorser.ProcessProposalReturns(nil, status.Error(codes.Aborted, "bad local endorser")) 588 peer3Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(nil, status.Error(codes.Aborted, "bad peer3 endorser")) 589 }, 590 expectedEndorsers: []string{"peer1:8051", "peer2:9051"}, 591 }, 592 { 593 name: "endorse retry - org3 fail - retry with layout 3", 594 plan: endorsementPlan{ 595 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 596 "g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 597 "g3": {{endorser: peer4Mock, height: 5}}, // msp3 598 }, 599 layouts: []endorsementLayout{ 600 {"g1": 1, "g3": 1}, 601 {"g2": 1, "g3": 1}, 602 {"g1": 1, "g2": 1}, 603 }, 604 postSetup: func(t *testing.T, def *preparedTest) { 605 peer2Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(nil, status.Error(codes.Aborted, "bad peer2 endorser")) 606 peer3Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(createProposalResponse(t, peer3Mock.address, "mock_response", 200, ""), nil) 607 peer4Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(nil, status.Error(codes.Aborted, "bad peer4 endorser")) 608 }, 609 expectedEndorsers: []string{"localhost:7051", "peer3:10051"}, 610 }, 611 { 612 name: "endorse retry - org3 fail & 1 org2 peer fail - requires 2 from org1", 613 plan: endorsementPlan{ 614 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 615 "g2": {{endorser: peer2Mock, height: 5}, {endorser: peer3Mock, height: 4}}, // msp2 616 "g3": {{endorser: peer4Mock, height: 5}}, // msp3 617 }, 618 layouts: []endorsementLayout{ 619 {"g1": 1, "g2": 1, "g3": 1}, 620 {"g1": 1, "g2": 2}, 621 {"g1": 2, "g2": 1}, 622 }, 623 postSetup: func(t *testing.T, def *preparedTest) { 624 peer2Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(nil, status.Error(codes.Aborted, "bad peer2 endorser")) 625 peer3Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(createProposalResponse(t, peer3Mock.address, "mock_response", 200, ""), nil) 626 peer4Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(nil, status.Error(codes.Aborted, "bad peer4 endorser")) 627 }, 628 expectedEndorsers: []string{"localhost:7051", "peer1:8051", "peer3:10051"}, 629 }, 630 { 631 name: "endorse retry - org 2 & org3 fail - fails to endorse", 632 plan: endorsementPlan{ 633 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 634 "g2": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 635 "g3": {{endorser: peer4Mock, height: 5}}, // msp3 636 }, 637 layouts: []endorsementLayout{ 638 {"g1": 1, "g3": 1}, 639 {"g2": 1, "g3": 1}, 640 {"g1": 1, "g2": 1}, 641 }, 642 postSetup: func(t *testing.T, def *preparedTest) { 643 peer2Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(nil, status.Error(codes.Aborted, "bad peer2 endorser")) 644 peer3Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(nil, status.Error(codes.Aborted, "bad peer3 endorser")) 645 peer4Mock.client.(*mocks.EndorserClient).ProcessProposalReturns(nil, status.Error(codes.Aborted, "bad peer4 endorser")) 646 }, 647 errCode: codes.Aborted, 648 errString: "failed to collect enough transaction endorsements, see attached details for more info", 649 errDetails: []*pb.ErrorDetail{ 650 {Address: "peer2:9051", MspId: "msp2", Message: "rpc error: code = Aborted desc = bad peer2 endorser"}, 651 {Address: "peer3:10051", MspId: "msp2", Message: "rpc error: code = Aborted desc = bad peer3 endorser"}, 652 {Address: "peer4:11051", MspId: "msp3", Message: "rpc error: code = Aborted desc = bad peer4 endorser"}, 653 }, 654 }, 655 { 656 name: "endorse with multiple layouts - non-availability forces second layout", 657 plan: endorsementPlan{ 658 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 659 "g2": {{endorser: unavailable1Mock, height: 3}, {endorser: unavailable2Mock, height: 4}}, // msp2 660 "g3": {{endorser: peer4Mock, height: 5}}, // msp3 661 }, 662 layouts: []endorsementLayout{ 663 {"g1": 1, "g2": 1}, 664 {"g1": 1, "g3": 1}, 665 {"g2": 1, "g3": 1}, 666 }, 667 expectedEndorsers: []string{"localhost:7051", "peer4:11051"}, 668 }, 669 { 670 name: "non-local endorsers", 671 plan: endorsementPlan{ 672 "g1": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 673 "g2": {{endorser: peer4Mock, height: 5}}, // msp3 674 }, 675 layouts: []endorsementLayout{ 676 {"g1": 1, "g2": 1}, 677 }, 678 members: []networkMember{ 679 {"id2", "peer2:9051", "msp2", 3}, 680 {"id3", "peer3:10051", "msp2", 4}, 681 {"id4", "peer4:11051", "msp3", 5}, 682 }, 683 expectedEndorsers: []string{"peer3:10051", "peer4:11051"}, 684 }, 685 { 686 name: "local endorser is not in the endorsement plan", 687 plan: endorsementPlan{ 688 "g1": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 689 "g2": {{endorser: peer4Mock, height: 5}}, // msp3 690 }, 691 layouts: []endorsementLayout{ 692 {"g1": 1, "g2": 1}, 693 }, 694 members: []networkMember{ 695 {"id1", "localhost:7051", "msp1", 3}, 696 {"id2", "peer2:9051", "msp2", 3}, 697 {"id3", "peer3:10051", "msp2", 4}, 698 {"id4", "peer4:11051", "msp3", 5}, 699 }, 700 expectedEndorsers: []string{"peer3:10051", "peer4:11051"}, 701 }, 702 { 703 name: "non-local endorsers with transient data will fail", 704 plan: endorsementPlan{ 705 "g1": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 706 "g2": {{endorser: peer4Mock, height: 5}}, // msp3 707 }, 708 members: []networkMember{ 709 {"id2", "peer2:9051", "msp2", 3}, 710 {"id3", "peer3:10051", "msp2", 4}, 711 {"id4", "peer4:11051", "msp3", 5}, 712 }, 713 transientData: map[string][]byte{"transient-key": []byte("transient-value")}, 714 errCode: codes.FailedPrecondition, 715 errString: "no endorsers found in the gateway's organization; retry specifying endorsing organization(s) to protect transient data", 716 }, 717 { 718 name: "extra endorsers with transient data", 719 plan: endorsementPlan{ 720 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 721 "g2": {{endorser: peer4Mock, height: 5}}, // msp3 722 }, 723 transientData: map[string][]byte{"transient-key": []byte("transient-value")}, 724 expectedEndorsers: []string{"localhost:7051", "peer4:11051"}, 725 }, 726 { 727 name: "non-local endorsers with transient data and set endorsing orgs", 728 plan: endorsementPlan{ 729 "g1": {{endorser: peer2Mock, height: 3}, {endorser: peer3Mock, height: 4}}, // msp2 730 "g2": {{endorser: peer4Mock, height: 5}}, // msp3 731 }, 732 members: []networkMember{ 733 {"id2", "peer2:9051", "msp2", 3}, 734 {"id3", "peer3:10051", "msp2", 4}, 735 {"id4", "peer4:11051", "msp3", 5}, 736 }, 737 endorsingOrgs: []string{"msp2", "msp3"}, 738 transientData: map[string][]byte{"transient-key": []byte("transient-value")}, 739 expectedEndorsers: []string{"peer3:10051", "peer4:11051"}, 740 }, 741 { 742 name: "endorse with multiple layouts - non-availability of peers fails on all layouts", 743 plan: endorsementPlan{ 744 "g1": {{endorser: localhostMock, height: 4}, {endorser: peer1Mock, height: 4}}, // msp1 745 "g2": {{endorser: unavailable1Mock, height: 3}, {endorser: unavailable2Mock, height: 4}}, // msp2 746 "g3": {{endorser: unavailable3Mock, height: 5}}, // msp3 747 }, 748 layouts: []endorsementLayout{ 749 {"g1": 1, "g2": 1}, 750 {"g1": 1, "g3": 1}, 751 {"g2": 1, "g3": 1}, 752 }, 753 errCode: codes.FailedPrecondition, 754 // the following is a substring of the error message - the endpoints get listed in indeterminate order which would lead to flaky test 755 errString: "failed to select a set of endorsers that satisfy the endorsement policy due to unavailability of peers", 756 }, 757 { 758 name: "non-matching responses", 759 plan: endorsementPlan{ 760 "g1": {{endorser: localhostMock, height: 4}}, // msp1 761 "g2": {{endorser: peer2Mock, height: 5}}, // msp2 762 }, 763 localResponse: "different_response", 764 errCode: codes.Aborted, 765 errString: "failed to collect enough transaction endorsements", 766 errDetails: []*pb.ErrorDetail{ 767 { 768 Address: "peer2:9051", 769 MspId: "msp2", 770 Message: "ProposalResponsePayloads do not match", 771 }, 772 }, 773 }, 774 { 775 name: "discovery fails", 776 plan: endorsementPlan{ 777 "g1": {{endorser: localhostMock, height: 2}}, 778 }, 779 postSetup: func(t *testing.T, def *preparedTest) { 780 def.discovery.PeersForEndorsementReturns(nil, fmt.Errorf("peach-melba")) 781 }, 782 errCode: codes.FailedPrecondition, 783 errString: "no combination of peers can be derived which satisfy the endorsement policy: peach-melba", 784 }, 785 { 786 name: "discovery returns incomplete protos - nil layout", 787 plan: endorsementPlan{ 788 "g1": {{endorser: localhostMock, height: 2}}, 789 }, 790 postSetup: func(t *testing.T, def *preparedTest) { 791 ed := &dp.EndorsementDescriptor{ 792 Chaincode: "my_channel", 793 Layouts: []*dp.Layout{nil}, 794 } 795 def.discovery.PeersForEndorsementReturns(ed, nil) 796 }, 797 errCode: codes.FailedPrecondition, 798 errString: "failed to select a set of endorsers that satisfy the endorsement policy", 799 }, 800 { 801 name: "discovery returns incomplete protos - nil state info", 802 plan: endorsementPlan{ 803 "g1": {{endorser: localhostMock, height: 2}}, 804 }, 805 postSetup: func(t *testing.T, def *preparedTest) { 806 ed := &dp.EndorsementDescriptor{ 807 Chaincode: "my_channel", 808 Layouts: []*dp.Layout{{QuantitiesByGroup: map[string]uint32{"g1": 1}}}, 809 EndorsersByGroups: map[string]*dp.Peers{"g1": {Peers: []*dp.Peer{{StateInfo: nil}}}}, 810 } 811 def.discovery.PeersForEndorsementReturns(ed, nil) 812 }, 813 errCode: codes.FailedPrecondition, 814 errString: "failed to select a set of endorsers that satisfy the endorsement policy", 815 }, 816 { 817 name: "process proposal fails", 818 plan: endorsementPlan{ 819 "g1": {{endorser: localhostMock, height: 1}}, 820 }, 821 endpointDefinition: &endpointDef{ 822 proposalError: status.Error(codes.Aborted, "wibble"), 823 }, 824 errCode: codes.Aborted, 825 errString: "failed to endorse transaction, see attached details for more info", 826 errDetails: []*pb.ErrorDetail{ 827 { 828 Address: "localhost:7051", 829 MspId: "msp1", 830 Message: "rpc error: code = Aborted desc = wibble", 831 }, 832 { 833 Address: "peer1:8051", 834 MspId: "msp1", 835 Message: "rpc error: code = Aborted desc = wibble", 836 }, 837 }, 838 }, 839 { 840 name: "local endorser succeeds, remote endorser fails", 841 plan: endorsementPlan{ 842 "g1": {{endorser: localhostMock, height: 1}}, 843 "g2": {{endorser: peer4Mock, height: 1}}, 844 }, 845 endpointDefinition: &endpointDef{ 846 proposalError: status.Error(codes.Aborted, "remote-wobble"), 847 }, 848 postSetup: func(t *testing.T, def *preparedTest) { 849 def.localEndorser.ProcessProposalReturns(createProposalResponse(t, localhostMock.address, "all_good", 200, ""), nil) 850 }, 851 errCode: codes.Aborted, 852 errString: "failed to collect enough transaction endorsements, see attached details for more info", 853 errDetails: []*pb.ErrorDetail{{ 854 Address: "peer4:11051", 855 MspId: "msp3", 856 Message: "rpc error: code = Aborted desc = remote-wobble", 857 }}, 858 }, 859 { 860 name: "process proposal chaincode error", 861 plan: endorsementPlan{ 862 "g1": {{endorser: localhostMock, height: 2}}, 863 }, 864 endpointDefinition: &endpointDef{ 865 proposalResponseStatus: 400, 866 proposalResponseMessage: "Mock chaincode error", 867 }, 868 errCode: codes.Aborted, 869 errString: "failed to endorse transaction, see attached details for more info", 870 errDetails: []*pb.ErrorDetail{ 871 { 872 Address: "localhost:7051", 873 MspId: "msp1", 874 Message: "chaincode response 400, Mock chaincode error", 875 }, 876 { 877 Address: "peer1:8051", 878 MspId: "msp1", 879 Message: "chaincode response 400, Mock chaincode error", 880 }, 881 }, 882 }, 883 { 884 name: "local endorser succeeds, remote endorser chaincode error", 885 plan: endorsementPlan{ 886 "g1": {{endorser: localhostMock, height: 1}}, 887 "g2": {{endorser: peer4Mock, height: 1}}, 888 }, 889 endpointDefinition: &endpointDef{ 890 proposalResponseStatus: 400, 891 proposalResponseMessage: "Mock chaincode error", 892 }, 893 postSetup: func(t *testing.T, def *preparedTest) { 894 def.localEndorser.ProcessProposalReturns(createProposalResponse(t, localhostMock.address, "all_good", 200, ""), nil) 895 }, 896 errCode: codes.Aborted, 897 errString: "failed to collect enough transaction endorsements, see attached details for more info", 898 errDetails: []*pb.ErrorDetail{{ 899 Address: "peer4:11051", 900 MspId: "msp3", 901 Message: "chaincode response 400, Mock chaincode error", 902 }}, 903 }, 904 { 905 name: "first endorser returns chaincode interest", 906 plan: endorsementPlan{ 907 "g1": {{endorser: localhostMock, height: 3}}, 908 "g2": {{endorser: peer2Mock, height: 3}}, 909 }, 910 interest: &peer.ChaincodeInterest{ 911 Chaincodes: []*peer.ChaincodeCall{{ 912 Name: testChaincode, 913 CollectionNames: []string{"mycollection1", "mycollection2"}, 914 NoPrivateReads: true, 915 }}, 916 }, 917 expectedEndorsers: []string{"localhost:7051", "peer2:9051"}, 918 }, 919 { 920 name: "context timeout during first endorsement", 921 plan: endorsementPlan{ 922 "g1": {{endorser: localhostMock, height: 3}}, // msp1 923 "g2": {{endorser: peer4Mock, height: 5}}, // msp3 924 }, 925 postSetup: func(t *testing.T, def *preparedTest) { 926 var cancel context.CancelFunc 927 def.ctx, cancel = context.WithTimeout(def.ctx, 100*time.Millisecond) 928 929 def.localEndorser.ProcessProposalStub = func(ctx context.Context, proposal *peer.SignedProposal, option ...grpc.CallOption) (*peer.ProposalResponse, error) { 930 cancel() 931 time.Sleep(200 * time.Millisecond) 932 return createProposalResponse(t, peer1Mock.address, "mock_response", 200, ""), nil 933 } 934 }, 935 errCode: codes.DeadlineExceeded, 936 errString: "endorsement timeout expired while collecting first endorsement", 937 }, 938 { 939 name: "context timeout collecting endorsements", 940 plan: endorsementPlan{ 941 "g1": {{endorser: localhostMock, height: 3}}, // msp1 942 "g2": {{endorser: peer4Mock, height: 5}}, // msp3 943 }, 944 postSetup: func(t *testing.T, def *preparedTest) { 945 var cancel context.CancelFunc 946 def.ctx, cancel = context.WithTimeout(def.ctx, 100*time.Millisecond) 947 948 peer4Mock.client.(*mocks.EndorserClient).ProcessProposalStub = func(ctx context.Context, proposal *peer.SignedProposal, option ...grpc.CallOption) (*peer.ProposalResponse, error) { 949 cancel() 950 time.Sleep(200 * time.Millisecond) 951 return createProposalResponse(t, peer4Mock.address, "mock_response", 200, ""), nil 952 } 953 }, 954 errCode: codes.DeadlineExceeded, 955 errString: "endorsement timeout expired while collecting endorsements", 956 }, 957 } 958 for _, tt := range tests { 959 t.Run(tt.name, func(t *testing.T) { 960 test := prepareTest(t, &tt) 961 962 response, err := test.server.Endorse(test.ctx, &pb.EndorseRequest{ProposedTransaction: test.signedProposal, EndorsingOrganizations: tt.endorsingOrgs}) 963 964 if checkError(t, &tt, err) { 965 require.Nil(t, response, "response on error") 966 return 967 } 968 969 // test the assertions 970 require.NoError(t, err) 971 972 // assert the preparedTxn is the payload from the proposal response 973 chaincodeAction, err := protoutil.GetActionFromEnvelopeMsg(response.GetPreparedTransaction()) 974 require.NoError(t, err) 975 require.Equal(t, []byte("mock_response"), chaincodeAction.GetResponse().GetPayload(), "Incorrect response") 976 977 // check the generated transaction envelope contains the correct endorsements 978 checkTransaction(t, tt.expectedEndorsers, response.GetPreparedTransaction()) 979 980 // check the correct endorsers (mocks) were called with the right parameters 981 checkEndorsers(t, tt.expectedEndorsers, test) 982 }) 983 } 984 } 985 986 func TestSubmit(t *testing.T) { 987 tests := []testDef{ 988 { 989 name: "two endorsers", 990 plan: endorsementPlan{ 991 "g1": {{endorser: localhostMock, height: 3}}, 992 "g2": {{endorser: peer1Mock, height: 3}}, 993 }, 994 }, 995 { 996 name: "discovery fails", 997 plan: endorsementPlan{ 998 "g1": {{endorser: localhostMock}}, 999 }, 1000 postSetup: func(t *testing.T, def *preparedTest) { 1001 def.discovery.ConfigReturnsOnCall(1, nil, fmt.Errorf("jabberwocky")) 1002 }, 1003 errCode: codes.FailedPrecondition, 1004 errString: "failed to get config for channel [test_channel]: jabberwocky", 1005 }, 1006 { 1007 name: "no orderers", 1008 plan: endorsementPlan{ 1009 "g1": {{endorser: localhostMock}}, 1010 }, 1011 postSetup: func(t *testing.T, def *preparedTest) { 1012 def.discovery.ConfigReturns(&dp.ConfigResult{ 1013 Orderers: map[string]*dp.Endpoints{}, 1014 Msps: map[string]*msp.FabricMSPConfig{}, 1015 }, nil) 1016 }, 1017 errCode: codes.Unavailable, 1018 errString: "no orderer nodes available", 1019 }, 1020 { 1021 name: "orderer broadcast fails", 1022 plan: endorsementPlan{ 1023 "g1": {{endorser: localhostMock}}, 1024 }, 1025 endpointDefinition: &endpointDef{ 1026 proposalResponseStatus: 200, 1027 ordererBroadcastError: status.Error(codes.FailedPrecondition, "Orderer not listening!"), 1028 }, 1029 errCode: codes.FailedPrecondition, 1030 errString: "Orderer not listening!", 1031 errDetails: []*pb.ErrorDetail{{ 1032 Address: "orderer:7050", 1033 MspId: "msp1", 1034 Message: "rpc error: code = FailedPrecondition desc = Orderer not listening!", 1035 }}, 1036 }, 1037 { 1038 name: "send to orderer fails", 1039 plan: endorsementPlan{ 1040 "g1": {{endorser: localhostMock}}, 1041 }, 1042 endpointDefinition: &endpointDef{ 1043 proposalResponseStatus: 200, 1044 ordererSendError: status.Error(codes.Internal, "Orderer says no!"), 1045 }, 1046 errCode: codes.Internal, 1047 errString: "Orderer says no!", 1048 errDetails: []*pb.ErrorDetail{{ 1049 Address: "orderer:7050", 1050 MspId: "msp1", 1051 Message: "rpc error: code = Internal desc = Orderer says no!", 1052 }}, 1053 }, 1054 { 1055 name: "receive from orderer fails", 1056 plan: endorsementPlan{ 1057 "g1": {{endorser: localhostMock}}, 1058 }, 1059 endpointDefinition: &endpointDef{ 1060 proposalResponseStatus: 200, 1061 ordererRecvError: status.Error(codes.FailedPrecondition, "Orderer not happy!"), 1062 }, 1063 errCode: codes.FailedPrecondition, 1064 errString: "Orderer not happy!", 1065 errDetails: []*pb.ErrorDetail{{ 1066 Address: "orderer:7050", 1067 MspId: "msp1", 1068 Message: "rpc error: code = FailedPrecondition desc = Orderer not happy!", 1069 }}, 1070 }, 1071 { 1072 name: "orderer Send() returns nil", 1073 plan: endorsementPlan{ 1074 "g1": {{endorser: localhostMock}}, 1075 }, 1076 postSetup: func(t *testing.T, def *preparedTest) { 1077 def.server.registry.endpointFactory.connectOrderer = func(_ *grpc.ClientConn) ab.AtomicBroadcastClient { 1078 abc := &mocks.ABClient{} 1079 abbc := &mocks.ABBClient{} 1080 abbc.RecvReturns(nil, nil) 1081 abc.BroadcastReturns(abbc, nil) 1082 return abc 1083 } 1084 }, 1085 errCode: codes.Aborted, 1086 errString: "received unsuccessful response from orderer", 1087 errDetails: []*pb.ErrorDetail{{ 1088 Address: "orderer:7050", 1089 MspId: "msp1", 1090 Message: "rpc error: code = Aborted desc = received unsuccessful response from orderer: " + cp.Status_name[int32(cp.Status_UNKNOWN)], 1091 }}, 1092 }, 1093 { 1094 name: "orderer returns unsuccessful response", 1095 plan: endorsementPlan{ 1096 "g1": {{endorser: localhostMock}}, 1097 }, 1098 postSetup: func(t *testing.T, def *preparedTest) { 1099 def.server.registry.endpointFactory.connectOrderer = func(_ *grpc.ClientConn) ab.AtomicBroadcastClient { 1100 abc := &mocks.ABClient{} 1101 abbc := &mocks.ABBClient{} 1102 response := &ab.BroadcastResponse{ 1103 Status: cp.Status_BAD_REQUEST, 1104 } 1105 abbc.RecvReturns(response, nil) 1106 abc.BroadcastReturns(abbc, nil) 1107 return abc 1108 } 1109 }, 1110 errCode: codes.Aborted, 1111 errString: "received unsuccessful response from orderer: " + cp.Status_name[int32(cp.Status_BAD_REQUEST)], 1112 errDetails: []*pb.ErrorDetail{{ 1113 Address: "orderer:7050", 1114 MspId: "msp1", 1115 Message: "rpc error: code = Aborted desc = received unsuccessful response from orderer: " + cp.Status_name[int32(cp.Status_BAD_REQUEST)], 1116 }}, 1117 }, 1118 { 1119 name: "dialing orderer endpoint fails", 1120 plan: endorsementPlan{ 1121 "g1": {{endorser: localhostMock}}, 1122 }, 1123 postSetup: func(t *testing.T, def *preparedTest) { 1124 def.dialer.Calls(func(_ context.Context, target string, _ ...grpc.DialOption) (*grpc.ClientConn, error) { 1125 if target == "orderer:7050" { 1126 return nil, fmt.Errorf("orderer not answering") 1127 } 1128 return nil, nil 1129 }) 1130 }, 1131 errCode: codes.Unavailable, 1132 errString: "no orderer nodes available", 1133 }, 1134 { 1135 name: "orderer retry", 1136 plan: endorsementPlan{ 1137 "g1": {{endorser: localhostMock}}, 1138 }, 1139 config: &dp.ConfigResult{ 1140 Orderers: map[string]*dp.Endpoints{ 1141 "msp1": { 1142 Endpoint: []*dp.Endpoint{ 1143 {Host: "orderer1", Port: 7050}, 1144 {Host: "orderer2", Port: 7050}, 1145 {Host: "orderer3", Port: 7050}, 1146 }, 1147 }, 1148 }, 1149 Msps: map[string]*msp.FabricMSPConfig{ 1150 "msp1": { 1151 TlsRootCerts: [][]byte{}, 1152 }, 1153 }, 1154 }, 1155 postSetup: func(t *testing.T, def *preparedTest) { 1156 abc := &mocks.ABClient{} 1157 abbc := &mocks.ABBClient{} 1158 abbc.SendReturnsOnCall(0, status.Error(codes.Unavailable, "First orderer error")) 1159 abbc.SendReturnsOnCall(1, status.Error(codes.Unavailable, "Second orderer error")) 1160 abbc.SendReturnsOnCall(2, nil) // third time lucky 1161 abbc.RecvReturns(&ab.BroadcastResponse{ 1162 Info: "success", 1163 Status: cp.Status(200), 1164 }, nil) 1165 abc.BroadcastReturns(abbc, nil) 1166 def.server.registry.endpointFactory = &endpointFactory{ 1167 timeout: 5 * time.Second, 1168 connectEndorser: func(conn *grpc.ClientConn) peer.EndorserClient { 1169 return &mocks.EndorserClient{} 1170 }, 1171 connectOrderer: func(_ *grpc.ClientConn) ab.AtomicBroadcastClient { 1172 return abc 1173 }, 1174 dialer: func(ctx context.Context, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 1175 return nil, nil 1176 }, 1177 } 1178 }, 1179 }, 1180 { 1181 name: "multiple orderers all fail", 1182 plan: endorsementPlan{ 1183 "g1": {{endorser: localhostMock}}, 1184 }, 1185 config: &dp.ConfigResult{ 1186 Orderers: map[string]*dp.Endpoints{ 1187 "msp1": { 1188 Endpoint: []*dp.Endpoint{ 1189 {Host: "orderer1", Port: 7050}, 1190 {Host: "orderer2", Port: 7050}, 1191 {Host: "orderer3", Port: 7050}, 1192 }, 1193 }, 1194 }, 1195 Msps: map[string]*msp.FabricMSPConfig{ 1196 "msp1": { 1197 TlsRootCerts: [][]byte{}, 1198 }, 1199 }, 1200 }, 1201 endpointDefinition: &endpointDef{ 1202 proposalResponseStatus: 200, 1203 ordererBroadcastError: status.Error(codes.Unavailable, "Orderer not listening!"), 1204 }, 1205 errCode: codes.Unavailable, 1206 errString: "no orderers could successfully process transaction", 1207 errDetails: []*pb.ErrorDetail{ 1208 { 1209 Address: "orderer1:7050", 1210 MspId: "msp1", 1211 Message: "rpc error: code = Unavailable desc = Orderer not listening!", 1212 }, 1213 { 1214 Address: "orderer2:7050", 1215 MspId: "msp1", 1216 Message: "rpc error: code = Unavailable desc = Orderer not listening!", 1217 }, 1218 { 1219 Address: "orderer3:7050", 1220 MspId: "msp1", 1221 Message: "rpc error: code = Unavailable desc = Orderer not listening!", 1222 }, 1223 }, 1224 }, 1225 } 1226 for _, tt := range tests { 1227 t.Run(tt.name, func(t *testing.T) { 1228 test := prepareTest(t, &tt) 1229 1230 // first call endorse to prepare the tx 1231 endorseResponse, err := test.server.Endorse(test.ctx, &pb.EndorseRequest{ProposedTransaction: test.signedProposal}) 1232 require.NoError(t, err) 1233 1234 preparedTx := endorseResponse.GetPreparedTransaction() 1235 1236 // sign the envelope 1237 preparedTx.Signature = []byte("mysignature") 1238 1239 // submit 1240 submitResponse, err := test.server.Submit(test.ctx, &pb.SubmitRequest{PreparedTransaction: preparedTx, ChannelId: testChannel}) 1241 1242 if checkError(t, &tt, err) { 1243 require.Nil(t, submitResponse, "response on error") 1244 return 1245 } 1246 1247 require.NoError(t, err) 1248 require.True(t, proto.Equal(&pb.SubmitResponse{}, submitResponse), "Incorrect response") 1249 }) 1250 } 1251 } 1252 1253 func TestSubmitUnsigned(t *testing.T) { 1254 server := &Server{} 1255 req := &pb.SubmitRequest{ 1256 TransactionId: "transaction-id", 1257 ChannelId: "channel-id", 1258 PreparedTransaction: &cp.Envelope{}, 1259 } 1260 _, err := server.Submit(context.Background(), req) 1261 require.Error(t, err) 1262 require.Equal(t, err, status.Error(codes.InvalidArgument, "prepared transaction must be signed")) 1263 } 1264 1265 func TestCommitStatus(t *testing.T) { 1266 tests := []testDef{ 1267 { 1268 name: "error finding transaction status", 1269 finderErr: errors.New("FINDER_ERROR"), 1270 errCode: codes.Aborted, 1271 errString: "FINDER_ERROR", 1272 }, 1273 { 1274 name: "returns transaction status", 1275 finderStatus: &commit.Status{ 1276 Code: peer.TxValidationCode_MVCC_READ_CONFLICT, 1277 BlockNumber: 101, 1278 }, 1279 expectedResponse: &pb.CommitStatusResponse{ 1280 Result: peer.TxValidationCode_MVCC_READ_CONFLICT, 1281 BlockNumber: 101, 1282 }, 1283 }, 1284 { 1285 name: "passes channel name to finder", 1286 postSetup: func(t *testing.T, test *preparedTest) { 1287 test.finder.TransactionStatusCalls(func(ctx context.Context, channelName string, transactionID string) (*commit.Status, error) { 1288 require.Equal(t, testChannel, channelName) 1289 status := &commit.Status{ 1290 Code: peer.TxValidationCode_MVCC_READ_CONFLICT, 1291 BlockNumber: 101, 1292 } 1293 return status, nil 1294 }) 1295 }, 1296 }, 1297 { 1298 name: "passes transaction ID to finder", 1299 postSetup: func(t *testing.T, test *preparedTest) { 1300 test.finder.TransactionStatusCalls(func(ctx context.Context, channelName string, transactionID string) (*commit.Status, error) { 1301 require.Equal(t, "TX_ID", transactionID) 1302 status := &commit.Status{ 1303 Code: peer.TxValidationCode_MVCC_READ_CONFLICT, 1304 BlockNumber: 101, 1305 } 1306 return status, nil 1307 }) 1308 }, 1309 }, 1310 { 1311 name: "failed policy or signature check", 1312 policyErr: errors.New("POLICY_ERROR"), 1313 errCode: codes.PermissionDenied, 1314 errString: "POLICY_ERROR", 1315 }, 1316 { 1317 name: "passes channel name to policy checker", 1318 postSetup: func(t *testing.T, test *preparedTest) { 1319 test.policy.CheckACLCalls(func(policyName string, channelName string, data interface{}) error { 1320 require.Equal(t, testChannel, channelName) 1321 return nil 1322 }) 1323 }, 1324 finderStatus: &commit.Status{ 1325 Code: peer.TxValidationCode_MVCC_READ_CONFLICT, 1326 BlockNumber: 101, 1327 }, 1328 }, 1329 { 1330 name: "passes identity to policy checker", 1331 identity: []byte("IDENTITY"), 1332 postSetup: func(t *testing.T, test *preparedTest) { 1333 test.policy.CheckACLCalls(func(policyName string, channelName string, data interface{}) error { 1334 require.IsType(t, &protoutil.SignedData{}, data) 1335 signedData := data.(*protoutil.SignedData) 1336 require.Equal(t, []byte("IDENTITY"), signedData.Identity) 1337 return nil 1338 }) 1339 }, 1340 finderStatus: &commit.Status{ 1341 Code: peer.TxValidationCode_MVCC_READ_CONFLICT, 1342 BlockNumber: 101, 1343 }, 1344 }, 1345 { 1346 name: "context timeout", 1347 finderErr: context.DeadlineExceeded, 1348 errCode: codes.DeadlineExceeded, 1349 errString: "context deadline exceeded", 1350 }, 1351 { 1352 name: "context canceled", 1353 finderErr: context.Canceled, 1354 errCode: codes.Canceled, 1355 errString: "context canceled", 1356 }, 1357 } 1358 for _, tt := range tests { 1359 t.Run(tt.name, func(t *testing.T) { 1360 test := prepareTest(t, &tt) 1361 1362 request := &pb.CommitStatusRequest{ 1363 ChannelId: testChannel, 1364 Identity: tt.identity, 1365 TransactionId: "TX_ID", 1366 } 1367 requestBytes, err := proto.Marshal(request) 1368 require.NoError(t, err) 1369 1370 signedRequest := &pb.SignedCommitStatusRequest{ 1371 Request: requestBytes, 1372 Signature: []byte{}, 1373 } 1374 1375 response, err := test.server.CommitStatus(test.ctx, signedRequest) 1376 1377 if checkError(t, &tt, err) { 1378 require.Nil(t, response, "response on error") 1379 return 1380 } 1381 1382 require.NoError(t, err) 1383 if tt.expectedResponse != nil { 1384 require.True(t, proto.Equal(tt.expectedResponse, response), "incorrect response", response) 1385 } 1386 }) 1387 } 1388 } 1389 1390 func TestChaincodeEvents(t *testing.T) { 1391 now := time.Now() 1392 transactionId := "TRANSACTION_ID" 1393 1394 matchChaincodeEvent := &peer.ChaincodeEvent{ 1395 ChaincodeId: testChaincode, 1396 TxId: transactionId, 1397 EventName: "EVENT_NAME", 1398 Payload: []byte("PAYLOAD"), 1399 } 1400 1401 mismatchChaincodeEvent := &peer.ChaincodeEvent{ 1402 ChaincodeId: "WRONG_CHAINCODE_ID", 1403 TxId: transactionId, 1404 EventName: "EVENT_NAME", 1405 Payload: []byte("PAYLOAD"), 1406 } 1407 1408 txHeader := &cp.Header{ 1409 ChannelHeader: protoutil.MarshalOrPanic(&cp.ChannelHeader{ 1410 Type: int32(cp.HeaderType_ENDORSER_TRANSACTION), 1411 Timestamp: ×tamp.Timestamp{ 1412 Seconds: now.Unix(), 1413 Nanos: int32(now.Nanosecond()), 1414 }, 1415 TxId: transactionId, 1416 }), 1417 } 1418 1419 matchTxEnvelope := &cp.Envelope{ 1420 Payload: protoutil.MarshalOrPanic(&cp.Payload{ 1421 Header: txHeader, 1422 Data: protoutil.MarshalOrPanic(&peer.Transaction{ 1423 Actions: []*peer.TransactionAction{ 1424 { 1425 Payload: protoutil.MarshalOrPanic(&peer.ChaincodeActionPayload{ 1426 Action: &peer.ChaincodeEndorsedAction{ 1427 ProposalResponsePayload: protoutil.MarshalOrPanic(&peer.ProposalResponsePayload{ 1428 Extension: protoutil.MarshalOrPanic(&peer.ChaincodeAction{ 1429 Events: protoutil.MarshalOrPanic(matchChaincodeEvent), 1430 }), 1431 }), 1432 }, 1433 }), 1434 }, 1435 }, 1436 }), 1437 }), 1438 } 1439 1440 mismatchTxEnvelope := &cp.Envelope{ 1441 Payload: protoutil.MarshalOrPanic(&cp.Payload{ 1442 Header: txHeader, 1443 Data: protoutil.MarshalOrPanic(&peer.Transaction{ 1444 Actions: []*peer.TransactionAction{ 1445 { 1446 Payload: protoutil.MarshalOrPanic(&peer.ChaincodeActionPayload{ 1447 Action: &peer.ChaincodeEndorsedAction{ 1448 ProposalResponsePayload: protoutil.MarshalOrPanic(&peer.ProposalResponsePayload{ 1449 Extension: protoutil.MarshalOrPanic(&peer.ChaincodeAction{ 1450 Events: protoutil.MarshalOrPanic(mismatchChaincodeEvent), 1451 }), 1452 }), 1453 }, 1454 }), 1455 }, 1456 }, 1457 }), 1458 }), 1459 } 1460 1461 block100Proto := &cp.Block{ 1462 Header: &cp.BlockHeader{ 1463 Number: 100, 1464 }, 1465 Metadata: &cp.BlockMetadata{ 1466 Metadata: [][]byte{ 1467 nil, 1468 nil, 1469 { 1470 byte(peer.TxValidationCode_VALID), 1471 }, 1472 nil, 1473 nil, 1474 }, 1475 }, 1476 Data: &cp.BlockData{ 1477 Data: [][]byte{ 1478 protoutil.MarshalOrPanic(mismatchTxEnvelope), 1479 }, 1480 }, 1481 } 1482 1483 block101Proto := &cp.Block{ 1484 Header: &cp.BlockHeader{ 1485 Number: 101, 1486 }, 1487 Metadata: &cp.BlockMetadata{ 1488 Metadata: [][]byte{ 1489 nil, 1490 nil, 1491 { 1492 byte(peer.TxValidationCode_VALID), 1493 byte(peer.TxValidationCode_VALID), 1494 byte(peer.TxValidationCode_VALID), 1495 }, 1496 nil, 1497 nil, 1498 }, 1499 }, 1500 Data: &cp.BlockData{ 1501 Data: [][]byte{ 1502 protoutil.MarshalOrPanic(&cp.Envelope{ 1503 Payload: protoutil.MarshalOrPanic(&cp.Payload{ 1504 Header: &cp.Header{ 1505 ChannelHeader: protoutil.MarshalOrPanic(&cp.ChannelHeader{ 1506 Type: int32(cp.HeaderType_CONFIG_UPDATE), 1507 }), 1508 }, 1509 }), 1510 }), 1511 protoutil.MarshalOrPanic(mismatchTxEnvelope), 1512 protoutil.MarshalOrPanic(matchTxEnvelope), 1513 }, 1514 }, 1515 } 1516 1517 tests := []testDef{ 1518 { 1519 name: "error reading events", 1520 eventErr: errors.New("EVENT_ERROR"), 1521 errCode: codes.Aborted, 1522 errString: "EVENT_ERROR", 1523 }, 1524 { 1525 name: "returns chaincode events", 1526 blocks: []*cp.Block{ 1527 block101Proto, 1528 }, 1529 expectedResponses: []proto.Message{ 1530 &pb.ChaincodeEventsResponse{ 1531 BlockNumber: block101Proto.GetHeader().GetNumber(), 1532 Events: []*peer.ChaincodeEvent{ 1533 { 1534 ChaincodeId: testChaincode, 1535 TxId: matchChaincodeEvent.GetTxId(), 1536 EventName: matchChaincodeEvent.GetEventName(), 1537 Payload: matchChaincodeEvent.GetPayload(), 1538 }, 1539 }, 1540 }, 1541 }, 1542 }, 1543 { 1544 name: "skips blocks containing only non-matching chaincode events", 1545 blocks: []*cp.Block{ 1546 block100Proto, 1547 block101Proto, 1548 }, 1549 expectedResponses: []proto.Message{ 1550 &pb.ChaincodeEventsResponse{ 1551 BlockNumber: block101Proto.GetHeader().GetNumber(), 1552 Events: []*peer.ChaincodeEvent{ 1553 { 1554 ChaincodeId: testChaincode, 1555 TxId: matchChaincodeEvent.GetTxId(), 1556 EventName: matchChaincodeEvent.GetEventName(), 1557 Payload: matchChaincodeEvent.GetPayload(), 1558 }, 1559 }, 1560 }, 1561 }, 1562 }, 1563 { 1564 name: "passes channel name to ledger provider", 1565 postTest: func(t *testing.T, test *preparedTest) { 1566 require.Equal(t, 1, test.ledgerProvider.LedgerCallCount()) 1567 require.Equal(t, testChannel, test.ledgerProvider.LedgerArgsForCall(0)) 1568 }, 1569 }, 1570 { 1571 name: "returns error obtaining ledger", 1572 blocks: []*cp.Block{ 1573 block101Proto, 1574 }, 1575 errCode: codes.NotFound, 1576 errString: "LEDGER_PROVIDER_ERROR", 1577 postSetup: func(t *testing.T, test *preparedTest) { 1578 test.ledgerProvider.LedgerReturns(nil, errors.New("LEDGER_PROVIDER_ERROR")) 1579 }, 1580 }, 1581 { 1582 name: "returns error obtaining ledger height", 1583 blocks: []*cp.Block{ 1584 block101Proto, 1585 }, 1586 errCode: codes.Aborted, 1587 errString: "LEDGER_INFO_ERROR", 1588 postSetup: func(t *testing.T, test *preparedTest) { 1589 test.ledger.GetBlockchainInfoReturns(nil, errors.New("LEDGER_INFO_ERROR")) 1590 }, 1591 }, 1592 { 1593 name: "uses block height as start block if next commit is specified as start position", 1594 blocks: []*cp.Block{ 1595 block101Proto, 1596 }, 1597 postSetup: func(t *testing.T, test *preparedTest) { 1598 ledgerInfo := &cp.BlockchainInfo{ 1599 Height: 101, 1600 } 1601 test.ledger.GetBlockchainInfoReturns(ledgerInfo, nil) 1602 }, 1603 startPosition: &ab.SeekPosition{ 1604 Type: &ab.SeekPosition_NextCommit{ 1605 NextCommit: &ab.SeekNextCommit{}, 1606 }, 1607 }, 1608 postTest: func(t *testing.T, test *preparedTest) { 1609 require.Equal(t, 1, test.ledger.GetBlocksIteratorCallCount()) 1610 require.EqualValues(t, 101, test.ledger.GetBlocksIteratorArgsForCall(0)) 1611 }, 1612 }, 1613 { 1614 name: "uses specified start block", 1615 blocks: []*cp.Block{ 1616 block101Proto, 1617 }, 1618 postSetup: func(t *testing.T, test *preparedTest) { 1619 ledgerInfo := &cp.BlockchainInfo{ 1620 Height: 101, 1621 } 1622 test.ledger.GetBlockchainInfoReturns(ledgerInfo, nil) 1623 }, 1624 startPosition: &ab.SeekPosition{ 1625 Type: &ab.SeekPosition_Specified{ 1626 Specified: &ab.SeekSpecified{ 1627 Number: 99, 1628 }, 1629 }, 1630 }, 1631 postTest: func(t *testing.T, test *preparedTest) { 1632 require.Equal(t, 1, test.ledger.GetBlocksIteratorCallCount()) 1633 require.EqualValues(t, 99, test.ledger.GetBlocksIteratorArgsForCall(0)) 1634 }, 1635 }, 1636 { 1637 name: "defaults to next commit if start position not specified", 1638 blocks: []*cp.Block{ 1639 block101Proto, 1640 }, 1641 postSetup: func(t *testing.T, test *preparedTest) { 1642 ledgerInfo := &cp.BlockchainInfo{ 1643 Height: 101, 1644 } 1645 test.ledger.GetBlockchainInfoReturns(ledgerInfo, nil) 1646 }, 1647 postTest: func(t *testing.T, test *preparedTest) { 1648 require.Equal(t, 1, test.ledger.GetBlocksIteratorCallCount()) 1649 require.EqualValues(t, 101, test.ledger.GetBlocksIteratorArgsForCall(0)) 1650 }, 1651 }, 1652 { 1653 name: "returns error for unsupported start position type", 1654 blocks: []*cp.Block{ 1655 block101Proto, 1656 }, 1657 startPosition: &ab.SeekPosition{ 1658 Type: &ab.SeekPosition_Oldest{ 1659 Oldest: &ab.SeekOldest{}, 1660 }, 1661 }, 1662 errCode: codes.InvalidArgument, 1663 errString: "invalid start position type: *orderer.SeekPosition_Oldest", 1664 }, 1665 { 1666 name: "returns error obtaining ledger iterator", 1667 blocks: []*cp.Block{ 1668 block101Proto, 1669 }, 1670 errCode: codes.Aborted, 1671 errString: "LEDGER_ITERATOR_ERROR", 1672 postSetup: func(t *testing.T, test *preparedTest) { 1673 test.ledger.GetBlocksIteratorReturns(nil, errors.New("LEDGER_ITERATOR_ERROR")) 1674 }, 1675 }, 1676 { 1677 name: "returns canceled status error when client closes stream", 1678 blocks: []*cp.Block{ 1679 block101Proto, 1680 }, 1681 errCode: codes.Canceled, 1682 postSetup: func(t *testing.T, test *preparedTest) { 1683 test.eventsServer.SendReturns(io.EOF) 1684 }, 1685 }, 1686 { 1687 name: "returns status error from send to client", 1688 blocks: []*cp.Block{ 1689 block101Proto, 1690 }, 1691 errCode: codes.Aborted, 1692 errString: "SEND_ERROR", 1693 postSetup: func(t *testing.T, test *preparedTest) { 1694 test.eventsServer.SendReturns(status.Error(codes.Aborted, "SEND_ERROR")) 1695 }, 1696 }, 1697 { 1698 name: "failed policy or signature check", 1699 policyErr: errors.New("POLICY_ERROR"), 1700 errCode: codes.PermissionDenied, 1701 errString: "POLICY_ERROR", 1702 }, 1703 { 1704 name: "passes channel name to policy checker", 1705 postTest: func(t *testing.T, test *preparedTest) { 1706 require.Equal(t, 1, test.policy.CheckACLCallCount()) 1707 _, channelName, _ := test.policy.CheckACLArgsForCall(0) 1708 require.Equal(t, testChannel, channelName) 1709 }, 1710 }, 1711 { 1712 name: "passes identity to policy checker", 1713 identity: []byte("IDENTITY"), 1714 postTest: func(t *testing.T, test *preparedTest) { 1715 require.Equal(t, 1, test.policy.CheckACLCallCount()) 1716 _, _, data := test.policy.CheckACLArgsForCall(0) 1717 require.IsType(t, &protoutil.SignedData{}, data) 1718 signedData := data.(*protoutil.SignedData) 1719 require.Equal(t, []byte("IDENTITY"), signedData.Identity) 1720 }, 1721 }, 1722 } 1723 for _, tt := range tests { 1724 t.Run(tt.name, func(t *testing.T) { 1725 test := prepareTest(t, &tt) 1726 1727 request := &pb.ChaincodeEventsRequest{ 1728 ChannelId: testChannel, 1729 Identity: tt.identity, 1730 ChaincodeId: testChaincode, 1731 } 1732 if tt.startPosition != nil { 1733 request.StartPosition = tt.startPosition 1734 } 1735 requestBytes, err := proto.Marshal(request) 1736 require.NoError(t, err) 1737 1738 signedRequest := &pb.SignedChaincodeEventsRequest{ 1739 Request: requestBytes, 1740 Signature: []byte{}, 1741 } 1742 1743 err = test.server.ChaincodeEvents(signedRequest, test.eventsServer) 1744 1745 if checkError(t, &tt, err) { 1746 return 1747 } 1748 1749 for i, expectedResponse := range tt.expectedResponses { 1750 actualResponse := test.eventsServer.SendArgsForCall(i) 1751 require.True(t, proto.Equal(expectedResponse, actualResponse), "response[%d] mismatch: %v", i, actualResponse) 1752 } 1753 1754 if tt.postTest != nil { 1755 tt.postTest(t, test) 1756 } 1757 }) 1758 } 1759 } 1760 1761 func TestNilArgs(t *testing.T) { 1762 server := newServer( 1763 &mocks.EndorserClient{}, 1764 &mocks.Discovery{}, 1765 &mocks.CommitFinder{}, 1766 &mocks.ACLChecker{}, 1767 &mocks.LedgerProvider{}, 1768 gdiscovery.NetworkMember{ 1769 PKIid: common.PKIidType("id1"), 1770 Endpoint: "localhost:7051", 1771 }, 1772 "msp1", 1773 &comm.SecureOptions{}, 1774 config.GetOptions(viper.New()), 1775 ) 1776 ctx := context.Background() 1777 1778 _, err := server.Evaluate(ctx, nil) 1779 require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "an evaluate request is required")) 1780 1781 _, err = server.Evaluate(ctx, &pb.EvaluateRequest{ProposedTransaction: nil}) 1782 require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "failed to unpack transaction proposal: a signed proposal is required")) 1783 1784 _, err = server.Endorse(ctx, nil) 1785 require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "an endorse request is required")) 1786 1787 _, err = server.Endorse(ctx, &pb.EndorseRequest{ProposedTransaction: nil}) 1788 require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "the proposed transaction must contain a signed proposal")) 1789 1790 _, err = server.Endorse(ctx, &pb.EndorseRequest{ProposedTransaction: &peer.SignedProposal{ProposalBytes: []byte("jibberish")}}) 1791 require.ErrorContains(t, err, "rpc error: code = InvalidArgument desc = error unmarshalling Proposal") 1792 1793 _, err = server.Submit(ctx, nil) 1794 require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "a submit request is required")) 1795 1796 _, err = server.CommitStatus(ctx, nil) 1797 require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "a commit status request is required")) 1798 } 1799 1800 func TestRpcErrorWithBadDetails(t *testing.T) { 1801 err := newRpcError(codes.InvalidArgument, "terrible error", nil) 1802 require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "terrible error")) 1803 } 1804 1805 func prepareTest(t *testing.T, tt *testDef) *preparedTest { 1806 localEndorser := &mocks.EndorserClient{} 1807 localResponse := tt.localResponse 1808 if localResponse == "" { 1809 localResponse = "mock_response" 1810 } 1811 epDef := tt.endpointDefinition 1812 if epDef == nil { 1813 epDef = defaultEndpointDef 1814 } 1815 if epDef.proposalError != nil { 1816 localEndorser.ProcessProposalReturns(createErrorResponse(t, 500, epDef.proposalError.Error(), nil), nil) 1817 } else { 1818 localEndorser.ProcessProposalReturns(createProposalResponseWithInterest(t, localhostMock.address, localResponse, epDef.proposalResponseStatus, epDef.proposalResponseMessage, tt.interest), nil) 1819 } 1820 1821 for _, e := range endorsers { 1822 e.client = &mocks.EndorserClient{} 1823 if epDef.proposalError != nil { 1824 e.client.(*mocks.EndorserClient).ProcessProposalReturns(createErrorResponse(t, 500, epDef.proposalError.Error(), nil), nil) 1825 } else { 1826 e.client.(*mocks.EndorserClient).ProcessProposalReturns(createProposalResponseWithInterest(t, e.address, epDef.proposalResponseValue, epDef.proposalResponseStatus, epDef.proposalResponseMessage, tt.interest), nil) 1827 } 1828 } 1829 1830 mockSigner := &idmocks.SignerSerializer{} 1831 mockSigner.SignReturns([]byte("my_signature"), nil) 1832 1833 mockFinder := &mocks.CommitFinder{} 1834 mockFinder.TransactionStatusReturns(tt.finderStatus, tt.finderErr) 1835 1836 mockPolicy := &mocks.ACLChecker{} 1837 mockPolicy.CheckACLReturns(tt.policyErr) 1838 1839 mockBlockIterator := &mocks.ResultsIterator{} 1840 blockChannel := make(chan *cp.Block, len(tt.blocks)) 1841 for _, block := range tt.blocks { 1842 blockChannel <- block 1843 } 1844 close(blockChannel) 1845 mockBlockIterator.NextCalls(func() (commonledger.QueryResult, error) { 1846 if tt.eventErr != nil { 1847 return nil, tt.eventErr 1848 } 1849 1850 block := <-blockChannel 1851 if block == nil { 1852 return nil, errors.New("NO_MORE_BLOCKS") 1853 } 1854 1855 return block, nil 1856 }) 1857 1858 mockLedger := &mocks.Ledger{} 1859 ledgerInfo := &cp.BlockchainInfo{ 1860 Height: 1, 1861 } 1862 mockLedger.GetBlockchainInfoReturns(ledgerInfo, nil) 1863 mockLedger.GetBlocksIteratorReturns(mockBlockIterator, nil) 1864 1865 mockLedgerProvider := &mocks.LedgerProvider{} 1866 mockLedgerProvider.LedgerReturns(mockLedger, nil) 1867 1868 validProposal := createProposal(t, testChannel, testChaincode, tt.transientData) 1869 validSignedProposal, err := protoutil.GetSignedProposal(validProposal, mockSigner) 1870 require.NoError(t, err) 1871 1872 ca, err := tlsgen.NewCA() 1873 require.NoError(t, err) 1874 configResult := &dp.ConfigResult{ 1875 Orderers: map[string]*dp.Endpoints{ 1876 "msp1": { 1877 Endpoint: []*dp.Endpoint{ 1878 {Host: "orderer", Port: 7050}, 1879 }, 1880 }, 1881 }, 1882 Msps: map[string]*msp.FabricMSPConfig{ 1883 "msp1": { 1884 TlsRootCerts: [][]byte{ca.CertBytes()}, 1885 }, 1886 }, 1887 } 1888 1889 if tt.config != nil { 1890 configResult = tt.config 1891 } 1892 1893 members := []networkMember{ 1894 {"id1", "localhost:7051", "msp1", 0}, 1895 {"id2", "peer1:8051", "msp1", 0}, 1896 {"id3", "peer2:9051", "msp2", 0}, 1897 {"id4", "peer3:10051", "msp2", 0}, 1898 {"id5", "peer4:11051", "msp3", 0}, 1899 } 1900 1901 if tt.members != nil { 1902 members = tt.members 1903 } 1904 1905 disc := mockDiscovery(t, tt.plan, tt.layouts, members, configResult) 1906 1907 options := config.Options{ 1908 Enabled: true, 1909 EndorsementTimeout: endorsementTimeout, 1910 } 1911 1912 member := gdiscovery.NetworkMember{ 1913 PKIid: common.PKIidType("id1"), 1914 Endpoint: "localhost:7051", 1915 } 1916 1917 server := newServer(localEndorser, disc, mockFinder, mockPolicy, mockLedgerProvider, member, "msp1", &comm.SecureOptions{}, options) 1918 1919 dialer := &mocks.Dialer{} 1920 dialer.Returns(nil, nil) 1921 server.registry.endpointFactory = createEndpointFactory(t, epDef, dialer.Spy) 1922 1923 ctx := context.WithValue(context.Background(), contextKey("orange"), "apples") 1924 1925 pt := &preparedTest{ 1926 server: server, 1927 ctx: ctx, 1928 signedProposal: validSignedProposal, 1929 localEndorser: localEndorser, 1930 discovery: disc, 1931 dialer: dialer, 1932 finder: mockFinder, 1933 eventsServer: &mocks.ChaincodeEventsServer{}, 1934 policy: mockPolicy, 1935 ledgerProvider: mockLedgerProvider, 1936 ledger: mockLedger, 1937 blockIterator: mockBlockIterator, 1938 } 1939 if tt.postSetup != nil { 1940 tt.postSetup(t, pt) 1941 } 1942 return pt 1943 } 1944 1945 func checkError(t *testing.T, tt *testDef, err error) (checked bool) { 1946 stringCheck := tt.errString != "" 1947 codeCheck := tt.errCode != codes.OK 1948 detailsCheck := len(tt.errDetails) > 0 1949 1950 checked = stringCheck || codeCheck || detailsCheck 1951 if !checked { 1952 return 1953 } 1954 1955 require.NotNil(t, err, "error") 1956 1957 if stringCheck { 1958 require.ErrorContains(t, err, tt.errString, "error string") 1959 } 1960 1961 s, ok := status.FromError(err) 1962 if !ok { 1963 s = status.FromContextError(err) 1964 } 1965 1966 if codeCheck { 1967 require.Equal(t, tt.errCode.String(), s.Code().String(), "error status code") 1968 } 1969 1970 if detailsCheck { 1971 require.Len(t, s.Details(), len(tt.errDetails)) 1972 for _, detail := range s.Details() { 1973 require.Contains(t, tt.errDetails, detail, "error details, expected: %v", tt.errDetails) 1974 } 1975 } 1976 1977 return 1978 } 1979 1980 func checkEndorsers(t *testing.T, endorsers []string, test *preparedTest) { 1981 // check the correct endorsers (mock) were called with the right parameters 1982 if endorsers == nil { 1983 endorsers = []string{"localhost:7051"} 1984 } 1985 for _, e := range endorsers { 1986 var ec *mocks.EndorserClient 1987 if e == test.server.registry.localEndorser.address { 1988 ec = test.localEndorser 1989 } else { 1990 ec = test.server.registry.remoteEndorsers[e].client.(*mocks.EndorserClient) 1991 } 1992 require.Equal(t, 1, ec.ProcessProposalCallCount(), "Expected ProcessProposal() to be invoked on %s", e) 1993 ectx, prop, _ := ec.ProcessProposalArgsForCall(0) 1994 require.Equal(t, test.signedProposal, prop) 1995 require.Equal(t, "apples", ectx.Value(contextKey("orange"))) 1996 // context timeout was set to -1s, so deadline should be in the past 1997 deadline, ok := ectx.Deadline() 1998 require.True(t, ok) 1999 require.Negative(t, time.Until(deadline)) 2000 } 2001 } 2002 2003 func checkTransaction(t *testing.T, expectedEndorsers []string, transaction *cp.Envelope) { 2004 // check the prepared transaction contains the correct endorsements 2005 var actualEndorsers []string 2006 2007 payload, err := protoutil.UnmarshalPayload(transaction.GetPayload()) 2008 require.NoError(t, err) 2009 txn, err := protoutil.UnmarshalTransaction(payload.GetData()) 2010 require.NoError(t, err) 2011 for _, action := range txn.GetActions() { 2012 cap, err := protoutil.UnmarshalChaincodeActionPayload(action.GetPayload()) 2013 require.NoError(t, err) 2014 for _, endorsement := range cap.GetAction().GetEndorsements() { 2015 actualEndorsers = append(actualEndorsers, string(endorsement.GetEndorser())) 2016 } 2017 } 2018 2019 require.ElementsMatch(t, expectedEndorsers, actualEndorsers) 2020 } 2021 2022 func mockDiscovery(t *testing.T, plan endorsementPlan, layouts []endorsementLayout, members []networkMember, config *dp.ConfigResult) *mocks.Discovery { 2023 discovery := &mocks.Discovery{} 2024 2025 var peers []gdiscovery.NetworkMember 2026 var infoset []api.PeerIdentityInfo 2027 for _, member := range members { 2028 peers = append(peers, gdiscovery.NetworkMember{ 2029 Endpoint: member.endpoint, 2030 PKIid: []byte(member.id), 2031 Properties: &gossip.Properties{Chaincodes: []*gossip.Chaincode{{Name: testChaincode}}, LedgerHeight: member.height}, 2032 }) 2033 infoset = append(infoset, api.PeerIdentityInfo{Organization: []byte(member.mspid), PKIId: []byte(member.id)}) 2034 } 2035 ed := createMockEndorsementDescriptor(t, plan, layouts) 2036 discovery.PeersForEndorsementReturns(ed, nil) 2037 discovery.PeersOfChannelReturns(peers) 2038 discovery.IdentityInfoReturns(infoset) 2039 discovery.ConfigReturns(config, nil) 2040 return discovery 2041 } 2042 2043 func createMockEndorsementDescriptor(t *testing.T, plan endorsementPlan, layouts []endorsementLayout) *dp.EndorsementDescriptor { 2044 quantitiesByGroup := map[string]uint32{} 2045 endorsersByGroups := map[string]*dp.Peers{} 2046 for group, endorsers := range plan { 2047 quantitiesByGroup[group] = 1 // for now 2048 var peers []*dp.Peer 2049 for _, endorser := range endorsers { 2050 peers = append(peers, createMockPeer(t, &endorser)) 2051 } 2052 endorsersByGroups[group] = &dp.Peers{Peers: peers} 2053 } 2054 var layoutDef []*dp.Layout 2055 if layouts != nil { 2056 for _, layout := range layouts { 2057 layoutDef = append(layoutDef, &dp.Layout{QuantitiesByGroup: layout}) 2058 } 2059 } else { 2060 // default single layout - one from each group 2061 layoutDef = []*dp.Layout{{QuantitiesByGroup: quantitiesByGroup}} 2062 } 2063 descriptor := &dp.EndorsementDescriptor{ 2064 Chaincode: "my_channel", 2065 Layouts: layoutDef, 2066 EndorsersByGroups: endorsersByGroups, 2067 } 2068 return descriptor 2069 } 2070 2071 func createMockPeer(t *testing.T, endorser *endorserState) *dp.Peer { 2072 aliveMsgBytes, err := proto.Marshal( 2073 &gossip.GossipMessage{ 2074 Content: &gossip.GossipMessage_AliveMsg{ 2075 AliveMsg: &gossip.AliveMessage{ 2076 Membership: &gossip.Member{Endpoint: endorser.endorser.address}, 2077 }, 2078 }, 2079 }) 2080 2081 require.NoError(t, err) 2082 2083 stateInfoBytes, err := proto.Marshal( 2084 &gossip.GossipMessage{ 2085 Content: &gossip.GossipMessage_StateInfo{ 2086 StateInfo: &gossip.StateInfo{ 2087 Properties: &gossip.Properties{ 2088 LedgerHeight: endorser.height, 2089 }, 2090 }, 2091 }, 2092 }) 2093 2094 require.NoError(t, err) 2095 2096 return &dp.Peer{ 2097 StateInfo: &gossip.Envelope{ 2098 Payload: stateInfoBytes, 2099 }, 2100 MembershipInfo: &gossip.Envelope{ 2101 Payload: aliveMsgBytes, 2102 }, 2103 Identity: marshal(&msp.SerializedIdentity{ 2104 IdBytes: []byte(endorser.endorser.address), 2105 Mspid: endorser.endorser.mspid, 2106 }, t), 2107 } 2108 } 2109 2110 func createEndpointFactory(t *testing.T, definition *endpointDef, dialer dialer) *endpointFactory { 2111 var endpoint string 2112 ca, err := tlsgen.NewCA() 2113 require.NoError(t, err, "failed to create CA") 2114 pair, err := ca.NewClientCertKeyPair() 2115 require.NoError(t, err, "failed to create client key pair") 2116 return &endpointFactory{ 2117 timeout: 5 * time.Second, 2118 connectEndorser: func(conn *grpc.ClientConn) peer.EndorserClient { 2119 if ep, ok := endorsers[endpoint]; ok && ep.client != nil { 2120 return ep.client 2121 } 2122 return nil 2123 }, 2124 connectOrderer: func(_ *grpc.ClientConn) ab.AtomicBroadcastClient { 2125 abc := &mocks.ABClient{} 2126 if definition.ordererBroadcastError != nil { 2127 abc.BroadcastReturns(nil, definition.ordererBroadcastError) 2128 return abc 2129 } 2130 abbc := &mocks.ABBClient{} 2131 abbc.SendReturns(definition.ordererSendError) 2132 abbc.RecvReturns(&ab.BroadcastResponse{ 2133 Info: definition.ordererResponse, 2134 Status: cp.Status(definition.ordererStatus), 2135 }, definition.ordererRecvError) 2136 abc.BroadcastReturns(abbc, nil) 2137 return abc 2138 }, 2139 dialer: func(ctx context.Context, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 2140 endpoint = target 2141 return dialer(ctx, target, opts...) 2142 }, 2143 clientKey: pair.Key, 2144 clientCert: pair.Cert, 2145 } 2146 } 2147 2148 func createProposal(t *testing.T, channel string, chaincode string, transient map[string][]byte, args ...[]byte) *peer.Proposal { 2149 invocationSpec := &peer.ChaincodeInvocationSpec{ 2150 ChaincodeSpec: &peer.ChaincodeSpec{ 2151 Type: peer.ChaincodeSpec_NODE, 2152 ChaincodeId: &peer.ChaincodeID{Name: chaincode}, 2153 Input: &peer.ChaincodeInput{Args: args}, 2154 }, 2155 } 2156 2157 proposal, _, err := protoutil.CreateChaincodeProposalWithTransient( 2158 cp.HeaderType_ENDORSER_TRANSACTION, 2159 channel, 2160 invocationSpec, 2161 []byte{}, 2162 transient, 2163 ) 2164 2165 require.NoError(t, err, "Failed to create the proposal") 2166 2167 return proposal 2168 } 2169 2170 func createProposalResponse(t *testing.T, endorser, value string, status int32, errMessage string) *peer.ProposalResponse { 2171 response := &peer.Response{ 2172 Status: status, 2173 Payload: []byte(value), 2174 Message: errMessage, 2175 } 2176 action := &peer.ChaincodeAction{ 2177 Response: response, 2178 } 2179 payload := &peer.ProposalResponsePayload{ 2180 ProposalHash: []byte{}, 2181 Extension: marshal(action, t), 2182 } 2183 endorsement := &peer.Endorsement{ 2184 Endorser: []byte(endorser), 2185 } 2186 2187 return &peer.ProposalResponse{ 2188 Payload: marshal(payload, t), 2189 Response: response, 2190 Endorsement: endorsement, 2191 } 2192 } 2193 2194 func createProposalResponseWithInterest(t *testing.T, endorser, value string, status int32, errMessage string, interest *peer.ChaincodeInterest) *peer.ProposalResponse { 2195 response := createProposalResponse(t, endorser, value, status, errMessage) 2196 if interest != nil { 2197 response.Interest = interest 2198 } 2199 return response 2200 } 2201 2202 func createErrorResponse(t *testing.T, status int32, errMessage string, payload []byte) *peer.ProposalResponse { 2203 return &peer.ProposalResponse{ 2204 Response: &peer.Response{ 2205 Status: status, 2206 Payload: payload, 2207 Message: errMessage, 2208 }, 2209 } 2210 } 2211 2212 func marshal(msg proto.Message, t *testing.T) []byte { 2213 buf, err := proto.Marshal(msg) 2214 require.NoError(t, err, "Failed to marshal message") 2215 return buf 2216 }