github.com/yimialmonte/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 })