Skip to content

Commit

Permalink
Add query to get instantiated chaincodes on a channel
Browse files Browse the repository at this point in the history
This change adds a query which returns the details of all
chaincodes instantiated on a specific channel. The return
value is a ChaincodeQueryResponse proto which contains an
array of ChaincodeInfo protos, which each contain the
chaincode name, version, path, input arguments, ESCC name,
and VSCC name.

https://jira.hyperledger.org/browse/FAB-2236

Change-Id: I22ee931b3b3d05eaa5ba87d57611b8a66abf969d
Signed-off-by: Will Lahti <wtlahti@us.ibm.com>
  • Loading branch information
wlahti committed Feb 24, 2017
1 parent d1df522 commit 306aa7d
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 10 deletions.
8 changes: 7 additions & 1 deletion core/chaincode/shim/mockstub.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@ func (iter *MockStateRangeQueryIterator) HasNext() bool {

current := iter.Current
for current != nil {
// if this is an open-ended query for all keys, return true
if iter.StartKey == "" && iter.EndKey == "" {
return true
}
comp1 := strings.Compare(current.Value.(string), iter.StartKey)
comp2 := strings.Compare(current.Value.(string), iter.EndKey)
if comp1 >= 0 {
Expand Down Expand Up @@ -356,7 +360,9 @@ func (iter *MockStateRangeQueryIterator) Next() (string, []byte, error) {
for iter.Current != nil {
comp1 := strings.Compare(iter.Current.Value.(string), iter.StartKey)
comp2 := strings.Compare(iter.Current.Value.(string), iter.EndKey)
if comp1 >= 0 && comp2 <= 0 {
// compare to start and end keys. or, if this is an open-ended query for
// all keys, it should always return the key and value
if (comp1 >= 0 && comp2 <= 0) || (iter.StartKey == "" && iter.EndKey == "") {
key := iter.Current.Value.(string)
value, err := iter.Stub.GetState(key)
iter.Current = iter.Current.Next()
Expand Down
26 changes: 26 additions & 0 deletions core/chaincode/shim/mockstub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,32 @@ func TestMockStateRangeQueryIterator(t *testing.T) {
}
}

// TestMockStateRangeQueryIterator_openEnded tests running an open-ended query
// for all keys on the MockStateRangeQueryIterator
func TestMockStateRangeQueryIterator_openEnded(t *testing.T) {
stub := NewMockStub("rangeTest", nil)
stub.MockTransactionStart("init")
stub.PutState("1", []byte{61})
stub.PutState("0", []byte{62})
stub.PutState("5", []byte{65})
stub.PutState("3", []byte{63})
stub.PutState("4", []byte{64})
stub.PutState("6", []byte{66})
stub.MockTransactionEnd("init")

rqi := NewMockStateRangeQueryIterator(stub, "", "")

count := 0
for rqi.HasNext() {
rqi.Next()
count++
}

if count != rqi.Stub.Keys.Len() {
t.FailNow()
}
}

// TestSetChaincodeLoggingLevel uses the utlity function defined in chaincode.go to
// set the chaincodeLogger's logging level
func TestSetChaincodeLoggingLevel(t *testing.T) {
Expand Down
57 changes: 57 additions & 0 deletions core/scc/lccc/lccc.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ const (
//GETCCDATA get ChaincodeData
GETCCDATA = "getccdata"

//GETCHAINCODES gets the instantiated chaincodes on a channel
GETCHAINCODES = "getchaincodes"

//characters used in chaincodenamespace
specialChars = "/:[]${}"
)
Expand Down Expand Up @@ -260,6 +263,55 @@ func (lccc *LifeCycleSysCC) getChaincodeDeploymentSpec(code []byte) (*pb.Chainco
return cds, nil
}

// getChaincodes returns all chaincodes instantiated on this LCCC's channel
func (lccc *LifeCycleSysCC) getChaincodes(stub shim.ChaincodeStubInterface) pb.Response {
// get all rows from LCCC
itr, err := stub.GetStateByRange("", "")

if err != nil {
return shim.Error(err.Error())
}
defer itr.Close()

// array to store metadata for all chaincode entries from LCCC
ccInfoArray := make([]*pb.ChaincodeInfo, 0)

for itr.HasNext() {
_, value, err := itr.Next()
if err != nil {
return shim.Error(err.Error())
}

ccdata := &ccprovider.ChaincodeData{}
if err = proto.Unmarshal(value, ccdata); err != nil {
return shim.Error(err.Error())
}

ccdepspec := &pb.ChaincodeDeploymentSpec{}
if err = proto.Unmarshal(ccdata.DepSpec, ccdepspec); err != nil {
return shim.Error(err.Error())
}

path := ccdepspec.GetChaincodeSpec().ChaincodeId.Path
input := ccdepspec.GetChaincodeSpec().Input.String()

ccInfo := &pb.ChaincodeInfo{Name: ccdata.Name, Version: ccdata.Version, Path: path, Input: input, Escc: ccdata.Escc, Vscc: ccdata.Vscc}

// add this specific chaincode's metadata to the array of all chaincodes
ccInfoArray = append(ccInfoArray, ccInfo)
}
// add array with info about all instantiated chaincodes to the query
// response proto
cqr := &pb.ChaincodeQueryResponse{Chaincodes: ccInfoArray}

cqrbytes, err := proto.Marshal(cqr)
if err != nil {
return shim.Error(err.Error())
}

return shim.Success(cqrbytes)
}

//do access control
func (lccc *LifeCycleSysCC) acl(stub shim.ChaincodeStubInterface, chainname string, cds *pb.ChaincodeDeploymentSpec) error {
return nil
Expand Down Expand Up @@ -552,6 +604,11 @@ func (lccc *LifeCycleSysCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response
default:
return shim.Success(cd.DepSpec)
}
case GETCHAINCODES:
if len(args) != 1 {
return shim.Error(InvalidArgsLenErr(len(args)).Error())
}
return lccc.getChaincodes(stub)
}

return shim.Error(InvalidFunctionErr(function).Error())
Expand Down
47 changes: 46 additions & 1 deletion core/scc/lccc/lccc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ func TestDeploy(t *testing.T) {
t.FailNow()
}

cds, err := constructDeploymentSpec("example02", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", "0", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, true)
ccname := "example02"
path := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02"
version := "0"
cds, err := constructDeploymentSpec(ccname, path, version, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, true)
if err != nil {
t.FailNow()
}
defer os.Remove(lccctestpath + "/example02.0")
var b []byte
if b, err = proto.Marshal(cds); err != nil || b == nil {
Expand All @@ -91,6 +97,27 @@ func TestDeploy(t *testing.T) {
if res := stub.MockInvoke("1", args); res.Status != shim.OK {
t.FailNow()
}

args = [][]byte{[]byte(GETCHAINCODES)}
res := stub.MockInvoke("1", args)
if res.Status != shim.OK {
t.FailNow()
}

cqr := &pb.ChaincodeQueryResponse{}
err = proto.Unmarshal(res.Payload, cqr)
if err != nil {
t.FailNow()
}
// deployed one chaincode so query should return an array with one chaincode
if len(cqr.GetChaincodes()) != 1 {
t.FailNow()
}

// check that the ChaincodeInfo values match the input values
if cqr.GetChaincodes()[0].Name != ccname || cqr.GetChaincodes()[0].Version != version || cqr.GetChaincodes()[0].Path != path {
t.FailNow()
}
}

//TestInstall tests the install function
Expand Down Expand Up @@ -329,6 +356,24 @@ func TestMultipleDeploy(t *testing.T) {
if res := stub.MockInvoke("1", args); res.Status != shim.OK {
t.FailNow()
}

args = [][]byte{[]byte(GETCHAINCODES)}
res := stub.MockInvoke("1", args)
if res.Status != shim.OK {
t.FailNow()
}

cqr := &pb.ChaincodeQueryResponse{}
err = proto.Unmarshal(res.Payload, cqr)
if err != nil {
t.FailNow()
}

// deployed two chaincodes so query should return an array with two chaincodes
if len(cqr.GetChaincodes()) != 2 {
t.FailNow()
}

}

//TestRetryFailedDeploy tests re-deploying after a failure
Expand Down
3 changes: 3 additions & 0 deletions protos/peer/admin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 79 additions & 0 deletions protos/peer/query.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions protos/peer/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright IBM Corp. 2017 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.
*/

syntax = "proto3";

option go_package = "github.com/hyperledger/fabric/protos/peer";

package protos;

// ChaincodeQueryResponse returns information about each chaincode that pertains
// to a query in lccc.go, such as GetChaincodes (returns all chaincodes
// instantiated on a channel), and GetInstalledChaincodes (returns all chaincodes
// installed on a peer)
message ChaincodeQueryResponse {
repeated ChaincodeInfo chaincodes = 1;
}

// ChaincodeInfo contains general information about an installed/instantiated
// chaincode
message ChaincodeInfo {
string name = 1;
string version = 2;
// the path as specified by the install/instantiate transaction
string path = 3;
// the chaincode function upon instantiation and its arguments. This will be
// blank if the query is returning information about installed chaincodes.
string input = 4;
string escc = 5;
string vscc = 6;
}
Loading

0 comments on commit 306aa7d

Please sign in to comment.