Skip to content

Commit

Permalink
FAB-3030 intf. to deal with different package types
Browse files Browse the repository at this point in the history
https://jira.hyperledger.org/browse/FAB-3030

We have now two ways to install a chaincode on the FS
   raw ChaincodeDeploymentSpec (current model)
   SignedChaincodeDeploymentSpec (under implementation)

This checking prepares higher level components such as
LCCC use  fabric to use either package types seamlessly.

This paves the way for install to use either types of
packages.

Change-Id: I24c68db5e58d1b64956806bec9919128788c7730
Signed-off-by: Srinivasan Muralidharan <muralisr@us.ibm.com>
  • Loading branch information
Srinivasan Muralidharan committed Apr 7, 2017
1 parent a443a59 commit c810332
Show file tree
Hide file tree
Showing 7 changed files with 627 additions and 41 deletions.
6 changes: 3 additions & 3 deletions core/common/ccpackage/ccpackage.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import (
"github.com/hyperledger/fabric/protos/utils"
)

// extractSignedCCDepSpec extracts the messages from the envelope
func extractSignedCCDepSpec(env *common.Envelope) (*common.ChannelHeader, *peer.SignedChaincodeDeploymentSpec, error) {
// ExtractSignedCCDepSpec extracts the messages from the envelope
func ExtractSignedCCDepSpec(env *common.Envelope) (*common.ChannelHeader, *peer.SignedChaincodeDeploymentSpec, error) {
p := &common.Payload{}
err := proto.Unmarshal(env.Payload, p)
if err != nil {
Expand Down Expand Up @@ -214,7 +214,7 @@ func SignExistingPackage(env *common.Envelope, owner msp.SigningIdentity) (*comm
return nil, fmt.Errorf("owner not provided")
}

ch, sdepspec, err := extractSignedCCDepSpec(env)
ch, sdepspec, err := ExtractSignedCCDepSpec(env)
if err != nil {
return nil, err
}
Expand Down
81 changes: 47 additions & 34 deletions core/common/ccprovider/ccprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,30 @@ var ccproviderLogger = logging.MustGetLogger("ccprovider")

var chaincodeInstallPath string

//CCPackage encapsulates a chaincode package which can be
// raw ChaincodeDeploymentSpec
// SignedChaincodeDeploymentSpec
// Attempt to keep the interface at a level with minimal
// interface for possible generalization.
type CCPackage interface {
//InitFromBuffer initialize the package from bytes
InitFromBuffer(buf []byte) (*ChaincodeData, error)

// InitFromFS gets the chaincode from the filesystem (includes the raw bytes too)
InitFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error)

// GetDepSpec gets the ChaincodeDeploymentSpec from the package
GetDepSpec() *pb.ChaincodeDeploymentSpec

// PutChaincodeToFS writes the chaincode to the filesystem
PutChaincodeToFS() error

// ValidateCC validates and returns the chaincode deployment spec corresponding to
// ChaincodeData. The validation is based on the metadata from ChaincodeData
// One use of this method is to validate the chaincode before launching
ValidateCC(ccdata *ChaincodeData) (*pb.ChaincodeDeploymentSpec, error)
}

//SetChaincodesPath sets the chaincode path for this peer
func SetChaincodesPath(path string) {
if s, err := os.Stat(path); err != nil {
Expand Down Expand Up @@ -63,49 +87,38 @@ func GetChaincodePackage(ccname string, ccversion string) ([]byte, error) {
return ccbytes, nil
}

//GetChaincodeFromFS returns the chaincode and its package from the file system
// GetChaincodeFromFS this is a wrapper for hiding package implementation.
func GetChaincodeFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) {
//NOTE- this is the only place from where we get code from file system
//this API needs to be modified to take other params for security.
//this implementation needs to be enhanced to do those security checks
ccbytes, err := GetChaincodePackage(ccname, ccversion)
if err != nil {
return nil, nil, err
}

cdsfs := &pb.ChaincodeDeploymentSpec{}
err = proto.Unmarshal(ccbytes, cdsfs)
if err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal fs deployment spec for %s, %s", ccname, ccversion)
}

return ccbytes, cdsfs, nil
cccdspack := &CDSPackage{}
return cccdspack.InitFromFS(ccname, ccversion)
}

//PutChaincodeIntoFS - serializes chaincode to a package on the file system
// PutChaincodeIntoFS is a wrapper for putting raw ChaincodeDeploymentSpec
//using CDSPackage. This is only used in UTs
func PutChaincodeIntoFS(depSpec *pb.ChaincodeDeploymentSpec) error {
//NOTE- this is only place from where we put code into file system
//this API needs to be modified to take other params for security.
//this implementation needs to be enhanced to do those security checks
ccname := depSpec.ChaincodeSpec.ChaincodeId.Name
ccversion := depSpec.ChaincodeSpec.ChaincodeId.Version

//return error if chaincode exists
path := fmt.Sprintf("%s/%s.%s", chaincodeInstallPath, ccname, ccversion)
if _, err := os.Stat(path); err == nil {
return fmt.Errorf("chaincode %s exists", path)
}

b, err := proto.Marshal(depSpec)
buf, err := proto.Marshal(depSpec)
if err != nil {
return fmt.Errorf("failed to marshal fs deployment spec for %s, %s", ccname, ccversion)
return err
}

if err = ioutil.WriteFile(path, b, 0644); err != nil {
cccdspack := &CDSPackage{}
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
return err
}
return cccdspack.PutChaincodeToFS()
}

return nil
// GetCCPackage tries each known package implementation one by one
// till the right package is found
func GetCCPackage(buf []byte) (CCPackage, error) {
cccdspack := &CDSPackage{}
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
ccscdspack := &SignedCDSPackage{}
if _, err := ccscdspack.InitFromBuffer(buf); err != nil {
return nil, err
}
return ccscdspack, nil
}
return cccdspack, nil
}

// GetInstalledChaincodes returns a map whose key is the chaincode id and
Expand Down
116 changes: 116 additions & 0 deletions core/common/ccprovider/cdspackage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
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.
*/

package ccprovider

import (
"fmt"
"io/ioutil"
"os"

"github.com/golang/protobuf/proto"

pb "github.com/hyperledger/fabric/protos/peer"
)

//CDSPackage encapsulates ChaincodeDeploymentSpec.
type CDSPackage struct {
buf []byte
depSpec *pb.ChaincodeDeploymentSpec
}

// GetDepSpec gets the ChaincodeDeploymentSpec from the package
func (ccpack *CDSPackage) GetDepSpec() *pb.ChaincodeDeploymentSpec {
return ccpack.depSpec
}

// ValidateCC returns error if the chaincode is not found or if its not a
// ChaincodeDeploymentSpec
func (ccpack *CDSPackage) ValidateCC(ccdata *ChaincodeData) (*pb.ChaincodeDeploymentSpec, error) {
if ccpack.depSpec == nil {
return nil, fmt.Errorf("uninitialized package")
}
if ccdata.Name != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name || ccdata.Version != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version {
return nil, fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId)
}
//for now just return chaincode. When we introduce Hash we will do more checks
return ccpack.depSpec, nil
}

