diff --git a/core/chaincode/chaincode_support_test.go b/core/chaincode/chaincode_support_test.go index fb772c98e8e..a704370f8e2 100644 --- a/core/chaincode/chaincode_support_test.go +++ b/core/chaincode/chaincode_support_test.go @@ -1,17 +1,7 @@ /* -Copyright IBM Corp. 2017 All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +SPDX-License-Identifier: Apache-2.0 */ package chaincode @@ -395,8 +385,10 @@ func initializeCC(t *testing.T, chainID, ccname string, ccSide *mockpeer.MockCCC // correct block number for ending sim respSet = &mockpeer.MockResponseSet{errorFunc, nil, []*mockpeer.MockResponse{ - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutStateInfo{Key: "A", Value: []byte("100")}), Txid: txid}}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutStateInfo{Key: "B", Value: []byte("200")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "", Key: "A", Value: []byte("100")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "", Key: "B", Value: []byte("200")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "c1", Key: "C", Value: []byte("300")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "c2", Key: "C", Value: []byte("300")}), Txid: txid}}, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.OK, Payload: []byte("OK")}), ChaincodeEvent: &pb.ChaincodeEvent{ChaincodeId: ccname}, Txid: txid}}}} cccid.Version = "1" @@ -427,11 +419,11 @@ func invokeCC(t *testing.T, chainID, ccname string, ccSide *mockpeer.MockCCComm) mockAclProvider.On("CheckACL", aclmgmt.PROPOSE, chainID, sprop).Return(nil) respSet := &mockpeer.MockResponseSet{errorFunc, nil, []*mockpeer.MockResponse{ - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: []byte("A"), Txid: txid}}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: []byte("B"), Txid: txid}}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutStateInfo{Key: "A", Value: []byte("90")}), Txid: txid}}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutStateInfo{Key: "B", Value: []byte("210")}), Txid: txid}}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutStateInfo{Key: "TODEL", Value: []byte("-to-be-deleted-")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: putils.MarshalOrPanic(&pb.GetState{Collection: "", Key: "A"}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: putils.MarshalOrPanic(&pb.GetState{Collection: "", Key: "B"}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "", Key: "A", Value: []byte("90")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "", Key: "B", Value: []byte("210")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "", Key: "TODEL", Value: []byte("-to-be-deleted-")}), Txid: txid}}, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.OK, Payload: []byte("OK")}), Txid: txid}}}} cccid := ccprovider.NewCCContext(chainID, ccname, "0", txid, false, sprop, prop) @@ -439,8 +431,8 @@ func invokeCC(t *testing.T, chainID, ccname string, ccSide *mockpeer.MockCCComm) //delete the extra var respSet = &mockpeer.MockResponseSet{errorFunc, nil, []*mockpeer.MockResponse{ - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: []byte("TODEL"), Txid: "3"}}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_DEL_STATE, Payload: []byte("TODEL"), Txid: "3"}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: putils.MarshalOrPanic(&pb.GetState{Collection: "", Key: "TODEL"}), Txid: "3"}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_DEL_STATE, Payload: putils.MarshalOrPanic(&pb.DelState{Collection: "", Key: "TODEL"}), Txid: "3"}}, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.OK, Payload: []byte("OK")}), Txid: "3"}}}} cccid.TxID = "3" @@ -449,7 +441,7 @@ func invokeCC(t *testing.T, chainID, ccname string, ccSide *mockpeer.MockCCComm) //get the extra var and delete it //NOTE- we are calling ExecuteWithErrorFilter which returns error if chaincode returns ERROR response respSet = &mockpeer.MockResponseSet{errorFunc, nil, []*mockpeer.MockResponse{ - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: []byte("TODEL"), Txid: "4"}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: putils.MarshalOrPanic(&pb.GetState{Collection: "", Key: "TODEL"}), Txid: "4"}}, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.ERROR, Message: "variable not found"}), Txid: "4"}}}} cccid.TxID = "4" @@ -460,7 +452,55 @@ func invokeCC(t *testing.T, chainID, ccname string, ccSide *mockpeer.MockCCComm) return nil } -func getQueryStateByRange(t *testing.T, chainID, ccname string, ccSide *mockpeer.MockCCComm) error { +func invokePrivateDataGetPutDelCC(t *testing.T, chainID, ccname string, ccSide *mockpeer.MockCCComm) error { + done := setuperror() + + errorFunc := func(ind int, err error) { + done <- err + } + + chaincodeID := &pb.ChaincodeID{Name: ccname, Version: "0"} + ci := &pb.ChaincodeInput{[][]byte{[]byte("invokePrivateData")}, nil} + cis := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value["GOLANG"]), ChaincodeId: chaincodeID, Input: ci}} + txid := util.GenerateUUID() + ctxt, txsim, sprop, prop := startTx(t, chainID, cis, txid) + + respSet := &mockpeer.MockResponseSet{errorFunc, nil, []*mockpeer.MockResponse{ + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: putils.MarshalOrPanic(&pb.GetState{Collection: "", Key: "C"}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.ERROR, Message: "variable not found"}), Txid: txid}}}} + + cccid := ccprovider.NewCCContext(chainID, ccname, "0", txid, false, sprop, prop) + execCC(t, ctxt, ccSide, cccid, false, true, done, cis, respSet) + + respSet = &mockpeer.MockResponseSet{errorFunc, nil, []*mockpeer.MockResponse{ + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: putils.MarshalOrPanic(&pb.GetState{Collection: "c1", Key: "C"}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "c1", Key: "C", Value: []byte("310")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "c1", Key: "A", Value: []byte("100")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: putils.MarshalOrPanic(&pb.PutState{Collection: "c1", Key: "B", Value: []byte("100")}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_DEL_STATE, Payload: putils.MarshalOrPanic(&pb.DelState{Collection: "c2", Key: "C"}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.OK, Payload: []byte("OK")}), Txid: txid}}}} + + cccid = ccprovider.NewCCContext(chainID, ccname, "0", txid, false, sprop, prop) + execCC(t, ctxt, ccSide, cccid, false, false, done, cis, respSet) + + endTx(t, cccid, txsim, cis) + + txid = util.GenerateUUID() + ctxt, txsim, sprop, prop = startTx(t, chainID, cis, txid) + + respSet = &mockpeer.MockResponseSet{errorFunc, nil, []*mockpeer.MockResponse{ + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: putils.MarshalOrPanic(&pb.GetState{Collection: "c2", Key: "C"}), Txid: txid}}, + &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.ERROR, Message: "variable not found"}), Txid: txid}}}} + + cccid = ccprovider.NewCCContext(chainID, ccname, "0", txid, false, sprop, prop) + execCC(t, ctxt, ccSide, cccid, false, true, done, cis, respSet) + + endTx(t, cccid, txsim, cis) + + return nil +} + +func getQueryStateByRange(t *testing.T, collection, chainID, ccname string, ccSide *mockpeer.MockCCComm) error { done := setuperror() errorFunc := func(ind int, err error) { @@ -491,14 +531,27 @@ func getQueryStateByRange(t *testing.T, chainID, ccname string, ccSide *mockpeer return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_QUERY_STATE_CLOSE, Payload: putils.MarshalOrPanic(&pb.QueryStateClose{Id: qr.Id}), Txid: txid} } - respSet := &mockpeer.MockResponseSet{errorFunc, nil, []*mockpeer.MockResponse{ - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE_BY_RANGE, Payload: putils.MarshalOrPanic(&pb.GetStateByRange{StartKey: "A", EndKey: "B"}), Txid: txid}}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, queryStateNextFunc}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR}, queryStateCloseFunc}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.OK, Payload: []byte("OK")}), Txid: txid}}}} + var mkpeer []*mockpeer.MockResponse + + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE_BY_RANGE, Payload: putils.MarshalOrPanic(&pb.GetStateByRange{Collection: collection, StartKey: "A", EndKey: "B"}), Txid: txid}}) + + if collection == "" { + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, queryStateNextFunc}) + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR}, queryStateCloseFunc}) + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.OK, Payload: []byte("OK")}), Txid: txid}}) + } else { + // Range queries on private data is not yet implemented. + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.ERROR, Message: "Not Yet Supported"}), Txid: txid}}) + } + + respSet := &mockpeer.MockResponseSet{errorFunc, nil, mkpeer} cccid := ccprovider.NewCCContext(chainID, ccname, "0", txid, false, sprop, prop) - execCC(t, ctxt, ccSide, cccid, false, false, done, cis, respSet) + if collection == "" { + execCC(t, ctxt, ccSide, cccid, false, false, done, cis, respSet) + } else { + execCC(t, ctxt, ccSide, cccid, false, true, done, cis, respSet) + } endTx(t, cccid, txsim, cis) @@ -613,7 +666,7 @@ func cc2cc(t *testing.T, chainID, chainID2, ccname string, ccSide *mockpeer.Mock return nil } -func getQueryResult(t *testing.T, chainID, ccname string, ccSide *mockpeer.MockCCComm) error { +func getQueryResult(t *testing.T, collection, chainID, ccname string, ccSide *mockpeer.MockCCComm) error { done := setuperror() errorFunc := func(ind int, err error) { @@ -654,15 +707,27 @@ func getQueryResult(t *testing.T, chainID, ccname string, ccSide *mockpeer.MockC return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_QUERY_STATE_CLOSE, Payload: putils.MarshalOrPanic(&pb.QueryStateClose{Id: qr.Id}), Txid: txid} } - respSet := &mockpeer.MockResponseSet{errorFunc, nil, []*mockpeer.MockResponse{ - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_QUERY_RESULT, Payload: putils.MarshalOrPanic(&pb.GetQueryResult{Query: "goodquery"}), Txid: txid}}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, queryStateNextFunc}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, queryStateNextFunc}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR}, queryStateCloseFunc}, - &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.OK, Payload: []byte("OK")}), Txid: txid}}}} + var mkpeer []*mockpeer.MockResponse + + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_TRANSACTION}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_QUERY_RESULT, Payload: putils.MarshalOrPanic(&pb.GetQueryResult{Collection: "", Query: "goodquery"}), Txid: txid}}) + + if collection == "" { + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, queryStateNextFunc}) + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR}, queryStateCloseFunc}) + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.OK, Payload: []byte("OK")}), Txid: txid}}) + } else { + // Get query results on private data is not yet implemented. + mkpeer = append(mkpeer, &mockpeer.MockResponse{&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR}, &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: putils.MarshalOrPanic(&pb.Response{Status: shim.ERROR, Message: "Not Yet Supported"}), Txid: txid}}) + } + + respSet := &mockpeer.MockResponseSet{errorFunc, nil, mkpeer} cccid := ccprovider.NewCCContext(chainID, ccname, "0", txid, false, sprop, prop) - execCC(t, ctxt, ccSide, cccid, false, false, done, cis, respSet) + if collection == "" { + execCC(t, ctxt, ccSide, cccid, false, false, done, cis, respSet) + } else { + execCC(t, ctxt, ccSide, cccid, false, true, done, cis, respSet) + } endTx(t, cccid, txsim, cis) @@ -809,14 +874,17 @@ func TestCCFramework(t *testing.T) { //call's invoke and do some GET invokeCC(t, chainID, ccname, ccSide) + //call's invoke and do some GET/PUT/DEL on private data + invokePrivateDataGetPutDelCC(t, chainID, ccname, ccSide) + //call's query state range - getQueryStateByRange(t, chainID, ccname, ccSide) + getQueryStateByRange(t, "", chainID, ccname, ccSide) //call's cc2cc (variation with syscc calls) cc2cc(t, chainID, chainID2, ccname, ccSide) //call's query result - getQueryResult(t, chainID, ccname, ccSide) + getQueryResult(t, "", chainID, ccname, ccSide) //call's history result getHistory(t, chainID, ccname, ccSide) diff --git a/core/chaincode/exectransaction_test.go b/core/chaincode/exectransaction_test.go index 0f258e42e09..cf3328ab5a6 100644 --- a/core/chaincode/exectransaction_test.go +++ b/core/chaincode/exectransaction_test.go @@ -1,17 +1,7 @@ /* -Copyright IBM Corp. 2016 All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +SPDX-License-Identifier: Apache-2.0 */ package chaincode @@ -86,6 +76,9 @@ func initPeer(chainIDs ...string) (net.Listener, error) { peer.MockSetMSPIDGetter(mspGetter) + // For unit-test, tls is not required. + viper.Set("peer.tls.enabled", false) + var opts []grpc.ServerOption if viper.GetBool("peer.tls.enabled") { creds, err := credentials.NewServerTLSFromFile(config.GetPath("peer.tls.cert.file"), config.GetPath("peer.tls.key.file")) @@ -281,9 +274,30 @@ func endTxSimulation(chainID string, ccid *pb.ChaincodeID, txsim ledger.TxSimula //see comment on _commitLock_ _commitLock_.Lock() defer _commitLock_.Unlock() - if err := lgr.CommitWithPvtData(&ledger.BlockAndPvtData{ - Block: block, - }); err != nil { + + blockAndPvtData := &ledger.BlockAndPvtData{ + Block: block, + BlockPvtData: make(map[uint64]*ledger.TxPvtData), + } + + // All tests are performed with just one transaction in a block. + // Hence, we can simiplify the procedure of constructing the + // block with private data. There is not enough need to + // add more than one transaction in a block for testing chaincode + // API. + + // ASSUMPTION: Only one transaction in a block. + seqInBlock := uint64(0) + + if txSimulationResults.PvtSimulationResults != nil { + + blockAndPvtData.BlockPvtData[seqInBlock] = &ledger.TxPvtData{ + SeqInBlock: seqInBlock, + WriteSet: txSimulationResults.PvtSimulationResults, + } + } + + if err := lgr.CommitWithPvtData(blockAndPvtData); err != nil { return err } } @@ -364,6 +378,7 @@ func deploy2(ctx context.Context, cccid *ccprovider.CCContext, chaincodeDeployme if _, _, err = ExecuteWithErrorFilter(ctx, lsccid, cis); err != nil { return nil, fmt.Errorf("Error deploying chaincode (1): %s", err) } + if b, _, err = ExecuteWithErrorFilter(ctx, cccid, chaincodeDeploymentSpec); err != nil { return nil, fmt.Errorf("Error deploying chaincode(2): %s", err) } @@ -1072,7 +1087,8 @@ func TestChaincodeInvokeChaincodeErrorCase(t *testing.T) { // Test the invocation of a transaction. func TestQueries(t *testing.T) { - testForSkip(t) + // Allow queries test alone so that end to end test can be performed. It takes less than 5 seconds. + //testForSkip(t) chainID := util.GetTestChainID() @@ -1107,6 +1123,7 @@ func TestQueries(t *testing.T) { return } + var keys []interface{} // Add 101 marbles for testing range queries and rich queries (for capable ledgers) // The tests will test both range and rich queries and queries with query limits for i := 1; i <= 101; i++ { @@ -1137,6 +1154,7 @@ func TestQueries(t *testing.T) { theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) return } + } //The following range query for "marble001" to "marble011" should return 10 marbles @@ -1153,7 +1171,6 @@ func TestQueries(t *testing.T) { return } - var keys []interface{} err = json.Unmarshal(retval, &keys) if len(keys) != 10 { t.Fail() @@ -1270,7 +1287,6 @@ func TestQueries(t *testing.T) { theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) return } - //Reset the query limit to 5 viper.Set("ledger.state.queryLimit", 5) @@ -1290,7 +1306,6 @@ func TestQueries(t *testing.T) { //unmarshal the results err = json.Unmarshal(retval, &keys) - //check to see if there are 5 values if len(keys) != 5 { t.Fail() @@ -1401,7 +1416,7 @@ func TestQueries(t *testing.T) { err = json.Unmarshal(retval, &history) if len(history) != 3 { t.Fail() - t.Logf("Error detected with the history query, should have returned 3 but returned %v", len(keys)) + t.Logf("Error detected with the history query, should have returned 3 but returned %v", len(history)) theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) return } @@ -1770,7 +1785,7 @@ func TestChaincodeInitializeInitError(t *testing.T) { var nextBlockNumber uint64 - // the chaincode to install and instanciate + // the chaincode to install and instantiate chaincodeName := generateChaincodeName(tc.chaincodeType) chaincodePath := tc.chaincodePath chaincodeVersion := "1.0.0.0" diff --git a/core/chaincode/executetransaction_pvtdata_test.go b/core/chaincode/executetransaction_pvtdata_test.go new file mode 100644 index 00000000000..ba071c22b90 --- /dev/null +++ b/core/chaincode/executetransaction_pvtdata_test.go @@ -0,0 +1,541 @@ +// +build experimental + +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package chaincode + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/hyperledger/fabric/common/util" + "github.com/hyperledger/fabric/core/common/ccprovider" + "github.com/hyperledger/fabric/core/ledger/ledgerconfig" + pb "github.com/hyperledger/fabric/protos/peer" + "github.com/spf13/viper" + "golang.org/x/net/context" +) + +// Test the invocation of a transaction for private data. +func TestQueriesPrivateData(t *testing.T) { + + chainID := util.GetTestChainID() + + lis, err := initPeer(chainID) + if err != nil { + t.Fail() + t.Logf("Error creating peer: %s", err) + } + + defer finitPeer(lis, chainID) + + var ctxt = context.Background() + + url := "github.com/hyperledger/fabric/examples/chaincode/go/map" + cID := &pb.ChaincodeID{Name: "tmap", Path: url, Version: "0"} + + f := "init" + args := util.ToChaincodeArgs(f) + + spec := &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + + cccid := ccprovider.NewCCContext(chainID, "tmap", "0", "", false, nil, nil) + + var nextBlockNumber uint64 = 1 + _, err = deploy(ctxt, cccid, spec, nextBlockNumber) + nextBlockNumber++ + ccID := spec.ChaincodeId.Name + if err != nil { + t.Fail() + t.Logf("Error initializing chaincode %s(%s)", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + // Add 101 marbles for testing range queries and rich queries (for capable ledgers) + // on both public and private data. The tests will test both range and rich queries + // and queries with query limits + for i := 1; i <= 101; i++ { + f = "put" + + // 51 owned by tom, 50 by jerry + owner := "tom" + if i%2 == 0 { + owner = "jerry" + } + + // one marble color is red, 100 are blue + color := "blue" + if i == 12 { + color = "red" + } + + key := fmt.Sprintf("marble%03d", i) + argsString := fmt.Sprintf("{\"docType\":\"marble\",\"name\":\"%s\",\"color\":\"%s\",\"size\":35,\"owner\":\"%s\"}", key, color, owner) + args = util.ToChaincodeArgs(f, key, argsString) + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, _, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + f = "putPrivate" + + key = fmt.Sprintf("pmarble%03d", i) + args = util.ToChaincodeArgs(f, "c1", key, argsString) + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, _, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + } + + // Insert a marble in 3 private collections + for i := 2; i <= 4; i++ { + collection := fmt.Sprintf("c%d", i) + value := fmt.Sprintf("value_c%d", i) + + f = "putPrivate" + t.Logf("invoking PutPrivateData with collection:<%s> key:%s", collection, "marble001") + args = util.ToChaincodeArgs(f, collection, "pmarble001", value) + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, _, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + } + + // read a marble from collection c3 + f = "getPrivate" + args = util.ToChaincodeArgs(f, "c3", "pmarble001") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err := invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + var val string + err = json.Unmarshal(retval, &val) + expectedValue := fmt.Sprintf("value_c%d", 3) + if val != expectedValue { + t.Fail() + t.Logf("Error detected with the GetPrivateData: expected '%s' but got '%s'", expectedValue, val) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + // delete a marble from collection c3 + f = "removePrivate" + args = util.ToChaincodeArgs(f, "c3", "pmarble001") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + // delete a marble from collection c4 + f = "removePrivate" + args = util.ToChaincodeArgs(f, "c4", "pmarble001") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + // read deleted marble from collection c3 to verify whether delete executed correctly + f = "getPrivate" + args = util.ToChaincodeArgs(f, "c3", "pmarble001") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + err = json.Unmarshal(retval, &val) + if val != "" { + t.Fail() + t.Logf("Error detected with the GetPrivateData") + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + // try to read the marble inserted in collection c2 from public state to check + // whether it returns the marble (for correct operation, it should not return) + f = "get" + args = util.ToChaincodeArgs(f, "pmarble001") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + err = json.Unmarshal(retval, &val) + if val != "" { + t.Fail() + t.Logf("Error detected with the GetState: %s", val) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + //The following range query for "marble001" to "marble011" should return 10 marbles + f = "keysPrivate" + args = util.ToChaincodeArgs(f, "c1", "pmarble001", "pmarble011") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + var keys []interface{} + err = json.Unmarshal(retval, &keys) + if len(keys) != 10 { + t.Fail() + t.Logf("Error detected with the range query, should have returned 10 but returned %v", len(keys)) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //The following range query for "marble001" to "marble011" should return 10 marbles + f = "keys" + args = util.ToChaincodeArgs(f, "marble001", "marble011") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + err = json.Unmarshal(retval, &keys) + if len(keys) != 10 { + t.Fail() + t.Logf("Error detected with the range query, should have returned 10 but returned %v", len(keys)) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //FAB-1163- The following range query should timeout and produce an error + //the peer should handle this gracefully and not die + + //save the original timeout and set a new timeout of 1 sec + origTimeout := theChaincodeSupport.executetimeout + theChaincodeSupport.executetimeout = time.Duration(1) * time.Second + + //chaincode to sleep for 2 secs with timeout 1 + args = util.ToChaincodeArgs(f, "marble001", "marble002", "2000") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + if err == nil { + t.Fail() + t.Logf("expected timeout error but succeeded") + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //restore timeout + theChaincodeSupport.executetimeout = origTimeout + + // querying for all marbles will return 101 marbles + // this query should return exactly 101 results (one call to Next()) + //The following range query for "marble001" to "marble102" should return 101 marbles + f = "keys" + args = util.ToChaincodeArgs(f, "marble001", "marble102") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //unmarshal the results + err = json.Unmarshal(retval, &keys) + + //check to see if there are 101 values + //default query limit of 10000 is used, this query is effectively unlimited + if len(keys) != 101 { + t.Fail() + t.Logf("Error detected with the range query, should have returned 101 but returned %v", len(keys)) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + // querying for all simple key. This query should return exactly 101 simple keys (one + // call to Next()) no composite keys. + //The following open ended range query for "" to "" should return 101 marbles + f = "keys" + args = util.ToChaincodeArgs(f, "", "") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //unmarshal the results + err = json.Unmarshal(retval, &keys) + + //check to see if there are 101 values + //default query limit of 10000 is used, this query is effectively unlimited + if len(keys) != 101 { + t.Fail() + t.Logf("Error detected with the range query, should have returned 101 but returned %v", len(keys)) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + // ExecuteQuery supported only for CouchDB and + // query limits apply for CouchDB range and rich queries only + if ledgerconfig.IsCouchDBEnabled() == true { + + // corner cases for shim batching. currnt shim batch size is 100 + // this query should return exactly 100 results (no call to Next()) + f = "query" + args = util.ToChaincodeArgs(f, "{\"selector\":{\"color\":\"blue\"}}") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, _, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //unmarshal the results + err = json.Unmarshal(retval, &keys) + + //check to see if there are 100 values + if len(keys) != 100 { + t.Fail() + t.Logf("Error detected with the rich query, should have returned 100 but returned %v %s", len(keys), keys) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + f = "queryPrivate" + args = util.ToChaincodeArgs(f, "c1", "{\"selector\":{\"color\":\"blue\"}}") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, _, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //unmarshal the results + err = json.Unmarshal(retval, &keys) + + //check to see if there are 100 values + if len(keys) != 100 { + t.Fail() + t.Logf("Error detected with the rich query, should have returned 100 but returned %v %s", len(keys), keys) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + //Reset the query limit to 5 + viper.Set("ledger.state.queryLimit", 5) + + //The following range query for "marble01" to "marble11" should return 5 marbles due to the queryLimit + f = "keys" + args = util.ToChaincodeArgs(f, "marble001", "marble011") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err := invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //unmarshal the results + err = json.Unmarshal(retval, &keys) + //check to see if there are 5 values + if len(keys) != 5 { + t.Fail() + t.Logf("Error detected with the range query, should have returned 5 but returned %v", len(keys)) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //Reset the query limit to 10000 + viper.Set("ledger.state.queryLimit", 10000) + + //The following rich query for should return 50 marbles + f = "query" + args = util.ToChaincodeArgs(f, "{\"selector\":{\"owner\":\"jerry\"}}") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //unmarshal the results + err = json.Unmarshal(retval, &keys) + + //check to see if there are 50 values + //default query limit of 10000 is used, this query is effectively unlimited + if len(keys) != 50 { + t.Fail() + t.Logf("Error detected with the rich query, should have returned 50 but returned %v", len(keys)) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //Reset the query limit to 5 + viper.Set("ledger.state.queryLimit", 5) + + //The following rich query should return 5 marbles due to the queryLimit + f = "query" + args = util.ToChaincodeArgs(f, "{\"selector\":{\"owner\":\"jerry\"}}") + + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //unmarshal the results + err = json.Unmarshal(retval, &keys) + + //check to see if there are 5 values + if len(keys) != 5 { + t.Fail() + t.Logf("Error detected with the rich query, should have returned 5 but returned %v", len(keys)) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + } + + // modifications for history query + f = "put" + args = util.ToChaincodeArgs(f, "marble012", "{\"docType\":\"marble\",\"name\":\"marble012\",\"color\":\"red\",\"size\":30,\"owner\":\"jerry\"}") + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, _, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + f = "put" + args = util.ToChaincodeArgs(f, "marble012", "{\"docType\":\"marble\",\"name\":\"marble012\",\"color\":\"red\",\"size\":30,\"owner\":\"jerry\"}") + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, _, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + //The following history query for "marble12" should return 3 records + f = "history" + args = util.ToChaincodeArgs(f, "marble012") + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: cID, Input: &pb.ChaincodeInput{Args: args}} + _, _, retval, err = invoke(ctxt, chainID, spec, nextBlockNumber, nil) + nextBlockNumber++ + if err != nil { + t.Fail() + t.Logf("Error invoking <%s>: %s", ccID, err) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + var history []interface{} + err = json.Unmarshal(retval, &history) + if len(history) != 3 { + t.Fail() + t.Logf("Error detected with the history query, should have returned 3 but returned %v", len(history)) + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) + return + } + + theChaincodeSupport.Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) +} diff --git a/core/chaincode/handler.go b/core/chaincode/handler.go index cd6a54f7d07..9c0c2fa8adc 100644 --- a/core/chaincode/handler.go +++ b/core/chaincode/handler.go @@ -1,17 +1,7 @@ /* -Copyright IBM Corp. 2016 All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +SPDX-License-Identifier: Apache-2.0 */ package chaincode @@ -35,7 +25,6 @@ import ( "github.com/hyperledger/fabric/core/peer" pb "github.com/hyperledger/fabric/protos/peer" "github.com/looplab/fsm" - logging "github.com/op/go-logging" "github.com/pkg/errors" "golang.org/x/net/context" ) @@ -602,10 +591,8 @@ func (handler *Handler) handleGetState(msg *pb.ChaincodeMessage) { defer func() { handler.deleteTXIDEntry(msg.Txid) - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s]handleGetState serial send %s", - shorttxid(serialSendMsg.Txid), serialSendMsg.Type) - } + chaincodeLogger.Debugf("[%s]handleGetState serial send %s", + shorttxid(serialSendMsg.Txid), serialSendMsg.Type) handler.serialSendAsync(serialSendMsg, nil) }() @@ -614,15 +601,22 @@ func (handler *Handler) handleGetState(msg *pb.ChaincodeMessage) { } key := string(msg.Payload) - chaincodeID := handler.getCCRootName() - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s] getting state for chaincode %s, key %s, channel %s", - shorttxid(msg.Txid), chaincodeID, key, txContext.chainID) + getState := &pb.GetState{} + unmarshalErr := proto.Unmarshal(msg.Payload, getState) + if unmarshalErr != nil { + serialSendMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR, Payload: []byte(unmarshalErr.Error()), Txid: msg.Txid} } + chaincodeID := handler.getCCRootName() + chaincodeLogger.Debugf("[%s] getting state for chaincode %s, key %s, channel %s", + shorttxid(msg.Txid), chaincodeID, getState.Key, txContext.chainID) var res []byte var err error - res, err = txContext.txsimulator.GetState(chaincodeID, key) + if isCollectionSet(getState.Collection) { + res, err = txContext.txsimulator.GetPrivateData(chaincodeID, getState.Collection, getState.Key) + } else { + res, err = txContext.txsimulator.GetState(chaincodeID, getState.Key) + } if err != nil { // Send error msg back to chaincode. GetState will not trigger event @@ -637,9 +631,7 @@ func (handler *Handler) handleGetState(msg *pb.ChaincodeMessage) { serialSendMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Payload: res, Txid: msg.Txid} } else { // Send response msg back to chaincode. GetState will not trigger event - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s]Got state. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_RESPONSE) - } + chaincodeLogger.Debugf("[%s]Got state. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_RESPONSE) serialSendMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Payload: res, Txid: msg.Txid} } @@ -710,8 +702,14 @@ func (handler *Handler) handleGetStateByRange(msg *pb.ChaincodeMessage) { chaincodeLogger.Errorf(errFmt, errArgs...) serialSendMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR, Payload: payload, Txid: msg.Txid} } + var rangeIter commonledger.ResultsIterator + var err error - rangeIter, err := txContext.txsimulator.GetStateRangeScanIterator(chaincodeID, getStateByRange.StartKey, getStateByRange.EndKey) + if isCollectionSet(getStateByRange.Collection) { + rangeIter, err = txContext.txsimulator.GetPrivateDataRangeScanIterator(chaincodeID, getStateByRange.Collection, getStateByRange.StartKey, getStateByRange.EndKey) + } else { + rangeIter, err = txContext.txsimulator.GetStateRangeScanIterator(chaincodeID, getStateByRange.StartKey, getStateByRange.EndKey) + } if err != nil { errHandler(err, nil, "Failed to get ledger scan iterator. Sending %s", pb.ChaincodeMessage_ERROR) return @@ -999,7 +997,14 @@ func (handler *Handler) handleGetQueryResult(msg *pb.ChaincodeMessage) { chaincodeID := handler.getCCRootName() - executeIter, err := txContext.txsimulator.ExecuteQuery(chaincodeID, getQueryResult.Query) + var err error + var executeIter commonledger.ResultsIterator + if isCollectionSet(getQueryResult.Collection) { + executeIter, err = txContext.txsimulator.ExecuteQueryOnPrivateData(chaincodeID, getQueryResult.Collection, getQueryResult.Query) + } else { + executeIter, err = txContext.txsimulator.ExecuteQuery(chaincodeID, getQueryResult.Query) + } + if err != nil { errHandler([]byte(err.Error()), nil, "Failed to get ledger query iterator. Sending %s", pb.ChaincodeMessage_ERROR) return @@ -1118,13 +1123,18 @@ func (handler *Handler) handleGetHistoryForKey(msg *pb.ChaincodeMessage) { }() } +func isCollectionSet(collection string) bool { + if collection == "" { + return false + } + return true +} + // Handles request to ledger to put state func (handler *Handler) enterBusyState(e *fsm.Event, state string) { go func() { msg, _ := e.Args[0].(*pb.ChaincodeMessage) - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s]state is %s", shorttxid(msg.Txid), state) - } + chaincodeLogger.Debugf("[%s]state is %s", shorttxid(msg.Txid), state) // Check if this is the unique request from this chaincode txid uniqueReq := handler.createTXIDEntry(msg.Txid) if !uniqueReq { @@ -1140,10 +1150,8 @@ func (handler *Handler) enterBusyState(e *fsm.Event, state string) { defer func() { handler.deleteTXIDEntry(msg.Txid) - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s]enterBusyState trigger event %s", - shorttxid(triggerNextStateMsg.Txid), triggerNextStateMsg.Type) - } + chaincodeLogger.Debugf("[%s]enterBusyState trigger event %s", + shorttxid(triggerNextStateMsg.Txid), triggerNextStateMsg.Type) handler.triggerNextState(triggerNextStateMsg, true) }() @@ -1161,22 +1169,35 @@ func (handler *Handler) enterBusyState(e *fsm.Event, state string) { var res []byte if msg.Type.String() == pb.ChaincodeMessage_PUT_STATE.String() { - putStateInfo := &pb.PutStateInfo{} - unmarshalErr := proto.Unmarshal(msg.Payload, putStateInfo) + putState := &pb.PutState{} + unmarshalErr := proto.Unmarshal(msg.Payload, putState) if unmarshalErr != nil { errHandler([]byte(unmarshalErr.Error()), "[%s]Unable to decipher payload. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR) return } - err = txContext.txsimulator.SetState(chaincodeID, putStateInfo.Key, putStateInfo.Value) + if isCollectionSet(putState.Collection) { + err = txContext.txsimulator.SetPrivateData(chaincodeID, putState.Collection, putState.Key, putState.Value) + } else { + err = txContext.txsimulator.SetState(chaincodeID, putState.Key, putState.Value) + } + } else if msg.Type.String() == pb.ChaincodeMessage_DEL_STATE.String() { // Invoke ledger to delete state - key := string(msg.Payload) - err = txContext.txsimulator.DeleteState(chaincodeID, key) - } else if msg.Type.String() == pb.ChaincodeMessage_INVOKE_CHAINCODE.String() { - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s] C-call-C", shorttxid(msg.Txid)) + delState := &pb.DelState{} + unmarshalErr := proto.Unmarshal(msg.Payload, delState) + if unmarshalErr != nil { + errHandler([]byte(unmarshalErr.Error()), "[%s]Unable to decipher payload. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR) + return } + + if isCollectionSet(delState.Collection) { + err = txContext.txsimulator.DeletePrivateData(chaincodeID, delState.Collection, delState.Key) + } else { + err = txContext.txsimulator.DeleteState(chaincodeID, delState.Key) + } + } else if msg.Type.String() == pb.ChaincodeMessage_INVOKE_CHAINCODE.String() { + chaincodeLogger.Debugf("[%s] C-call-C", shorttxid(msg.Txid)) chaincodeSpec := &pb.ChaincodeSpec{} unmarshalErr := proto.Unmarshal(msg.Payload, chaincodeSpec) if unmarshalErr != nil { @@ -1193,10 +1214,8 @@ func (handler *Handler) enterBusyState(e *fsm.Event, state string) { // use caller's channel as the called chaincode is in the same channel calledCcIns.ChainID = txContext.chainID } - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s] C-call-C %s on channel %s", - shorttxid(msg.Txid), calledCcIns.ChaincodeName, calledCcIns.ChainID) - } + chaincodeLogger.Debugf("[%s] C-call-C %s on channel %s", + shorttxid(msg.Txid), calledCcIns.ChaincodeName, calledCcIns.ChainID) err := handler.checkACL(txContext.signedProp, txContext.proposal, calledCcIns) if err != nil { @@ -1229,10 +1248,8 @@ func (handler *Handler) enterBusyState(e *fsm.Event, state string) { ctxt = context.WithValue(ctxt, TXSimulatorKey, txsim) ctxt = context.WithValue(ctxt, HistoryQueryExecutorKey, historyQueryExecutor) - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s] calling lscc to get chaincode data for %s on channel %s", - shorttxid(msg.Txid), calledCcIns.ChaincodeName, calledCcIns.ChainID) - } + chaincodeLogger.Debugf("[%s] calling lscc to get chaincode data for %s on channel %s", + shorttxid(msg.Txid), calledCcIns.ChaincodeName, calledCcIns.ChainID) //Call LSCC to get the called chaincode artifacts @@ -1251,7 +1268,7 @@ func (handler *Handler) enterBusyState(e *fsm.Event, state string) { err = ccprovider.CheckInsantiationPolicy(calledCcIns.ChaincodeName, cd.Version, cd) if err != nil { - errHandler([]byte(err.Error()), "[%s]CheckInstantiationPolicy, error %s. Sending %s", shorttxid(msg.Txid), err, pb.ChaincodeMessage_ERROR) + errHandler([]byte(err.Error()), "[%s]CheckInsantiationPolicy, error %s. Sending %s", shorttxid(msg.Txid), err, pb.ChaincodeMessage_ERROR) return } } else { @@ -1262,10 +1279,8 @@ func (handler *Handler) enterBusyState(e *fsm.Event, state string) { cccid := ccprovider.NewCCContext(calledCcIns.ChainID, calledCcIns.ChaincodeName, cd.Version, msg.Txid, false, txContext.signedProp, txContext.proposal) // Launch the new chaincode if not already running - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s] launching chaincode %s on channel %s", - shorttxid(msg.Txid), calledCcIns.ChaincodeName, calledCcIns.ChainID) - } + chaincodeLogger.Debugf("[%s] launching chaincode %s on channel %s", + shorttxid(msg.Txid), calledCcIns.ChaincodeName, calledCcIns.ChainID) cciSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: chaincodeSpec} _, chaincodeInput, launchErr := handler.chaincodeSupport.Launch(ctxt, cccid, cciSpec) if launchErr != nil { @@ -1297,9 +1312,7 @@ func (handler *Handler) enterBusyState(e *fsm.Event, state string) { } // Send response msg back to chaincode. - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s]Completed %s. Sending %s", shorttxid(msg.Txid), msg.Type.String(), pb.ChaincodeMessage_RESPONSE) - } + chaincodeLogger.Debugf("[%s]Completed %s. Sending %s", shorttxid(msg.Txid), msg.Type.String(), pb.ChaincodeMessage_RESPONSE) triggerNextStateMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Payload: res, Txid: msg.Txid} }() } @@ -1418,9 +1431,7 @@ func (handler *Handler) sendExecuteMessage(ctxt context.Context, chainID string, if err != nil { return nil, err } - if chaincodeLogger.IsEnabledFor(logging.DEBUG) { - chaincodeLogger.Debugf("[%s]Inside sendExecuteMessage. Message %s", shorttxid(msg.Txid), msg.Type.String()) - } + chaincodeLogger.Debugf("[%s]Inside sendExecuteMessage. Message %s", shorttxid(msg.Txid), msg.Type.String()) //if security is disabled the context elements will just be nil if err = handler.setChaincodeProposal(signedProp, prop, msg); err != nil { diff --git a/core/chaincode/platforms/golang/platform.go b/core/chaincode/platforms/golang/platform.go index 6b784c19553..c76f366b61a 100644 --- a/core/chaincode/platforms/golang/platform.go +++ b/core/chaincode/platforms/golang/platform.go @@ -30,6 +30,7 @@ import ( "sort" + "github.com/hyperledger/fabric/common/metadata" "github.com/hyperledger/fabric/core/chaincode/platforms/util" cutil "github.com/hyperledger/fabric/core/container/util" pb "github.com/hyperledger/fabric/protos/peer" @@ -457,11 +458,17 @@ func (goPlatform *Platform) GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec, } const ldflags = "-linkmode external -extldflags '-static'" + var gotags string + // check if experimental features are enabled + if metadata.Experimental == "true" { + gotags = " experimental" + } + logger.Infof("building chaincode with tags: %s", gotags) codepackage := bytes.NewReader(cds.CodePackage) binpackage := bytes.NewBuffer(nil) err = util.DockerBuild(util.DockerBuildOptions{ - Cmd: fmt.Sprintf("GOPATH=/chaincode/input:$GOPATH go build -ldflags \"%s\" -o /chaincode/output/chaincode %s", ldflags, pkgname), + Cmd: fmt.Sprintf("GOPATH=/chaincode/input:$GOPATH go build -tags \"%s\" -ldflags \"%s\" -o /chaincode/output/chaincode %s", gotags, ldflags, pkgname), InputStream: codepackage, OutputStream: binpackage, }) diff --git a/core/chaincode/shim/chaincode.go b/core/chaincode/shim/chaincode.go index 61c4146fc42..21777d1aa8c 100644 --- a/core/chaincode/shim/chaincode.go +++ b/core/chaincode/shim/chaincode.go @@ -1,17 +1,7 @@ /* -Copyright IBM Corp. 2016 All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +SPDX-License-Identifier: Apache-2.0 */ // Package shim provides APIs for the chaincode to access its state @@ -398,9 +388,6 @@ func (stub *ChaincodeStub) GetDecorations() map[string][]byte { return stub.decorations } -// --------- Security functions ---------- -//CHAINCODE SEC INTERFACE FUNCS TOBE IMPLEMENTED BY ANGELO - // ------------- Call Chaincode functions --------------- // InvokeChaincode documentation can be found in interfaces.go @@ -416,7 +403,9 @@ func (stub *ChaincodeStub) InvokeChaincode(chaincodeName string, args [][]byte, // GetState documentation can be found in interfaces.go func (stub *ChaincodeStub) GetState(key string) ([]byte, error) { - return stub.handler.handleGetState(key, stub.TxID) + // Access public data by setting the collection to empty string + collection := "" + return stub.handler.handleGetState(collection, key, stub.TxID) } // PutState documentation can be found in interfaces.go @@ -424,12 +413,27 @@ func (stub *ChaincodeStub) PutState(key string, value []byte) error { if key == "" { return errors.New("key must not be an empty string") } - return stub.handler.handlePutState(key, value, stub.TxID) + // Access public data by setting the collection to empty string + collection := "" + return stub.handler.handlePutState(collection, key, value, stub.TxID) +} + +// GetQueryResult documentation can be found in interfaces.go +func (stub *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error) { + // Access public data by setting the collection to empty string + collection := "" + response, err := stub.handler.handleGetQueryResult(collection, query, stub.TxID) + if err != nil { + return nil, err + } + return &StateQueryIterator{CommonIterator: &CommonIterator{stub.handler, stub.TxID, response, 0}}, nil } // DelState documentation can be found in interfaces.go func (stub *ChaincodeStub) DelState(key string) error { - return stub.handler.handleDelState(key, stub.TxID) + // Access public data by setting the collection to empty string + collection := "" + return stub.handler.handleDelState(collection, key, stub.TxID) } // CommonIterator documentation can be found in interfaces.go @@ -457,8 +461,8 @@ const ( HISTORY_QUERY_RESULT ) -func (stub *ChaincodeStub) handleGetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) { - response, err := stub.handler.handleGetStateByRange(startKey, endKey, stub.TxID) +func (stub *ChaincodeStub) handleGetStateByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error) { + response, err := stub.handler.handleGetStateByRange(collection, startKey, endKey, stub.TxID) if err != nil { return nil, err } @@ -473,16 +477,8 @@ func (stub *ChaincodeStub) GetStateByRange(startKey, endKey string) (StateQueryI if err := validateSimpleKeys(startKey, endKey); err != nil { return nil, err } - return stub.handleGetStateByRange(startKey, endKey) -} - -// GetQueryResult documentation can be found in interfaces.go -func (stub *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error) { - response, err := stub.handler.handleGetQueryResult(query, stub.TxID) - if err != nil { - return nil, err - } - return &StateQueryIterator{CommonIterator: &CommonIterator{stub.handler, stub.TxID, response, 0}}, nil + collection := "" + return stub.handleGetStateByRange(collection, startKey, endKey) } // GetHistoryForKey documentation can be found in interfaces.go @@ -563,8 +559,9 @@ func validateSimpleKeys(simpleKeys ...string) error { //a partial composite key. For a full composite key, an iter with empty response //would be returned. func (stub *ChaincodeStub) GetStateByPartialCompositeKey(objectType string, attributes []string) (StateQueryIteratorInterface, error) { + collection := "" if partialCompositeKey, err := stub.CreateCompositeKey(objectType, attributes); err == nil { - return stub.handleGetStateByRange(partialCompositeKey, partialCompositeKey+string(maxUnicodeRuneValue)) + return stub.handleGetStateByRange(collection, partialCompositeKey, partialCompositeKey+string(maxUnicodeRuneValue)) } else { return nil, err } diff --git a/core/chaincode/shim/chaincode_experimental.go b/core/chaincode/shim/chaincode_experimental.go new file mode 100644 index 00000000000..3db423be221 --- /dev/null +++ b/core/chaincode/shim/chaincode_experimental.go @@ -0,0 +1,80 @@ +// +build experimental + +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package shim + +import ( + "fmt" +) + +// private state functions + +// GetPrivateData documentation can be found in interfaces.go +func (stub *ChaincodeStub) GetPrivateData(collection string, key string) ([]byte, error) { + if collection == "" { + return nil, fmt.Errorf("collection must not be an empty string") + } + return stub.handler.handleGetState(collection, key, stub.TxID) +} + +// PutPrivateData documentation can be found in interfaces.go +func (stub *ChaincodeStub) PutPrivateData(collection string, key string, value []byte) error { + if collection == "" { + return fmt.Errorf("collection must not be an empty string") + } + if key == "" { + return fmt.Errorf("key must not be an empty string") + } + return stub.handler.handlePutState(collection, key, value, stub.TxID) +} + +// DelPrivateData documentation can be found in interfaces.go +func (stub *ChaincodeStub) DelPrivateData(collection string, key string) error { + if collection == "" { + return fmt.Errorf("collection must not be an empty string") + } + return stub.handler.handleDelState(collection, key, stub.TxID) +} + +// GetPrivateDataByRange documentation can be found in interfaces.go +func (stub *ChaincodeStub) GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error) { + if collection == "" { + return nil, fmt.Errorf("collection must not be an empty string") + } + if startKey == "" { + startKey = emptyKeySubstitute + } + if err := validateSimpleKeys(startKey, endKey); err != nil { + return nil, err + } + return stub.handleGetStateByRange(collection, startKey, endKey) +} + +// GetPrivateDataByPartialCompositeKey documentation can be found in interfaces.go +func (stub *ChaincodeStub) GetPrivateDataByPartialCompositeKey(collection, objectType string, attributes []string) (StateQueryIteratorInterface, error) { + if collection == "" { + return nil, fmt.Errorf("collection must not be an empty string") + } + if partialCompositeKey, err := stub.CreateCompositeKey(objectType, attributes); err == nil { + return stub.handleGetStateByRange(collection, partialCompositeKey, partialCompositeKey+string(maxUnicodeRuneValue)) + } else { + return nil, err + } +} + +// GetPrivateDataQueryResult documentation can be found in interfaces.go +func (stub *ChaincodeStub) GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error) { + if collection == "" { + return nil, fmt.Errorf("collection must not be an empty string") + } + response, err := stub.handler.handleGetQueryResult(collection, query, stub.TxID) + if err != nil { + return nil, err + } + return &StateQueryIterator{CommonIterator: &CommonIterator{stub.handler, stub.TxID, response, 0}}, nil +} diff --git a/core/chaincode/shim/handler.go b/core/chaincode/shim/handler.go index ec3cd569dab..53b648213d3 100644 --- a/core/chaincode/shim/handler.go +++ b/core/chaincode/shim/handler.go @@ -1,6 +1,8 @@ /* Copyright IBM Corp. 2016 All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -366,34 +368,40 @@ func (handler *Handler) afterError(e *fsm.Event) { /* TODO- revisit. This may no longer be needed with the serialized/streamlined messaging model * There are two situations in which the ERROR event can be triggered: - * 1. When an error is encountered within handleInit or handleTransaction - some issue at the chaincode side; In this case there will be no responseChannel and the message has been sent to the validator. - * 2. The chaincode has initiated a request (get/put/del state) to the validator and is expecting a response on the responseChannel; If ERROR is received from validator, this needs to be notified on the responseChannel. + * 1. When an error is encountered within handleInit or handleTransaction - some issue at the chaincode side; In this case there will be no responseChannel and the message has been sent to the peer. + * 2. The chaincode has initiated a request (get/put/del state) to the peer and is expecting a response on the responseChannel; If ERROR is received from peer, this needs to be notified on the responseChannel. */ if err := handler.sendChannel(msg); err == nil { - chaincodeLogger.Debugf("[%s]Error received from validator %s, communicated(state:%s)", shorttxid(msg.Txid), msg.Type, handler.FSM.Current()) + chaincodeLogger.Debugf("[%s]Error received from peer %s, communicated(state:%s)", shorttxid(msg.Txid), msg.Type, handler.FSM.Current()) } } -// TODO: Implement method to get and put entire state map and not one key at a time? -// handleGetState communicates with the validator to fetch the requested state information from the ledger. -func (handler *Handler) handleGetState(key string, txid string) ([]byte, error) { - // Create the channel on which to communicate the response from validating peer +// callPeerWithChaincodeMsg sends a chaincode message (for e.g., GetState along with the key) to the peer for a given txid +// and receives the response. +func (handler *Handler) callPeerWithChaincodeMsg(msg *pb.ChaincodeMessage, txid string) (pb.ChaincodeMessage, error) { + // Create the channel on which to communicate the response from the peer var respChan chan pb.ChaincodeMessage var err error if respChan, err = handler.createChannel(txid); err != nil { - return nil, err + return pb.ChaincodeMessage{}, err } defer handler.deleteChannel(txid) - // Send GET_STATE message to validator chaincode support - payload := []byte(key) - msg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: payload, Txid: txid} - chaincodeLogger.Debugf("[%s]Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_GET_STATE) + return handler.sendReceive(msg, respChan) +} - var responseMsg pb.ChaincodeMessage +// TODO: Implement a method to get multiple keys at a time [FAB-1244] +// handleGetState communicates with the peer to fetch the requested state information from the ledger. +func (handler *Handler) handleGetState(collection string, key string, txid string) ([]byte, error) { + // Construct payload for GET_STATE + payloadBytes, _ := proto.Marshal(&pb.GetState{Collection: collection, Key: key}) - if responseMsg, err = handler.sendReceive(msg, respChan); err != nil { + msg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE, Payload: payloadBytes, Txid: txid} + chaincodeLogger.Debugf("[%s]Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_GET_STATE) + + responseMsg, err := handler.callPeerWithChaincodeMsg(msg, txid) + if err != nil { return nil, errors.WithMessage(err, fmt.Sprintf("[%s]error sending GET_STATE", shorttxid(txid))) } @@ -412,30 +420,18 @@ func (handler *Handler) handleGetState(key string, txid string) ([]byte, error) return nil, errors.Errorf("[%s]incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, pb.ChaincodeMessage_RESPONSE, pb.ChaincodeMessage_ERROR) } -// handlePutState communicates with the validator to put state information into the ledger. -func (handler *Handler) handlePutState(key string, value []byte, txid string) error { - // Check if this is a transaction - chaincodeLogger.Debugf("[%s]Inside putstate", shorttxid(txid)) - - //we constructed a valid object. No need to check for error - payloadBytes, _ := proto.Marshal(&pb.PutStateInfo{Key: key, Value: value}) +// TODO: Implement a method to set multiple keys at a time [FAB-1244] +// handlePutState communicates with the peer to put state information into the ledger. +func (handler *Handler) handlePutState(collection string, key string, value []byte, txid string) error { + // Construct payload for PUT_STATE + payloadBytes, _ := proto.Marshal(&pb.PutState{Collection: collection, Key: key, Value: value}) - // Create the channel on which to communicate the response from validating peer - var respChan chan pb.ChaincodeMessage - var err error - if respChan, err = handler.createChannel(txid); err != nil { - return err - } - - defer handler.deleteChannel(txid) - - // Send PUT_STATE message to validator chaincode support msg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_PUT_STATE, Payload: payloadBytes, Txid: txid} chaincodeLogger.Debugf("[%s]Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_PUT_STATE) - var responseMsg pb.ChaincodeMessage - - if responseMsg, err = handler.sendReceive(msg, respChan); err != nil { + // Execute the request and get response + responseMsg, err := handler.callPeerWithChaincodeMsg(msg, txid) + if err != nil { return errors.WithMessage(err, fmt.Sprintf("[%s]error sending PUT_STATE", msg.Txid)) } @@ -455,25 +451,17 @@ func (handler *Handler) handlePutState(key string, value []byte, txid string) er return errors.Errorf("[%s]incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, pb.ChaincodeMessage_RESPONSE, pb.ChaincodeMessage_ERROR) } -// handleDelState communicates with the validator to delete a key from the state in the ledger. -func (handler *Handler) handleDelState(key string, txid string) error { - // Create the channel on which to communicate the response from validating peer - var respChan chan pb.ChaincodeMessage - var err error - if respChan, err = handler.createChannel(txid); err != nil { - return err - } - - defer handler.deleteChannel(txid) - - // Send DEL_STATE message to validator chaincode support - payload := []byte(key) - msg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_DEL_STATE, Payload: payload, Txid: txid} - chaincodeLogger.Debugf("[%s]Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_DEL_STATE) +// handleDelState communicates with the peer to delete a key from the state in the ledger. +func (handler *Handler) handleDelState(collection string, key string, txid string) error { + //payloadBytes, _ := proto.Marshal(&pb.GetState{Collection: collection, Key: key}) + payloadBytes, _ := proto.Marshal(&pb.DelState{Collection: collection, Key: key}) - var responseMsg pb.ChaincodeMessage + msg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_DEL_STATE, Payload: payloadBytes, Txid: txid} + chaincodeLogger.Debugf("[%s]Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_GET_STATE) - if responseMsg, err = handler.sendReceive(msg, respChan); err != nil { + // Execute the request and get response + responseMsg, err := handler.callPeerWithChaincodeMsg(msg, txid) + if err != nil { return errors.Errorf("[%s]error sending DEL_STATE %s", shorttxid(msg.Txid), pb.ChaincodeMessage_DEL_STATE) } @@ -492,25 +480,16 @@ func (handler *Handler) handleDelState(key string, txid string) error { return errors.Errorf("[%s]incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, pb.ChaincodeMessage_RESPONSE, pb.ChaincodeMessage_ERROR) } -func (handler *Handler) handleGetStateByRange(startKey, endKey string, txid string) (*pb.QueryResponse, error) { - // Create the channel on which to communicate the response from validating peer - var respChan chan pb.ChaincodeMessage - var err error - if respChan, err = handler.createChannel(txid); err != nil { - return nil, err - } - - defer handler.deleteChannel(txid) - - // Send GET_STATE_BY_RANGE message to validator chaincode support +func (handler *Handler) handleGetStateByRange(collection, startKey, endKey string, txid string) (*pb.QueryResponse, error) { + // Send GET_STATE_BY_RANGE message to peer chaincode support //we constructed a valid object. No need to check for error - payloadBytes, _ := proto.Marshal(&pb.GetStateByRange{StartKey: startKey, EndKey: endKey}) + payloadBytes, _ := proto.Marshal(&pb.GetStateByRange{Collection: collection, StartKey: startKey, EndKey: endKey}) msg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_STATE_BY_RANGE, Payload: payloadBytes, Txid: txid} chaincodeLogger.Debugf("[%s]Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_GET_STATE_BY_RANGE) - var responseMsg pb.ChaincodeMessage - if responseMsg, err = handler.sendReceive(msg, respChan); err != nil { + responseMsg, err := handler.callPeerWithChaincodeMsg(msg, txid) + if err != nil { return nil, errors.Errorf("[%s]error sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_GET_STATE_BY_RANGE) } @@ -545,7 +524,7 @@ func (handler *Handler) handleQueryStateNext(id, txid string) (*pb.QueryResponse defer handler.deleteChannel(txid) - // Send QUERY_STATE_NEXT message to validator chaincode support + // Send QUERY_STATE_NEXT message to peer chaincode support //we constructed a valid object. No need to check for error payloadBytes, _ := proto.Marshal(&pb.QueryStateNext{Id: id}) @@ -589,7 +568,7 @@ func (handler *Handler) handleQueryStateClose(id, txid string) (*pb.QueryRespons defer handler.deleteChannel(txid) - // Send QUERY_STATE_CLOSE message to validator chaincode support + // Send QUERY_STATE_CLOSE message to peer chaincode support //we constructed a valid object. No need to check for error payloadBytes, _ := proto.Marshal(&pb.QueryStateClose{Id: id}) @@ -623,26 +602,16 @@ func (handler *Handler) handleQueryStateClose(id, txid string) (*pb.QueryRespons return nil, errors.Errorf("incorrect chaincode message %s received. Expecting %s or %s", responseMsg.Type, pb.ChaincodeMessage_RESPONSE, pb.ChaincodeMessage_ERROR) } -func (handler *Handler) handleGetQueryResult(query string, txid string) (*pb.QueryResponse, error) { - // Create the channel on which to communicate the response from validating peer - var respChan chan pb.ChaincodeMessage - var err error - if respChan, err = handler.createChannel(txid); err != nil { - return nil, err - } - - defer handler.deleteChannel(txid) - - // Send GET_QUERY_RESULT message to validator chaincode support +func (handler *Handler) handleGetQueryResult(collection string, query string, txid string) (*pb.QueryResponse, error) { + // Send GET_QUERY_RESULT message to peer chaincode support //we constructed a valid object. No need to check for error - payloadBytes, _ := proto.Marshal(&pb.GetQueryResult{Query: query}) + payloadBytes, _ := proto.Marshal(&pb.GetQueryResult{Collection: collection, Query: query}) msg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_GET_QUERY_RESULT, Payload: payloadBytes, Txid: txid} chaincodeLogger.Debugf("[%s]Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_GET_QUERY_RESULT) - var responseMsg pb.ChaincodeMessage - - if responseMsg, err = handler.sendReceive(msg, respChan); err != nil { + responseMsg, err := handler.callPeerWithChaincodeMsg(msg, txid) + if err != nil { return nil, errors.Errorf("[%s]error sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_GET_QUERY_RESULT) } @@ -677,7 +646,7 @@ func (handler *Handler) handleGetHistoryForKey(key string, txid string) (*pb.Que defer handler.deleteChannel(txid) - // Send GET_HISTORY_FOR_KEY message to validator chaincode support + // Send GET_HISTORY_FOR_KEY message to peer chaincode support //we constructed a valid object. No need to check for error payloadBytes, _ := proto.Marshal(&pb.GetHistoryForKey{Key: key}) @@ -715,7 +684,7 @@ func (handler *Handler) createResponse(status int32, payload []byte) pb.Response return pb.Response{Status: status, Payload: payload} } -// handleInvokeChaincode communicates with the validator to invoke another chaincode. +// handleInvokeChaincode communicates with the peer to invoke another chaincode. func (handler *Handler) handleInvokeChaincode(chaincodeName string, args [][]byte, txid string) pb.Response { //we constructed a valid object. No need to check for error payloadBytes, _ := proto.Marshal(&pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: chaincodeName}, Input: &pb.ChaincodeInput{Args: args}}) @@ -729,7 +698,7 @@ func (handler *Handler) handleInvokeChaincode(chaincodeName string, args [][]byt defer handler.deleteChannel(txid) - // Send INVOKE_CHAINCODE message to validator chaincode support + // Send INVOKE_CHAINCODE message to peer chaincode support msg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_INVOKE_CHAINCODE, Payload: payloadBytes, Txid: txid} chaincodeLogger.Debugf("[%s]Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_INVOKE_CHAINCODE) @@ -768,7 +737,7 @@ func (handler *Handler) handleInvokeChaincode(chaincodeName string, args [][]byt return handler.createResponse(ERROR, []byte(fmt.Sprintf("[%s]Incorrect chaincode message %s received. Expecting %s or %s", shorttxid(responseMsg.Txid), responseMsg.Type, pb.ChaincodeMessage_RESPONSE, pb.ChaincodeMessage_ERROR))) } -// handleMessage message handles loop for shim side of chaincode/validator stream. +// handleMessage message handles loop for shim side of chaincode/peer stream. func (handler *Handler) handleMessage(msg *pb.ChaincodeMessage) error { if msg.Type == pb.ChaincodeMessage_KEEPALIVE { // Received a keep alive message, we don't do anything with it for now diff --git a/core/chaincode/shim/interfaces_experimental.go b/core/chaincode/shim/interfaces_experimental.go new file mode 100644 index 00000000000..b3529dcd7fa --- /dev/null +++ b/core/chaincode/shim/interfaces_experimental.go @@ -0,0 +1,293 @@ +// +build experimental + +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package shim + +import ( + "github.com/golang/protobuf/ptypes/timestamp" + + "github.com/hyperledger/fabric/protos/ledger/queryresult" + pb "github.com/hyperledger/fabric/protos/peer" +) + +// Chaincode interface must be implemented by all chaincodes. The fabric runs +// the transactions by calling these functions as specified. +type Chaincode interface { + // Init is called during Instantiate transaction after the chaincode container + // has been established for the first time, allowing the chaincode to + // initialize its internal data + Init(stub ChaincodeStubInterface) pb.Response + + // Invoke is called to update or query the ledger in a proposal transaction. + // Updated state variables are not committed to the ledger until the + // transaction is committed. + Invoke(stub ChaincodeStubInterface) pb.Response +} + +// ChaincodeStubInterface is used by deployable chaincode apps to access and +// modify their ledgers +type ChaincodeStubInterface interface { + // GetArgs returns the arguments intended for the chaincode Init and Invoke + // as an array of byte arrays. + GetArgs() [][]byte + + // GetStringArgs returns the arguments intended for the chaincode Init and + // Invoke as a string array. Only use GetStringArgs if the client passes + // arguments intended to be used as strings. + GetStringArgs() []string + + // GetFunctionAndParameters returns the first argument as the function + // name and the rest of the arguments as parameters in a string array. + // Only use GetFunctionAndParameters if the client passes arguments intended + // to be used as strings. + GetFunctionAndParameters() (string, []string) + + // GetArgsSlice returns the arguments intended for the chaincode Init and + // Invoke as a byte array + GetArgsSlice() ([]byte, error) + + // GetTxID returns the tx_id of the transaction proposal (see ChannelHeader + // in protos/common/common.proto) + GetTxID() string + + // InvokeChaincode locally calls the specified chaincode `Invoke` using the + // same transaction context; that is, chaincode calling chaincode doesn't + // create a new transaction message. + // If the called chaincode is on the same channel, it simply adds the called + // chaincode read set and write set to the calling transaction. + // If the called chaincode is on a different channel, + // only the Response is returned to the calling chaincode; any PutState calls + // from the called chaincode will not have any effect on the ledger; that is, + // the called chaincode on a different channel will not have its read set + // and write set applied to the transaction. Only the calling chaincode's + // read set and write set will be applied to the transaction. Effectively + // the called chaincode on a different channel is a `Query`, which does not + // participate in state validation checks in subsequent commit phase. + // If `channel` is empty, the caller's channel is assumed. + InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response + + // GetState returns the value of the specified `key` from the + // ledger. Note that GetState doesn't read data from the writeset, which + // has not been committed to the ledger. In other words, GetState doesn't + // consider data modified by PutState that has not been committed. + // If the key does not exist in the state database, (nil, nil) is returned. + GetState(key string) ([]byte, error) + + // PutState puts the specified `key` and `value` into the transaction's + // writeset as a data-write proposal. PutState doesn't effect the ledger + // until the transaction is validated and successfully committed. + // Simple keys must not be an empty string and must not start with null + // character (0x00), in order to avoid range query collisions with + // composite keys, which internally get prefixed with 0x00 as composite + // key namespace. + PutState(key string, value []byte) error + + // DelState records the specified `key` to be deleted in the writeset of + // the transaction proposal. The `key` and its value will be deleted from + // the ledger when the transaction is validated and successfully committed. + DelState(key string) error + + // GetStateByRange returns a range iterator over a set of keys in the + // ledger. The iterator can be used to iterate over all keys + // between the startKey (inclusive) and endKey (exclusive). + // The keys are returned by the iterator in lexical order. Note + // that startKey and endKey can be empty string, which implies unbounded range + // query on start or end. + // Call Close() on the returned StateQueryIteratorInterface object when done. + // The query is re-executed during validation phase to ensure result set + // has not changed since transaction endorsement (phantom reads detected). + GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) + + // GetStateByPartialCompositeKey queries the state in the ledger based on + // a given partial composite key. This function returns an iterator + // which can be used to iterate over all composite keys whose prefix matches + // the given partial composite key. The `objectType` and attributes are + // expected to have only valid utf8 strings and should not contain + // U+0000 (nil byte) and U+10FFFF (biggest and unallocated code point). + // See related functions SplitCompositeKey and CreateCompositeKey. + // Call Close() on the returned StateQueryIteratorInterface object when done. + // The query is re-executed during validation phase to ensure result set + // has not changed since transaction endorsement (phantom reads detected). + GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error) + + // CreateCompositeKey combines the given `attributes` to form a composite + // key. The objectType and attributes are expected to have only valid utf8 + // strings and should not contain U+0000 (nil byte) and U+10FFFF + // (biggest and unallocated code point). + // The resulting composite key can be used as the key in PutState(). + CreateCompositeKey(objectType string, attributes []string) (string, error) + + // SplitCompositeKey splits the specified key into attributes on which the + // composite key was formed. Composite keys found during range queries + // or partial composite key queries can therefore be split into their + // composite parts. + SplitCompositeKey(compositeKey string) (string, []string, error) + + // GetQueryResult performs a "rich" query against a state database. It is + // only supported for state databases that support rich query, + // e.g.CouchDB. The query string is in the native syntax + // of the underlying state database. An iterator is returned + // which can be used to iterate (next) over the query result set. + // The query is NOT re-executed during validation phase, phantom reads are + // not detected. That is, other committed transactions may have added, + // updated, or removed keys that impact the result set, and this would not + // be detected at validation/commit time. Applications susceptible to this + // should therefore not use GetQueryResult as part of transactions that update + // ledger, and should limit use to read-only chaincode operations. + GetQueryResult(query string) (StateQueryIteratorInterface, error) + + // GetHistoryForKey returns a history of key values across time. + // For each historic key update, the historic value and associated + // transaction id and timestamp are returned. The timestamp is the + // timestamp provided by the client in the proposal header. + // GetHistoryForKey requires peer configuration + // core.ledger.history.enableHistoryDatabase to be true. + // The query is NOT re-executed during validation phase, phantom reads are + // not detected. That is, other committed transactions may have updated + // the key concurrently, impacting the result set, and this would not be + // detected at validation/commit time. Applications susceptible to this + // should therefore not use GetHistoryForKey as part of transactions that + // update ledger, and should limit use to read-only chaincode operations. + GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) + + // GetPrivateData returns the value of the specified `key` from the specified + // `collection`. Note that GetPrivateData doesn't read data from the + // private writeset, which has not been committed to the `collection`. In + // other words, GetPrivateData doesn't consider data modified by PutPrivateData + // that has not been committed. + GetPrivateData(collection, key string) ([]byte, error) + + // PutPrivateData puts the specified `key` and `value` into the transaction's + // private writeset. Note that only hash of the private writeset goes into the + // transaction proposal response (which is sent to the client who issued the + // transaction) and the actual private writeset gets temporarily stored in a + // transient store. PutPrivateData doesn't effect the `collection` until the + // transaction is validated and successfully committed. Simple keys must not be + // an empty string and must not start with null character (0x00), in order to + // avoid range query collisions with composite keys, which internally get + // prefixed with 0x00 as composite key namespace. + PutPrivateData(collection string, key string, value []byte) error + + // DelState records the specified `key` to be deleted in the private writeset of + // the transaction. Note that only hash of the private writeset goes into the + // transaction proposal response (which is sent to the client who issued the + // transaction) and the actual private writeset gets temporarily stored in a + // transient store. The `key` and its value will be deleted from the collection + // when the transaction is validated and successfully committed. + DelPrivateData(collection, key string) error + + // GetPrivateDataByRange returns a range iterator over a set of keys in a + // given private collection. The iterator can be used to iterate over all keys + // between the startKey (inclusive) and endKey (exclusive). + // The keys are returned by the iterator in lexical order. Note + // that startKey and endKey can be empty string, which implies unbounded range + // query on start or end. + // Call Close() on the returned StateQueryIteratorInterface object when done. + // The query is re-executed during validation phase to ensure result set + // has not changed since transaction endorsement (phantom reads detected). + GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error) + + // GetPrivateDataByPartialCompositeKey queries the state in a given private + // collection based on a given partial composite key. This function returns + // an iterator which can be used to iterate over all composite keys whose prefix + // matches the given partial composite key. The `objectType` and attributes are + // expected to have only valid utf8 strings and should not contain + // U+0000 (nil byte) and U+10FFFF (biggest and unallocated code point). + // See related functions SplitCompositeKey and CreateCompositeKey. + // Call Close() on the returned StateQueryIteratorInterface object when done. + // The query is re-executed during validation phase to ensure result set + // has not changed since transaction endorsement (phantom reads detected). + GetPrivateDataByPartialCompositeKey(collection, objectType string, keys []string) (StateQueryIteratorInterface, error) + + // GetPrivateDataQueryResult performs a "rich" query against a given private + // collection. It is only supported for state databases that support rich query, + // e.g.CouchDB. The query string is in the native syntax + // of the underlying state database. An iterator is returned + // which can be used to iterate (next) over the query result set. + // The query is NOT re-executed during validation phase, phantom reads are + // not detected. That is, other committed transactions may have added, + // updated, or removed keys that impact the result set, and this would not + // be detected at validation/commit time. Applications susceptible to this + // should therefore not use GetQueryResult as part of transactions that update + // ledger, and should limit use to read-only chaincode operations. + GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error) + + // GetCreator returns `SignatureHeader.Creator` (e.g. an identity) + // of the `SignedProposal`. This is the identity of the agent (or user) + // submitting the transaction. + GetCreator() ([]byte, error) + + // GetTransient returns the `ChaincodeProposalPayload.Transient` field. + // It is a map that contains data (e.g. cryptographic material) + // that might be used to implement some form of application-level + // confidentiality. The contents of this field, as prescribed by + // `ChaincodeProposalPayload`, are supposed to always + // be omitted from the transaction and excluded from the ledger. + GetTransient() (map[string][]byte, error) + + // GetBinding returns the transaction binding + GetBinding() ([]byte, error) + + // GetDecorations returns additional data (if applicable) + // about the proposal that originated from the peer + GetDecorations() map[string][]byte + + // GetSignedProposal returns the SignedProposal object, which contains all + // data elements part of a transaction proposal. + GetSignedProposal() (*pb.SignedProposal, error) + + // GetTxTimestamp returns the timestamp when the transaction was created. This + // is taken from the transaction ChannelHeader, therefore it will indicate the + // client's timestamp, and will have the same value across all endorsers. + GetTxTimestamp() (*timestamp.Timestamp, error) + + // SetEvent allows the chaincode to propose an event on the transaction + // proposal. If the transaction is validated and successfully committed, + // the event will be delivered to the current event listeners. + SetEvent(name string, payload []byte) error +} + +// CommonIteratorInterface allows a chaincode to check whether any more result +// to be fetched from an iterator and close it when done. +type CommonIteratorInterface interface { + // HasNext returns true if the range query iterator contains additional keys + // and values. + HasNext() bool + + // Close closes the iterator. This should be called when done + // reading from the iterator to free up resources. + Close() error +} + +// StateQueryIteratorInterface allows a chaincode to iterate over a set of +// key/value pairs returned by range and execute query. +type StateQueryIteratorInterface interface { + // Inherit HasNext() and Close() + CommonIteratorInterface + + // Next returns the next key and value in the range and execute query iterator. + Next() (*queryresult.KV, error) +} + +// HistoryQueryIteratorInterface allows a chaincode to iterate over a set of +// key/value pairs returned by a history query. +type HistoryQueryIteratorInterface interface { + // Inherit HasNext() and Close() + CommonIteratorInterface + + // Next returns the next key and value in the history query iterator. + Next() (*queryresult.KeyModification, error) +} + +// MockQueryIteratorInterface allows a chaincode to iterate over a set of +// key/value pairs returned by range query. +// TODO: Once the execute query and history query are implemented in MockStub, +// we need to update this interface +type MockQueryIteratorInterface interface { + StateQueryIteratorInterface +} diff --git a/core/chaincode/shim/interfaces.go b/core/chaincode/shim/interfaces_stable.go similarity index 95% rename from core/chaincode/shim/interfaces.go rename to core/chaincode/shim/interfaces_stable.go index 35033867801..b629fa3b82e 100644 --- a/core/chaincode/shim/interfaces.go +++ b/core/chaincode/shim/interfaces_stable.go @@ -1,17 +1,9 @@ -/* -Copyright IBM Corp. 2016 All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +// +build !experimental - http://www.apache.org/licenses/LICENSE-2.0 +/* +Copyright IBM Corp. All Rights Reserved. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +SPDX-License-Identifier: Apache-2.0 */ package shim diff --git a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/impl/Handler.java b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/impl/Handler.java index 2b796b01f62..48914e88532 100644 --- a/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/impl/Handler.java +++ b/core/chaincode/shim/java/src/main/java/org/hyperledger/fabric/shim/impl/Handler.java @@ -1,17 +1,7 @@ /* -Copyright DTCC, IBM 2016, 2017 All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.fabric.shim.impl; @@ -50,9 +40,11 @@ import org.hyperledger.fabric.protos.peer.ChaincodeEventPackage.ChaincodeEvent; import org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage; import org.hyperledger.fabric.protos.peer.ChaincodeShim.ChaincodeMessage.Type; +import org.hyperledger.fabric.protos.peer.ChaincodeShim.DelState; import org.hyperledger.fabric.protos.peer.ChaincodeShim.GetQueryResult; import org.hyperledger.fabric.protos.peer.ChaincodeShim.GetStateByRange; -import org.hyperledger.fabric.protos.peer.ChaincodeShim.PutStateInfo; +import org.hyperledger.fabric.protos.peer.ChaincodeShim.GetState; +import org.hyperledger.fabric.protos.peer.ChaincodeShim.PutState; import org.hyperledger.fabric.protos.peer.ChaincodeShim.QueryResponse; import org.hyperledger.fabric.protos.peer.ChaincodeShim.QueryStateClose; import org.hyperledger.fabric.protos.peer.ChaincodeShim.QueryStateNext; @@ -178,7 +170,7 @@ private synchronized void releaseResponseChannelForTx(String txId) { /** * Marks a UUID as either a transaction or a query - * + * * @param uuid * ID to be marked * @param isTransaction @@ -205,7 +197,7 @@ private void beforeRegistered(Event event) { /** * Handles requests to initialize chaincode - * + * * @param message * chaincode to be initialized */ @@ -333,12 +325,12 @@ private void afterError(Event event) { * TODO- revisit. This may no longer be needed with the * serialized/streamlined messaging model There are two situations in * which the ERROR event can be triggered: - * + * * 1. When an error is encountered within handleInit or * handleTransaction - some issue at the chaincode side; In this case * there will be no responseChannel and the message has been sent to the * validator. - * + * * 2. The chaincode has initiated a request (get/put/del state) to the * validator and is expecting a response on the responseChannel; If * ERROR is received from validator, this needs to be notified on the @@ -479,7 +471,7 @@ Chaincode.Response invokeChaincode(String txId, String chaincodeName, List 3 { + stime, _ = strconv.Atoi(args[3]) + } + + keysIter, err := stub.GetPrivateDataByRange(collection, startKey, endKey) + if err != nil { + return shim.Error(fmt.Sprintf("keys operation failed on private data. Error accessing state: %s", err)) + } + defer keysIter.Close() + + var keys []string + for keysIter.HasNext() { + //if sleeptime is specied, take a nap + if stime > 0 { + time.Sleep(time.Duration(stime) * time.Millisecond) + } + + response, iterErr := keysIter.Next() + if iterErr != nil { + return shim.Error(fmt.Sprintf("keys operation on private data failed. Error accessing state: %s", err)) + } + keys = append(keys, response.Key) + } + + for key, value := range keys { + fmt.Printf("key %d contains %s\n", key, value) + } + + jsonKeys, err := json.Marshal(keys) + if err != nil { + return shim.Error(fmt.Sprintf("keys operation on private data failed. Error marshaling JSON: %s", err)) + } + + return shim.Success(jsonKeys) + + case "queryPrivate": + collection := args[0] + query := args[1] + keysIter, err := stub.GetPrivateDataQueryResult(collection, query) + if err != nil { + return shim.Error(fmt.Sprintf("query operation on private data failed. Error accessing state: %s", err)) + } + defer keysIter.Close() + + var keys []string + for keysIter.HasNext() { + response, iterErr := keysIter.Next() + if iterErr != nil { + return shim.Error(fmt.Sprintf("query operation on private data failed. Error accessing state: %s", err)) + } + keys = append(keys, response.Key) + } + + jsonKeys, err := json.Marshal(keys) + if err != nil { + return shim.Error(fmt.Sprintf("query operation on private data failed. Error marshaling JSON: %s", err)) + } + + return shim.Success(jsonKeys) + + case "put": + if len(args) < 2 { + return shim.Error("put operation must include two arguments: [key, value]") + } + key := args[0] + value := args[1] + + if err := stub.PutState(key, []byte(value)); err != nil { + fmt.Printf("Error putting state %s", err) + return shim.Error(fmt.Sprintf("put operation failed. Error updating state: %s", err)) + } + + indexName := "compositeKeyTest" + compositeKeyTestIndex, err := stub.CreateCompositeKey(indexName, []string{key}) + if err != nil { + return shim.Error(err.Error()) + } + + valueByte := []byte{0x00} + if err := stub.PutState(compositeKeyTestIndex, valueByte); err != nil { + fmt.Printf("Error putting state with compositeKey %s", err) + return shim.Error(fmt.Sprintf("put operation failed. Error updating state with compositeKey: %s", err)) + } + + return shim.Success(nil) + + case "remove": + if len(args) < 1 { + return shim.Error("remove operation must include one argument: [key]") + } + key := args[0] + + err := stub.DelState(key) + if err != nil { + return shim.Error(fmt.Sprintf("remove operation failed. Error updating state: %s", err)) + } + return shim.Success(nil) + + case "get": + if len(args) < 1 { + return shim.Error("get operation must include one argument, a key") + } + key := args[0] + value, err := stub.GetState(key) + if err != nil { + return shim.Error(fmt.Sprintf("get operation failed. Error accessing state: %s", err)) + } + jsonVal, err := json.Marshal(string(value)) + return shim.Success(jsonVal) + + case "keys": + if len(args) < 2 { + return shim.Error("put operation must include two arguments, a key and value") + } + startKey := args[0] + endKey := args[1] + + //sleep needed to test peer's timeout behavior when using iterators + stime := 0 + if len(args) > 2 { + stime, _ = strconv.Atoi(args[2]) + } + + keysIter, err := stub.GetStateByRange(startKey, endKey) + if err != nil { + return shim.Error(fmt.Sprintf("keys operation failed. Error accessing state: %s", err)) + } + defer keysIter.Close() + + var keys []string + for keysIter.HasNext() { + //if sleeptime is specied, take a nap + if stime > 0 { + time.Sleep(time.Duration(stime) * time.Millisecond) + } + + response, iterErr := keysIter.Next() + if iterErr != nil { + return shim.Error(fmt.Sprintf("keys operation failed. Error accessing state: %s", err)) + } + keys = append(keys, response.Key) + } + + for key, value := range keys { + fmt.Printf("key %d contains %s\n", key, value) + } + + jsonKeys, err := json.Marshal(keys) + if err != nil { + return shim.Error(fmt.Sprintf("keys operation failed. Error marshaling JSON: %s", err)) + } + + return shim.Success(jsonKeys) + case "query": + query := args[0] + keysIter, err := stub.GetQueryResult(query) + if err != nil { + return shim.Error(fmt.Sprintf("query operation failed. Error accessing state: %s", err)) + } + defer keysIter.Close() + + var keys []string + for keysIter.HasNext() { + response, iterErr := keysIter.Next() + if iterErr != nil { + return shim.Error(fmt.Sprintf("query operation failed. Error accessing state: %s", err)) + } + keys = append(keys, response.Key) + } + + jsonKeys, err := json.Marshal(keys) + if err != nil { + return shim.Error(fmt.Sprintf("query operation failed. Error marshaling JSON: %s", err)) + } + + return shim.Success(jsonKeys) + case "history": + key := args[0] + keysIter, err := stub.GetHistoryForKey(key) + if err != nil { + return shim.Error(fmt.Sprintf("query operation failed. Error accessing state: %s", err)) + } + defer keysIter.Close() + + var keys []string + for keysIter.HasNext() { + response, iterErr := keysIter.Next() + if iterErr != nil { + return shim.Error(fmt.Sprintf("query operation failed. Error accessing state: %s", err)) + } + keys = append(keys, response.TxId) + } + + for key, txID := range keys { + fmt.Printf("key %d contains %s\n", key, txID) + } + + jsonKeys, err := json.Marshal(keys) + if err != nil { + return shim.Error(fmt.Sprintf("query operation failed. Error marshaling JSON: %s", err)) + } + + return shim.Success(jsonKeys) + + default: + return shim.Success([]byte("Unsupported operation")) + } +} + +func main() { + err := shim.Start(new(SimpleChaincode)) + if err != nil { + fmt.Printf("Error starting chaincode: %s", err) + } +} diff --git a/protos/peer/admin.pb.go b/protos/peer/admin.pb.go index 5bd7ad9a21b..964603d3858 100644 --- a/protos/peer/admin.pb.go +++ b/protos/peer/admin.pb.go @@ -30,7 +30,9 @@ It has these top-level messages: ChaincodeInvocationSpec ChaincodeEvent ChaincodeMessage - PutStateInfo + GetState + PutState + DelState GetStateByRange GetQueryResult GetHistoryForKey diff --git a/protos/peer/chaincode_shim.pb.go b/protos/peer/chaincode_shim.pb.go index ffcdd3332e8..32403aa21ab 100644 --- a/protos/peer/chaincode_shim.pb.go +++ b/protos/peer/chaincode_shim.pb.go @@ -96,7 +96,7 @@ type ChaincodeMessage struct { Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` Txid string `protobuf:"bytes,4,opt,name=txid" json:"txid,omitempty"` Proposal *SignedProposal `protobuf:"bytes,5,opt,name=proposal" json:"proposal,omitempty"` - // event emmited by chaincode. Used only with Init or Invoke. + // event emitted by chaincode. Used only with Init or Invoke. // This event is then stored (currently) // with Block.NonHashData.TransactionResult ChaincodeEvent *ChaincodeEvent `protobuf:"bytes,6,opt,name=chaincode_event,json=chaincodeEvent" json:"chaincode_event,omitempty"` @@ -149,39 +149,96 @@ func (m *ChaincodeMessage) GetChaincodeEvent() *ChaincodeEvent { return nil } -type PutStateInfo struct { - Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` - Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +type GetState struct { + Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + Collection string `protobuf:"bytes,2,opt,name=collection" json:"collection,omitempty"` } -func (m *PutStateInfo) Reset() { *m = PutStateInfo{} } -func (m *PutStateInfo) String() string { return proto.CompactTextString(m) } -func (*PutStateInfo) ProtoMessage() {} -func (*PutStateInfo) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} } +func (m *GetState) Reset() { *m = GetState{} } +func (m *GetState) String() string { return proto.CompactTextString(m) } +func (*GetState) ProtoMessage() {} +func (*GetState) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} } -func (m *PutStateInfo) GetKey() string { +func (m *GetState) GetKey() string { if m != nil { return m.Key } return "" } -func (m *PutStateInfo) GetValue() []byte { +func (m *GetState) GetCollection() string { + if m != nil { + return m.Collection + } + return "" +} + +type PutState struct { + Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + Collection string `protobuf:"bytes,3,opt,name=collection" json:"collection,omitempty"` +} + +func (m *PutState) Reset() { *m = PutState{} } +func (m *PutState) String() string { return proto.CompactTextString(m) } +func (*PutState) ProtoMessage() {} +func (*PutState) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{2} } + +func (m *PutState) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +func (m *PutState) GetValue() []byte { if m != nil { return m.Value } return nil } +func (m *PutState) GetCollection() string { + if m != nil { + return m.Collection + } + return "" +} + +type DelState struct { + Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + Collection string `protobuf:"bytes,2,opt,name=collection" json:"collection,omitempty"` +} + +func (m *DelState) Reset() { *m = DelState{} } +func (m *DelState) String() string { return proto.CompactTextString(m) } +func (*DelState) ProtoMessage() {} +func (*DelState) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{3} } + +func (m *DelState) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +func (m *DelState) GetCollection() string { + if m != nil { + return m.Collection + } + return "" +} + type GetStateByRange struct { - StartKey string `protobuf:"bytes,1,opt,name=startKey" json:"startKey,omitempty"` - EndKey string `protobuf:"bytes,2,opt,name=endKey" json:"endKey,omitempty"` + StartKey string `protobuf:"bytes,1,opt,name=startKey" json:"startKey,omitempty"` + EndKey string `protobuf:"bytes,2,opt,name=endKey" json:"endKey,omitempty"` + Collection string `protobuf:"bytes,3,opt,name=collection" json:"collection,omitempty"` } func (m *GetStateByRange) Reset() { *m = GetStateByRange{} } func (m *GetStateByRange) String() string { return proto.CompactTextString(m) } func (*GetStateByRange) ProtoMessage() {} -func (*GetStateByRange) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{2} } +func (*GetStateByRange) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{4} } func (m *GetStateByRange) GetStartKey() string { if m != nil { @@ -197,14 +254,22 @@ func (m *GetStateByRange) GetEndKey() string { return "" } +func (m *GetStateByRange) GetCollection() string { + if m != nil { + return m.Collection + } + return "" +} + type GetQueryResult struct { - Query string `protobuf:"bytes,1,opt,name=query" json:"query,omitempty"` + Query string `protobuf:"bytes,1,opt,name=query" json:"query,omitempty"` + Collection string `protobuf:"bytes,2,opt,name=collection" json:"collection,omitempty"` } func (m *GetQueryResult) Reset() { *m = GetQueryResult{} } func (m *GetQueryResult) String() string { return proto.CompactTextString(m) } func (*GetQueryResult) ProtoMessage() {} -func (*GetQueryResult) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{3} } +func (*GetQueryResult) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{5} } func (m *GetQueryResult) GetQuery() string { if m != nil { @@ -213,6 +278,13 @@ func (m *GetQueryResult) GetQuery() string { return "" } +func (m *GetQueryResult) GetCollection() string { + if m != nil { + return m.Collection + } + return "" +} + type GetHistoryForKey struct { Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` } @@ -220,7 +292,7 @@ type GetHistoryForKey struct { func (m *GetHistoryForKey) Reset() { *m = GetHistoryForKey{} } func (m *GetHistoryForKey) String() string { return proto.CompactTextString(m) } func (*GetHistoryForKey) ProtoMessage() {} -func (*GetHistoryForKey) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{4} } +func (*GetHistoryForKey) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{6} } func (m *GetHistoryForKey) GetKey() string { if m != nil { @@ -236,7 +308,7 @@ type QueryStateNext struct { func (m *QueryStateNext) Reset() { *m = QueryStateNext{} } func (m *QueryStateNext) String() string { return proto.CompactTextString(m) } func (*QueryStateNext) ProtoMessage() {} -func (*QueryStateNext) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{5} } +func (*QueryStateNext) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{7} } func (m *QueryStateNext) GetId() string { if m != nil { @@ -252,7 +324,7 @@ type QueryStateClose struct { func (m *QueryStateClose) Reset() { *m = QueryStateClose{} } func (m *QueryStateClose) String() string { return proto.CompactTextString(m) } func (*QueryStateClose) ProtoMessage() {} -func (*QueryStateClose) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{6} } +func (*QueryStateClose) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{8} } func (m *QueryStateClose) GetId() string { if m != nil { @@ -268,7 +340,7 @@ type QueryResultBytes struct { func (m *QueryResultBytes) Reset() { *m = QueryResultBytes{} } func (m *QueryResultBytes) String() string { return proto.CompactTextString(m) } func (*QueryResultBytes) ProtoMessage() {} -func (*QueryResultBytes) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{7} } +func (*QueryResultBytes) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{9} } func (m *QueryResultBytes) GetResultBytes() []byte { if m != nil { @@ -286,7 +358,7 @@ type QueryResponse struct { func (m *QueryResponse) Reset() { *m = QueryResponse{} } func (m *QueryResponse) String() string { return proto.CompactTextString(m) } func (*QueryResponse) ProtoMessage() {} -func (*QueryResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{8} } +func (*QueryResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{10} } func (m *QueryResponse) GetResults() []*QueryResultBytes { if m != nil { @@ -311,7 +383,9 @@ func (m *QueryResponse) GetId() string { func init() { proto.RegisterType((*ChaincodeMessage)(nil), "protos.ChaincodeMessage") - proto.RegisterType((*PutStateInfo)(nil), "protos.PutStateInfo") + proto.RegisterType((*GetState)(nil), "protos.GetState") + proto.RegisterType((*PutState)(nil), "protos.PutState") + proto.RegisterType((*DelState)(nil), "protos.DelState") proto.RegisterType((*GetStateByRange)(nil), "protos.GetStateByRange") proto.RegisterType((*GetQueryResult)(nil), "protos.GetQueryResult") proto.RegisterType((*GetHistoryForKey)(nil), "protos.GetHistoryForKey") @@ -429,54 +503,56 @@ var _ChaincodeSupport_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("peer/chaincode_shim.proto", fileDescriptor3) } var fileDescriptor3 = []byte{ - // 771 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x94, 0x6f, 0x6f, 0xe2, 0x46, - 0x10, 0xc6, 0x8f, 0x3f, 0x49, 0x60, 0x20, 0xb0, 0xb7, 0xb9, 0xa6, 0x3e, 0xa4, 0xaa, 0xd4, 0xaa, - 0x2a, 0xfa, 0xc6, 0xb4, 0xb4, 0xaa, 0xfa, 0xae, 0x22, 0xb0, 0x21, 0x56, 0x88, 0xcd, 0xad, 0x9d, - 0xd3, 0xd1, 0x37, 0x96, 0x03, 0x1b, 0x63, 0x15, 0x58, 0x77, 0x77, 0x39, 0x9d, 0x3f, 0x61, 0xbf, - 0x4c, 0x3f, 0xc4, 0x69, 0x6d, 0x4c, 0xb8, 0x9c, 0xf2, 0xca, 0x7e, 0x66, 0x7e, 0xf3, 0xcc, 0xec, - 0x6a, 0x35, 0xf0, 0x36, 0x61, 0x4c, 0xf4, 0x17, 0xab, 0x30, 0xde, 0x2e, 0xf8, 0x92, 0x05, 0x72, - 0x15, 0x6f, 0xac, 0x44, 0x70, 0xc5, 0xf1, 0x69, 0xf6, 0x91, 0x9d, 0xce, 0x33, 0x84, 0x7d, 0x64, - 0x5b, 0x95, 0x33, 0x9d, 0x8b, 0x2c, 0x97, 0x08, 0x9e, 0x70, 0x19, 0xae, 0xf7, 0xc1, 0xef, 0x23, - 0xce, 0xa3, 0x35, 0xeb, 0x67, 0xea, 0x61, 0xf7, 0xd8, 0x57, 0xf1, 0x86, 0x49, 0x15, 0x6e, 0x92, - 0x1c, 0x30, 0xff, 0xaf, 0x02, 0x1a, 0x15, 0x7e, 0x77, 0x4c, 0xca, 0x30, 0x62, 0xf8, 0x57, 0xa8, - 0xaa, 0x34, 0x61, 0x46, 0xa9, 0x5b, 0xea, 0xb5, 0x06, 0xdf, 0xe5, 0xa8, 0xb4, 0x9e, 0x73, 0x96, - 0x9f, 0x26, 0x8c, 0x66, 0x28, 0xfe, 0x13, 0xea, 0x07, 0x6b, 0xa3, 0xdc, 0x2d, 0xf5, 0x1a, 0x83, - 0x8e, 0x95, 0x37, 0xb7, 0x8a, 0xe6, 0x96, 0x5f, 0x10, 0xf4, 0x09, 0xc6, 0x06, 0x9c, 0x25, 0x61, - 0xba, 0xe6, 0xe1, 0xd2, 0xa8, 0x74, 0x4b, 0xbd, 0x26, 0x2d, 0x24, 0xc6, 0x50, 0x55, 0x9f, 0xe2, - 0xa5, 0x51, 0xed, 0x96, 0x7a, 0x75, 0x9a, 0xfd, 0xe3, 0x01, 0xd4, 0x8a, 0x23, 0x1a, 0x27, 0x59, - 0x9b, 0xcb, 0x62, 0x3c, 0x2f, 0x8e, 0xb6, 0x6c, 0x39, 0xdb, 0x67, 0xe9, 0x81, 0xc3, 0x7f, 0x41, - 0xfb, 0xd9, 0x95, 0x19, 0xa7, 0x5f, 0x96, 0x1e, 0x4e, 0x46, 0x74, 0x96, 0xb6, 0x16, 0x5f, 0x68, - 0xf3, 0xbf, 0x32, 0x54, 0xf5, 0x59, 0xf1, 0x39, 0xd4, 0xef, 0x9d, 0x31, 0xb9, 0xb6, 0x1d, 0x32, - 0x46, 0xaf, 0x70, 0x13, 0x6a, 0x94, 0x4c, 0x6c, 0xcf, 0x27, 0x14, 0x95, 0x70, 0x0b, 0xa0, 0x50, - 0x64, 0x8c, 0xca, 0xb8, 0x06, 0x55, 0xdb, 0xb1, 0x7d, 0x54, 0xc1, 0x75, 0x38, 0xa1, 0x64, 0x38, - 0x9e, 0xa3, 0x2a, 0x6e, 0x43, 0xc3, 0xa7, 0x43, 0xc7, 0x1b, 0x8e, 0x7c, 0xdb, 0x75, 0xd0, 0x89, - 0xb6, 0x1c, 0xb9, 0x77, 0xb3, 0x29, 0xf1, 0xc9, 0x18, 0x9d, 0x6a, 0x94, 0x50, 0xea, 0x52, 0x74, - 0xa6, 0x33, 0x13, 0xe2, 0x07, 0x9e, 0x3f, 0xf4, 0x09, 0xaa, 0x69, 0x39, 0xbb, 0x2f, 0x64, 0x5d, - 0xcb, 0x31, 0x99, 0xee, 0x25, 0xe0, 0x37, 0x80, 0x6c, 0xe7, 0xbd, 0x7b, 0x4b, 0x82, 0xd1, 0xcd, - 0xd0, 0x76, 0x46, 0xee, 0x98, 0xa0, 0x46, 0x3e, 0xa0, 0x37, 0x73, 0x1d, 0x8f, 0xa0, 0x73, 0x7c, - 0x09, 0xf8, 0x60, 0x18, 0x5c, 0xcd, 0x03, 0x3a, 0x74, 0x26, 0x04, 0xb5, 0x74, 0xad, 0x8e, 0xbf, - 0xbb, 0x27, 0x74, 0x1e, 0x50, 0xe2, 0xdd, 0x4f, 0x7d, 0xd4, 0xd6, 0xd1, 0x3c, 0x92, 0xf3, 0x0e, - 0xf9, 0xe0, 0x23, 0x84, 0xbf, 0x81, 0xd7, 0xc7, 0xd1, 0xd1, 0xd4, 0xf5, 0x08, 0x7a, 0xad, 0xa7, - 0xb9, 0x25, 0x64, 0x36, 0x9c, 0xda, 0xef, 0x09, 0xc2, 0xf8, 0x5b, 0xb8, 0xd0, 0x8e, 0x37, 0xb6, - 0xe7, 0xbb, 0x74, 0x1e, 0x5c, 0xbb, 0x34, 0xb8, 0x25, 0x73, 0x74, 0x61, 0xfe, 0x01, 0xcd, 0xd9, - 0x4e, 0x79, 0x2a, 0x54, 0xcc, 0xde, 0x3e, 0x72, 0x8c, 0xa0, 0xf2, 0x0f, 0x4b, 0xb3, 0x87, 0x56, - 0xa7, 0xfa, 0x17, 0xbf, 0x81, 0x93, 0x8f, 0xe1, 0x7a, 0xc7, 0xb2, 0x47, 0xd4, 0xa4, 0xb9, 0x30, - 0x09, 0xb4, 0x27, 0x2c, 0xaf, 0xbb, 0x4a, 0x69, 0xb8, 0x8d, 0x18, 0xee, 0x40, 0x4d, 0xaa, 0x50, - 0xa8, 0xdb, 0x43, 0xfd, 0x41, 0xe3, 0x4b, 0x38, 0x65, 0xdb, 0xa5, 0xce, 0x94, 0xb3, 0xcc, 0x5e, - 0x99, 0x3f, 0x41, 0x6b, 0xc2, 0xd4, 0xbb, 0x1d, 0x13, 0x29, 0x65, 0x72, 0xb7, 0x56, 0xba, 0xdd, - 0xbf, 0x5a, 0xee, 0x2d, 0x72, 0x61, 0xfe, 0x08, 0x68, 0xc2, 0xd4, 0x4d, 0x2c, 0x15, 0x17, 0xe9, - 0x35, 0x17, 0xda, 0xf3, 0xab, 0x51, 0xcd, 0x2e, 0xb4, 0x32, 0xab, 0x6c, 0x2c, 0x87, 0x7d, 0x52, - 0xb8, 0x05, 0xe5, 0x78, 0xb9, 0x47, 0xca, 0xf1, 0xd2, 0xfc, 0x01, 0xda, 0x4f, 0xc4, 0x68, 0xcd, - 0x25, 0xfb, 0x0a, 0xf9, 0x1d, 0xd0, 0xd1, 0x3c, 0x57, 0xa9, 0x62, 0x12, 0x77, 0xa1, 0x21, 0x9e, - 0x64, 0x06, 0x37, 0xe9, 0x71, 0xc8, 0xdc, 0xc2, 0x79, 0x51, 0x95, 0xf0, 0xad, 0x64, 0x78, 0x00, - 0x67, 0x79, 0x5e, 0xe3, 0x95, 0x5e, 0x63, 0x60, 0x14, 0x6f, 0xfb, 0xb9, 0x3b, 0x2d, 0x40, 0xfc, - 0x16, 0x6a, 0xab, 0x50, 0x06, 0x1b, 0x2e, 0xf2, 0xdb, 0xae, 0xd1, 0xb3, 0x55, 0x28, 0xef, 0xb8, - 0x28, 0xa6, 0xac, 0x14, 0x53, 0x0e, 0x3e, 0x1c, 0x6d, 0x09, 0x6f, 0x97, 0x24, 0x5c, 0x28, 0x3c, - 0x86, 0x1a, 0x65, 0x51, 0x2c, 0x15, 0x13, 0xd8, 0x78, 0x69, 0x47, 0x74, 0x5e, 0xcc, 0x98, 0xaf, - 0x7a, 0xa5, 0x5f, 0x4a, 0x57, 0x2e, 0x98, 0x5c, 0x44, 0xd6, 0x2a, 0x4d, 0x98, 0x58, 0xb3, 0x65, - 0xc4, 0x84, 0xf5, 0x18, 0x3e, 0x88, 0x78, 0x51, 0xd4, 0xe9, 0xb5, 0xf6, 0xf7, 0xcf, 0x51, 0xac, - 0x56, 0xbb, 0x07, 0x6b, 0xc1, 0x37, 0xfd, 0x23, 0xb4, 0x9f, 0xa3, 0xf9, 0x7a, 0x93, 0x7d, 0x8d, - 0x3e, 0xe4, 0xbb, 0xf2, 0xb7, 0xcf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xad, 0x9f, 0x80, 0x68, 0x4f, - 0x05, 0x00, 0x00, + // 807 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x95, 0xdf, 0x6e, 0xe2, 0x46, + 0x14, 0xc6, 0x97, 0x3f, 0x49, 0xcc, 0x21, 0x81, 0xd9, 0xc9, 0x36, 0xf5, 0x22, 0xb5, 0xa5, 0x56, + 0x2f, 0xe8, 0x8d, 0x69, 0x69, 0x2f, 0x7a, 0xb1, 0x52, 0x45, 0xf0, 0x84, 0x58, 0x21, 0x36, 0x3b, + 0x76, 0x56, 0x4b, 0x6f, 0x2c, 0x03, 0xb3, 0xc6, 0xaa, 0x61, 0x5c, 0x7b, 0x58, 0xad, 0x9f, 0xb0, + 0x2f, 0xd3, 0x87, 0xa8, 0xc6, 0xc6, 0x84, 0x25, 0x8a, 0x56, 0xda, 0x2b, 0xe6, 0x3b, 0xe7, 0x77, + 0xbe, 0x73, 0x0e, 0x1a, 0x06, 0x78, 0x1d, 0x33, 0x96, 0xf4, 0x17, 0x2b, 0x3f, 0xdc, 0x2c, 0xf8, + 0x92, 0x79, 0xe9, 0x2a, 0x5c, 0xeb, 0x71, 0xc2, 0x05, 0xc7, 0xa7, 0xf9, 0x47, 0xda, 0xe9, 0x1c, + 0x21, 0xec, 0x23, 0xdb, 0x88, 0x82, 0xe9, 0x5c, 0xe6, 0xb9, 0x38, 0xe1, 0x31, 0x4f, 0xfd, 0x68, + 0x17, 0xfc, 0x21, 0xe0, 0x3c, 0x88, 0x58, 0x3f, 0x57, 0xf3, 0xed, 0x87, 0xbe, 0x08, 0xd7, 0x2c, + 0x15, 0xfe, 0x3a, 0x2e, 0x00, 0xed, 0xbf, 0x3a, 0xa0, 0x51, 0xe9, 0x77, 0xcf, 0xd2, 0xd4, 0x0f, + 0x18, 0xfe, 0x15, 0xea, 0x22, 0x8b, 0x99, 0x5a, 0xe9, 0x56, 0x7a, 0xad, 0xc1, 0x77, 0x05, 0x9a, + 0xea, 0xc7, 0x9c, 0xee, 0x66, 0x31, 0xa3, 0x39, 0x8a, 0xff, 0x80, 0xc6, 0xde, 0x5a, 0xad, 0x76, + 0x2b, 0xbd, 0xe6, 0xa0, 0xa3, 0x17, 0xcd, 0xf5, 0xb2, 0xb9, 0xee, 0x96, 0x04, 0x7d, 0x84, 0xb1, + 0x0a, 0x67, 0xb1, 0x9f, 0x45, 0xdc, 0x5f, 0xaa, 0xb5, 0x6e, 0xa5, 0x77, 0x4e, 0x4b, 0x89, 0x31, + 0xd4, 0xc5, 0xa7, 0x70, 0xa9, 0xd6, 0xbb, 0x95, 0x5e, 0x83, 0xe6, 0x67, 0x3c, 0x00, 0xa5, 0x5c, + 0x51, 0x3d, 0xc9, 0xdb, 0x5c, 0x95, 0xe3, 0x39, 0x61, 0xb0, 0x61, 0xcb, 0xe9, 0x2e, 0x4b, 0xf7, + 0x1c, 0xfe, 0x13, 0xda, 0x47, 0x5f, 0x99, 0x7a, 0xfa, 0x79, 0xe9, 0x7e, 0x33, 0x22, 0xb3, 0xb4, + 0xb5, 0xf8, 0x4c, 0x6b, 0xff, 0x56, 0xa1, 0x2e, 0x77, 0xc5, 0x17, 0xd0, 0x78, 0xb0, 0x0c, 0x72, + 0x63, 0x5a, 0xc4, 0x40, 0x2f, 0xf0, 0x39, 0x28, 0x94, 0x8c, 0x4d, 0xc7, 0x25, 0x14, 0x55, 0x70, + 0x0b, 0xa0, 0x54, 0xc4, 0x40, 0x55, 0xac, 0x40, 0xdd, 0xb4, 0x4c, 0x17, 0xd5, 0x70, 0x03, 0x4e, + 0x28, 0x19, 0x1a, 0x33, 0x54, 0xc7, 0x6d, 0x68, 0xba, 0x74, 0x68, 0x39, 0xc3, 0x91, 0x6b, 0xda, + 0x16, 0x3a, 0x91, 0x96, 0x23, 0xfb, 0x7e, 0x3a, 0x21, 0x2e, 0x31, 0xd0, 0xa9, 0x44, 0x09, 0xa5, + 0x36, 0x45, 0x67, 0x32, 0x33, 0x26, 0xae, 0xe7, 0xb8, 0x43, 0x97, 0x20, 0x45, 0xca, 0xe9, 0x43, + 0x29, 0x1b, 0x52, 0x1a, 0x64, 0xb2, 0x93, 0x80, 0x5f, 0x01, 0x32, 0xad, 0x77, 0xf6, 0x1d, 0xf1, + 0x46, 0xb7, 0x43, 0xd3, 0x1a, 0xd9, 0x06, 0x41, 0xcd, 0x62, 0x40, 0x67, 0x6a, 0x5b, 0x0e, 0x41, + 0x17, 0xf8, 0x0a, 0xf0, 0xde, 0xd0, 0xbb, 0x9e, 0x79, 0x74, 0x68, 0x8d, 0x09, 0x6a, 0xc9, 0x5a, + 0x19, 0x7f, 0xfb, 0x40, 0xe8, 0xcc, 0xa3, 0xc4, 0x79, 0x98, 0xb8, 0xa8, 0x2d, 0xa3, 0x45, 0xa4, + 0xe0, 0x2d, 0xf2, 0xde, 0x45, 0x08, 0x7f, 0x03, 0x2f, 0x0f, 0xa3, 0xa3, 0x89, 0xed, 0x10, 0xf4, + 0x52, 0x4e, 0x73, 0x47, 0xc8, 0x74, 0x38, 0x31, 0xdf, 0x11, 0x84, 0xf1, 0xb7, 0x70, 0x29, 0x1d, + 0x6f, 0x4d, 0xc7, 0xb5, 0xe9, 0xcc, 0xbb, 0xb1, 0xa9, 0x77, 0x47, 0x66, 0xe8, 0x52, 0x7b, 0x03, + 0xca, 0x98, 0x09, 0x47, 0xf8, 0x82, 0x61, 0x04, 0xb5, 0xbf, 0x59, 0x96, 0x5f, 0xb2, 0x06, 0x95, + 0x47, 0xfc, 0x3d, 0xc0, 0x82, 0x47, 0x11, 0x5b, 0x88, 0x90, 0x6f, 0xf2, 0x5b, 0xd4, 0xa0, 0x07, + 0x11, 0x8d, 0x82, 0x32, 0xdd, 0x3e, 0x5b, 0xfd, 0x0a, 0x4e, 0x3e, 0xfa, 0xd1, 0x96, 0xe5, 0x85, + 0xe7, 0xb4, 0x10, 0x47, 0x9e, 0xb5, 0x27, 0x9e, 0x6f, 0x40, 0x31, 0x58, 0xf4, 0xb5, 0x13, 0x31, + 0x68, 0x97, 0xfb, 0x5c, 0x67, 0xd4, 0xdf, 0x04, 0x0c, 0x77, 0x40, 0x49, 0x85, 0x9f, 0x88, 0xbb, + 0xbd, 0xd3, 0x5e, 0xe3, 0x2b, 0x38, 0x65, 0x9b, 0xa5, 0xcc, 0x14, 0x56, 0x3b, 0xf5, 0xc5, 0x21, + 0x6f, 0xa0, 0x35, 0x66, 0xe2, 0xed, 0x96, 0x25, 0x19, 0x65, 0xe9, 0x36, 0x12, 0x72, 0xd9, 0x7f, + 0xa4, 0xdc, 0xb5, 0x28, 0xc4, 0x17, 0xc7, 0xfd, 0x09, 0xd0, 0x98, 0x89, 0xdb, 0x30, 0x15, 0x3c, + 0xc9, 0x6e, 0x78, 0x22, 0x7b, 0x3f, 0x59, 0x5a, 0xeb, 0x42, 0x2b, 0x6f, 0x95, 0xaf, 0x65, 0xb1, + 0x4f, 0x02, 0xb7, 0xa0, 0x1a, 0x2e, 0x77, 0x48, 0x35, 0x5c, 0x6a, 0x3f, 0x42, 0xfb, 0x91, 0x18, + 0x45, 0x3c, 0x65, 0x4f, 0x90, 0xdf, 0x01, 0x1d, 0xcc, 0x7b, 0x9d, 0x09, 0x96, 0xe2, 0x2e, 0x34, + 0x93, 0x47, 0x99, 0xc3, 0xe7, 0xf4, 0x30, 0xa4, 0x6d, 0xe0, 0xa2, 0xac, 0x8a, 0xf9, 0x26, 0x65, + 0x78, 0x00, 0x67, 0x45, 0x5e, 0xe2, 0xb5, 0x5e, 0x73, 0xa0, 0x96, 0xbf, 0xd9, 0x63, 0x77, 0x5a, + 0x82, 0xf8, 0x35, 0x28, 0x2b, 0x3f, 0xf5, 0xd6, 0x3c, 0x29, 0xee, 0x82, 0x42, 0xcf, 0x56, 0x7e, + 0x7a, 0xcf, 0x93, 0x72, 0xca, 0x5a, 0x39, 0xe5, 0xe0, 0xfd, 0xc1, 0xeb, 0xe7, 0x6c, 0xe3, 0x98, + 0x27, 0x02, 0x1b, 0xa0, 0x50, 0x16, 0x84, 0xa9, 0x60, 0x09, 0x56, 0x9f, 0x7b, 0xfb, 0x3a, 0xcf, + 0x66, 0xb4, 0x17, 0xbd, 0xca, 0x2f, 0x95, 0x6b, 0x1b, 0x34, 0x9e, 0x04, 0xfa, 0x2a, 0x8b, 0x59, + 0x12, 0xb1, 0x65, 0xc0, 0x12, 0xfd, 0x83, 0x3f, 0x4f, 0xc2, 0x45, 0x59, 0x27, 0x9f, 0xeb, 0xbf, + 0x7e, 0x0e, 0x42, 0xb1, 0xda, 0xce, 0xf5, 0x05, 0x5f, 0xf7, 0x0f, 0xd0, 0x7e, 0x81, 0x16, 0xcf, + 0x76, 0xda, 0x97, 0xe8, 0xbc, 0xf8, 0x0f, 0xf8, 0xed, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xdc, + 0xba, 0x68, 0xc2, 0x27, 0x06, 0x00, 0x00, } diff --git a/protos/peer/chaincode_shim.proto b/protos/peer/chaincode_shim.proto index 222c5b314d7..4e3bf8a2569 100644 --- a/protos/peer/chaincode_shim.proto +++ b/protos/peer/chaincode_shim.proto @@ -1,17 +1,7 @@ /* -Copyright IBM Corp. 2017 All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +SPDX-License-Identifier: Apache-2.0 */ syntax = "proto3"; @@ -55,24 +45,40 @@ message ChaincodeMessage { SignedProposal proposal = 5; - //event emmited by chaincode. Used only with Init or Invoke. + //event emitted by chaincode. Used only with Init or Invoke. // This event is then stored (currently) //with Block.NonHashData.TransactionResult ChaincodeEvent chaincode_event = 6; } -message PutStateInfo { +// TODO: We need to finalize the design on chaincode container +// compatibility upon upgrade, see FAB-5777. + +message GetState { + string key = 1; + string collection = 2; +} + +message PutState { string key = 1; bytes value = 2; + string collection = 3; +} + +message DelState { + string key = 1; + string collection = 2; } message GetStateByRange { string startKey = 1; string endKey = 2; + string collection = 3; } message GetQueryResult { string query = 1; + string collection = 2; } message GetHistoryForKey {