Skip to content

Commit

Permalink
[FAB-3156] check correctness of instant'n policy
Browse files Browse the repository at this point in the history
The purpose of this change set is that for each invocation, we check that
the instantiated chaincode matches the installed one (if any) in terms of
its instantiation policy, just to avoid repercussions from any rogue
instantiation of a chaincode.

Change-Id: I4dbdb3459076a8d6d3f145d2ee7b89022f107e4b
Signed-off-by: Alessandro Sorniotti <ale.linux@sopit.net>
  • Loading branch information
ale-linux committed May 10, 2017
1 parent 41f80f9 commit 341ac6c
Show file tree
Hide file tree
Showing 6 changed files with 421 additions and 11 deletions.
106 changes: 106 additions & 0 deletions core/common/ccprovider/ccinfocache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
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"
"sync"

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

// ccInfoCacheImpl implements CCInfoProvider by providing an in-memory
// cache layer on top of the internal CCInfoProvider instance
type ccInfoCacheImpl struct {
sync.RWMutex

cache map[string]CCPackage
ccfs CCInfoProvider
}

// NewCCInfoCache returns a new cache on top of the supplied CCInfoProvider instance
func NewCCInfoCache(ccfs CCInfoProvider) CCInfoProvider {
return &ccInfoCacheImpl{
cache: make(map[string]CCPackage),
ccfs: ccfs,
}
}

func (c *ccInfoCacheImpl) GetChaincode(ccname string, ccversion string) (CCPackage, error) {
// c.cache is guaranteed to be non-nil

key := ccname + "/" + ccversion

c.RLock()
ccpack, in := c.cache[key]
c.RUnlock()

if !in {
var err error

// the chaincode data is not in the cache
// try to look it up from the file system
ccpack, err = c.ccfs.GetChaincode(ccname, ccversion)
if err != nil || ccpack == nil {
return nil, fmt.Errorf("cannot retrieve package for chaincode %s/%s, error %s", ccname, ccversion, err)
}

// we have a non-nil CCPackage, put it in the cache
c.Lock()
c.cache[key] = ccpack
c.Unlock()
}

return ccpack, nil
}

func (c *ccInfoCacheImpl) PutChaincode(depSpec *peer.ChaincodeDeploymentSpec) (CCPackage, error) {
// c.cache is guaranteed to be non-nil

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

if ccname == "" {
return nil, fmt.Errorf("the chaincode name cannot be an emoty string")
}

if ccversion == "" {
return nil, fmt.Errorf("the chaincode version cannot be an emoty string")
}

key := ccname + "/" + ccversion

c.RLock()
_, in := c.cache[key]
c.RUnlock()

if in {
return nil, fmt.Errorf("attempted to put chaincode data twice for %s/%s", ccname, ccversion)
}

ccpack, err := c.ccfs.PutChaincode(depSpec)
if err != nil || ccpack == nil {
return nil, fmt.Errorf("PutChaincodeIntoFS failed, error %s", err)
}

// we have a non-nil CCPackage, put it in the cache
c.Lock()
c.cache[key] = ccpack
c.Unlock()

return ccpack, nil
}
183 changes: 183 additions & 0 deletions core/common/ccprovider/ccinfocache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
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 (
"archive/tar"
"bytes"
"compress/gzip"
"testing"

"os"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/core/container/util"
"github.com/hyperledger/fabric/protos/peer"
"github.com/stretchr/testify/assert"
)

func getDepSpec(name string, path string, version string, initArgs [][]byte) (*peer.ChaincodeDeploymentSpec, error) {
spec := &peer.ChaincodeSpec{Type: 1, ChaincodeId: &peer.ChaincodeID{Name: name, Path: path, Version: version}, Input: &peer.ChaincodeInput{Args: initArgs}}

codePackageBytes := bytes.NewBuffer(nil)
gz := gzip.NewWriter(codePackageBytes)
tw := tar.NewWriter(gz)

err := util.WriteBytesToPackage("src/garbage.go", []byte(name+path+version), tw)
if err != nil {
return nil, err
}

tw.Close()
gz.Close()

return &peer.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes.Bytes()}, nil
}

