github.com/fnagchunpeng/fabric@v2.1.1+incompatible/core/endorser/endorser_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package endorser_test
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  
    13  	. "github.com/onsi/ginkgo"
    14  	. "github.com/onsi/gomega"
    15  	"github.com/pkg/errors"
    16  
    17  	cb "github.com/hyperledger/fabric-protos-go/common"
    18  	"github.com/hyperledger/fabric-protos-go/ledger/rwset"
    19  	mspproto "github.com/hyperledger/fabric-protos-go/msp"
    20  	pb "github.com/hyperledger/fabric-protos-go/peer"
    21  	"github.com/hyperledger/fabric/common/metrics/metricsfakes"
    22  	"github.com/hyperledger/fabric/core/chaincode/lifecycle"
    23  	"github.com/hyperledger/fabric/core/endorser"
    24  	"github.com/hyperledger/fabric/core/endorser/fake"
    25  	"github.com/hyperledger/fabric/core/ledger"
    26  	"github.com/hyperledger/fabric/protoutil"
    27  
    28  	"github.com/golang/protobuf/proto"
    29  	ledgermock "github.com/hyperledger/fabric/core/ledger/mock"
    30  )
    31  
    32  var _ = Describe("Endorser", func() {
    33  	var (
    34  		fakeProposalDuration         *metricsfakes.Histogram
    35  		fakeProposalsReceived        *metricsfakes.Counter
    36  		fakeSuccessfulProposals      *metricsfakes.Counter
    37  		fakeProposalValidationFailed *metricsfakes.Counter
    38  		fakeProposalACLCheckFailed   *metricsfakes.Counter
    39  		fakeInitFailed               *metricsfakes.Counter
    40  		fakeEndorsementsFailed       *metricsfakes.Counter
    41  		fakeDuplicateTxsFailure      *metricsfakes.Counter
    42  		fakeSimulateFailure          *metricsfakes.Counter
    43  
    44  		fakeLocalIdentity                *fake.Identity
    45  		fakeLocalMSPIdentityDeserializer *fake.IdentityDeserializer
    46  
    47  		fakeChannelIdentity                *fake.Identity
    48  		fakeChannelMSPIdentityDeserializer *fake.IdentityDeserializer
    49  
    50  		fakeChannelFetcher *fake.ChannelFetcher
    51  
    52  		fakePrivateDataDistributor *fake.PrivateDataDistributor
    53  
    54  		fakeSupport              *fake.Support
    55  		fakeTxSimulator          *fake.TxSimulator
    56  		fakeHistoryQueryExecutor *fake.HistoryQueryExecutor
    57  
    58  		signedProposal *pb.SignedProposal
    59  		channelID      string
    60  		chaincodeName  string
    61  
    62  		chaincodeResponse *pb.Response
    63  		chaincodeEvent    *pb.ChaincodeEvent
    64  		chaincodeInput    *pb.ChaincodeInput
    65  
    66  		e *endorser.Endorser
    67  	)
    68  
    69  	BeforeEach(func() {
    70  		fakeProposalDuration = &metricsfakes.Histogram{}
    71  		fakeProposalDuration.WithReturns(fakeProposalDuration)
    72  
    73  		fakeProposalACLCheckFailed = &metricsfakes.Counter{}
    74  		fakeProposalACLCheckFailed.WithReturns(fakeProposalACLCheckFailed)
    75  
    76  		fakeInitFailed = &metricsfakes.Counter{}
    77  		fakeInitFailed.WithReturns(fakeInitFailed)
    78  
    79  		fakeEndorsementsFailed = &metricsfakes.Counter{}
    80  		fakeEndorsementsFailed.WithReturns(fakeEndorsementsFailed)
    81  
    82  		fakeDuplicateTxsFailure = &metricsfakes.Counter{}
    83  		fakeDuplicateTxsFailure.WithReturns(fakeDuplicateTxsFailure)
    84  
    85  		fakeProposalsReceived = &metricsfakes.Counter{}
    86  		fakeSuccessfulProposals = &metricsfakes.Counter{}
    87  		fakeProposalValidationFailed = &metricsfakes.Counter{}
    88  
    89  		fakeSimulateFailure = &metricsfakes.Counter{}
    90  		fakeSimulateFailure.WithReturns(fakeSimulateFailure)
    91  
    92  		fakeLocalIdentity = &fake.Identity{}
    93  		fakeLocalMSPIdentityDeserializer = &fake.IdentityDeserializer{}
    94  		fakeLocalMSPIdentityDeserializer.DeserializeIdentityReturns(fakeLocalIdentity, nil)
    95  
    96  		fakeChannelIdentity = &fake.Identity{}
    97  		fakeChannelMSPIdentityDeserializer = &fake.IdentityDeserializer{}
    98  		fakeChannelMSPIdentityDeserializer.DeserializeIdentityReturns(fakeChannelIdentity, nil)
    99  
   100  		fakeChannelFetcher = &fake.ChannelFetcher{}
   101  		fakeChannelFetcher.ChannelReturns(&endorser.Channel{
   102  			IdentityDeserializer: fakeChannelMSPIdentityDeserializer,
   103  		})
   104  
   105  		fakePrivateDataDistributor = &fake.PrivateDataDistributor{}
   106  
   107  		channelID = "channel-id"
   108  		chaincodeName = "chaincode-name"
   109  		chaincodeInput = &pb.ChaincodeInput{
   110  			Args: [][]byte{[]byte("arg1"), []byte("arg2"), []byte("arg3")},
   111  		}
   112  
   113  		chaincodeResponse = &pb.Response{
   114  			Status:  200,
   115  			Payload: []byte("response-payload"),
   116  		}
   117  		chaincodeEvent = &pb.ChaincodeEvent{
   118  			ChaincodeId: "chaincode-id",
   119  			TxId:        "event-txid",
   120  			EventName:   "event-name",
   121  			Payload:     []byte("event-payload"),
   122  		}
   123  
   124  		fakeSupport = &fake.Support{}
   125  		fakeSupport.ExecuteReturns(
   126  			chaincodeResponse,
   127  			chaincodeEvent,
   128  			nil,
   129  		)
   130  
   131  		fakeSupport.ChaincodeEndorsementInfoReturns(&lifecycle.ChaincodeEndorsementInfo{
   132  			Version:           "chaincode-definition-version",
   133  			EndorsementPlugin: "plugin-name",
   134  		}, nil)
   135  
   136  		fakeSupport.GetLedgerHeightReturns(7, nil)
   137  
   138  		fakeSupport.EndorseWithPluginReturns(
   139  			&pb.Endorsement{
   140  				Endorser:  []byte("endorser-identity"),
   141  				Signature: []byte("endorser-signature"),
   142  			},
   143  			[]byte("endorser-modified-payload"),
   144  			nil,
   145  		)
   146  
   147  		fakeTxSimulator = &fake.TxSimulator{}
   148  		fakeTxSimulator.GetTxSimulationResultsReturns(
   149  			&ledger.TxSimulationResults{
   150  				PubSimulationResults: &rwset.TxReadWriteSet{},
   151  				PvtSimulationResults: &rwset.TxPvtReadWriteSet{},
   152  			},
   153  			nil,
   154  		)
   155  
   156  		fakeHistoryQueryExecutor = &fake.HistoryQueryExecutor{}
   157  		fakeSupport.GetHistoryQueryExecutorReturns(fakeHistoryQueryExecutor, nil)
   158  
   159  		fakeSupport.GetTxSimulatorReturns(fakeTxSimulator, nil)
   160  
   161  		fakeSupport.GetTransactionByIDReturns(nil, fmt.Errorf("txid-error"))
   162  
   163  		e = &endorser.Endorser{
   164  			LocalMSP:               fakeLocalMSPIdentityDeserializer,
   165  			PrivateDataDistributor: fakePrivateDataDistributor,
   166  			Metrics: &endorser.Metrics{
   167  				ProposalDuration:         fakeProposalDuration,
   168  				ProposalsReceived:        fakeProposalsReceived,
   169  				SuccessfulProposals:      fakeSuccessfulProposals,
   170  				ProposalValidationFailed: fakeProposalValidationFailed,
   171  				ProposalACLCheckFailed:   fakeProposalACLCheckFailed,
   172  				InitFailed:               fakeInitFailed,
   173  				EndorsementsFailed:       fakeEndorsementsFailed,
   174  				DuplicateTxsFailure:      fakeDuplicateTxsFailure,
   175  				SimulationFailure:        fakeSimulateFailure,
   176  			},
   177  			Support:        fakeSupport,
   178  			ChannelFetcher: fakeChannelFetcher,
   179  		}
   180  	})
   181  
   182  	JustBeforeEach(func() {
   183  		signedProposal = &pb.SignedProposal{
   184  			ProposalBytes: protoutil.MarshalOrPanic(&pb.Proposal{
   185  				Header: protoutil.MarshalOrPanic(&cb.Header{
   186  					ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
   187  						Type:      int32(cb.HeaderType_ENDORSER_TRANSACTION),
   188  						ChannelId: channelID,
   189  						Extension: protoutil.MarshalOrPanic(&pb.ChaincodeHeaderExtension{
   190  							ChaincodeId: &pb.ChaincodeID{
   191  								Name: chaincodeName,
   192  							},
   193  						}),
   194  						TxId: "6f142589e4ef6a1e62c9c816e2074f70baa9f7cf67c2f0c287d4ef907d6d2015",
   195  					}),
   196  					SignatureHeader: protoutil.MarshalOrPanic(&cb.SignatureHeader{
   197  						Creator: protoutil.MarshalOrPanic(&mspproto.SerializedIdentity{
   198  							Mspid: "msp-id",
   199  						}),
   200  						Nonce: []byte("nonce"),
   201  					}),
   202  				}),
   203  				Payload: protoutil.MarshalOrPanic(&pb.ChaincodeProposalPayload{
   204  					Input: protoutil.MarshalOrPanic(&pb.ChaincodeInvocationSpec{
   205  						ChaincodeSpec: &pb.ChaincodeSpec{
   206  							Input: chaincodeInput,
   207  						},
   208  					}),
   209  				}),
   210  			}),
   211  			Signature: []byte("signature"),
   212  		}
   213  	})
   214  
   215  	It("successfully endorses the proposal", func() {
   216  		proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   217  		Expect(err).NotTo(HaveOccurred())
   218  		Expect(proposalResponse.Endorsement).To(Equal(&pb.Endorsement{
   219  			Endorser:  []byte("endorser-identity"),
   220  			Signature: []byte("endorser-signature"),
   221  		}))
   222  		Expect(proposalResponse.Timestamp).To(BeNil())
   223  		Expect(proposalResponse.Version).To(Equal(int32(1)))
   224  		Expect(proposalResponse.Payload).To(Equal([]byte("endorser-modified-payload")))
   225  		Expect(proto.Equal(proposalResponse.Response, &pb.Response{
   226  			Status:  200,
   227  			Payload: []byte("response-payload"),
   228  		})).To(BeTrue())
   229  
   230  		Expect(fakeSupport.EndorseWithPluginCallCount()).To(Equal(1))
   231  		pluginName, cid, propRespPayloadBytes, sp := fakeSupport.EndorseWithPluginArgsForCall(0)
   232  		Expect(sp).To(Equal(signedProposal))
   233  		Expect(pluginName).To(Equal("plugin-name"))
   234  		Expect(cid).To(Equal("channel-id"))
   235  
   236  		prp := &pb.ProposalResponsePayload{}
   237  		err = proto.Unmarshal(propRespPayloadBytes, prp)
   238  		Expect(err).NotTo(HaveOccurred())
   239  		Expect(fmt.Sprintf("%x", prp.ProposalHash)).To(Equal("6fa450b00ebef6c7de9f3479148f6d6ff2c645762e17fcaae989ff7b668be001"))
   240  
   241  		ccAct := &pb.ChaincodeAction{}
   242  		err = proto.Unmarshal(prp.Extension, ccAct)
   243  		Expect(err).NotTo(HaveOccurred())
   244  		Expect(ccAct.Events).To(Equal(protoutil.MarshalOrPanic(chaincodeEvent)))
   245  		Expect(proto.Equal(ccAct.Response, &pb.Response{
   246  			Status:  200,
   247  			Payload: []byte("response-payload"),
   248  		})).To(BeTrue())
   249  		Expect(fakeSupport.GetHistoryQueryExecutorCallCount()).To(Equal(1))
   250  		ledgerName := fakeSupport.GetHistoryQueryExecutorArgsForCall(0)
   251  		Expect(ledgerName).To(Equal("channel-id"))
   252  	})
   253  
   254  	Context("when the chaincode endorsement fails", func() {
   255  		BeforeEach(func() {
   256  			fakeSupport.EndorseWithPluginReturns(nil, nil, fmt.Errorf("fake-endorserment-error"))
   257  		})
   258  
   259  		It("returns the error, but with no payload encoded", func() {
   260  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   261  			Expect(err).NotTo(HaveOccurred())
   262  			Expect(proposalResponse.Payload).To(BeNil())
   263  			Expect(proposalResponse.Response).To(Equal(&pb.Response{
   264  				Status:  500,
   265  				Message: "endorsing with plugin failed: fake-endorserment-error",
   266  			}))
   267  		})
   268  	})
   269  
   270  	It("checks for duplicate transactions", func() {
   271  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   272  		Expect(err).NotTo(HaveOccurred())
   273  		Expect(fakeSupport.GetTransactionByIDCallCount()).To(Equal(1))
   274  		channelID, txid := fakeSupport.GetTransactionByIDArgsForCall(0)
   275  		Expect(channelID).To(Equal("channel-id"))
   276  		Expect(txid).To(Equal("6f142589e4ef6a1e62c9c816e2074f70baa9f7cf67c2f0c287d4ef907d6d2015"))
   277  	})
   278  
   279  	Context("when the txid is duplicated", func() {
   280  		BeforeEach(func() {
   281  			fakeSupport.GetTransactionByIDReturns(nil, nil)
   282  		})
   283  
   284  		It("wraps and returns an error and responds to the client", func() {
   285  			proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   286  			Expect(err).To(MatchError("duplicate transaction found [6f142589e4ef6a1e62c9c816e2074f70baa9f7cf67c2f0c287d4ef907d6d2015]. Creator [0a066d73702d6964]"))
   287  			Expect(proposalResponse).To(Equal(&pb.ProposalResponse{
   288  				Response: &pb.Response{
   289  					Status:  500,
   290  					Message: "duplicate transaction found [6f142589e4ef6a1e62c9c816e2074f70baa9f7cf67c2f0c287d4ef907d6d2015]. Creator [0a066d73702d6964]",
   291  				},
   292  			}))
   293  		})
   294  	})
   295  
   296  	It("gets a transaction simulator", func() {
   297  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   298  		Expect(err).NotTo(HaveOccurred())
   299  		Expect(fakeSupport.GetTxSimulatorCallCount()).To(Equal(1))
   300  		ledgerName, txid := fakeSupport.GetTxSimulatorArgsForCall(0)
   301  		Expect(ledgerName).To(Equal("channel-id"))
   302  		Expect(txid).To(Equal("6f142589e4ef6a1e62c9c816e2074f70baa9f7cf67c2f0c287d4ef907d6d2015"))
   303  	})
   304  
   305  	Context("when getting the tx simulator fails", func() {
   306  		BeforeEach(func() {
   307  			fakeSupport.GetTxSimulatorReturns(nil, fmt.Errorf("fake-simulator-error"))
   308  		})
   309  
   310  		It("returns a response with the error", func() {
   311  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   312  			Expect(err).NotTo(HaveOccurred())
   313  			Expect(proposalResponse.Payload).To(BeNil())
   314  			Expect(proposalResponse.Response).To(Equal(&pb.Response{
   315  				Status:  500,
   316  				Message: "fake-simulator-error",
   317  			}))
   318  		})
   319  	})
   320  
   321  	It("gets a history query executor", func() {
   322  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   323  		Expect(err).NotTo(HaveOccurred())
   324  		Expect(fakeSupport.GetHistoryQueryExecutorCallCount()).To(Equal(1))
   325  		ledgerName := fakeSupport.GetHistoryQueryExecutorArgsForCall(0)
   326  		Expect(ledgerName).To(Equal("channel-id"))
   327  	})
   328  
   329  	Context("when getting the history query executor fails", func() {
   330  		BeforeEach(func() {
   331  			fakeSupport.GetHistoryQueryExecutorReturns(nil, fmt.Errorf("fake-history-error"))
   332  		})
   333  
   334  		It("returns a response with the error", func() {
   335  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   336  			Expect(err).NotTo(HaveOccurred())
   337  			Expect(proposalResponse.Payload).To(BeNil())
   338  			Expect(proposalResponse.Response).To(Equal(&pb.Response{
   339  				Status:  500,
   340  				Message: "fake-history-error",
   341  			}))
   342  		})
   343  	})
   344  
   345  	It("gets the channel context", func() {
   346  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   347  		Expect(err).NotTo(HaveOccurred())
   348  		Expect(fakeChannelFetcher.ChannelCallCount()).To(Equal(1))
   349  		channelID := fakeChannelFetcher.ChannelArgsForCall(0)
   350  		Expect(channelID).To(Equal("channel-id"))
   351  	})
   352  
   353  	Context("when the channel context cannot be retrieved", func() {
   354  		BeforeEach(func() {
   355  			fakeChannelFetcher.ChannelReturns(nil)
   356  		})
   357  
   358  		It("returns a response with the error", func() {
   359  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   360  			Expect(err).NotTo(HaveOccurred())
   361  			Expect(proposalResponse.Payload).To(BeNil())
   362  			Expect(proposalResponse.Response).To(Equal(&pb.Response{
   363  				Status:  500,
   364  				Message: "channel 'channel-id' not found",
   365  			}))
   366  		})
   367  	})
   368  
   369  	It("checks the submitter's identity", func() {
   370  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   371  		Expect(err).NotTo(HaveOccurred())
   372  		Expect(fakeChannelMSPIdentityDeserializer.DeserializeIdentityCallCount()).To(Equal(1))
   373  		identity := fakeChannelMSPIdentityDeserializer.DeserializeIdentityArgsForCall(0)
   374  		Expect(identity).To(Equal(protoutil.MarshalOrPanic(&mspproto.SerializedIdentity{
   375  			Mspid: "msp-id",
   376  		})))
   377  
   378  		Expect(fakeLocalMSPIdentityDeserializer.DeserializeIdentityCallCount()).To(Equal(0))
   379  	})
   380  
   381  	Context("when the proposal is not validly signed", func() {
   382  		BeforeEach(func() {
   383  			fakeChannelMSPIdentityDeserializer.DeserializeIdentityReturns(nil, fmt.Errorf("fake-deserialize-error"))
   384  		})
   385  
   386  		It("wraps and returns an error and responds to the client", func() {
   387  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   388  			Expect(err).To(MatchError("error validating proposal: access denied: channel [channel-id] creator org [msp-id]"))
   389  			Expect(proposalResponse).To(Equal(&pb.ProposalResponse{
   390  				Response: &pb.Response{
   391  					Status:  500,
   392  					Message: "error validating proposal: access denied: channel [channel-id] creator org [msp-id]",
   393  				},
   394  			}))
   395  		})
   396  	})
   397  
   398  	It("checks the ACLs for the identity", func() {
   399  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   400  		Expect(err).NotTo(HaveOccurred())
   401  		Expect(fakeProposalACLCheckFailed.WithCallCount()).To(Equal(0))
   402  		Expect(fakeInitFailed.WithCallCount()).To(Equal(0))
   403  		Expect(fakeEndorsementsFailed.WithCallCount()).To(Equal(0))
   404  		Expect(fakeDuplicateTxsFailure.WithCallCount()).To(Equal(0))
   405  	})
   406  
   407  	Context("when the acl check fails", func() {
   408  		BeforeEach(func() {
   409  			fakeSupport.CheckACLReturns(fmt.Errorf("fake-acl-error"))
   410  		})
   411  
   412  		It("wraps and returns an error and responds to the client", func() {
   413  			proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   414  			Expect(err).To(MatchError("fake-acl-error"))
   415  			Expect(proposalResponse).To(Equal(&pb.ProposalResponse{
   416  				Response: &pb.Response{
   417  					Status:  500,
   418  					Message: "fake-acl-error",
   419  				},
   420  			}))
   421  		})
   422  
   423  		Context("when it's for a system chaincode", func() {
   424  			BeforeEach(func() {
   425  				fakeSupport.IsSysCCReturns(true)
   426  			})
   427  
   428  			It("skips the acl check", func() {
   429  				proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   430  				Expect(err).NotTo(HaveOccurred())
   431  				Expect(proposalResponse.Response.Status).To(Equal(int32(200)))
   432  			})
   433  		})
   434  	})
   435  
   436  	It("gets the chaincode definition", func() {
   437  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   438  		Expect(err).NotTo(HaveOccurred())
   439  		Expect(fakeSupport.ChaincodeEndorsementInfoCallCount()).To(Equal(1))
   440  		channelID, chaincodeName, txSim := fakeSupport.ChaincodeEndorsementInfoArgsForCall(0)
   441  		Expect(channelID).To(Equal("channel-id"))
   442  		Expect(chaincodeName).To(Equal("chaincode-name"))
   443  		Expect(txSim).To(Equal(fakeTxSimulator))
   444  	})
   445  
   446  	Context("when the chaincode definition is not found", func() {
   447  		BeforeEach(func() {
   448  			fakeSupport.ChaincodeEndorsementInfoReturns(nil, fmt.Errorf("fake-definition-error"))
   449  		})
   450  
   451  		It("returns an error in the response", func() {
   452  			proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   453  			Expect(err).NotTo(HaveOccurred())
   454  			Expect(proposalResponse.Response).To(Equal(&pb.Response{
   455  				Status:  500,
   456  				Message: "make sure the chaincode chaincode-name has been successfully defined on channel channel-id and try again: fake-definition-error",
   457  			}))
   458  		})
   459  	})
   460  
   461  	It("calls the chaincode", func() {
   462  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   463  		Expect(err).NotTo(HaveOccurred())
   464  		Expect(fakeSupport.ExecuteCallCount()).To(Equal(1))
   465  		txParams, chaincodeName, input := fakeSupport.ExecuteArgsForCall(0)
   466  		Expect(txParams.ChannelID).To(Equal("channel-id"))
   467  		Expect(txParams.SignedProp).To(Equal(signedProposal))
   468  		Expect(txParams.TXSimulator).To(Equal(fakeTxSimulator))
   469  		Expect(txParams.HistoryQueryExecutor).To(Equal(fakeHistoryQueryExecutor))
   470  		Expect(chaincodeName).To(Equal("chaincode-name"))
   471  		Expect(proto.Equal(input, &pb.ChaincodeInput{
   472  			Args: [][]byte{[]byte("arg1"), []byte("arg2"), []byte("arg3")},
   473  		})).To(BeTrue())
   474  	})
   475  
   476  	Context("when calling the chaincode returns an error", func() {
   477  		BeforeEach(func() {
   478  			fakeSupport.ExecuteReturns(nil, nil, fmt.Errorf("fake-chaincode-execution-error"))
   479  		})
   480  
   481  		It("returns a response with the error and no payload", func() {
   482  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   483  			Expect(err).NotTo(HaveOccurred())
   484  			Expect(proposalResponse.Payload).To(BeNil())
   485  			Expect(proposalResponse.Response).To(Equal(&pb.Response{
   486  				Status:  500,
   487  				Message: "error in simulation: fake-chaincode-execution-error",
   488  			}))
   489  			Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
   490  		})
   491  	})
   492  
   493  	It("distributes private data", func() {
   494  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   495  		Expect(err).NotTo(HaveOccurred())
   496  		Expect(fakePrivateDataDistributor.DistributePrivateDataCallCount()).To(Equal(1))
   497  		cid, txid, privateData, blkHt := fakePrivateDataDistributor.DistributePrivateDataArgsForCall(0)
   498  		Expect(cid).To(Equal("channel-id"))
   499  		Expect(txid).To(Equal("6f142589e4ef6a1e62c9c816e2074f70baa9f7cf67c2f0c287d4ef907d6d2015"))
   500  		Expect(blkHt).To(Equal(uint64(7)))
   501  
   502  		// TODO, this deserves a better test, but there was none before and this logic,
   503  		// really seems far too jumbled to be in the endorser package.  There are seperate
   504  		// tests of the private data assembly functions in their test file.
   505  		Expect(privateData).NotTo(BeNil())
   506  		Expect(privateData.EndorsedAt).To(Equal(uint64(7)))
   507  	})
   508  
   509  	Context("when the private data cannot be distributed", func() {
   510  		BeforeEach(func() {
   511  			fakePrivateDataDistributor.DistributePrivateDataReturns(fmt.Errorf("fake-private-data-error"))
   512  		})
   513  
   514  		It("returns a response with the error and no payload", func() {
   515  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   516  			Expect(err).NotTo(HaveOccurred())
   517  			Expect(proposalResponse.Payload).To(BeNil())
   518  			Expect(proposalResponse.Response).To(Equal(&pb.Response{
   519  				Status:  500,
   520  				Message: "error in simulation: fake-private-data-error",
   521  			}))
   522  			Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
   523  		})
   524  	})
   525  
   526  	It("checks the block height", func() {
   527  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   528  		Expect(err).NotTo(HaveOccurred())
   529  		Expect(fakeSupport.GetLedgerHeightCallCount()).To(Equal(1))
   530  	})
   531  
   532  	Context("when the block height cannot be determined", func() {
   533  		BeforeEach(func() {
   534  			fakeSupport.GetLedgerHeightReturns(0, fmt.Errorf("fake-block-height-error"))
   535  		})
   536  
   537  		It("returns a response with the error and no payload", func() {
   538  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   539  			Expect(err).NotTo(HaveOccurred())
   540  			Expect(proposalResponse.Payload).To(BeNil())
   541  			Expect(proposalResponse.Response).To(Equal(&pb.Response{
   542  				Status:  500,
   543  				Message: "error in simulation: failed to obtain ledger height for channel 'channel-id': fake-block-height-error",
   544  			}))
   545  
   546  			Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
   547  		})
   548  	})
   549  
   550  	It("records metrics about the proposal processing", func() {
   551  		_, err := e.ProcessProposal(context.Background(), signedProposal)
   552  		Expect(err).NotTo(HaveOccurred())
   553  
   554  		Expect(fakeProposalsReceived.AddCallCount()).To(Equal(1))
   555  		Expect(fakeSuccessfulProposals.AddCallCount()).To(Equal(1))
   556  		Expect(fakeProposalValidationFailed.AddCallCount()).To(Equal(0))
   557  
   558  		Expect(fakeProposalDuration.WithCallCount()).To(Equal(1))
   559  		Expect(fakeProposalDuration.WithArgsForCall(0)).To(Equal([]string{
   560  			"channel", "channel-id",
   561  			"chaincode", "chaincode-name",
   562  			"success", "true",
   563  		}))
   564  	})
   565  
   566  	Context("when the channel id is empty", func() {
   567  		BeforeEach(func() {
   568  			channelID = ""
   569  		})
   570  
   571  		It("returns a successful proposal response with no endorsement", func() {
   572  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   573  			Expect(err).NotTo(HaveOccurred())
   574  			Expect(proposalResponse.Endorsement).To(BeNil())
   575  			Expect(proposalResponse.Timestamp).To(BeNil())
   576  			Expect(proposalResponse.Version).To(Equal(int32(0)))
   577  			Expect(proposalResponse.Payload).To(BeNil())
   578  			Expect(proto.Equal(proposalResponse.Response, &pb.Response{
   579  				Status:  200,
   580  				Payload: []byte("response-payload"),
   581  			})).To(BeTrue())
   582  		})
   583  
   584  		It("does not attempt to get a history query executor", func() {
   585  			_, err := e.ProcessProposal(context.Background(), signedProposal)
   586  			Expect(err).NotTo(HaveOccurred())
   587  			Expect(fakeSupport.GetHistoryQueryExecutorCallCount()).To(Equal(0))
   588  		})
   589  
   590  		It("does not attempt to deduplicate the txid", func() {
   591  			_, err := e.ProcessProposal(context.Background(), signedProposal)
   592  			Expect(err).NotTo(HaveOccurred())
   593  			Expect(fakeSupport.GetTransactionByIDCallCount()).To(Equal(0))
   594  		})
   595  
   596  		It("does not attempt to get a tx simulator", func() {
   597  			_, err := e.ProcessProposal(context.Background(), signedProposal)
   598  			Expect(err).NotTo(HaveOccurred())
   599  			Expect(fakeSupport.GetTxSimulatorCallCount()).To(Equal(0))
   600  		})
   601  
   602  		It("uses the local MSP to authorize the creator", func() {
   603  			_, err := e.ProcessProposal(context.Background(), signedProposal)
   604  			Expect(err).NotTo(HaveOccurred())
   605  			Expect(fakeChannelMSPIdentityDeserializer.DeserializeIdentityCallCount()).To(Equal(0))
   606  
   607  			Expect(fakeLocalMSPIdentityDeserializer.DeserializeIdentityCallCount()).To(Equal(1))
   608  			identity := fakeLocalMSPIdentityDeserializer.DeserializeIdentityArgsForCall(0)
   609  			Expect(identity).To(Equal(protoutil.MarshalOrPanic(&mspproto.SerializedIdentity{
   610  				Mspid: "msp-id",
   611  			})))
   612  		})
   613  
   614  		Context("when the proposal is not validly signed", func() {
   615  			BeforeEach(func() {
   616  				fakeLocalMSPIdentityDeserializer.DeserializeIdentityReturns(nil, fmt.Errorf("fake-deserialize-error"))
   617  			})
   618  
   619  			It("wraps and returns an error and responds to the client", func() {
   620  				proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   621  				Expect(err).To(MatchError("error validating proposal: access denied: channel [] creator org [msp-id]"))
   622  				Expect(proposalResponse).To(Equal(&pb.ProposalResponse{
   623  					Response: &pb.Response{
   624  						Status:  500,
   625  						Message: "error validating proposal: access denied: channel [] creator org [msp-id]",
   626  					},
   627  				}))
   628  			})
   629  		})
   630  
   631  		It("records metrics but without a channel ID set", func() {
   632  			_, err := e.ProcessProposal(context.Background(), signedProposal)
   633  			Expect(err).NotTo(HaveOccurred())
   634  			Expect(fakeProposalsReceived.AddCallCount()).To(Equal(1))
   635  			Expect(fakeSuccessfulProposals.AddCallCount()).To(Equal(1))
   636  			Expect(fakeProposalValidationFailed.AddCallCount()).To(Equal(0))
   637  
   638  			Expect(fakeProposalDuration.WithCallCount()).To(Equal(1))
   639  			Expect(fakeProposalDuration.WithArgsForCall(0)).To(Equal([]string{
   640  				"channel", "",
   641  				"chaincode", "chaincode-name",
   642  				"success", "true",
   643  			}))
   644  			Expect(fakeProposalACLCheckFailed.WithCallCount()).To(Equal(0))
   645  			Expect(fakeInitFailed.WithCallCount()).To(Equal(0))
   646  			Expect(fakeEndorsementsFailed.WithCallCount()).To(Equal(0))
   647  			Expect(fakeDuplicateTxsFailure.WithCallCount()).To(Equal(0))
   648  		})
   649  
   650  		Context("when the chaincode response is >= 500", func() {
   651  			BeforeEach(func() {
   652  				chaincodeResponse.Status = 500
   653  			})
   654  
   655  			It("returns the result, but with the proposal encoded, and no endorsements", func() {
   656  				proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   657  				Expect(err).NotTo(HaveOccurred())
   658  				Expect(proposalResponse.Endorsement).To(BeNil())
   659  				Expect(proposalResponse.Timestamp).To(BeNil())
   660  				Expect(proposalResponse.Version).To(Equal(int32(0)))
   661  				Expect(proto.Equal(proposalResponse.Response, &pb.Response{
   662  					Status:  500,
   663  					Payload: []byte("response-payload"),
   664  				})).To(BeTrue())
   665  
   666  				// This is almost definitely a bug, but, adding a test in case someone is relying on this behavior.
   667  				// When the response is >= 500, we return a payload, but not on success.  A payload is only meaningful
   668  				// if it is endorsed, so it's unclear why we're returning it here.
   669  				prp := &pb.ProposalResponsePayload{}
   670  				err = proto.Unmarshal(proposalResponse.Payload, prp)
   671  				Expect(err).NotTo(HaveOccurred())
   672  				Expect(fmt.Sprintf("%x", prp.ProposalHash)).To(Equal("f2c27f04f897dc28fd1b2983e7b22ebc8fbbb3d0617c140d913b33e463886788"))
   673  
   674  				ccAct := &pb.ChaincodeAction{}
   675  				err = proto.Unmarshal(prp.Extension, ccAct)
   676  				Expect(err).NotTo(HaveOccurred())
   677  				Expect(proto.Equal(ccAct.Response, &pb.Response{
   678  					Status:  500,
   679  					Payload: []byte("response-payload"),
   680  				})).To(BeTrue())
   681  
   682  				// This is an especially weird bit of the behavior, the chaincode event is nil-ed before creating
   683  				// the proposal response. (That probably shouldn't be created)
   684  				Expect(ccAct.Events).To(BeNil())
   685  			})
   686  		})
   687  
   688  		Context("when the 200 < chaincode response < 500", func() {
   689  			BeforeEach(func() {
   690  				chaincodeResponse.Status = 499
   691  			})
   692  
   693  			It("returns the result, but with the proposal encoded, and no endorsements", func() {
   694  				proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   695  				Expect(err).NotTo(HaveOccurred())
   696  				Expect(proposalResponse.Endorsement).To(BeNil())
   697  				Expect(proposalResponse.Timestamp).To(BeNil())
   698  				Expect(proposalResponse.Version).To(Equal(int32(0)))
   699  				Expect(proto.Equal(proposalResponse.Response, &pb.Response{
   700  					Status:  499,
   701  					Payload: []byte("response-payload"),
   702  				})).To(BeTrue())
   703  				Expect(proposalResponse.Payload).To(BeNil())
   704  			})
   705  		})
   706  	})
   707  
   708  	Context("when the proposal is malformed", func() {
   709  		JustBeforeEach(func() {
   710  			signedProposal = &pb.SignedProposal{
   711  				ProposalBytes: []byte("garbage"),
   712  			}
   713  		})
   714  
   715  		It("wraps and returns an error and responds to the client", func() {
   716  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   717  			Expect(err).To(MatchError("error unmarshaling Proposal: proto: can't skip unknown wire type 7"))
   718  			Expect(proposalResponse).To(Equal(&pb.ProposalResponse{
   719  				Response: &pb.Response{
   720  					Status:  500,
   721  					Message: "error unmarshaling Proposal: proto: can't skip unknown wire type 7",
   722  				},
   723  			}))
   724  		})
   725  	})
   726  
   727  	Context("when the chaincode response is >= 500", func() {
   728  		BeforeEach(func() {
   729  			chaincodeResponse.Status = 500
   730  		})
   731  
   732  		It("returns the result, but with the proposal encoded, and no endorsements", func() {
   733  			proposalResponse, err := e.ProcessProposal(context.Background(), signedProposal)
   734  			Expect(err).NotTo(HaveOccurred())
   735  			Expect(proposalResponse.Endorsement).To(BeNil())
   736  			Expect(proposalResponse.Timestamp).To(BeNil())
   737  			Expect(proposalResponse.Version).To(Equal(int32(0)))
   738  			Expect(proto.Equal(proposalResponse.Response, &pb.Response{
   739  				Status:  500,
   740  				Payload: []byte("response-payload"),
   741  			})).To(BeTrue())
   742  			Expect(proposalResponse.Payload).NotTo(BeNil())
   743  		})
   744  	})
   745  
   746  	Context("when the chaincode name is qscc", func() {
   747  		BeforeEach(func() {
   748  			chaincodeName = "qscc"
   749  		})
   750  
   751  		It("skips fetching the tx simulator and history query exucutor", func() {
   752  			_, err := e.ProcessProposal(context.Background(), signedProposal)
   753  			Expect(err).NotTo(HaveOccurred())
   754  			Expect(fakeSupport.GetTxSimulatorCallCount()).To(Equal(0))
   755  			Expect(fakeSupport.GetHistoryQueryExecutorCallCount()).To(Equal(0))
   756  		})
   757  	})
   758  
   759  	Context("when the chaincode name is cscc", func() {
   760  		BeforeEach(func() {
   761  			chaincodeName = "cscc"
   762  		})
   763  
   764  		It("skips fetching the tx simulator and history query exucutor", func() {
   765  			_, err := e.ProcessProposal(context.Background(), signedProposal)
   766  			Expect(err).NotTo(HaveOccurred())
   767  			Expect(fakeSupport.GetTxSimulatorCallCount()).To(Equal(0))
   768  			Expect(fakeSupport.GetHistoryQueryExecutorCallCount()).To(Equal(0))
   769  		})
   770  	})
   771  
   772  	Context("when the chaincode response is >= 400 but < 500", func() {
   773  		BeforeEach(func() {
   774  			chaincodeResponse.Status = 400
   775  		})
   776  
   777  		It("returns the response with no payload", func() {
   778  			proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   779  			Expect(err).NotTo(HaveOccurred())
   780  			Expect(proposalResponse.Payload).To(BeNil())
   781  			Expect(proto.Equal(proposalResponse.Response, &pb.Response{
   782  				Status:  400,
   783  				Payload: []byte("response-payload"),
   784  			})).To(BeTrue())
   785  		})
   786  	})
   787  
   788  	Context("when we're in the degenerate legacy lifecycle case", func() {
   789  		BeforeEach(func() {
   790  			chaincodeName = "lscc"
   791  			chaincodeInput.Args = [][]byte{
   792  				[]byte("deploy"),
   793  				nil,
   794  				protoutil.MarshalOrPanic(&pb.ChaincodeDeploymentSpec{
   795  					ChaincodeSpec: &pb.ChaincodeSpec{
   796  						ChaincodeId: &pb.ChaincodeID{
   797  							Name:    "deploy-name",
   798  							Version: "deploy-version",
   799  						},
   800  						Input: &pb.ChaincodeInput{
   801  							Args: [][]byte{[]byte("target-arg")},
   802  						},
   803  					},
   804  				}),
   805  			}
   806  
   807  			fakeTxSimulator.GetTxSimulationResultsReturns(
   808  				&ledger.TxSimulationResults{
   809  					PubSimulationResults: &rwset.TxReadWriteSet{},
   810  					// We don't return private data in this case because lscc forbids it
   811  				},
   812  				nil,
   813  			)
   814  		})
   815  
   816  		It("triggers the legacy init, and returns the response from lscc", func() {
   817  			proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   818  			Expect(err).NotTo(HaveOccurred())
   819  			Expect(proto.Equal(proposalResponse.Response, &pb.Response{
   820  				Status:  200,
   821  				Payload: []byte("response-payload"),
   822  			})).To(BeTrue())
   823  
   824  			Expect(fakeSupport.ExecuteLegacyInitCallCount()).To(Equal(1))
   825  			_, name, version, input := fakeSupport.ExecuteLegacyInitArgsForCall(0)
   826  			Expect(name).To(Equal("deploy-name"))
   827  			Expect(version).To(Equal("deploy-version"))
   828  			Expect(input.Args).To(Equal([][]byte{[]byte("target-arg")}))
   829  		})
   830  
   831  		Context("when the chaincode spec contains a code package", func() {
   832  			BeforeEach(func() {
   833  				chaincodeInput.Args = [][]byte{
   834  					[]byte("deploy"),
   835  					nil,
   836  					protoutil.MarshalOrPanic(&pb.ChaincodeDeploymentSpec{
   837  						ChaincodeSpec: &pb.ChaincodeSpec{
   838  							ChaincodeId: &pb.ChaincodeID{
   839  								Name:    "deploy-name",
   840  								Version: "deploy-version",
   841  							},
   842  							Input: &pb.ChaincodeInput{
   843  								Args: [][]byte{[]byte("target-arg")},
   844  							},
   845  						},
   846  						CodePackage: []byte("some-code"),
   847  					}),
   848  				}
   849  			})
   850  
   851  			It("returns an error to the client", func() {
   852  				proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   853  				Expect(err).NotTo(HaveOccurred())
   854  				Expect(proposalResponse.Response).To(Equal(&pb.Response{
   855  					Status:  500,
   856  					Message: "error in simulation: lscc upgrade/deploy should not include a code packages",
   857  				}))
   858  				Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
   859  			})
   860  		})
   861  
   862  		Context("when the simulation uses private data", func() {
   863  			BeforeEach(func() {
   864  				fakeTxSimulator.GetTxSimulationResultsReturns(
   865  					&ledger.TxSimulationResults{
   866  						PubSimulationResults: &rwset.TxReadWriteSet{},
   867  						PvtSimulationResults: &rwset.TxPvtReadWriteSet{},
   868  					},
   869  					nil,
   870  				)
   871  			})
   872  
   873  			It("returns an error to the client", func() {
   874  				proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   875  				Expect(err).NotTo(HaveOccurred())
   876  				Expect(proposalResponse.Response).To(Equal(&pb.Response{
   877  					Status:  500,
   878  					Message: "error in simulation: Private data is forbidden to be used in instantiate",
   879  				}))
   880  				Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
   881  			})
   882  		})
   883  
   884  		Context("when retrieving simulation results", func() {
   885  			BeforeEach(func() {
   886  				fakeTxSimulator.GetTxSimulationResultsReturns(
   887  					&ledger.TxSimulationResults{
   888  						PubSimulationResults: &rwset.TxReadWriteSet{},
   889  						PvtSimulationResults: &rwset.TxPvtReadWriteSet{},
   890  					},
   891  					errors.New("bad simulation"),
   892  				)
   893  			})
   894  
   895  			It("returns an error to the client", func() {
   896  				proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   897  				Expect(err).NotTo(HaveOccurred())
   898  				Expect(proposalResponse.Response).To(Equal(&pb.Response{
   899  					Status:  500,
   900  					Message: "error in simulation: bad simulation",
   901  				}))
   902  				Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
   903  			})
   904  		})
   905  
   906  		Context("when retrieving public simulation results fails", func() {
   907  			BeforeEach(func() {
   908  				fakeTxSimulator.GetTxSimulationResultsReturns(
   909  					&ledger.TxSimulationResults{},
   910  					nil,
   911  				)
   912  			})
   913  
   914  			It("returns an error to the client", func() {
   915  				proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   916  				Expect(err).NotTo(HaveOccurred())
   917  				Expect(proposalResponse.Response).To(Equal(&pb.Response{
   918  					Status:  500,
   919  					Message: "error in simulation: proto: Marshal called with nil",
   920  				}))
   921  				Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
   922  			})
   923  		})
   924  
   925  		Context("when the init fails", func() {
   926  			BeforeEach(func() {
   927  				fakeSupport.ExecuteLegacyInitReturns(nil, nil, fmt.Errorf("fake-legacy-init-error"))
   928  			})
   929  
   930  			It("returns an error and increments the metric", func() {
   931  				proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   932  				Expect(err).NotTo(HaveOccurred())
   933  				Expect(proposalResponse.Response).To(Equal(&pb.Response{
   934  					Status:  500,
   935  					Message: "error in simulation: fake-legacy-init-error",
   936  				}))
   937  
   938  				Expect(fakeInitFailed.WithCallCount()).To(Equal(1))
   939  				Expect(fakeInitFailed.WithArgsForCall(0)).To(Equal([]string{
   940  					"channel", "channel-id",
   941  					"chaincode", "deploy-name",
   942  				}))
   943  				Expect(fakeInitFailed.AddCallCount()).To(Equal(1))
   944  			})
   945  		})
   946  
   947  		Context("when the deploying chaincode is the name of a builtin system chaincode", func() {
   948  			BeforeEach(func() {
   949  				fakeSupport.IsSysCCStub = func(name string) bool {
   950  					return name == "deploy-name"
   951  				}
   952  			})
   953  
   954  			It("triggers the legacy init, and returns the response from lscc", func() {
   955  				proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   956  				Expect(err).NotTo(HaveOccurred())
   957  				Expect(proposalResponse.Response).To(Equal(&pb.Response{
   958  					Status:  500,
   959  					Message: "error in simulation: attempting to deploy a system chaincode deploy-name/channel-id",
   960  				}))
   961  				Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
   962  			})
   963  		})
   964  
   965  		Context("when unmarshalling UnmarshalChaincodeDeploymentSpec fails", func() {
   966  			BeforeEach(func() {
   967  				chaincodeInput = &pb.ChaincodeInput{
   968  					Args: [][]byte{[]byte("deploy"), nil, []byte("arg3")},
   969  				}
   970  			})
   971  
   972  			It("returns an error to the client", func() {
   973  				proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
   974  				Expect(err).NotTo(HaveOccurred())
   975  				Expect(proposalResponse.Response).To(Equal(&pb.Response{
   976  					Status:  500,
   977  					Message: "error in simulation: error unmarshaling ChaincodeDeploymentSpec: unexpected EOF",
   978  				}))
   979  				Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
   980  			})
   981  		})
   982  	})
   983  
   984  	Context("when retrieving simulation results", func() {
   985  		BeforeEach(func() {
   986  
   987  			mockDeployedCCInfoProvider := &ledgermock.DeployedChaincodeInfoProvider{}
   988  			fakeSupport.GetDeployedCCInfoProviderReturns(mockDeployedCCInfoProvider)
   989  
   990  			pvtSimResults := &rwset.TxPvtReadWriteSet{
   991  				DataModel: rwset.TxReadWriteSet_KV,
   992  				NsPvtRwset: []*rwset.NsPvtReadWriteSet{
   993  					{
   994  						Namespace: "myCC",
   995  						CollectionPvtRwset: []*rwset.CollectionPvtReadWriteSet{
   996  							{
   997  								CollectionName: "mycollection-1",
   998  								Rwset:          []byte{1, 2, 3, 4, 5, 6, 7, 8},
   999  							},
  1000  						},
  1001  					},
  1002  				},
  1003  			}
  1004  
  1005  			fakeTxSimulator.GetTxSimulationResultsReturns(
  1006  				&ledger.TxSimulationResults{
  1007  					PvtSimulationResults: pvtSimResults,
  1008  				},
  1009  				nil,
  1010  			)
  1011  		})
  1012  
  1013  		It("returns an error to the client", func() {
  1014  			proposalResponse, err := e.ProcessProposal(context.TODO(), signedProposal)
  1015  			Expect(err).NotTo(HaveOccurred())
  1016  			Expect(proposalResponse.Response).To(Equal(&pb.Response{
  1017  				Status:  500,
  1018  				Message: "error in simulation: failed to obtain collections config: no collection config for chaincode \"myCC\"",
  1019  			}))
  1020  			Expect(fakeSimulateFailure.AddCallCount()).To(Equal(1))
  1021  		})
  1022  	})
  1023  })