//InitFromBuffer sets the buffer if valid and returns ChaincodeData
func (ccpack *CDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, error) {
//incase ccpack is reused
ccpack.buf = nil
ccpack.depSpec = nil

depSpec := &pb.ChaincodeDeploymentSpec{}
err := proto.Unmarshal(buf, depSpec)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal fs deployment spec from bytes")
}
ccpack.buf = buf
ccpack.depSpec = depSpec
return &ChaincodeData{Name: depSpec.ChaincodeSpec.ChaincodeId.Name, Version: depSpec.ChaincodeSpec.ChaincodeId.Version}, nil
}

//InitFromFS returns the chaincode and its package from the file system
func (ccpack *CDSPackage) InitFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) {
//incase ccpack is reused
ccpack.buf = nil
ccpack.depSpec = nil

buf, err := GetChaincodePackage(ccname, ccversion)
if err != nil {
return nil, nil, err
}

depSpec := &pb.ChaincodeDeploymentSpec{}
err = proto.Unmarshal(buf, depSpec)
if err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal fs deployment spec for %s, %s", ccname, ccversion)
}

ccpack.buf = buf
ccpack.depSpec = depSpec

return buf, depSpec, nil
}

//PutChaincodeToFS - serializes chaincode to a package on the file system
func (ccpack *CDSPackage) PutChaincodeToFS() error {
if ccpack.buf == nil {
return fmt.Errorf("uninitialized package")
}

if ccpack.depSpec == nil {
return fmt.Errorf("depspec cannot be nil if buf is not nil")
}

ccname := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name
ccversion := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version

//return error if chaincode exists
path := fmt.Sprintf("%s/%s.%s", chaincodeInstallPath, ccname, ccversion)
if _, err := os.Stat(path); err == nil {
return fmt.Errorf("chaincode %s exists", path)
}

if err := ioutil.WriteFile(path, ccpack.buf, 0644); err != nil {
return err
}

return nil
}
137 changes: 137 additions & 0 deletions core/common/ccprovider/cdspackage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
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.
*/

