Skip to content

Commit 306aa7d

Browse files
committed
Add query to get instantiated chaincodes on a channel
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 <[email protected]>
1 parent d1df522 commit 306aa7d

File tree

8 files changed

+269
-10
lines changed

8 files changed

+269
-10
lines changed

core/chaincode/shim/mockstub.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ func (iter *MockStateRangeQueryIterator) HasNext() bool {
321321

322322
current := iter.Current
323323
for current != nil {
324+
// if this is an open-ended query for all keys, return true
325+
if iter.StartKey == "" && iter.EndKey == "" {
326+
return true
327+
}
324328
comp1 := strings.Compare(current.Value.(string), iter.StartKey)
325329
comp2 := strings.Compare(current.Value.(string), iter.EndKey)
326330
if comp1 >= 0 {
@@ -356,7 +360,9 @@ func (iter *MockStateRangeQueryIterator) Next() (string, []byte, error) {
356360
for iter.Current != nil {
357361
comp1 := strings.Compare(iter.Current.Value.(string), iter.StartKey)
358362
comp2 := strings.Compare(iter.Current.Value.(string), iter.EndKey)
359-
if comp1 >= 0 && comp2 <= 0 {
363+
// compare to start and end keys. or, if this is an open-ended query for
364+
// all keys, it should always return the key and value
365+
if (comp1 >= 0 && comp2 <= 0) || (iter.StartKey == "" && iter.EndKey == "") {
360366
key := iter.Current.Value.(string)
361367
value, err := iter.Stub.GetState(key)
362368
iter.Current = iter.Current.Next()

core/chaincode/shim/mockstub_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,32 @@ func TestMockStateRangeQueryIterator(t *testing.T) {
5555
}
5656
}
5757

58+
// TestMockStateRangeQueryIterator_openEnded tests running an open-ended query
59+
// for all keys on the MockStateRangeQueryIterator
60+
func TestMockStateRangeQueryIterator_openEnded(t *testing.T) {
61+
stub := NewMockStub("rangeTest", nil)
62+
stub.MockTransactionStart("init")
63+
stub.PutState("1", []byte{61})
64+
stub.PutState("0", []byte{62})
65+
stub.PutState("5", []byte{65})
66+
stub.PutState("3", []byte{63})
67+
stub.PutState("4", []byte{64})
68+
stub.PutState("6", []byte{66})
69+
stub.MockTransactionEnd("init")
70+
71+
rqi := NewMockStateRangeQueryIterator(stub, "", "")
72+
73+
count := 0
74+
for rqi.HasNext() {
75+
rqi.Next()
76+
count++
77+
}
78+
79+
if count != rqi.Stub.Keys.Len() {
80+
t.FailNow()
81+
}
82+
}
83+
5884
// TestSetChaincodeLoggingLevel uses the utlity function defined in chaincode.go to
5985
// set the chaincodeLogger's logging level
6086
func TestSetChaincodeLoggingLevel(t *testing.T) {

core/scc/lccc/lccc.go

+57
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ const (
6464
//GETCCDATA get ChaincodeData
6565
GETCCDATA = "getccdata"
6666

67+
//GETCHAINCODES gets the instantiated chaincodes on a channel
68+
GETCHAINCODES = "getchaincodes"
69+
6770
//characters used in chaincodenamespace
6871
specialChars = "/:[]${}"
6972
)
@@ -260,6 +263,55 @@ func (lccc *LifeCycleSysCC) getChaincodeDeploymentSpec(code []byte) (*pb.Chainco
260263
return cds, nil
261264
}
262265

266+
// getChaincodes returns all chaincodes instantiated on this LCCC's channel
267+
func (lccc *LifeCycleSysCC) getChaincodes(stub shim.ChaincodeStubInterface) pb.Response {
268+
// get all rows from LCCC
269+
itr, err := stub.GetStateByRange("", "")
270+
271+
if err != nil {
272+
return shim.Error(err.Error())
273+
}
274+
defer itr.Close()
275+
276+
// array to store metadata for all chaincode entries from LCCC
277+
ccInfoArray := make([]*pb.ChaincodeInfo, 0)
278+
279+
for itr.HasNext() {
280+
_, value, err := itr.Next()
281+
if err != nil {
282+
return shim.Error(err.Error())
283+
}
284+
285+
ccdata := &ccprovider.ChaincodeData{}
286+
if err = proto.Unmarshal(value, ccdata); err != nil {
287+
return shim.Error(err.Error())
288+
}
289+
290+
ccdepspec := &pb.ChaincodeDeploymentSpec{}
291+
if err = proto.Unmarshal(ccdata.DepSpec, ccdepspec); err != nil {
292+
return shim.Error(err.Error())
293+
}
294+
295+
path := ccdepspec.GetChaincodeSpec().ChaincodeId.Path
296+
input := ccdepspec.GetChaincodeSpec().Input.String()
297+
298+
ccInfo := &pb.ChaincodeInfo{Name: ccdata.Name, Version: ccdata.Version, Path: path, Input: input, Escc: ccdata.Escc, Vscc: ccdata.Vscc}
299+
300+
// add this specific chaincode's metadata to the array of all chaincodes
301+
ccInfoArray = append(ccInfoArray, ccInfo)
302+
}
303+
// add array with info about all instantiated chaincodes to the query
304+
// response proto
305+
cqr := &pb.ChaincodeQueryResponse{Chaincodes: ccInfoArray}
306+
307+
cqrbytes, err := proto.Marshal(cqr)
308+
if err != nil {
309+
return shim.Error(err.Error())
310+
}
311+
312+
return shim.Success(cqrbytes)
313+
}
314+
263315
//do access control
264316
func (lccc *LifeCycleSysCC) acl(stub shim.ChaincodeStubInterface, chainname string, cds *pb.ChaincodeDeploymentSpec) error {
265317
return nil
@@ -552,6 +604,11 @@ func (lccc *LifeCycleSysCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response
552604
default:
553605
return shim.Success(cd.DepSpec)
554606
}
607+
case GETCHAINCODES:
608+
if len(args) != 1 {
609+
return shim.Error(InvalidArgsLenErr(len(args)).Error())
610+
}
611+
return lccc.getChaincodes(stub)
555612
}
556613

557614
return shim.Error(InvalidFunctionErr(function).Error())

core/scc/lccc/lccc_test.go

+46-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,13 @@ func TestDeploy(t *testing.T) {
8080
t.FailNow()
8181
}
8282

83-
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)
83+
ccname := "example02"
84+
path := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02"
85+
version := "0"
86+
cds, err := constructDeploymentSpec(ccname, path, version, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, true)
87+
if err != nil {
88+
t.FailNow()
89+
}
8490
defer os.Remove(lccctestpath + "/example02.0")
8591
var b []byte
8692
if b, err = proto.Marshal(cds); err != nil || b == nil {
@@ -91,6 +97,27 @@ func TestDeploy(t *testing.T) {
9197
if res := stub.MockInvoke("1", args); res.Status != shim.OK {
9298
t.FailNow()
9399
}
100+
101+
args = [][]byte{[]byte(GETCHAINCODES)}
102+
res := stub.MockInvoke("1", args)
103+
if res.Status != shim.OK {
104+
t.FailNow()
105+
}
106+
107+
cqr := &pb.ChaincodeQueryResponse{}
108+
err = proto.Unmarshal(res.Payload, cqr)
109+
if err != nil {
110+
t.FailNow()
111+
}
112+
// deployed one chaincode so query should return an array with one chaincode
113+
if len(cqr.GetChaincodes()) != 1 {
114+
t.FailNow()
115+
}
116+
117+
// check that the ChaincodeInfo values match the input values
118+
if cqr.GetChaincodes()[0].Name != ccname || cqr.GetChaincodes()[0].Version != version || cqr.GetChaincodes()[0].Path != path {
119+
t.FailNow()
120+
}
94121
}
95122

96123
//TestInstall tests the install function
@@ -329,6 +356,24 @@ func TestMultipleDeploy(t *testing.T) {
329356
if res := stub.MockInvoke("1", args); res.Status != shim.OK {
330357
t.FailNow()
331358
}
359+
360+
args = [][]byte{[]byte(GETCHAINCODES)}
361+
res := stub.MockInvoke("1", args)
362+
if res.Status != shim.OK {
363+
t.FailNow()
364+
}
365+
366+
cqr := &pb.ChaincodeQueryResponse{}
367+
err = proto.Unmarshal(res.Payload, cqr)
368+
if err != nil {
369+
t.FailNow()
370+
}
371+
372+
// deployed two chaincodes so query should return an array with two chaincodes
373+
if len(cqr.GetChaincodes()) != 2 {
374+
t.FailNow()
375+
}
376+
332377
}
333378

334379
//TestRetryFailedDeploy tests re-deploying after a failure

protos/peer/admin.pb.go

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protos/peer/query.pb.go

+79
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protos/peer/query.proto

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
syntax = "proto3";
18+
19+
option go_package = "github.com/hyperledger/fabric/protos/peer";
20+
21+
package protos;
22+
23+
// ChaincodeQueryResponse returns information about each chaincode that pertains
24+
// to a query in lccc.go, such as GetChaincodes (returns all chaincodes
25+
// instantiated on a channel), and GetInstalledChaincodes (returns all chaincodes
26+
// installed on a peer)
27+
message ChaincodeQueryResponse {
28+
repeated ChaincodeInfo chaincodes = 1;
29+
}
30+
31+
// ChaincodeInfo contains general information about an installed/instantiated
32+
// chaincode
33+
message ChaincodeInfo {
34+
string name = 1;
35+
string version = 2;
36+
// the path as specified by the install/instantiate transaction
37+
string path = 3;
38+
// the chaincode function upon instantiation and its arguments. This will be
39+
// blank if the query is returning information about installed chaincodes.
40+
string input = 4;
41+
string escc = 5;
42+
string vscc = 6;
43+
}

0 commit comments

Comments
 (0)