From 306aa7d86c180de6876b95169048926c28537269 Mon Sep 17 00:00:00 2001 From: Will Lahti Date: Mon, 20 Feb 2017 11:33:39 -0500 Subject: [PATCH] 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 --- core/chaincode/shim/mockstub.go | 8 ++- core/chaincode/shim/mockstub_test.go | 26 +++++++++ core/scc/lccc/lccc.go | 57 ++++++++++++++++++++ core/scc/lccc/lccc_test.go | 47 ++++++++++++++++- protos/peer/admin.pb.go | 3 ++ protos/peer/query.pb.go | 79 ++++++++++++++++++++++++++++ protos/peer/query.proto | 43 +++++++++++++++ protos/peer/transaction.pb.go | 16 +++--- 8 files changed, 269 insertions(+), 10 deletions(-) create mode 100644 protos/peer/query.pb.go create mode 100644 protos/peer/query.proto diff --git a/core/chaincode/shim/mockstub.go b/core/chaincode/shim/mockstub.go index fd37177fff2..e4e5abf4ace 100644 --- a/core/chaincode/shim/mockstub.go +++ b/core/chaincode/shim/mockstub.go @@ -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 { @@ -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() diff --git a/core/chaincode/shim/mockstub_test.go b/core/chaincode/shim/mockstub_test.go index b71aaee7e74..4a4b9887af4 100644 --- a/core/chaincode/shim/mockstub_test.go +++ b/core/chaincode/shim/mockstub_test.go @@ -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) { diff --git a/core/scc/lccc/lccc.go b/core/scc/lccc/lccc.go index 339cdce517f..bacb089e152 100644 --- a/core/scc/lccc/lccc.go +++ b/core/scc/lccc/lccc.go @@ -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 = "/:[]${}" ) @@ -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 @@ -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()) diff --git a/core/scc/lccc/lccc_test.go b/core/scc/lccc/lccc_test.go index 385ee0c93d2..ecc3ec6f46f 100644 --- a/core/scc/lccc/lccc_test.go +++ b/core/scc/lccc/lccc_test.go @@ -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 { @@ -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 @@ -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 diff --git a/protos/peer/admin.pb.go b/protos/peer/admin.pb.go index 550e549ecbd..c3d552c9ea2 100644 --- a/protos/peer/admin.pb.go +++ b/protos/peer/admin.pb.go @@ -15,6 +15,7 @@ It is generated from these files: peer/peer.proto peer/proposal.proto peer/proposal_response.proto + peer/query.proto peer/transaction.proto It has these top-level messages: @@ -56,6 +57,8 @@ It has these top-level messages: Response ProposalResponsePayload Endorsement + ChaincodeQueryResponse + ChaincodeInfo SignedTransaction ProcessedTransaction Transaction diff --git a/protos/peer/query.pb.go b/protos/peer/query.pb.go new file mode 100644 index 00000000000..baafebb7268 --- /dev/null +++ b/protos/peer/query.pb.go @@ -0,0 +1,79 @@ +// Code generated by protoc-gen-go. +// source: peer/query.proto +// DO NOT EDIT! + +package peer + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// 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) +type ChaincodeQueryResponse struct { + Chaincodes []*ChaincodeInfo `protobuf:"bytes,1,rep,name=chaincodes" json:"chaincodes,omitempty"` +} + +func (m *ChaincodeQueryResponse) Reset() { *m = ChaincodeQueryResponse{} } +func (m *ChaincodeQueryResponse) String() string { return proto.CompactTextString(m) } +func (*ChaincodeQueryResponse) ProtoMessage() {} +func (*ChaincodeQueryResponse) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{0} } + +func (m *ChaincodeQueryResponse) GetChaincodes() []*ChaincodeInfo { + if m != nil { + return m.Chaincodes + } + return nil +} + +// ChaincodeInfo contains general information about an installed/instantiated +// chaincode +type ChaincodeInfo struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"` + // the path as specified by the install/instantiate transaction + Path string `protobuf:"bytes,3,opt,name=path" json:"path,omitempty"` + // the chaincode function upon instantiation and its arguments. This will be + // blank if the query is returning information about installed chaincodes. + Input string `protobuf:"bytes,4,opt,name=input" json:"input,omitempty"` + Escc string `protobuf:"bytes,5,opt,name=escc" json:"escc,omitempty"` + Vscc string `protobuf:"bytes,6,opt,name=vscc" json:"vscc,omitempty"` +} + +func (m *ChaincodeInfo) Reset() { *m = ChaincodeInfo{} } +func (m *ChaincodeInfo) String() string { return proto.CompactTextString(m) } +func (*ChaincodeInfo) ProtoMessage() {} +func (*ChaincodeInfo) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{1} } + +func init() { + proto.RegisterType((*ChaincodeQueryResponse)(nil), "protos.ChaincodeQueryResponse") + proto.RegisterType((*ChaincodeInfo)(nil), "protos.ChaincodeInfo") +} + +func init() { proto.RegisterFile("peer/query.proto", fileDescriptor9) } + +var fileDescriptor9 = []byte{ + // 226 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x54, 0x90, 0xbf, 0x4a, 0x04, 0x31, + 0x10, 0xc6, 0x89, 0xf7, 0x47, 0x1c, 0x11, 0x24, 0xa8, 0xa4, 0x3c, 0xae, 0x3a, 0x11, 0x36, 0xa0, + 0xf8, 0x02, 0x5a, 0x59, 0x89, 0x5b, 0xda, 0x65, 0x73, 0x73, 0x97, 0x80, 0x9b, 0xc4, 0x24, 0xbb, + 0xb0, 0x4f, 0xe1, 0x2b, 0xcb, 0x24, 0xac, 0x78, 0x55, 0xbe, 0xf9, 0x7d, 0xbf, 0x29, 0x32, 0x70, + 0x1d, 0x10, 0xa3, 0xfc, 0x1e, 0x30, 0x4e, 0x4d, 0x88, 0x3e, 0x7b, 0xbe, 0x2e, 0x4f, 0xda, 0xbe, + 0xc3, 0xdd, 0xab, 0x51, 0xd6, 0x69, 0xbf, 0xc7, 0x0f, 0xea, 0x5b, 0x4c, 0xc1, 0xbb, 0x84, 0xfc, + 0x19, 0x40, 0xcf, 0x4d, 0x12, 0x6c, 0xb3, 0xd8, 0x5d, 0x3e, 0xde, 0xd6, 0xed, 0xd4, 0xfc, 0xed, + 0xbc, 0xb9, 0x83, 0x6f, 0xff, 0x89, 0xdb, 0x1f, 0x06, 0x57, 0x27, 0x2d, 0xe7, 0xb0, 0x74, 0xaa, + 0x47, 0xc1, 0x36, 0x6c, 0x77, 0xd1, 0x96, 0xcc, 0x05, 0x9c, 0x8f, 0x18, 0x93, 0xf5, 0x4e, 0x9c, + 0x15, 0x3c, 0x8f, 0x64, 0x07, 0x95, 0x8d, 0x58, 0x54, 0x9b, 0x32, 0xbf, 0x81, 0x95, 0x75, 0x61, + 0xc8, 0x62, 0x59, 0x60, 0x1d, 0xc8, 0xc4, 0xa4, 0xb5, 0x58, 0x55, 0x93, 0x32, 0xb1, 0x91, 0xd8, + 0xba, 0x32, 0xca, 0x2f, 0x0f, 0x9f, 0xf7, 0x47, 0x9b, 0xcd, 0xd0, 0x35, 0xda, 0xf7, 0xd2, 0x4c, + 0x01, 0xe3, 0x17, 0xee, 0x8f, 0x18, 0xe5, 0x41, 0x75, 0xd1, 0x6a, 0x59, 0xff, 0x24, 0xe9, 0x46, + 0x5d, 0xbd, 0xcb, 0xd3, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x60, 0x06, 0xba, 0xdd, 0x32, 0x01, + 0x00, 0x00, +} diff --git a/protos/peer/query.proto b/protos/peer/query.proto new file mode 100644 index 00000000000..4ad8e765d56 --- /dev/null +++ b/protos/peer/query.proto @@ -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; +} diff --git a/protos/peer/transaction.pb.go b/protos/peer/transaction.pb.go index 357acf64adc..1d6f2f63a27 100644 --- a/protos/peer/transaction.pb.go +++ b/protos/peer/transaction.pb.go @@ -31,7 +31,7 @@ type SignedTransaction struct { func (m *SignedTransaction) Reset() { *m = SignedTransaction{} } func (m *SignedTransaction) String() string { return proto.CompactTextString(m) } func (*SignedTransaction) ProtoMessage() {} -func (*SignedTransaction) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{0} } +func (*SignedTransaction) Descriptor() ([]byte, []int) { return fileDescriptor10, []int{0} } // ProcessedTransaction wraps an Envelope that includes a transaction along with an indication // of whether the transaction was validated or invalidated by committing peer. @@ -49,7 +49,7 @@ type ProcessedTransaction struct { func (m *ProcessedTransaction) Reset() { *m = ProcessedTransaction{} } func (m *ProcessedTransaction) String() string { return proto.CompactTextString(m) } func (*ProcessedTransaction) ProtoMessage() {} -func (*ProcessedTransaction) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{1} } +func (*ProcessedTransaction) Descriptor() ([]byte, []int) { return fileDescriptor10, []int{1} } func (m *ProcessedTransaction) GetTransactionEnvelope() *common.Envelope { if m != nil { @@ -79,7 +79,7 @@ type Transaction struct { func (m *Transaction) Reset() { *m = Transaction{} } func (m *Transaction) String() string { return proto.CompactTextString(m) } func (*Transaction) ProtoMessage() {} -func (*Transaction) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{2} } +func (*Transaction) Descriptor() ([]byte, []int) { return fileDescriptor10, []int{2} } func (m *Transaction) GetActions() []*TransactionAction { if m != nil { @@ -101,7 +101,7 @@ type TransactionAction struct { func (m *TransactionAction) Reset() { *m = TransactionAction{} } func (m *TransactionAction) String() string { return proto.CompactTextString(m) } func (*TransactionAction) ProtoMessage() {} -func (*TransactionAction) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{3} } +func (*TransactionAction) Descriptor() ([]byte, []int) { return fileDescriptor10, []int{3} } // ChaincodeActionPayload is the message to be used for the TransactionAction's // payload when the Header's type is set to CHAINCODE. It carries the @@ -124,7 +124,7 @@ type ChaincodeActionPayload struct { func (m *ChaincodeActionPayload) Reset() { *m = ChaincodeActionPayload{} } func (m *ChaincodeActionPayload) String() string { return proto.CompactTextString(m) } func (*ChaincodeActionPayload) ProtoMessage() {} -func (*ChaincodeActionPayload) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{4} } +func (*ChaincodeActionPayload) Descriptor() ([]byte, []int) { return fileDescriptor10, []int{4} } func (m *ChaincodeActionPayload) GetAction() *ChaincodeEndorsedAction { if m != nil { @@ -148,7 +148,7 @@ type ChaincodeEndorsedAction struct { func (m *ChaincodeEndorsedAction) Reset() { *m = ChaincodeEndorsedAction{} } func (m *ChaincodeEndorsedAction) String() string { return proto.CompactTextString(m) } func (*ChaincodeEndorsedAction) ProtoMessage() {} -func (*ChaincodeEndorsedAction) Descriptor() ([]byte, []int) { return fileDescriptor9, []int{5} } +func (*ChaincodeEndorsedAction) Descriptor() ([]byte, []int) { return fileDescriptor10, []int{5} } func (m *ChaincodeEndorsedAction) GetEndorsements() []*Endorsement { if m != nil { @@ -166,9 +166,9 @@ func init() { proto.RegisterType((*ChaincodeEndorsedAction)(nil), "protos.ChaincodeEndorsedAction") } -func init() { proto.RegisterFile("peer/transaction.proto", fileDescriptor9) } +func init() { proto.RegisterFile("peer/transaction.proto", fileDescriptor10) } -var fileDescriptor9 = []byte{ +var fileDescriptor10 = []byte{ // 416 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x93, 0xcf, 0x6b, 0xdb, 0x30, 0x14, 0xc7, 0x49, 0xc7, 0xd2, 0xed, 0xa5, 0x87, 0x46, 0x29, 0xa9, 0x1b, 0x0a, 0x1d, 0x3e, 0x75,