package ccprovider

import (
"io/ioutil"
"os"
"testing"

pb "github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/protos/utils"
)

func setupccdir() string {
tempDir, err := ioutil.TempDir("/tmp", "ccprovidertest")
if err != nil {
panic(err)
}
SetChaincodesPath(tempDir)
return tempDir
}

func TestPutCDSCC(t *testing.T) {
ccdir := setupccdir()
defer os.RemoveAll(ccdir)

cds := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: 1, ChaincodeId: &pb.ChaincodeID{Name: "testcc", Version: "0"}, Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("")}}}, CodePackage: []byte("code")}

b := utils.MarshalOrPanic(cds)

ccpack := &CDSPackage{}
_, err := ccpack.InitFromBuffer(b)
if err != nil {
t.Fatalf("error owner creating package %s", err)
return
}

if err = ccpack.PutChaincodeToFS(); err != nil {
t.Fatalf("error putting package on the FS %s", err)
return
}

if _, err = ccpack.ValidateCC(&ChaincodeData{Name: "testcc", Version: "0"}); err != nil {
t.Fatalf("error validating package %s", err)
return
}
}

func TestPutCDSErrorPaths(t *testing.T) {
ccdir := setupccdir()
defer os.RemoveAll(ccdir)

cds := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: 1, ChaincodeId: &pb.ChaincodeID{Name: "testcc", Version: "0"}, Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("")}}}, CodePackage: []byte("code")}

b := utils.MarshalOrPanic(cds)

ccpack := &CDSPackage{}
_, err := ccpack.InitFromBuffer(b)
if err != nil {
t.Fatalf("error owner creating package %s", err)
return
}

//validate with invalid name
if _, err = ccpack.ValidateCC(&ChaincodeData{Name: "invalname", Version: "0"}); err == nil {
t.Fatalf("expected error validating package")
return
}
//remove the buffer
ccpack.buf = nil
if err = ccpack.PutChaincodeToFS(); err == nil {
t.Fatalf("expected error putting package on the FS")
return
}

//put back the buffer but remove the depspec
ccpack.buf = b
savDepSpec := ccpack.depSpec
ccpack.depSpec = nil
if err = ccpack.PutChaincodeToFS(); err == nil {
t.Fatalf("expected error putting package on the FS")
return
}

//put back dep spec
ccpack.depSpec = savDepSpec

//...but remove the chaincode directory
os.RemoveAll(ccdir)
if err = ccpack.PutChaincodeToFS(); err == nil {
t.Fatalf("expected error putting package on the FS")
return
}
}

func TestCDSGetCCPackage(t *testing.T) {
cds := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: 1, ChaincodeId: &pb.ChaincodeID{Name: "testcc", Version: "0"}, Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("")}}}, CodePackage: []byte("code")}

b := utils.MarshalOrPanic(cds)

ccpack, err := GetCCPackage(b)
if err != nil {
t.Fatalf("failed to get CDS CCPackage %s", err)
return
}

cccdspack, ok := ccpack.(*CDSPackage)
if !ok || cccdspack == nil {
t.Fatalf("failed to get CDS CCPackage")
return
}

cds2 := cccdspack.GetDepSpec()
if cds2 == nil {
t.Fatalf("nil dep spec in CDS CCPackage")
return
}

if cds2.ChaincodeSpec.ChaincodeId.Name != cds.ChaincodeSpec.ChaincodeId.Name || cds2.ChaincodeSpec.ChaincodeId.Version != cds.ChaincodeSpec.ChaincodeId.Version {
t.Fatalf("dep spec in CDS CCPackage does not match %v != %v", cds, cds2)
return
}
}
Loading

0 comments on commit c810332

Please sign in to comment.