go_study/fabric-main/core/chaincode/handler_test.go

3109 lines
103 KiB
Go

/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package chaincode_test
import (
"io"
"time"
"github.com/golang/protobuf/proto"
pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/common/metrics/metricsfakes"
"github.com/hyperledger/fabric/common/util"
ar "github.com/hyperledger/fabric/core/aclmgmt/resources"
"github.com/hyperledger/fabric/core/chaincode"
"github.com/hyperledger/fabric/core/chaincode/fake"
"github.com/hyperledger/fabric/core/chaincode/mock"
"github.com/hyperledger/fabric/core/common/ccprovider"
"github.com/hyperledger/fabric/core/common/sysccprovider"
"github.com/hyperledger/fabric/core/scc"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
)
var _ = Describe("Handler", func() {
var (
fakeTransactionRegistry *mock.TransactionRegistry
fakeContextRegistry *fake.ContextRegistry
fakeChatStream *mock.ChaincodeStream
builtinSCCs scc.BuiltinSCCs
fakeTxSimulator *mock.TxSimulator
fakeHistoryQueryExecutor *mock.HistoryQueryExecutor
fakeQueryResponseBuilder *fake.QueryResponseBuilder
fakeACLProvider *mock.ACLProvider
fakeInvoker *mock.Invoker
fakeLedgerGetter *mock.LedgerGetter
fakeHandlerRegistry *fake.Registry
fakeApplicationConfigRetriever *fake.ApplicationConfigRetriever
fakeCollectionStore *mock.CollectionStore
fakeShimRequestsReceived *metricsfakes.Counter
fakeShimRequestsCompleted *metricsfakes.Counter
fakeShimRequestDuration *metricsfakes.Histogram
fakeExecuteTimeouts *metricsfakes.Counter
fakeCapabilites *mock.ApplicationCapabilities
responseNotifier chan *pb.ChaincodeMessage
txContext *chaincode.TransactionContext
handler *chaincode.Handler
)
BeforeEach(func() {
fakeTransactionRegistry = &mock.TransactionRegistry{}
fakeTransactionRegistry.AddReturns(true)
fakeChatStream = &mock.ChaincodeStream{}
fakeTxSimulator = &mock.TxSimulator{}
fakeHistoryQueryExecutor = &mock.HistoryQueryExecutor{}
fakeCollectionStore = &mock.CollectionStore{}
responseNotifier = make(chan *pb.ChaincodeMessage, 1)
txContext = &chaincode.TransactionContext{
ChannelID: "channel-id",
NamespaceID: "cc-instance-name",
TXSimulator: fakeTxSimulator,
HistoryQueryExecutor: fakeHistoryQueryExecutor,
ResponseNotifier: responseNotifier,
CollectionStore: fakeCollectionStore,
}
txContext.InitializeCollectionACLCache()
fakeACLProvider = &mock.ACLProvider{}
fakeInvoker = &mock.Invoker{}
fakeLedgerGetter = &mock.LedgerGetter{}
fakeQueryResponseBuilder = &fake.QueryResponseBuilder{}
fakeHandlerRegistry = &fake.Registry{}
fakeContextRegistry = &fake.ContextRegistry{}
fakeContextRegistry.GetReturns(txContext)
fakeContextRegistry.CreateReturns(txContext, nil)
fakeApplicationConfig := &mock.ApplicationConfig{}
fakeCapabilites = &mock.ApplicationCapabilities{}
fakeCapabilites.KeyLevelEndorsementReturns(true)
fakeCapabilites.PurgePvtDataReturns(true)
fakeApplicationConfig.CapabilitiesReturns(fakeCapabilites)
fakeApplicationConfigRetriever = &fake.ApplicationConfigRetriever{}
fakeApplicationConfigRetriever.GetApplicationConfigReturns(fakeApplicationConfig, true)
fakeShimRequestsReceived = &metricsfakes.Counter{}
fakeShimRequestsReceived.WithReturns(fakeShimRequestsReceived)
fakeShimRequestsCompleted = &metricsfakes.Counter{}
fakeShimRequestsCompleted.WithReturns(fakeShimRequestsCompleted)
fakeShimRequestDuration = &metricsfakes.Histogram{}
fakeShimRequestDuration.WithReturns(fakeShimRequestDuration)
fakeExecuteTimeouts = &metricsfakes.Counter{}
fakeExecuteTimeouts.WithReturns(fakeExecuteTimeouts)
builtinSCCs = map[string]struct{}{}
chaincodeMetrics := &chaincode.HandlerMetrics{
ShimRequestsReceived: fakeShimRequestsReceived,
ShimRequestsCompleted: fakeShimRequestsCompleted,
ShimRequestDuration: fakeShimRequestDuration,
ExecuteTimeouts: fakeExecuteTimeouts,
}
handler = &chaincode.Handler{
ACLProvider: fakeACLProvider,
ActiveTransactions: fakeTransactionRegistry,
Invoker: fakeInvoker,
LedgerGetter: fakeLedgerGetter,
QueryResponseBuilder: fakeQueryResponseBuilder,
Registry: fakeHandlerRegistry,
BuiltinSCCs: builtinSCCs,
TXContexts: fakeContextRegistry,
UUIDGenerator: chaincode.UUIDGeneratorFunc(func() string {
return "generated-query-id"
}),
AppConfig: fakeApplicationConfigRetriever,
Metrics: chaincodeMetrics,
}
chaincode.SetHandlerChatStream(handler, fakeChatStream)
chaincode.SetHandlerChaincodeID(handler, "test-handler-name:1.0")
})
Describe("HandleTransaction", func() {
var (
incomingMessage *pb.ChaincodeMessage
fakeMessageHandler *fake.MessageHandler
expectedResponse *pb.ChaincodeMessage
)
BeforeEach(func() {
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_GET_STATE,
Txid: "tx-id",
ChannelId: "channel-id",
}
expectedResponse = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_UNDEFINED,
Payload: []byte("handler-response-payload"),
Txid: "response-tx-id",
ChannelId: "response-channel-id",
}
fakeMessageHandler = &fake.MessageHandler{}
fakeMessageHandler.HandleReturns(expectedResponse, nil)
})
It("registers the transaction ID from the message", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Expect(fakeTransactionRegistry.AddCallCount()).To(Equal(1))
channelID, transactionID := fakeTransactionRegistry.AddArgsForCall(0)
Expect(channelID).To(Equal("channel-id"))
Expect(transactionID).To(Equal("tx-id"))
})
It("ensures there is a valid transaction simulator available in the context", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Expect(fakeContextRegistry.GetCallCount()).To(Equal(1))
channelID, txID := fakeContextRegistry.GetArgsForCall(0)
Expect(channelID).To(Equal("channel-id"))
Expect(txID).To(Equal("tx-id"))
})
It("calls the delegate with the correct arguments", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Expect(fakeMessageHandler.HandleCallCount()).To(Equal(1))
msg, ctx := fakeMessageHandler.HandleArgsForCall(0)
Expect(msg).To(Equal(incomingMessage))
Expect(ctx).To(Equal(txContext))
})
It("sends the response message returned by the delegate", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(expectedResponse))
})
It("deregisters the transaction ID before sending the response", func() {
fakeTransactionRegistry.RemoveStub = func(channelID, txID string) {
Consistently(fakeChatStream.SendCallCount).Should(Equal(0))
}
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Expect(fakeTransactionRegistry.RemoveCallCount()).To(Equal(1))
channelID, transactionID := fakeTransactionRegistry.RemoveArgsForCall(0)
Expect(channelID).To(Equal("channel-id"))
Expect(transactionID).To(Equal("tx-id"))
})
It("records shim requests received before requests completed", func() {
fakeShimRequestsReceived.AddStub = func(delta float64) {
defer GinkgoRecover()
Expect(fakeShimRequestsCompleted.AddCallCount()).To(Equal(0))
}
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(expectedResponse))
Expect(fakeShimRequestsReceived.WithCallCount()).To(Equal(1))
labelValues := fakeShimRequestsReceived.WithArgsForCall(0)
Expect(labelValues).To(Equal([]string{
"type", "GET_STATE",
"channel", "channel-id",
"chaincode", "test-handler-name:1.0",
}))
Expect(fakeShimRequestsReceived.AddCallCount()).To(Equal(1))
Expect(fakeShimRequestsReceived.AddArgsForCall(0)).To(BeNumerically("~", 1.0))
})
It("records transactions completed after transactions received", func() {
fakeShimRequestsCompleted.AddStub = func(delta float64) {
defer GinkgoRecover()
Expect(fakeShimRequestsReceived.AddCallCount()).To(Equal(1))
}
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
Expect(fakeShimRequestsCompleted.WithCallCount()).To(Equal(1))
labelValues := fakeShimRequestsCompleted.WithArgsForCall(0)
Expect(labelValues).To(Equal([]string{
"type", "GET_STATE",
"channel", "channel-id",
"chaincode", "test-handler-name:1.0",
"success", "true",
}))
Expect(fakeShimRequestsCompleted.AddCallCount()).To(Equal(1))
Expect(fakeShimRequestsCompleted.AddArgsForCall(0)).To(BeNumerically("~", 1.0))
})
It("records transactions duration", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
Expect(fakeShimRequestDuration.WithCallCount()).To(Equal(1))
labelValues := fakeShimRequestDuration.WithArgsForCall(0)
Expect(labelValues).To(Equal([]string{
"type", "GET_STATE",
"channel", "channel-id",
"chaincode", "test-handler-name:1.0",
"success", "true",
}))
Expect(fakeShimRequestDuration.ObserveArgsForCall(0)).NotTo(BeZero())
Expect(fakeShimRequestDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0))
})
Context("when the transaction returns an error", func() {
BeforeEach(func() {
fakeMessageHandler.HandleReturns(nil, errors.New("I am a total failure"))
})
It("records metrics with success=false", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
Expect(fakeShimRequestsCompleted.WithCallCount()).To(Equal(1))
labelValues := fakeShimRequestsCompleted.WithArgsForCall(0)
Expect(labelValues).To(Equal([]string{
"type", "GET_STATE",
"channel", "channel-id",
"chaincode", "test-handler-name:1.0",
"success", "false",
}))
Expect(fakeShimRequestsCompleted.AddCallCount()).To(Equal(1))
Expect(fakeShimRequestsCompleted.AddArgsForCall(0)).To(BeNumerically("~", 1.0))
Expect(fakeShimRequestDuration.WithCallCount()).To(Equal(1))
labelValues = fakeShimRequestDuration.WithArgsForCall(0)
Expect(labelValues).To(Equal([]string{
"type", "GET_STATE",
"channel", "channel-id",
"chaincode", "test-handler-name:1.0",
"success", "false",
}))
Expect(fakeShimRequestDuration.ObserveArgsForCall(0)).NotTo(BeZero())
Expect(fakeShimRequestDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0))
})
})
Context("when the transaction ID has already been registered", func() {
BeforeEach(func() {
fakeTransactionRegistry.AddReturns(false)
})
It("returns without sending a response", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Consistently(fakeChatStream.SendCallCount).Should(Equal(0))
})
It("does not call the delegate", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Expect(fakeMessageHandler.HandleCallCount()).To(Equal(0))
})
It("does not attempt to deregister the transaction ID", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Expect(fakeTransactionRegistry.RemoveCallCount()).To(Equal(0))
})
})
Context("when the transaction context does not exist", func() {
BeforeEach(func() {
fakeContextRegistry.GetReturns(nil)
})
It("sends an error message", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_ERROR,
Payload: []byte("GET_STATE failed: transaction ID: tx-id: no ledger context"),
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
It("deregisters the message transaction ID", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Expect(fakeTransactionRegistry.RemoveCallCount()).To(Equal(1))
channelID, transactionID := fakeTransactionRegistry.RemoveArgsForCall(0)
Expect(channelID).To(Equal("channel-id"))
Expect(transactionID).To(Equal("tx-id"))
})
})
Context("when the transaction context is missing a transaction simulator", func() {
BeforeEach(func() {
txContext.TXSimulator = nil
})
It("sends an error response", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_ERROR,
Payload: []byte("GET_STATE failed: transaction ID: tx-id: no ledger context"),
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
It("deregisters the message transaction ID", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Expect(fakeTransactionRegistry.RemoveCallCount()).To(Equal(1))
channelID, transactionID := fakeTransactionRegistry.RemoveArgsForCall(0)
Expect(channelID).To(Equal("channel-id"))
Expect(transactionID).To(Equal("tx-id"))
})
})
Context("when the incoming message is INVOKE_CHAINCODE", func() {
var chaincodeSpec *pb.ChaincodeSpec
BeforeEach(func() {
chaincodeSpec = &pb.ChaincodeSpec{
Type: pb.ChaincodeSpec_GOLANG,
ChaincodeId: &pb.ChaincodeID{
Name: "target-chaincode-name",
Version: "target-chaincode-version",
},
Input: &pb.ChaincodeInput{
Args: util.ToChaincodeArgs("command", "arg"),
},
}
payloadBytes, err := proto.Marshal(chaincodeSpec)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Type = pb.ChaincodeMessage_INVOKE_CHAINCODE
incomingMessage.Payload = payloadBytes
})
It("validates the transaction context", func() {
txContext.TXSimulator = nil
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_ERROR,
Payload: []byte("INVOKE_CHAINCODE failed: transaction ID: tx-id: could not get valid transaction"),
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
Context("and the channel ID is not set", func() {
BeforeEach(func() {
incomingMessage.ChannelId = ""
})
It("validates the transaction context", func() {
txContext.TXSimulator = nil
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_ERROR,
Payload: []byte("INVOKE_CHAINCODE failed: transaction ID: tx-id: could not get valid transaction"),
Txid: "tx-id",
}))
})
Context("when the target is system chaincode", func() {
BeforeEach(func() {
builtinSCCs["target-chaincode-name"] = struct{}{}
})
It("gets the transaction context without validation", func() {
txContext.TXSimulator = nil
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Expect(fakeContextRegistry.GetCallCount()).To(Equal(1))
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(expectedResponse))
})
Context("and the transaction context is missing", func() {
It("returns an error", func() {
fakeContextRegistry.GetReturns(nil)
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_ERROR,
Payload: []byte("INVOKE_CHAINCODE failed: transaction ID: tx-id: failed to get transaction context"),
Txid: "tx-id",
}))
})
})
})
Context("when the payload fails to unmarshal into a chaincode spec", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("sends an error response", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg.Type).To(Equal(pb.ChaincodeMessage_ERROR))
Expect(msg.Txid).To(Equal("tx-id"))
Expect(string(msg.Payload)).To(HavePrefix("INVOKE_CHAINCODE failed: transaction ID: tx-id"))
})
})
})
})
Context("when the delegate returns an error", func() {
BeforeEach(func() {
fakeMessageHandler.HandleReturns(nil, errors.New("watermelon-swirl"))
})
It("sends an error response", func() {
handler.HandleTransaction(incomingMessage, fakeMessageHandler.Handle)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_ERROR,
Payload: []byte("GET_STATE failed: transaction ID: tx-id: watermelon-swirl"),
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
})
})
Describe("HandlePutState", func() {
var incomingMessage *pb.ChaincodeMessage
var request *pb.PutState
BeforeEach(func() {
request = &pb.PutState{
Key: "put-state-key",
Value: []byte("put-state-value"),
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_PUT_STATE,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
})
It("returns a response message", func() {
resp, err := handler.HandlePutState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandlePutState(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when the collection is not provided", func() {
It("calls SetState on the transaction simulator", func() {
_, err := handler.HandlePutState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.SetStateCallCount()).To(Equal(1))
ccname, key, value := fakeTxSimulator.SetStateArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(key).To(Equal("put-state-key"))
Expect(value).To(Equal([]byte("put-state-value")))
})
Context("when SeteState fails", func() {
BeforeEach(func() {
fakeTxSimulator.SetStateReturns(errors.New("king-kong"))
})
It("returns an error", func() {
_, err := handler.HandlePutState(incomingMessage, txContext)
Expect(err).To(MatchError("king-kong"))
})
})
})
Context("when the collection is provided", func() {
BeforeEach(func() {
request.Collection = "collection-name"
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, true, nil) // to
})
It("calls SetPrivateData on the transaction simulator", func() {
_, err := handler.HandlePutState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.SetPrivateDataCallCount()).To(Equal(1))
ccname, collection, key, value := fakeTxSimulator.SetPrivateDataArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(collection).To(Equal("collection-name"))
Expect(key).To(Equal("put-state-key"))
Expect(value).To(Equal([]byte("put-state-value")))
})
Context("when SetPrivateData fails due to ledger error", func() {
BeforeEach(func() {
fakeTxSimulator.SetPrivateDataReturns(errors.New("godzilla"))
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, true, nil) // to
})
It("returns an error", func() {
_, err := handler.HandlePutState(incomingMessage, txContext)
Expect(err).To(MatchError("godzilla"))
})
})
Context("when SetPrivateData fails due to Init transaction", func() {
BeforeEach(func() {
txContext.IsInitTransaction = true
})
It("returns the error from errorIfInitTransaction", func() {
_, err := handler.HandlePutState(incomingMessage, txContext)
Expect(err).To(MatchError("private data APIs are not allowed in chaincode Init()"))
})
})
Context("when SetPrivateData fails due to no write access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, nil)
})
It("returns the error from errorIfCreatorHasNoWriteAccess", func() {
_, err := handler.HandlePutState(incomingMessage, txContext)
Expect(err).To(MatchError("tx creator does not have write access" +
" permission on privatedata in chaincodeName:cc-instance-name" +
" collectionName: collection-name"))
})
})
})
})
Describe("HandlePutStateMetadata", func() {
var incomingMessage *pb.ChaincodeMessage
var request *pb.PutStateMetadata
BeforeEach(func() {
request = &pb.PutStateMetadata{
Key: "put-state-key",
Metadata: &pb.StateMetadata{
Metakey: "put-state-metakey",
Value: []byte("put-state-metadata-value"),
},
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_PUT_STATE_METADATA,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
})
It("returns a response message", func() {
resp, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
It("acquires application config for the channel", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeApplicationConfigRetriever.GetApplicationConfigCallCount()).To(Equal(1))
cid := fakeApplicationConfigRetriever.GetApplicationConfigArgsForCall(0)
Expect(cid).To(Equal("channel-id"))
})
Context("when getting the app config metadata fails", func() {
BeforeEach(func() {
fakeApplicationConfigRetriever.GetApplicationConfigReturns(nil, false)
})
It("returns an error", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("application config does not exist for channel-id"))
})
})
Context("when key level endorsement is not supported", func() {
BeforeEach(func() {
fakeCapabilites.KeyLevelEndorsementReturns(false)
})
It("returns an error", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("key level endorsement is not enabled, channel application capability of V1_3 or later is required"))
})
})
Context("when purge private data is not supported", func() {
BeforeEach(func() {
fakeCapabilites.PurgePvtDataReturns(false)
})
It("returns an error", func() {
_, err := handler.HandlePurgePrivateData(incomingMessage, txContext)
Expect(err).To(MatchError("purge private data is not enabled, channel application capability of V2_5 or later is required"))
})
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when the collection is not provided", func() {
It("calls SetStateMetadata on the transaction simulator", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.SetStateMetadataCallCount()).To(Equal(1))
ccname, key, value := fakeTxSimulator.SetStateMetadataArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(key).To(Equal("put-state-key"))
Expect(value).To(Equal(map[string][]byte{
"put-state-metakey": []byte("put-state-metadata-value"),
}))
})
Context("when SetStateMetadata fails", func() {
BeforeEach(func() {
fakeTxSimulator.SetStateMetadataReturns(errors.New("king-kong"))
})
It("returns an error", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("king-kong"))
})
})
})
Context("when the collection is provided", func() {
BeforeEach(func() {
request.Collection = "collection-name"
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, true, nil) // to
})
It("calls SetPrivateDataMetadata on the transaction simulator", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.SetPrivateDataMetadataCallCount()).To(Equal(1))
ccname, collection, key, value := fakeTxSimulator.SetPrivateDataMetadataArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(collection).To(Equal("collection-name"))
Expect(key).To(Equal("put-state-key"))
Expect(value).To(Equal(map[string][]byte{
"put-state-metakey": []byte("put-state-metadata-value"),
}))
})
Context("when SetPrivateDataMetadata fails due to ledger error", func() {
BeforeEach(func() {
fakeTxSimulator.SetPrivateDataMetadataReturns(errors.New("godzilla"))
})
It("returns an error", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("godzilla"))
})
})
Context("when SetPrivateDataMetadata fails due to Init transaction", func() {
BeforeEach(func() {
txContext.IsInitTransaction = true
})
It("returns the error from errorIfInitTransaction", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("private data APIs are not allowed in chaincode Init()"))
})
})
Context("when SetPrivateDataMetadata fails due to no write access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, nil)
})
It("returns the error from errorIfCreatorHasNoWriteAccess", func() {
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("tx creator does not have write access" +
" permission on privatedata in chaincodeName:cc-instance-name" +
" collectionName: collection-name"))
})
})
})
})
Describe("HandleDelState", func() {
var incomingMessage *pb.ChaincodeMessage
var request *pb.DelState
BeforeEach(func() {
request = &pb.DelState{
Key: "del-state-key",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_DEL_STATE,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
})
It("returns a response message", func() {
resp, err := handler.HandleDelState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleDelState(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when collection is not set", func() {
It("calls DeleteState on the transaction simulator", func() {
_, err := handler.HandleDelState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.DeleteStateCallCount()).To(Equal(1))
ccname, key := fakeTxSimulator.DeleteStateArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(key).To(Equal("del-state-key"))
})
Context("when DeleteState returns an error", func() {
BeforeEach(func() {
fakeTxSimulator.DeleteStateReturns(errors.New("orange"))
})
It("return an error", func() {
_, err := handler.HandleDelState(incomingMessage, txContext)
Expect(err).To(MatchError("orange"))
})
})
})
Context("when collection is set", func() {
BeforeEach(func() {
request.Collection = "collection-name"
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, true, nil) // to
})
It("calls DeletePrivateData on the transaction simulator", func() {
_, err := handler.HandleDelState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.DeletePrivateDataCallCount()).To(Equal(1))
ccname, collection, key := fakeTxSimulator.DeletePrivateDataArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(collection).To(Equal("collection-name"))
Expect(key).To(Equal("del-state-key"))
})
Context("when DeletePrivateData fails due to ledger error", func() {
BeforeEach(func() {
fakeTxSimulator.DeletePrivateDataReturns(errors.New("mango"))
})
It("returns an error", func() {
_, err := handler.HandleDelState(incomingMessage, txContext)
Expect(err).To(MatchError("mango"))
})
})
Context("when DeletePrivateData fails due to Init transaction", func() {
BeforeEach(func() {
txContext.IsInitTransaction = true
})
It("returns the error from errorIfInitTransaction", func() {
_, err := handler.HandleDelState(incomingMessage, txContext)
Expect(err).To(MatchError("private data APIs are not allowed in chaincode Init()"))
})
})
Context("when DeletePrivateData fails due to no write access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, nil)
})
It("returns the error from errorIfCreatorHasNoWriteAccess", func() {
_, err := handler.HandleDelState(incomingMessage, txContext)
Expect(err).To(MatchError("tx creator does not have write access" +
" permission on privatedata in chaincodeName:cc-instance-name" +
" collectionName: collection-name"))
})
})
})
})
Describe("HandleGetState", func() {
var (
incomingMessage *pb.ChaincodeMessage
request *pb.GetState
expectedResponse *pb.ChaincodeMessage
)
BeforeEach(func() {
request = &pb.GetState{
Key: "get-state-key",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_GET_STATE,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
expectedResponse = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Txid: "tx-id",
ChannelId: "channel-id",
}
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when collection is set", func() {
BeforeEach(func() {
request.Collection = "collection-name"
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
fakeCollectionStore.RetrieveReadWritePermissionReturns(true, false, nil)
fakeTxSimulator.GetPrivateDataReturns([]byte("get-private-data-response"), nil)
expectedResponse.Payload = []byte("get-private-data-response")
})
It("calls GetPrivateData on the transaction simulator", func() {
_, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.GetPrivateDataCallCount()).To(Equal(1))
ccname, collection, key := fakeTxSimulator.GetPrivateDataArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(collection).To(Equal("collection-name"))
Expect(key).To(Equal("get-state-key"))
})
Context("and GetPrivateData fails due to ledger error", func() {
BeforeEach(func() {
fakeTxSimulator.GetPrivateDataReturns(nil, errors.New("french fries"))
})
It("returns the error from GetPrivateData", func() {
_, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).To(MatchError("french fries"))
})
})
Context("and GetPrivateData fails due to no read access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, nil)
})
It("returns the error from errorIfCreatorHasNoReadAccess", func() {
_, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).To(MatchError("tx creator does not have read access" +
" permission on privatedata in chaincodeName:cc-instance-name" +
" collectionName: collection-name"))
})
})
Context("and GetPrivateData fails due to error in checking the read access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, errors.New("no collection config"))
})
It("returns the error from errorIfCreatorHasNoReadAccess", func() {
_, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).To(MatchError("no collection config"))
})
})
Context("and GetPrivateData fails due to Init transaction", func() {
BeforeEach(func() {
txContext.IsInitTransaction = true
})
It("returns the error from errorIfInitTransaction", func() {
_, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).To(MatchError("private data APIs are not allowed in chaincode Init()"))
})
})
Context("and GetPrivateData returns the response message", func() {
BeforeEach(func() {
// txContext.CollectionACLCache.put("collection-name", true, false)
// fakeCollectionStore.HasReadAccessReturns(false, nil) // to
// ensure that the access cache is used
})
It("returns the response message from GetPrivateData", func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(true, false, nil) // to
resp, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Payload: []byte("get-private-data-response"),
Txid: "tx-id",
ChannelId: "channel-id",
}))
// as the cache hit should happen in CollectionACLCache, the following
// RetrieveReadWritePermissionReturns should not be called
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, nil) // to
resp, err = handler.HandleGetState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Payload: []byte("get-private-data-response"),
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
})
It("returns the response message from GetPrivateData", func() {
resp, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Payload: []byte("get-private-data-response"),
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
})
Context("when collection is not set", func() {
BeforeEach(func() {
fakeTxSimulator.GetStateReturns([]byte("get-state-response"), nil)
expectedResponse.Payload = []byte("get-state-response")
})
It("calls GetState on the transaction simulator", func() {
_, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.GetStateCallCount()).To(Equal(1))
ccname, key := fakeTxSimulator.GetStateArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(key).To(Equal("get-state-key"))
})
Context("and GetState fails", func() {
BeforeEach(func() {
fakeTxSimulator.GetStateReturns(nil, errors.New("tomato"))
})
It("returns the error from GetState", func() {
_, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).To(MatchError("tomato"))
})
})
It("returns the response from GetState", func() {
resp, err := handler.HandleGetState(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Payload: []byte("get-state-response"),
Txid: "tx-id",
ChannelId: "channel-id",
}))
})
})
})
Describe("HandleGetPrivateDataHash", func() {
var (
incomingMessage *pb.ChaincodeMessage
request *pb.GetState
expectedResponse *pb.ChaincodeMessage
)
BeforeEach(func() {
request = &pb.GetState{
Collection: "collection-name",
Key: "get-pvtdata-hash-key",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_GET_PRIVATE_DATA_HASH,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
expectedResponse = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Payload: []byte("get-private-data-hash-response"),
Txid: "tx-id",
ChannelId: "channel-id",
}
fakeTxSimulator.GetPrivateDataHashReturns([]byte("get-private-data-hash-response"), nil)
})
It("calls GetPrivateDataHash on the transaction simulator and receives expected response", func() {
response, err := handler.HandleGetPrivateDataHash(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.GetPrivateDataHashCallCount()).To(Equal(1))
ccname, collection, key := fakeTxSimulator.GetPrivateDataHashArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(collection).To(Equal("collection-name"))
Expect(key).To(Equal("get-pvtdata-hash-key"))
Expect(response).To(Equal(expectedResponse))
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleGetPrivateDataHash(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("and GetPrivateDataHash fails due to ledger error", func() {
BeforeEach(func() {
fakeTxSimulator.GetPrivateDataHashReturns(nil, errors.New("french fries"))
})
It("returns the error from GetPrivateData", func() {
_, err := handler.HandleGetPrivateDataHash(incomingMessage, txContext)
Expect(err).To(MatchError("french fries"))
})
})
Context("and GetPrivateData fails due to Init transaction", func() {
BeforeEach(func() {
txContext.IsInitTransaction = true
})
It("returns the error from errorIfInitTransaction", func() {
_, err := handler.HandleGetPrivateDataHash(incomingMessage, txContext)
Expect(err).To(MatchError("private data APIs are not allowed in chaincode Init()"))
})
})
})
Describe("HandleGetStateMetadata", func() {
var (
incomingMessage *pb.ChaincodeMessage
request *pb.GetStateMetadata
expectedResponse *pb.ChaincodeMessage
)
BeforeEach(func() {
request = &pb.GetStateMetadata{
Key: "get-state-key",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_GET_STATE_METADATA,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
expectedResponse = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Txid: "tx-id",
ChannelId: "channel-id",
}
})
It("acquires application config for the channel", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeApplicationConfigRetriever.GetApplicationConfigCallCount()).To(Equal(1))
cid := fakeApplicationConfigRetriever.GetApplicationConfigArgsForCall(0)
Expect(cid).To(Equal("channel-id"))
})
Context("when getting the app config metadata fails", func() {
BeforeEach(func() {
fakeApplicationConfigRetriever.GetApplicationConfigReturns(nil, false)
})
It("returns an error", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("application config does not exist for channel-id"))
})
})
Context("when key level endorsement is not supported", func() {
BeforeEach(func() {
fakeCapabilites.KeyLevelEndorsementReturns(false)
})
It("returns an error", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("key level endorsement is not enabled, channel application capability of V1_3 or later is required"))
})
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when collection is set", func() {
BeforeEach(func() {
request.Collection = "collection-name"
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
metadata := map[string][]byte{
"get-state-metakey": []byte("get-private-metadata-response"),
}
fakeCollectionStore.RetrieveReadWritePermissionReturns(true, false, nil)
fakeTxSimulator.GetPrivateDataMetadataReturns(metadata, nil)
responsePayload, err := proto.Marshal(&pb.StateMetadataResult{
Entries: []*pb.StateMetadata{{
Metakey: "get-state-metakey",
Value: []byte("get-private-metadata-response"),
}},
})
Expect(err).NotTo(HaveOccurred())
expectedResponse.Payload = responsePayload
})
It("calls GetPrivateDataMetadata on the transaction simulator", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.GetPrivateDataMetadataCallCount()).To(Equal(1))
ccname, collection, key := fakeTxSimulator.GetPrivateDataMetadataArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(collection).To(Equal("collection-name"))
Expect(key).To(Equal("get-state-key"))
})
It("returns the response message from GetPrivateDataMetadata", func() {
resp, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(expectedResponse))
})
Context("and GetPrivateDataMetadata fails due to ledger error", func() {
BeforeEach(func() {
fakeTxSimulator.GetPrivateDataMetadataReturns(nil, errors.New("french fries"))
})
It("returns the error from GetPrivateDataMetadata", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("french fries"))
})
})
Context("and GetPrivateDataMetadata fails due to no read access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, nil)
})
It("returns the error from GetPrivateDataMetadata", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("tx creator does not have read access" +
" permission on privatedata in chaincodeName:cc-instance-name" +
" collectionName: collection-name"))
})
})
Context("and GetPrivateDataMetadata fails due to error in checking the read access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, errors.New("no collection config"))
})
It("returns the error from GetPrivateDataMetadata", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("no collection config"))
})
})
Context("and GetPrivateDataMetadata fails due to Init transaction", func() {
BeforeEach(func() {
txContext.IsInitTransaction = true
})
It("returns the error from errorIfInitTransaction", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("private data APIs are not allowed in chaincode Init()"))
})
})
})
Context("when collection is not set", func() {
BeforeEach(func() {
metadata := map[string][]byte{
"get-state-metakey": []byte("get-state-metadata-response"),
}
fakeTxSimulator.GetStateMetadataReturns(metadata, nil)
responsePayload, err := proto.Marshal(&pb.StateMetadataResult{
Entries: []*pb.StateMetadata{{
Metakey: "get-state-metakey",
Value: []byte("get-state-metadata-response"),
}},
})
Expect(err).NotTo(HaveOccurred())
expectedResponse.Payload = responsePayload
})
It("calls GetStateMetadata on the transaction simulator", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.GetStateMetadataCallCount()).To(Equal(1))
ccname, key := fakeTxSimulator.GetStateMetadataArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(key).To(Equal("get-state-key"))
})
It("returns the response from GetStateMetadata", func() {
resp, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(expectedResponse))
})
Context("and GetStateMetadata fails", func() {
BeforeEach(func() {
fakeTxSimulator.GetStateMetadataReturns(nil, errors.New("tomato"))
})
It("returns the error from GetStateMetadata", func() {
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
Expect(err).To(MatchError("tomato"))
})
})
})
})
Describe("HandleGetStateByRange", func() {
var (
incomingMessage *pb.ChaincodeMessage
request *pb.GetStateByRange
expectedResponse *pb.ChaincodeMessage
fakeIterator *mock.QueryResultsIterator
expectedQueryResponse *pb.QueryResponse
expectedPayload []byte
)
BeforeEach(func() {
request = &pb.GetStateByRange{
StartKey: "get-state-start-key",
EndKey: "get-state-end-key",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_GET_STATE,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
fakeIterator = &mock.QueryResultsIterator{}
fakeTxSimulator.GetStateRangeScanIteratorReturns(fakeIterator, nil)
expectedQueryResponse = &pb.QueryResponse{
Results: nil,
HasMore: true,
Id: "query-response-id",
}
fakeQueryResponseBuilder.BuildQueryResponseReturns(expectedQueryResponse, nil)
expectedPayload, err = proto.Marshal(expectedQueryResponse)
Expect(err).NotTo(HaveOccurred())
expectedResponse = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Txid: "tx-id",
Payload: expectedPayload,
ChannelId: "channel-id",
}
})
It("initializes a query context", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(Equal(&chaincode.PendingQueryResult{}))
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(Equal(fakeIterator))
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(*retCount).To(Equal(int32(0)))
})
It("returns the response message", func() {
resp, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(expectedResponse))
})
Context("when collection is not set", func() {
It("calls GetStateRangeScanIterator on the transaction simulator", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.GetStateRangeScanIteratorCallCount()).To(Equal(1))
ccname, startKey, endKey := fakeTxSimulator.GetStateRangeScanIteratorArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(startKey).To(Equal("get-state-start-key"))
Expect(endKey).To(Equal("get-state-end-key"))
})
Context("and GetStateRangeScanIterator fails", func() {
BeforeEach(func() {
fakeTxSimulator.GetStateRangeScanIteratorReturns(nil, errors.New("tomato"))
})
It("returns the error from GetStateRangeScanIterator", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).To(MatchError("tomato"))
})
})
It("returns the response from GetStateRangeScanIterator", func() {
resp, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(expectedResponse))
})
})
Context("when collection is set", func() {
BeforeEach(func() {
request.Collection = "collection-name"
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
fakeCollectionStore.RetrieveReadWritePermissionReturns(true, false, nil)
fakeTxSimulator.GetPrivateDataRangeScanIteratorReturns(fakeIterator, nil)
})
It("calls GetPrivateDataRangeScanIterator on the transaction simulator", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.GetPrivateDataRangeScanIteratorCallCount()).To(Equal(1))
ccname, collection, startKey, endKey := fakeTxSimulator.GetPrivateDataRangeScanIteratorArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(collection).To(Equal("collection-name"))
Expect(startKey).To(Equal("get-state-start-key"))
Expect(endKey).To(Equal("get-state-end-key"))
})
Context("and GetPrivateDataRangeScanIterator fails due to ledger error", func() {
BeforeEach(func() {
fakeTxSimulator.GetPrivateDataRangeScanIteratorReturns(nil, errors.New("french fries"))
})
It("returns the error from GetPrivateDataRangeScanIterator", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).To(MatchError("french fries"))
})
})
Context("and GetPrivateDataRangeScanIterator fails due to no read access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, nil)
})
It("returns the error from GetPrivateDataRangeScanIterator", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).To(MatchError("tx creator does not have read access" +
" permission on privatedata in chaincodeName:cc-instance-name" +
" collectionName: collection-name"))
})
})
Context("and GetPrivateDataRangeScanIterator fails due to error in checking the read access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, errors.New("no collection config"))
})
It("returns the error from GetPrivateDataRangeScanIterator", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).To(MatchError("no collection config"))
})
})
Context("and GetPrivateDataRangeScanIterator fails due to Init transaction", func() {
BeforeEach(func() {
txContext.IsInitTransaction = true
})
It("returns the error from errorIfInitTransaction", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).To(MatchError("private data APIs are not allowed in chaincode Init()"))
})
})
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when building the query response fails", func() {
BeforeEach(func() {
fakeQueryResponseBuilder.BuildQueryResponseReturns(nil, errors.New("garbanzo"))
})
It("returns the error", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).To(MatchError("garbanzo"))
})
It("cleans up the query context", func() {
handler.HandleGetStateByRange(incomingMessage, txContext)
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(BeNil())
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(BeNil())
})
})
Context("when marshling the response fails", func() {
BeforeEach(func() {
fakeQueryResponseBuilder.BuildQueryResponseReturns(nil, nil)
})
It("returns the error", func() {
_, err := handler.HandleGetStateByRange(incomingMessage, txContext)
Expect(err).To(MatchError("marshal failed: proto: Marshal called with nil"))
})
It("cleans up the query context", func() {
handler.HandleGetStateByRange(incomingMessage, txContext)
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(BeNil())
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(BeNil())
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(retCount).To(BeNil())
})
})
})
Describe("HandleQueryStateNext", func() {
var (
fakeIterator *mock.QueryResultsIterator
expectedQueryResponse *pb.QueryResponse
request *pb.QueryStateNext
incomingMessage *pb.ChaincodeMessage
)
BeforeEach(func() {
request = &pb.QueryStateNext{
Id: "query-state-next-id",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
fakeIterator = &mock.QueryResultsIterator{}
txContext.InitializeQueryContext("query-state-next-id", fakeIterator)
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_GET_STATE,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
expectedQueryResponse = &pb.QueryResponse{
Results: nil,
HasMore: true,
Id: "query-response-id",
}
fakeQueryResponseBuilder.BuildQueryResponseReturns(expectedQueryResponse, nil)
})
It("builds a query response", func() {
_, err := handler.HandleQueryStateNext(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeQueryResponseBuilder.BuildQueryResponseCallCount()).To(Equal(1))
tctx, iter, id, _, _ := fakeQueryResponseBuilder.BuildQueryResponseArgsForCall(0)
Expect(tctx).To(Equal(txContext))
Expect(iter).To(Equal(fakeIterator))
Expect(id).To(Equal("query-state-next-id"))
})
It("returns a chaincode message with the query response", func() {
resp, err := handler.HandleQueryStateNext(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
expectedPayload, err := proto.Marshal(expectedQueryResponse)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Payload: expectedPayload,
ChannelId: "channel-id",
Txid: "tx-id",
}))
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleQueryStateNext(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when the query iterator is missing", func() {
BeforeEach(func() {
txContext.CleanupQueryContext("query-state-next-id")
})
It("returns an error", func() {
_, err := handler.HandleQueryStateNext(incomingMessage, txContext)
Expect(err).To(MatchError("query iterator not found"))
})
})
Context("when building the query response fails", func() {
BeforeEach(func() {
fakeQueryResponseBuilder.BuildQueryResponseReturns(nil, errors.New("potato"))
})
It("returns an error", func() {
_, err := handler.HandleQueryStateNext(incomingMessage, txContext)
Expect(err).To(MatchError("potato"))
})
It("cleans up the query context", func() {
handler.HandleQueryStateNext(incomingMessage, txContext)
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(BeNil())
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(BeNil())
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(retCount).To(BeNil())
})
})
Context("when marshaling the query response fails", func() {
BeforeEach(func() {
fakeQueryResponseBuilder.BuildQueryResponseReturns(nil, nil)
})
It("returns an error", func() {
_, err := handler.HandleQueryStateNext(incomingMessage, txContext)
Expect(err).To(MatchError("marshal failed: proto: Marshal called with nil"))
})
It("cleans up the query context", func() {
handler.HandleQueryStateNext(incomingMessage, txContext)
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(BeNil())
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(BeNil())
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(retCount).To(BeNil())
})
})
})
Describe("HandleQueryStateClose", func() {
var (
fakeIterator *mock.QueryResultsIterator
expectedQueryResponse *pb.QueryResponse
request *pb.QueryStateClose
incomingMessage *pb.ChaincodeMessage
)
BeforeEach(func() {
request = &pb.QueryStateClose{
Id: "query-state-close-id",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
fakeIterator = &mock.QueryResultsIterator{}
txContext.InitializeQueryContext("query-state-close-id", fakeIterator)
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_GET_STATE,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
expectedQueryResponse = &pb.QueryResponse{
Id: "query-state-close-id",
}
})
It("returns a chaincode message with the query response", func() {
resp, err := handler.HandleQueryStateClose(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
expectedPayload, err := proto.Marshal(expectedQueryResponse)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_RESPONSE,
Payload: expectedPayload,
ChannelId: "channel-id",
Txid: "tx-id",
}))
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleQueryStateClose(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when the query iterator is missing", func() {
BeforeEach(func() {
txContext.CleanupQueryContext("query-state-close-id")
})
It("keeps calm and carries on", func() {
_, err := handler.HandleQueryStateClose(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
})
})
})
Describe("HandleGetQueryResult", func() {
var (
request *pb.GetQueryResult
incomingMessage *pb.ChaincodeMessage
expectedQueryResponse *pb.QueryResponse
fakeIterator *mock.QueryResultsIterator
)
BeforeEach(func() {
request = &pb.GetQueryResult{
Query: "query-result",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_GET_STATE,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
expectedQueryResponse = &pb.QueryResponse{
Id: "query-response-id",
}
fakeQueryResponseBuilder.BuildQueryResponseReturns(expectedQueryResponse, nil)
fakeIterator = &mock.QueryResultsIterator{}
fakeTxSimulator.ExecuteQueryReturns(fakeIterator, nil)
})
Context("when collection is not set", func() {
It("calls ExecuteQuery on the transaction simulator", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.ExecuteQueryCallCount()).To(Equal(1))
ccname, query := fakeTxSimulator.ExecuteQueryArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(query).To(Equal("query-result"))
})
Context("and ExecuteQuery fails", func() {
BeforeEach(func() {
fakeTxSimulator.ExecuteQueryReturns(nil, errors.New("mushrooms"))
})
It("returns the error", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).To(MatchError("mushrooms"))
})
})
})
Context("when collection is set", func() {
BeforeEach(func() {
request.Collection = "collection-name"
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
fakeCollectionStore.RetrieveReadWritePermissionReturns(true, false, nil)
fakeTxSimulator.ExecuteQueryOnPrivateDataReturns(fakeIterator, nil)
})
It("calls ExecuteQueryOnPrivateDataon the transaction simulator", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.ExecuteQueryOnPrivateDataCallCount()).To(Equal(1))
ccname, collection, query := fakeTxSimulator.ExecuteQueryOnPrivateDataArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(collection).To(Equal("collection-name"))
Expect(query).To(Equal("query-result"))
})
It("initializes a query context", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(Equal(&chaincode.PendingQueryResult{}))
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(Equal(fakeIterator))
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(*retCount).To(Equal(int32(0)))
})
Context("and ExecuteQueryOnPrivateData fails due to ledger error", func() {
BeforeEach(func() {
fakeTxSimulator.ExecuteQueryOnPrivateDataReturns(nil, errors.New("pizza"))
})
It("returns the error", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).To(MatchError("pizza"))
})
})
Context("and ExecuteQueryOnPrivateData fails due to no read access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, nil)
})
It("returns the error", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).To(MatchError("tx creator does not have read access" +
" permission on privatedata in chaincodeName:cc-instance-name" +
" collectionName: collection-name"))
})
})
Context("and ExecuteQueryOnPrivateData fails due to error in checking the read access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, errors.New("no collection config"))
})
It("returns the error", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).To(MatchError("no collection config"))
})
})
Context("and ExecuteQueryOnPrivateData fails due to Init transaction", func() {
BeforeEach(func() {
txContext.IsInitTransaction = true
})
It("returns the error from errorIfInitTransaction", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).To(MatchError("private data APIs are not allowed in chaincode Init()"))
})
})
})
It("builds the query response", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeQueryResponseBuilder.BuildQueryResponseCallCount()).To(Equal(1))
tctx, iter, iterID, _, _ := fakeQueryResponseBuilder.BuildQueryResponseArgsForCall(0)
Expect(tctx).To(Equal(txContext))
Expect(iter).To(Equal(fakeIterator))
Expect(iterID).To(Equal("generated-query-id"))
})
Context("when building the query response fails", func() {
BeforeEach(func() {
fakeQueryResponseBuilder.BuildQueryResponseReturns(nil, errors.New("peppers"))
})
It("returns the error", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).To(MatchError("peppers"))
})
It("cleans up the query context", func() {
handler.HandleGetQueryResult(incomingMessage, txContext)
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(BeNil())
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(BeNil())
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(retCount).To(BeNil())
})
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when marshaling the response fails", func() {
BeforeEach(func() {
fakeQueryResponseBuilder.BuildQueryResponseReturns(nil, nil)
})
It("returns an error", func() {
_, err := handler.HandleGetQueryResult(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("marshal failed:"))
})
It("cleans up the query context", func() {
handler.HandleGetQueryResult(incomingMessage, txContext)
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(BeNil())
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(BeNil())
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(retCount).To(BeNil())
})
})
})
Describe("HandlePurgePrivateData", func() {
var incomingMessage *pb.ChaincodeMessage
var request *pb.PurgePrivateState
BeforeEach(func() {
request = &pb.PurgePrivateState{
Key: "purge-state-key",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_PURGE_PRIVATE_DATA,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandlePurgePrivateData(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed"))
})
})
Context("when collection is not set", func() {
It("calls PurgePrivateState on the transaction simulator", func() {
_, err := handler.HandlePurgePrivateData(incomingMessage, txContext)
Expect(err).To(MatchError("only applicable for private data"))
})
Context("when PurgePrivateState returns an error", func() {
BeforeEach(func() {
fakeTxSimulator.PurgePrivateDataReturns(errors.New("orange"))
request.Collection = "collection-name"
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, true, nil) // to
})
It("return an error", func() {
_, err := handler.HandlePurgePrivateData(incomingMessage, txContext)
Expect(err).To(MatchError("orange"))
})
})
})
Context("when collection is set", func() {
BeforeEach(func() {
request.Collection = "collection-name"
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, true, nil) // to
})
It("calls PurgePrivateData on the transaction simulator", func() {
_, err := handler.HandlePurgePrivateData(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTxSimulator.PurgePrivateDataCallCount()).To(Equal(1))
ccname, collection, key := fakeTxSimulator.PurgePrivateDataArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(collection).To(Equal("collection-name"))
Expect(key).To(Equal("purge-state-key"))
})
Context("when PurgePrivateData fails due to ledger error", func() {
BeforeEach(func() {
fakeTxSimulator.PurgePrivateDataReturns(errors.New("mango"))
})
It("returns an error", func() {
_, err := handler.HandlePurgePrivateData(incomingMessage, txContext)
Expect(err).To(MatchError("mango"))
})
})
Context("when PurgePrivateData fails due to Init transaction", func() {
BeforeEach(func() {
txContext.IsInitTransaction = true
})
It("returns the error from errorIfInitTransaction", func() {
_, err := handler.HandlePurgePrivateData(incomingMessage, txContext)
Expect(err).To(MatchError("private data APIs are not allowed in chaincode Init()"))
})
})
Context("when PurgePrivateData fails due to no write access permission", func() {
BeforeEach(func() {
fakeCollectionStore.RetrieveReadWritePermissionReturns(false, false, nil)
})
It("returns the error from errorIfCreatorHasNoWriteAccess", func() {
_, err := handler.HandlePurgePrivateData(incomingMessage, txContext)
Expect(err).To(MatchError("tx creator does not have write access" +
" permission on privatedata in chaincodeName:cc-instance-name" +
" collectionName: collection-name"))
})
})
})
})
Describe("HandleGetHistoryForKey", func() {
var (
request *pb.GetHistoryForKey
incomingMessage *pb.ChaincodeMessage
expectedQueryResponse *pb.QueryResponse
fakeIterator *mock.QueryResultsIterator
)
BeforeEach(func() {
request = &pb.GetHistoryForKey{
Key: "history-key",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_GET_HISTORY_FOR_KEY,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
expectedQueryResponse = &pb.QueryResponse{
Id: "query-response-id",
}
fakeQueryResponseBuilder.BuildQueryResponseReturns(expectedQueryResponse, nil)
fakeIterator = &mock.QueryResultsIterator{}
fakeHistoryQueryExecutor.GetHistoryForKeyReturns(fakeIterator, nil)
})
It("calls GetHistoryForKey on the history query executor", func() {
_, err := handler.HandleGetHistoryForKey(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeHistoryQueryExecutor.GetHistoryForKeyCallCount()).To(Equal(1))
ccname, key := fakeHistoryQueryExecutor.GetHistoryForKeyArgsForCall(0)
Expect(ccname).To(Equal("cc-instance-name"))
Expect(key).To(Equal("history-key"))
})
It("initializes a query context", func() {
_, err := handler.HandleGetHistoryForKey(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(Equal(&chaincode.PendingQueryResult{}))
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(Equal(fakeIterator))
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(*retCount).To(Equal(int32(0)))
})
It("builds a query response", func() {
_, err := handler.HandleGetHistoryForKey(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeQueryResponseBuilder.BuildQueryResponseCallCount()).To(Equal(1))
tctx, iter, iterID, _, _ := fakeQueryResponseBuilder.BuildQueryResponseArgsForCall(0)
Expect(tctx).To(Equal(txContext))
Expect(iter).To(Equal(fakeIterator))
Expect(iterID).To(Equal("generated-query-id"))
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleGetHistoryForKey(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed:"))
})
})
Context("when the history query executor fails", func() {
BeforeEach(func() {
fakeHistoryQueryExecutor.GetHistoryForKeyReturns(nil, errors.New("pepperoni"))
})
It("returns an error", func() {
_, err := handler.HandleGetHistoryForKey(incomingMessage, txContext)
Expect(err).To(MatchError("pepperoni"))
})
})
Context("when building the query response fails", func() {
BeforeEach(func() {
fakeQueryResponseBuilder.BuildQueryResponseReturns(nil, errors.New("mushrooms"))
})
It("returns an error", func() {
_, err := handler.HandleGetHistoryForKey(incomingMessage, txContext)
Expect(err).To(MatchError("mushrooms"))
})
It("cleans up the query context", func() {
handler.HandleGetHistoryForKey(incomingMessage, txContext)
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(BeNil())
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(BeNil())
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(retCount).To(BeNil())
})
})
Context("when marshaling the query response fails", func() {
BeforeEach(func() {
fakeQueryResponseBuilder.BuildQueryResponseReturns(nil, nil)
})
It("returns an error", func() {
_, err := handler.HandleGetHistoryForKey(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("marshal failed:"))
})
It("cleans up the query context", func() {
handler.HandleGetHistoryForKey(incomingMessage, txContext)
pqr := txContext.GetPendingQueryResult("generated-query-id")
Expect(pqr).To(BeNil())
iter := txContext.GetQueryIterator("generated-query-id")
Expect(iter).To(BeNil())
retCount := txContext.GetTotalReturnCount("generated-query-id")
Expect(retCount).To(BeNil())
})
})
Context("when HistoryQueryExecutor is nil", func() {
BeforeEach(func() {
txContext.HistoryQueryExecutor = nil
})
It("returns an error", func() {
_, err := handler.HandleGetHistoryForKey(incomingMessage, txContext)
Expect(err).To(MatchError("history database is not enabled"))
})
})
})
Describe("HandleInvokeChaincode", func() {
var (
expectedSignedProp *pb.SignedProposal
expectedProposal *pb.Proposal
fakePeerLedger *mock.PeerLedger
newTxSimulator *mock.TxSimulator
newHistoryQueryExecutor *mock.HistoryQueryExecutor
request *pb.ChaincodeSpec
incomingMessage *pb.ChaincodeMessage
responseMessage *pb.ChaincodeMessage
)
BeforeEach(func() {
expectedProposal = &pb.Proposal{
Header: []byte("proposal-header"),
Payload: []byte("proposal-payload"),
}
expectedSignedProp = &pb.SignedProposal{
ProposalBytes: []byte("signed-proposal-bytes"),
Signature: []byte("signature"),
}
txContext.Proposal = expectedProposal
txContext.SignedProp = expectedSignedProp
newTxSimulator = &mock.TxSimulator{}
newHistoryQueryExecutor = &mock.HistoryQueryExecutor{}
fakePeerLedger = &mock.PeerLedger{}
fakePeerLedger.NewTxSimulatorReturns(newTxSimulator, nil)
fakeLedgerGetter.GetLedgerReturns(fakePeerLedger)
fakePeerLedger.NewHistoryQueryExecutorReturns(newHistoryQueryExecutor, nil)
request = &pb.ChaincodeSpec{
ChaincodeId: &pb.ChaincodeID{
Name: "target-chaincode-name:target-version",
},
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_INVOKE_CHAINCODE,
Payload: payload,
Txid: "tx-id",
ChannelId: "channel-id",
}
responseMessage = &pb.ChaincodeMessage{}
fakeInvoker.InvokeReturns(responseMessage, nil)
})
It("evaluates the access control policy", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeACLProvider.CheckACLCallCount()).To(Equal(1))
resource, chainID, proposal := fakeACLProvider.CheckACLArgsForCall(0)
Expect(resource).To(Equal(ar.Peer_ChaincodeToChaincode))
Expect(chainID).To(Equal("channel-id"))
Expect(proposal).To(Equal(expectedSignedProp))
})
Context("when the target channel is different from the context", func() {
BeforeEach(func() {
request = &pb.ChaincodeSpec{
ChaincodeId: &pb.ChaincodeID{
Name: "target-chaincode-name:target-version/target-channel-id",
},
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage.Payload = payload
})
It("uses the channel form the target for access checks", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeACLProvider.CheckACLCallCount()).To(Equal(1))
resource, chainID, proposal := fakeACLProvider.CheckACLArgsForCall(0)
Expect(resource).To(Equal(ar.Peer_ChaincodeToChaincode))
Expect(chainID).To(Equal("target-channel-id"))
Expect(proposal).To(Equal(expectedSignedProp))
})
It("gets the ledger for the target channel", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeLedgerGetter.GetLedgerCallCount()).To(Equal(1))
chainID := fakeLedgerGetter.GetLedgerArgsForCall(0)
Expect(chainID).To(Equal("target-channel-id"))
})
It("creates a new tx simulator for target execution", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakePeerLedger.NewTxSimulatorCallCount()).To(Equal(1))
txid := fakePeerLedger.NewTxSimulatorArgsForCall(0)
Expect(txid).To(Equal("tx-id"))
})
It("provides the new simulator in the context used for execution", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeInvoker.InvokeCallCount()).To(Equal(1))
txParams, _, _ := fakeInvoker.InvokeArgsForCall(0)
Expect(txParams.TXSimulator).To(BeIdenticalTo(newTxSimulator)) // same instance, not just equal
})
It("creates a new history query executor for target execution", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakePeerLedger.NewHistoryQueryExecutorCallCount()).To(Equal(1))
})
It("provides the new history query executor in the context used for execution", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeInvoker.InvokeCallCount()).To(Equal(1))
txParams, _, _ := fakeInvoker.InvokeArgsForCall(0)
Expect(txParams.HistoryQueryExecutor).To(BeIdenticalTo(newHistoryQueryExecutor)) // same instance, not just equal
})
It("marks the new transaction simulator as done after execute", func() {
fakeInvoker.InvokeStub = func(*ccprovider.TransactionParams, string, *pb.ChaincodeInput) (*pb.ChaincodeMessage, error) {
Expect(newTxSimulator.DoneCallCount()).To(Equal(0))
return responseMessage, nil
}
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeInvoker.InvokeCallCount()).To(Equal(1))
Expect(newTxSimulator.DoneCallCount()).To(Equal(1))
})
Context("when getting the ledger for the target channel fails", func() {
BeforeEach(func() {
fakeLedgerGetter.GetLedgerReturns(nil)
})
It("returns an error", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).To(MatchError("failed to find ledger for channel: target-channel-id"))
})
})
Context("when creating the new tx simulator fails", func() {
BeforeEach(func() {
fakePeerLedger.NewTxSimulatorReturns(nil, errors.New("bonkers"))
})
It("returns an error", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).To(MatchError("bonkers"))
})
})
Context("when creating the new history query executor fails", func() {
BeforeEach(func() {
fakePeerLedger.NewHistoryQueryExecutorReturns(nil, errors.New("razzies"))
})
It("returns an error", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).To(MatchError("razzies"))
})
})
})
Context("when the target is a system chaincode", func() {
BeforeEach(func() {
builtinSCCs["target-chaincode-name"] = struct{}{}
})
It("does not perform acl checks", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).NotTo(HaveOccurred())
Expect(fakeACLProvider.CheckACLCallCount()).To(Equal(0))
})
})
Context("when the target is not a system chaincode", func() {
Context("when the signed proposal is nil", func() {
BeforeEach(func() {
txContext.SignedProp = nil
})
It("returns an error", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).To(MatchError("signed proposal must not be nil from caller [channel-id.target-chaincode-name#target-version]"))
})
})
})
Context("when the access control check fails", func() {
BeforeEach(func() {
fakeACLProvider.CheckACLReturns(errors.New("no-soup-for-you"))
})
It("returns the error", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).To(MatchError("no-soup-for-you"))
})
})
Context("when execute fails", func() {
BeforeEach(func() {
fakeInvoker.InvokeReturns(nil, errors.New("lemons"))
})
It("returns an error", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).To(MatchError("execute failed: lemons"))
})
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("returns an error", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("unmarshal failed"))
})
})
Context("when marshaling the response fails", func() {
BeforeEach(func() {
fakeInvoker.InvokeReturns(nil, nil)
})
It("returns an error", func() {
_, err := handler.HandleInvokeChaincode(incomingMessage, txContext)
Expect(err).To(Not(BeNil()))
Expect(err.Error()).To(HavePrefix("marshal failed:"))
})
})
})
Describe("Execute", func() {
var (
incomingMessage *pb.ChaincodeMessage
expectedProposal *pb.Proposal
expectedSignedProp *pb.SignedProposal
txParams *ccprovider.TransactionParams
)
BeforeEach(func() {
request := &pb.ChaincodeInput{
Args: util.ToChaincodeArgs("arg1", "arg2"),
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
expectedProposal = &pb.Proposal{
Header: []byte("proposal-header"),
Payload: []byte("proposal-payload"),
}
expectedSignedProp = &pb.SignedProposal{
ProposalBytes: []byte("signed-proposal-bytes"),
Signature: []byte("signature"),
}
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_TRANSACTION,
Txid: "tx-id",
Payload: payload,
ChannelId: "channel-id",
}
txParams = &ccprovider.TransactionParams{
TxID: "tx-id",
ChannelID: "channel-id",
SignedProp: expectedSignedProp,
Proposal: expectedProposal,
}
})
It("creates transaction context", func() {
close(responseNotifier)
handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Expect(fakeContextRegistry.CreateCallCount()).To(Equal(1))
Expect(fakeContextRegistry.CreateArgsForCall(0)).To(Equal(txParams))
})
It("sends an execute message to the chaincode with the correct proposal", func() {
expectedMessage := *incomingMessage
expectedMessage.Proposal = expectedSignedProp
close(responseNotifier)
handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
Consistently(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).To(Equal(&expectedMessage))
Expect(msg.Proposal).To(Equal(expectedSignedProp))
})
It("waits for the chaincode to respond", func() {
doneCh := make(chan struct{})
go func() {
handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
close(doneCh)
}()
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
Consistently(fakeChatStream.SendCallCount).Should(Equal(1))
Consistently(doneCh).ShouldNot(Receive())
Eventually(responseNotifier).Should(BeSent(&pb.ChaincodeMessage{}))
Eventually(doneCh).Should(BeClosed())
})
It("returns the chaincode response", func() {
Eventually(responseNotifier).Should(BeSent(&pb.ChaincodeMessage{Txid: "a-transaction-id"}))
resp, err := handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&pb.ChaincodeMessage{Txid: "a-transaction-id"}))
})
It("deletes the transaction context", func() {
close(responseNotifier)
handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Expect(fakeContextRegistry.DeleteCallCount()).Should(Equal(1))
channelID, txid := fakeContextRegistry.DeleteArgsForCall(0)
Expect(channelID).To(Equal("channel-id"))
Expect(txid).To(Equal("tx-id"))
})
Context("when the serial send fails", func() {
BeforeEach(func() {
fakeChatStream.SendReturns(errors.New("where-is-waldo?"))
})
It("returns an error before timing out", func() {
respCh := make(chan *pb.ChaincodeMessage, 1)
go func() {
defer GinkgoRecover()
resp, err := handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Expect(err).NotTo(HaveOccurred())
Eventually(respCh).Should(BeSent(resp))
}()
Eventually(respCh).Should(Receive(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_ERROR,
Payload: []byte("[tx-id] error sending TRANSACTION: where-is-waldo?"),
Txid: "tx-id",
ChannelId: "channel-id",
})))
})
})
Context("when the proposal is missing", func() {
BeforeEach(func() {
txParams.Proposal = nil
})
It("sends a nil proposal", func() {
close(responseNotifier)
_, err := handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Expect(err).NotTo(HaveOccurred())
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg).NotTo(BeNil())
Expect(msg.Proposal).To(BeNil())
})
})
Context("when the signed proposal is missing", func() {
BeforeEach(func() {
txParams.SignedProp = nil
})
It("returns an error", func() {
close(responseNotifier)
_, err := handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Expect(err).To(MatchError("failed getting proposal context. Signed proposal is nil"))
})
It("deletes the transaction context", func() {
close(responseNotifier)
handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Expect(fakeContextRegistry.DeleteCallCount()).Should(Equal(1))
channelID, txid := fakeContextRegistry.DeleteArgsForCall(0)
Expect(channelID).To(Equal("channel-id"))
Expect(txid).To(Equal("tx-id"))
})
})
Context("when creating the transaction context fails", func() {
BeforeEach(func() {
fakeContextRegistry.CreateReturns(nil, errors.New("burger"))
})
It("returns an error", func() {
_, err := handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Expect(err).To(MatchError("burger"))
})
It("does not try to delete the tranasction context", func() {
handler.Execute(txParams, "chaincode-name", incomingMessage, time.Second)
Expect(fakeContextRegistry.CreateCallCount()).To(Equal(1))
Expect(fakeContextRegistry.DeleteCallCount()).To(Equal(0))
})
})
Context("when the chaincode stream terminates", func() {
It("returns an error", func() {
streamDoneChan := make(chan struct{})
chaincode.SetStreamDoneChan(handler, streamDoneChan)
errCh := make(chan error, 1)
go func() {
_, err := handler.Execute(txParams, "chaincode-name", incomingMessage, time.Hour)
errCh <- err
}()
Consistently(errCh).ShouldNot(Receive())
close(streamDoneChan)
Eventually(errCh).Should(Receive(MatchError("chaincode stream terminated")))
})
})
Context("when execute times out", func() {
It("returns an error", func() {
errCh := make(chan error, 1)
go func() {
_, err := handler.Execute(txParams, "chaincode-name", incomingMessage, time.Millisecond)
errCh <- err
}()
Eventually(errCh).Should(Receive(MatchError("timeout expired while executing transaction")))
})
It("records execute timeouts", func() {
errCh := make(chan error, 1)
go func() {
_, err := handler.Execute(txParams, "chaincode-name", incomingMessage, time.Millisecond)
errCh <- err
}()
Eventually(errCh).Should(Receive(MatchError("timeout expired while executing transaction")))
Expect(fakeExecuteTimeouts.WithCallCount()).To(Equal(1))
labelValues := fakeExecuteTimeouts.WithArgsForCall(0)
Expect(labelValues).To(Equal([]string{
"chaincode", "test-handler-name:1.0",
}))
Expect(fakeExecuteTimeouts.AddCallCount()).To(Equal(1))
Expect(fakeExecuteTimeouts.AddArgsForCall(0)).To(BeNumerically("~", 1.0))
})
It("deletes the transaction context", func() {
handler.Execute(txParams, "chaincode-name", incomingMessage, time.Millisecond)
Expect(fakeContextRegistry.DeleteCallCount()).Should(Equal(1))
channelID, txid := fakeContextRegistry.DeleteArgsForCall(0)
Expect(channelID).To(Equal("channel-id"))
Expect(txid).To(Equal("tx-id"))
})
})
})
Describe("HandleRegister", func() {
var incomingMessage *pb.ChaincodeMessage
BeforeEach(func() {
request := &pb.ChaincodeID{
Name: "chaincode-id-name",
Version: "chaincode-id-version",
}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_REGISTER,
Txid: "tx-id",
ChannelId: "channel-id",
Payload: payload,
}
})
It("registers the handler with the registry", func() {
handler.HandleRegister(incomingMessage)
Expect(fakeHandlerRegistry.RegisterCallCount()).To(Equal(1))
h := fakeHandlerRegistry.RegisterArgsForCall(0)
Expect(h).To(Equal(handler))
})
It("transitions the handler into ready state", func() {
handler.HandleRegister(incomingMessage)
Eventually(handler.State).Should(Equal(chaincode.Ready))
})
It("notifies the registry that the handler is ready", func() {
handler.HandleRegister(incomingMessage)
Expect(fakeHandlerRegistry.FailedCallCount()).To(Equal(0))
Expect(fakeHandlerRegistry.ReadyCallCount()).To(Equal(1))
name := fakeHandlerRegistry.ReadyArgsForCall(0)
Expect(name).To(Equal("chaincode-id-name"))
})
It("sends registered and ready messsages", func() {
handler.HandleRegister(incomingMessage)
Eventually(fakeChatStream.SendCallCount).Should(Equal(2))
Consistently(fakeChatStream.SendCallCount).Should(Equal(2))
registeredMessage := fakeChatStream.SendArgsForCall(0)
readyMessage := fakeChatStream.SendArgsForCall(1)
Expect(registeredMessage).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_REGISTERED,
}))
Expect(readyMessage).To(Equal(&pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_READY,
}))
})
Context("when sending the ready message fails", func() {
BeforeEach(func() {
fakeChatStream.SendReturnsOnCall(1, errors.New("carrot"))
})
It("state remains in established", func() {
Expect(handler.State()).To(Equal(chaincode.Created))
handler.HandleRegister(incomingMessage)
Expect(handler.State()).To(Equal(chaincode.Established))
})
It("notifies the registry of the failure", func() {
handler.HandleRegister(incomingMessage)
Expect(fakeHandlerRegistry.ReadyCallCount()).To(Equal(0))
Expect(fakeHandlerRegistry.FailedCallCount()).To(Equal(1))
name, err := fakeHandlerRegistry.FailedArgsForCall(0)
Expect(name).To(Equal("chaincode-id-name"))
Expect(err).To(MatchError("[] error sending READY: carrot"))
})
})
Context("when registering the handler with registry fails", func() {
BeforeEach(func() {
fakeHandlerRegistry.RegisterReturns(errors.New("cake"))
})
It("remains in created state", func() {
Expect(handler.State()).To(Equal(chaincode.Created))
handler.HandleRegister(incomingMessage)
Expect(handler.State()).To(Equal(chaincode.Created))
})
})
Context("when sending a registered message failed", func() {
BeforeEach(func() {
fakeChatStream.SendReturns(errors.New("potato"))
})
It("ready is not sent", func() {
handler.HandleRegister(incomingMessage)
Eventually(fakeChatStream.SendCallCount).Should(Equal(1))
Consistently(fakeChatStream.SendCallCount).Should(Equal(1))
msg := fakeChatStream.SendArgsForCall(0)
Expect(msg.Type).To(Equal(pb.ChaincodeMessage_REGISTERED))
})
It("remains in created state", func() {
Expect(handler.State()).To(Equal(chaincode.Created))
handler.HandleRegister(incomingMessage)
Expect(handler.State()).To(Equal(chaincode.Created))
})
})
Context("when unmarshalling the request fails", func() {
BeforeEach(func() {
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
})
It("sends no messages", func() {
handler.HandleRegister(incomingMessage)
Consistently(fakeChatStream.SendCallCount).Should(Equal(0))
})
})
})
Describe("ProcessStream", func() {
BeforeEach(func() {
incomingMessage := &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_KEEPALIVE,
Txid: "tx-id",
ChannelId: "channel-id",
}
fakeChatStream.RecvReturns(incomingMessage, nil)
})
It("receives messages until an error is received", func() {
fakeChatStream.RecvReturnsOnCall(99, nil, errors.New("done-for-now"))
handler.ProcessStream(fakeChatStream)
Eventually(fakeChatStream.RecvCallCount).Should(Equal(100))
})
It("manages the stream done channel", func() {
releaseChan := make(chan struct{})
fakeChatStream.RecvStub = func() (*pb.ChaincodeMessage, error) {
<-releaseChan
return nil, errors.New("cc-went-away")
}
go handler.ProcessStream(fakeChatStream)
Eventually(fakeChatStream.RecvCallCount).Should(Equal(1))
streamDoneChan := chaincode.StreamDone(handler)
Consistently(streamDoneChan).ShouldNot(Receive())
close(releaseChan)
Eventually(streamDoneChan).Should(BeClosed())
})
Context("when receive fails with an io.EOF", func() {
BeforeEach(func() {
fakeChatStream.RecvReturns(nil, io.EOF)
})
It("returns the error", func() {
err := handler.ProcessStream(fakeChatStream)
Expect(err).To(Equal(io.EOF))
})
})
Context("when receive fails", func() {
BeforeEach(func() {
fakeChatStream.RecvReturns(nil, errors.New("chocolate"))
})
It("returns an error", func() {
err := handler.ProcessStream(fakeChatStream)
Expect(err).To(MatchError("receive from chaincode support stream failed: chocolate"))
})
})
Context("when a nil message is received", func() {
BeforeEach(func() {
fakeChatStream.RecvReturns(nil, nil)
})
It("returns an error", func() {
err := handler.ProcessStream(fakeChatStream)
Expect(err).To(MatchError("received nil message, ending chaincode support stream"))
})
})
Describe("keepalive messages", func() {
var recvChan chan *pb.ChaincodeMessage
BeforeEach(func() {
recvChan = make(chan *pb.ChaincodeMessage, 1)
fakeChatStream.RecvStub = func() (*pb.ChaincodeMessage, error) {
msg := <-recvChan
return msg, nil
}
handler.Keepalive = 50 * time.Millisecond
})
It("sends a keep alive messages until the stream ends", func() {
errChan := make(chan error, 1)
go func() { errChan <- handler.ProcessStream(fakeChatStream) }()
Eventually(fakeChatStream.SendCallCount).Should(Equal(5))
recvChan <- nil
Eventually(errChan).Should(Receive())
for i := 0; i < 5; i++ {
m := fakeChatStream.SendArgsForCall(i)
Expect(m.Type).To(Equal(pb.ChaincodeMessage_KEEPALIVE))
}
})
Context("when keepalive is disabled", func() {
BeforeEach(func() {
handler.Keepalive = 0
})
It("does not send keep alive messages", func() {
errChan := make(chan error, 1)
go func() { errChan <- handler.ProcessStream(fakeChatStream) }()
Consistently(fakeChatStream.SendCallCount).Should(Equal(0))
recvChan <- nil
Eventually(errChan).Should(Receive())
})
})
})
Context("when handling a received message fails", func() {
var recvChan chan *pb.ChaincodeMessage
BeforeEach(func() {
recvChan = make(chan *pb.ChaincodeMessage, 1)
fakeChatStream.RecvStub = func() (*pb.ChaincodeMessage, error) {
msg := <-recvChan
return msg, nil
}
recvChan <- &pb.ChaincodeMessage{
Txid: "tx-id",
Type: pb.ChaincodeMessage_Type(9999),
}
})
It("returns an error", func() {
err := handler.ProcessStream(fakeChatStream)
Expect(err).To(MatchError("error handling message, ending stream: [tx-id] Fabric side handler cannot handle message (9999) while in created state"))
})
})
Context("when an async error is sent", func() {
var (
incomingMessage *pb.ChaincodeMessage
recvChan chan *pb.ChaincodeMessage
)
BeforeEach(func() {
request := &pb.ChaincodeInput{}
payload, err := proto.Marshal(request)
Expect(err).NotTo(HaveOccurred())
incomingMessage = &pb.ChaincodeMessage{
Type: pb.ChaincodeMessage_TRANSACTION,
Txid: "tx-id",
Payload: payload,
ChannelId: "channel-id",
}
recvChan = make(chan *pb.ChaincodeMessage, 1)
shadowRecvChan := recvChan
fakeChatStream.RecvStub = func() (*pb.ChaincodeMessage, error) {
msg := <-shadowRecvChan
return msg, nil
}
fakeChatStream.SendReturns(errors.New("candy"))
})
AfterEach(func() {
close(recvChan)
})
It("returns an error", func() {
errChan := make(chan error, 1)
go func() { errChan <- handler.ProcessStream(fakeChatStream) }()
Eventually(fakeChatStream.RecvCallCount).ShouldNot(Equal(0)) // wait for loop to start
handler.Execute(&ccprovider.TransactionParams{}, "chaincode-name", incomingMessage, time.Millisecond) // force async error
Eventually(errChan).Should(Receive(MatchError("received error while sending message, ending chaincode support stream: [tx-id] error sending TRANSACTION: candy")))
})
It("stops receiving messages", func() {
errChan := make(chan error, 1)
go func() { errChan <- handler.ProcessStream(fakeChatStream) }()
Eventually(fakeChatStream.RecvCallCount).ShouldNot(Equal(0)) // wait for loop to start
handler.Execute(&ccprovider.TransactionParams{}, "chaincode-name", incomingMessage, time.Millisecond) // force async error
Eventually(fakeChatStream.RecvCallCount).Should(Equal(1))
Consistently(fakeChatStream.RecvCallCount).Should(Equal(1))
})
})
})
Describe("Notify", func() {
var fakeIterator *mock.QueryResultsIterator
var incomingMessage *pb.ChaincodeMessage
BeforeEach(func() {
fakeIterator = &mock.QueryResultsIterator{}
incomingMessage = &pb.ChaincodeMessage{
Txid: "tx-id",
ChannelId: "channel-id",
}
})
It("gets the transaction context", func() {
handler.Notify(incomingMessage)
Expect(fakeContextRegistry.GetCallCount()).To(Equal(1))
channelID, txID := fakeContextRegistry.GetArgsForCall(0)
Expect(channelID).To(Equal("channel-id"))
Expect(txID).To(Equal("tx-id"))
})
It("sends the message on response notifier", func() {
handler.Notify(incomingMessage)
Eventually(responseNotifier).Should(Receive(Equal(incomingMessage)))
})
It("should close query iterators on the transaction context", func() {
txContext.InitializeQueryContext("query-id", fakeIterator)
Expect(fakeIterator.CloseCallCount()).To(Equal(0))
handler.Notify(incomingMessage)
Eventually(fakeIterator.CloseCallCount).Should(Equal(1))
})
Context("when the transaction context cannot be found", func() {
BeforeEach(func() {
fakeContextRegistry.GetReturns(nil)
})
It("keeps calm and carries on", func() {
handler.Notify(incomingMessage)
Expect(fakeContextRegistry.GetCallCount()).To(Equal(1))
})
})
})
Describe("ParseName", func() {
It("parses the chaincode name", func() {
ci := chaincode.ParseName("name")
Expect(ci).To(Equal(&sysccprovider.ChaincodeInstance{ChaincodeName: "name"}))
ci = chaincode.ParseName("name:version")
Expect(ci).To(Equal(&sysccprovider.ChaincodeInstance{ChaincodeName: "name", ChaincodeVersion: "version"}))
ci = chaincode.ParseName("name/channel-id")
Expect(ci).To(Equal(&sysccprovider.ChaincodeInstance{ChaincodeName: "name", ChannelID: "channel-id"}))
ci = chaincode.ParseName("name:version/channel-id")
Expect(ci).To(Equal(&sysccprovider.ChaincodeInstance{ChaincodeName: "name", ChaincodeVersion: "version", ChannelID: "channel-id"}))
})
})
DescribeTable("Handler State",
func(state chaincode.State, strval string) {
Expect(state.String()).To(Equal(strval))
},
Entry("created", chaincode.Created, "created"),
Entry("ready", chaincode.Ready, "ready"),
Entry("established", chaincode.Established, "established"),
Entry("unknown", chaincode.State(999), "UNKNOWN"),
)
})