func buildPackage(name string, path string, version string, initArgs [][]byte) (CCPackage, error) {
depSpec, err := getDepSpec(name, path, version, initArgs)
if err != nil {
return nil, err
}

buf, err := proto.Marshal(depSpec)
if err != nil {
return nil, err
}
cccdspack := &CDSPackage{}
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
return nil, err
}

return cccdspack, nil
}

type mockCCInfoFSStorageMgrImpl struct {
CCMap map[string]CCPackage
}

func (m *mockCCInfoFSStorageMgrImpl) PutChaincode(depSpec *peer.ChaincodeDeploymentSpec) (CCPackage, error) {
buf, err := proto.Marshal(depSpec)
if err != nil {
return nil, err
}
cccdspack := &CDSPackage{}
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
return nil, err
}

m.CCMap[depSpec.ChaincodeSpec.ChaincodeId.Name+depSpec.ChaincodeSpec.ChaincodeId.Version] = cccdspack

return cccdspack, nil
}

func (m *mockCCInfoFSStorageMgrImpl) GetChaincode(ccname string, ccversion string) (CCPackage, error) {
return m.CCMap[ccname+ccversion], nil
}

// here we test the cache implementation itself
func TestCCInfoCache(t *testing.T) {
ccname := "foo"
ccver := "1.0"
ccpath := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02"

ccinfoFs := &mockCCInfoFSStorageMgrImpl{CCMap: map[string]CCPackage{}}
cccache := NewCCInfoCache(ccinfoFs)

// test the get side

// the cc data is not yet in the cache
_, err := cccache.GetChaincode(ccname, ccver)
assert.Error(t, err)

// put it in the file system
pack, err := buildPackage(ccname, ccpath, ccver, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")})
assert.NoError(t, err)
ccinfoFs.CCMap[ccname+ccver] = pack

// expect it to be in the cache now
cd1, err := cccache.GetChaincode(ccname, ccver)
assert.NoError(t, err)

// it should still be in the cache
cd2, err := cccache.GetChaincode(ccname, ccver)
assert.NoError(t, err)

// they are not null
assert.NotNil(t, cd1)
assert.NotNil(t, cd2)

// test the put side now..
ccver = "2.0"

// create a dep spec to put
ds, err := getDepSpec(ccname, ccpath, ccver, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")})
assert.NoError(t, err)

// put it
_, err = cccache.PutChaincode(ds)
assert.NoError(t, err)

// expect it to be in the cache
cd1, err = cccache.GetChaincode(ccname, ccver)
assert.NoError(t, err)

// it should still be in the cache
cd2, err = cccache.GetChaincode(ccname, ccver)
assert.NoError(t, err)

// they are not null
assert.NotNil(t, cd1)
assert.NotNil(t, cd2)
}

// here we test the peer's built-in cache after enabling it
func TestCCInfoCachePeerInstance(t *testing.T) {
// enable the cache first: it's disabled by default
EnableCCInfoCache()

ccname := "foo"
ccver := "1.0"
ccpath := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02"

// the cc data is not yet in the cache
_, err := GetChaincodeFromFS(ccname, ccver)
assert.Error(t, err)

// create a dep spec to put
ds, err := getDepSpec(ccname, ccpath, ccver, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")})
assert.NoError(t, err)

// put it
err = PutChaincodeIntoFS(ds)
assert.NoError(t, err)

// expect it to be in the cache
cd, err := GetChaincodeFromFS(ccname, ccver)
assert.NoError(t, err)
assert.NotNil(t, cd)
}

var ccinfocachetestpath = "/tmp/ccinfocachetest"

func TestMain(m *testing.M) {
os.RemoveAll(ccinfocachetestpath)
defer os.RemoveAll(ccinfocachetestpath)

SetChaincodesPath(ccinfocachetestpath)
os.Exit(m.Run())
}
84 changes: 80 additions & 4 deletions core/common/ccprovider/ccprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,24 @@ func ChaincodePackageExists(ccname string, ccversion string) (bool, error) {
return false, err
}

// CCInfoProvider is responsible to provide backend storage for information
// about chaincodes. Multiple implementations can persist data to a file system
// or store them in a cache
type CCInfoProvider interface {
// GetChaincode returns information for the chaincode with the
// supplied name and version
GetChaincode(ccname string, ccversion string) (CCPackage, error)

// PutChaincode stores the supplied chaincode info
PutChaincode(depSpec *pb.ChaincodeDeploymentSpec) (CCPackage, error)
}

// CCInfoFSStorageMgr is an implementation of CCInfoProvider
// backed by the file system
type CCInfoFSImpl struct{}

// GetChaincodeFromFS this is a wrapper for hiding package implementation.
func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) {
func (*CCInfoFSImpl) GetChaincode(ccname string, ccversion string) (CCPackage, error) {
//try raw CDS
cccdspack := &CDSPackage{}
_, _, err := cccdspack.InitFromFS(ccname, ccversion)
Expand All @@ -129,16 +145,76 @@ func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) {

// PutChaincodeIntoFS is a wrapper for putting raw ChaincodeDeploymentSpec
//using CDSPackage. This is only used in UTs
func PutChaincodeIntoFS(depSpec *pb.ChaincodeDeploymentSpec) error {
func (*CCInfoFSImpl) PutChaincode(depSpec *pb.ChaincodeDeploymentSpec) (CCPackage, error) {
buf, err := proto.Marshal(depSpec)
if err != nil {
return err
return nil, err
}
cccdspack := &CDSPackage{}
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
return nil, err
}
err = cccdspack.PutChaincodeToFS()
if err != nil {
return nil, err
}

return cccdspack, nil
}

// The following lines create the cache of CCPackage data that sits
// on top of the file system and avoids a trip to the file system
// every time. The cache is disabled by default and only enabled
// if EnableCCInfoCache is called. This is an unfortunate hack
// required by some legacy tests that remove chaincode packages
// from the file system as a means of simulating particular test
// conditions. This way of testing is incompatible with the
// immutable nature of chaincode packages that is assumed by hlf v1
// and implemented by this cache. For this reason, tests are for now
// allowed to run with the cache disabled (unless they enable it)
// until a later time in which they are fixed. The peer process on
// the other hand requires the benefits of this cache and therefore
// enables it.
// TODO: (post v1) enable cache by default as soon as https://jira.hyperledger.org/browse/FAB-3785 is completed

// ccInfoFSStorageMgr is the storage manager used either by the cache or if the
// cache is bypassed
var ccInfoFSProvider = &CCInfoFSImpl{}

// ccInfoCache is the cache instance itself
var ccInfoCache = NewCCInfoCache(ccInfoFSProvider)

// ccInfoCacheEnabled keeps track of whether the cache is enable
// (it is disabled by default)
var ccInfoCacheEnabled bool

// EnableCCInfoCache can be called to enable the cache
func EnableCCInfoCache() {
ccInfoCacheEnabled = true
}

// GetChaincodeFromFS retrieves chaincode information from the cache (or the
// file system in case of a cache miss) if the cache is enabled, or directly
// from the file system otherwise
func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) {
if ccInfoCacheEnabled {
return ccInfoCache.GetChaincode(ccname, ccversion)
} else {
return ccInfoFSProvider.GetChaincode(ccname, ccversion)
}
}

// PutChaincodeIntoFS puts chaincode information in the file system (and
// also in the cache to prime it) if the cache is enabled, or directly
// from the file system otherwise
func PutChaincodeIntoFS(depSpec *pb.ChaincodeDeploymentSpec) error {
if ccInfoCacheEnabled {
_, err := ccInfoCache.PutChaincode(depSpec)
return err
} else {
_, err := ccInfoFSProvider.PutChaincode(depSpec)
return err
}
return cccdspack.PutChaincodeToFS()
}

// GetCCPackage tries each known package implementation one by one
Expand Down
Loading

0 comments on commit 341ac6c

Please sign in to comment.