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: &timestamp.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  }