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 })