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