From 3e7b2efd1a48b0996a46586dfaaac93fb8f3f754 Mon Sep 17 00:00:00 2001 From: Senthil Nathan N Date: Tue, 4 Dec 2018 20:48:27 +0530 Subject: [PATCH] pvtData APIs are not allowed in chaincode Init() private data APIs are not allowed in Init() since the private data collection configuration is not yet committed on the channel. This CR provide a better error message indicating that pvtdata APIs are not allowed in chaincode Init(). FAB-13050 #done Change-Id: I6e6a8abdf1f35f56307be90d8b6b2830c8f35755 Signed-off-by: senthil --- core/chaincode/exectransaction_test.go | 75 +++++++++++++++++-- core/chaincode/handler.go | 23 ++++++ core/chaincode/handler_test.go | 70 ++++++++++++++++- .../init_private_data/init_private_data.go | 38 ++++++++++ .../init_public_data/init_public_data.go | 38 ++++++++++ core/chaincode/transaction_context.go | 1 + core/chaincode/transaction_contexts.go | 1 + core/common/ccprovider/ccprovider.go | 1 + 8 files changed, 239 insertions(+), 8 deletions(-) create mode 100644 core/chaincode/testdata/chaincode/init_private_data/init_private_data.go create mode 100644 core/chaincode/testdata/chaincode/init_public_data/init_public_data.go diff --git a/core/chaincode/exectransaction_test.go b/core/chaincode/exectransaction_test.go index c7a2c5914b6..9cb40e23d4b 100644 --- a/core/chaincode/exectransaction_test.go +++ b/core/chaincode/exectransaction_test.go @@ -402,7 +402,7 @@ func getDeployLSCCSpec(chainID string, cds *pb.ChaincodeDeploymentSpec, ccp *com } // Deploy a chaincode - i.e., build and initialize. -func deploy(chainID string, cccid *ccprovider.CCContext, spec *pb.ChaincodeSpec, blockNumber uint64, chaincodeSupport *ChaincodeSupport) (b []byte, err error) { +func deploy(chainID string, cccid *ccprovider.CCContext, spec *pb.ChaincodeSpec, blockNumber uint64, chaincodeSupport *ChaincodeSupport) (resp *pb.Response, err error) { // First build and get the deployment spec cdDeploymentSpec, err := getDeploymentSpec(spec) if err != nil { @@ -412,7 +412,7 @@ func deploy(chainID string, cccid *ccprovider.CCContext, spec *pb.ChaincodeSpec, } func deployWithCollectionConfigs(chainID string, cccid *ccprovider.CCContext, spec *pb.ChaincodeSpec, - collectionConfigPkg *common.CollectionConfigPackage, blockNumber uint64, chaincodeSupport *ChaincodeSupport) (b []byte, err error) { + collectionConfigPkg *common.CollectionConfigPackage, blockNumber uint64, chaincodeSupport *ChaincodeSupport) (resp *pb.Response, err error) { // First build and get the deployment spec cdDeploymentSpec, err := getDeploymentSpec(spec) if err != nil { @@ -422,7 +422,7 @@ func deployWithCollectionConfigs(chainID string, cccid *ccprovider.CCContext, sp } func deploy2(chainID string, cccid *ccprovider.CCContext, chaincodeDeploymentSpec *pb.ChaincodeDeploymentSpec, - collectionConfigPkg *common.CollectionConfigPackage, blockNumber uint64, chaincodeSupport *ChaincodeSupport) (b []byte, err error) { + collectionConfigPkg *common.CollectionConfigPackage, blockNumber uint64, chaincodeSupport *ChaincodeSupport) (resp *pb.Response, err error) { cis, err := getDeployLSCCSpec(chainID, chaincodeDeploymentSpec, collectionConfigPkg) if err != nil { return nil, fmt.Errorf("Error creating lscc spec : %s\n", err) @@ -468,12 +468,11 @@ func deploy2(chainID string, cccid *ccprovider.CCContext, chaincodeDeploymentSpe return nil, fmt.Errorf("Error deploying chaincode (1): %s", err) } - var resp *pb.Response if resp, _, err = chaincodeSupport.ExecuteLegacyInit(txParams, cccid, chaincodeDeploymentSpec); err != nil { return nil, fmt.Errorf("Error deploying chaincode(2): %s", err) } - return resp.Payload, nil + return resp, nil } // Invoke a chaincode. @@ -965,6 +964,70 @@ func TestChaincodeInvokeChaincodeErrorCase(t *testing.T) { } } +func TestChaincodeInit(t *testing.T) { + chainID := util.GetTestChainID() + + _, chaincodeSupport, cleanup, err := initPeer(chainID) + if err != nil { + t.Fail() + t.Logf("Error creating peer: %s", err) + } + + defer cleanup() + + url := "github.com/hyperledger/fabric/core/chaincode/testdata/chaincode/init_private_data" + cID := &pb.ChaincodeID{Name: "init_pvtdata", Path: url, Version: "0"} + + f := "init" + args := util.ToChaincodeArgs(f) + + spec := &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + + cccid := &ccprovider.CCContext{ + Name: "tmap", + Version: "0", + } + + defer chaincodeSupport.Stop(&ccprovider.ChaincodeContainerInfo{ + Name: cID.Name, + Version: cID.Version, + Path: cID.Path, + Type: "GOLANG", + ContainerType: "DOCKER", + }) + + var nextBlockNumber uint64 = 1 + _, err = deploy(chainID, cccid, spec, nextBlockNumber, chaincodeSupport) + assert.Contains(t, err.Error(), "private data APIs are not allowed in chaincode Init") + + url = "github.com/hyperledger/fabric/core/chaincode/testdata/chaincode/init_public_data" + cID = &pb.ChaincodeID{Name: "init_public_data", Path: url, Version: "0"} + + f = "init" + args = util.ToChaincodeArgs(f) + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + + cccid = &ccprovider.CCContext{ + Name: "tmap", + Version: "0", + } + + defer chaincodeSupport.Stop(&ccprovider.ChaincodeContainerInfo{ + Name: cID.Name, + Version: cID.Version, + Path: cID.Path, + Type: "GOLANG", + ContainerType: "DOCKER", + }) + + resp, err := deploy(chainID, cccid, spec, nextBlockNumber, chaincodeSupport) + assert.NoError(t, err) + // why response status is defined as int32 when the status codes are + // defined as int (i.e., constant) + assert.Equal(t, int32(shim.OK), resp.Status) +} + // Test the invocation of a transaction. func TestQueries(t *testing.T) { // Allow queries test alone so that end to end test can be performed. It takes less than 5 seconds. @@ -1431,7 +1494,7 @@ func setupTestConfig() { } } -func deployChaincode(ctx context.Context, name string, version string, chaincodeType pb.ChaincodeSpec_Type, path string, args [][]byte, creator []byte, channel string, nextBlockNumber uint64, chaincodeSupport *ChaincodeSupport) ([]byte, *ccprovider.CCContext, error) { +func deployChaincode(ctx context.Context, name string, version string, chaincodeType pb.ChaincodeSpec_Type, path string, args [][]byte, creator []byte, channel string, nextBlockNumber uint64, chaincodeSupport *ChaincodeSupport) (*pb.Response, *ccprovider.CCContext, error) { chaincodeSpec := &pb.ChaincodeSpec{ ChaincodeId: &pb.ChaincodeID{ Name: name, diff --git a/core/chaincode/handler.go b/core/chaincode/handler.go index 546187d7cce..70e75de43a1 100644 --- a/core/chaincode/handler.go +++ b/core/chaincode/handler.go @@ -626,6 +626,9 @@ func (h *Handler) HandleGetState(msg *pb.ChaincodeMessage, txContext *Transactio chaincodeLogger.Debugf("[%s] getting state for chaincode %s, key %s, channel %s", shorttxid(msg.Txid), chaincodeName, getState.Key, txContext.ChainID) if isCollectionSet(collection) { + if txContext.IsInitTransaction { + return nil, errors.New("private data APIs are not allowed in chaincode Init()") + } if err := errorIfCreatorHasNoReadAccess(chaincodeName, collection, txContext); err != nil { return nil, err } @@ -663,6 +666,9 @@ func (h *Handler) HandleGetStateMetadata(msg *pb.ChaincodeMessage, txContext *Tr var metadata map[string][]byte if isCollectionSet(collection) { + if txContext.IsInitTransaction { + return nil, errors.New("private data APIs are not allowed in chaincode Init()") + } if err := errorIfCreatorHasNoReadAccess(chaincodeName, collection, txContext); err != nil { return nil, err } @@ -712,6 +718,9 @@ func (h *Handler) HandleGetStateByRange(msg *pb.ChaincodeMessage, txContext *Tra chaincodeName := h.ChaincodeName() collection := getStateByRange.Collection if isCollectionSet(collection) { + if txContext.IsInitTransaction { + return nil, errors.New("private data APIs are not allowed in chaincode Init()") + } if err := errorIfCreatorHasNoReadAccess(chaincodeName, collection, txContext); err != nil { return nil, err } @@ -833,6 +842,9 @@ func (h *Handler) HandleGetQueryResult(msg *pb.ChaincodeMessage, txContext *Tran chaincodeName := h.ChaincodeName() collection := getQueryResult.Collection if isCollectionSet(collection) { + if txContext.IsInitTransaction { + return nil, errors.New("private data APIs are not allowed in chaincode Init()") + } if err := errorIfCreatorHasNoReadAccess(chaincodeName, collection, txContext); err != nil { return nil, err } @@ -1006,6 +1018,9 @@ func (h *Handler) HandlePutState(msg *pb.ChaincodeMessage, txContext *Transactio chaincodeName := h.ChaincodeName() collection := putState.Collection if isCollectionSet(collection) { + if txContext.IsInitTransaction { + return nil, errors.New("private data APIs are not allowed in chaincode Init()") + } err = txContext.TXSimulator.SetPrivateData(chaincodeName, collection, putState.Key, putState.Value) } else { err = txContext.TXSimulator.SetState(chaincodeName, putState.Key, putState.Value) @@ -1035,6 +1050,9 @@ func (h *Handler) HandlePutStateMetadata(msg *pb.ChaincodeMessage, txContext *Tr chaincodeName := h.ChaincodeName() collection := putStateMetadata.Collection if isCollectionSet(collection) { + if txContext.IsInitTransaction { + return nil, errors.New("private data APIs are not allowed in chaincode Init()") + } err = txContext.TXSimulator.SetPrivateDataMetadata(chaincodeName, collection, putStateMetadata.Key, metadata) } else { err = txContext.TXSimulator.SetStateMetadata(chaincodeName, putStateMetadata.Key, metadata) @@ -1056,6 +1074,9 @@ func (h *Handler) HandleDelState(msg *pb.ChaincodeMessage, txContext *Transactio chaincodeName := h.ChaincodeName() collection := delState.Collection if isCollectionSet(collection) { + if txContext.IsInitTransaction { + return nil, errors.New("private data APIs are not allowed in chaincode Init()") + } err = txContext.TXSimulator.DeletePrivateData(chaincodeName, collection, delState.Key) } else { err = txContext.TXSimulator.DeleteState(chaincodeName, delState.Key) @@ -1181,6 +1202,8 @@ func (h *Handler) Execute(txParams *ccprovider.TransactionParams, cccid *ccprovi defer chaincodeLogger.Debugf("Exit") txParams.CollectionStore = h.getCollectionStore(msg.ChannelId) + txParams.IsInitTransaction = (msg.Type == pb.ChaincodeMessage_INIT) + txctx, err := h.TXContexts.Create(txParams) if err != nil { return nil, err diff --git a/core/chaincode/handler_test.go b/core/chaincode/handler_test.go index d832da007e1..904cbd0baa4 100644 --- a/core/chaincode/handler_test.go +++ b/core/chaincode/handler_test.go @@ -595,7 +595,7 @@ var _ = Describe("Handler", func() { Expect(value).To(Equal([]byte("put-state-value"))) }) - Context("when SetPrivateData fails", func() { + Context("when SetPrivateData fails due to ledger error", func() { BeforeEach(func() { fakeTxSimulator.SetPrivateDataReturns(errors.New("godzilla")) }) @@ -605,6 +605,17 @@ var _ = Describe("Handler", func() { 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()")) + }) + }) }) }) @@ -829,7 +840,7 @@ var _ = Describe("Handler", func() { Expect(key).To(Equal("del-state-key")) }) - Context("when DeletePrivateData returns an error", func() { + Context("when DeletePrivateData fails due to ledger error", func() { BeforeEach(func() { fakeTxSimulator.DeletePrivateDataReturns(errors.New("mango")) }) @@ -839,6 +850,17 @@ var _ = Describe("Handler", func() { 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()")) + }) + }) }) }) @@ -939,6 +961,17 @@ var _ = Describe("Handler", func() { }) }) + 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.AllowedCollectionAccess["collection-name"] = true @@ -1156,6 +1189,17 @@ var _ = Describe("Handler", func() { 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() { @@ -1354,6 +1398,17 @@ var _ = Describe("Handler", func() { 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() { @@ -1724,6 +1779,17 @@ var _ = Describe("Handler", func() { 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() { diff --git a/core/chaincode/testdata/chaincode/init_private_data/init_private_data.go b/core/chaincode/testdata/chaincode/init_private_data/init_private_data.go new file mode 100644 index 00000000000..175813264f6 --- /dev/null +++ b/core/chaincode/testdata/chaincode/init_private_data/init_private_data.go @@ -0,0 +1,38 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "fmt" + + "github.com/hyperledger/fabric/core/chaincode/shim" + pb "github.com/hyperledger/fabric/protos/peer" +) + +// SimpleChaincode example simple Chaincode implementation +type SimpleChaincode struct { +} + +// Init initializes a private state +func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { + if err := stub.PutPrivateData("dummyColl", "dummyKey", []byte("dummyValue")); err != nil { + return shim.Error(fmt.Sprintf("put operation failed. Error storing state: %s", err)) + } + return shim.Success(nil) +} + +// Invoke is a no-op +func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { + return shim.Success(nil) +} + +func main() { + err := shim.Start(new(SimpleChaincode)) + if err != nil { + fmt.Printf("Error starting chaincode: %s", err) + } +} diff --git a/core/chaincode/testdata/chaincode/init_public_data/init_public_data.go b/core/chaincode/testdata/chaincode/init_public_data/init_public_data.go new file mode 100644 index 00000000000..9d3b0ced572 --- /dev/null +++ b/core/chaincode/testdata/chaincode/init_public_data/init_public_data.go @@ -0,0 +1,38 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "fmt" + + "github.com/hyperledger/fabric/core/chaincode/shim" + pb "github.com/hyperledger/fabric/protos/peer" +) + +// SimpleChaincode example simple Chaincode implementation +type SimpleChaincode struct { +} + +// Init initializes a public state +func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { + if err := stub.PutState("dummyKey", []byte("dummyValue")); err != nil { + return shim.Error(fmt.Sprintf("put operation failed. Error storing state: %s", err)) + } + return shim.Success(nil) +} + +// Invoke is a no-op +func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { + return shim.Success(nil) +} + +func main() { + err := shim.Start(new(SimpleChaincode)) + if err != nil { + fmt.Printf("Error starting chaincode: %s", err) + } +} diff --git a/core/chaincode/transaction_context.go b/core/chaincode/transaction_context.go index 2c4b8eda4ab..6e49e862799 100644 --- a/core/chaincode/transaction_context.go +++ b/core/chaincode/transaction_context.go @@ -23,6 +23,7 @@ type TransactionContext struct { TXSimulator ledger.TxSimulator HistoryQueryExecutor ledger.HistoryQueryExecutor CollectionStore privdata.CollectionStore + IsInitTransaction bool // tracks open iterators used for range queries queryMutex sync.Mutex diff --git a/core/chaincode/transaction_contexts.go b/core/chaincode/transaction_contexts.go index edae064b8f5..b3b1cf6dabb 100644 --- a/core/chaincode/transaction_contexts.go +++ b/core/chaincode/transaction_contexts.go @@ -67,6 +67,7 @@ func (c *TransactionContexts) Create(txParams *ccprovider.TransactionParams) (*T TXSimulator: txParams.TXSimulator, HistoryQueryExecutor: txParams.HistoryQueryExecutor, CollectionStore: txParams.CollectionStore, + IsInitTransaction: txParams.IsInitTransaction, queryIteratorMap: map[string]commonledger.ResultsIterator{}, pendingQueryResults: map[string]*PendingQueryResult{}, diff --git a/core/common/ccprovider/ccprovider.go b/core/common/ccprovider/ccprovider.go index 41d22abf81f..982070f313b 100644 --- a/core/common/ccprovider/ccprovider.go +++ b/core/common/ccprovider/ccprovider.go @@ -511,6 +511,7 @@ type TransactionParams struct { TXSimulator ledger.TxSimulator HistoryQueryExecutor ledger.HistoryQueryExecutor CollectionStore privdata.CollectionStore + IsInitTransaction bool // this is additional data passed to the chaincode ProposalDecorations map[string][]byte