Skip to content

Commit 341ac6c

Browse files
committed
[FAB-3156] check correctness of instant'n policy
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 <[email protected]>
1 parent 41f80f9 commit 341ac6c

File tree

6 files changed

+421
-11
lines changed

6 files changed

+421
-11
lines changed

core/common/ccprovider/ccinfocache.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
package ccprovider
18+
19+
import (
20+
"fmt"
21+
"sync"
22+
23+
"github.com/hyperledger/fabric/protos/peer"
24+
)
25+
26+
// ccInfoCacheImpl implements CCInfoProvider by providing an in-memory
27+
// cache layer on top of the internal CCInfoProvider instance
28+
type ccInfoCacheImpl struct {
29+
sync.RWMutex
30+
31+
cache map[string]CCPackage
32+
ccfs CCInfoProvider
33+
}
34+
35+
// NewCCInfoCache returns a new cache on top of the supplied CCInfoProvider instance
36+
func NewCCInfoCache(ccfs CCInfoProvider) CCInfoProvider {
37+
return &ccInfoCacheImpl{
38+
cache: make(map[string]CCPackage),
39+
ccfs: ccfs,
40+
}
41+
}
42+
43+
func (c *ccInfoCacheImpl) GetChaincode(ccname string, ccversion string) (CCPackage, error) {
44+
// c.cache is guaranteed to be non-nil
45+
46+
key := ccname + "/" + ccversion
47+
48+
c.RLock()
49+
ccpack, in := c.cache[key]
50+
c.RUnlock()
51+
52+
if !in {
53+
var err error
54+
55+
// the chaincode data is not in the cache
56+
// try to look it up from the file system
57+
ccpack, err = c.ccfs.GetChaincode(ccname, ccversion)
58+
if err != nil || ccpack == nil {
59+
return nil, fmt.Errorf("cannot retrieve package for chaincode %s/%s, error %s", ccname, ccversion, err)
60+
}
61+
62+
// we have a non-nil CCPackage, put it in the cache
63+
c.Lock()
64+
c.cache[key] = ccpack
65+
c.Unlock()
66+
}
67+
68+
return ccpack, nil
69+
}
70+
71+
func (c *ccInfoCacheImpl) PutChaincode(depSpec *peer.ChaincodeDeploymentSpec) (CCPackage, error) {
72+
// c.cache is guaranteed to be non-nil
73+
74+
ccname := depSpec.ChaincodeSpec.ChaincodeId.Name
75+
ccversion := depSpec.ChaincodeSpec.ChaincodeId.Version
76+
77+
if ccname == "" {
78+
return nil, fmt.Errorf("the chaincode name cannot be an emoty string")
79+
}
80+
81+
if ccversion == "" {
82+
return nil, fmt.Errorf("the chaincode version cannot be an emoty string")
83+
}
84+
85+
key := ccname + "/" + ccversion
86+
87+
c.RLock()
88+
_, in := c.cache[key]
89+
c.RUnlock()
90+
91+
if in {
92+
return nil, fmt.Errorf("attempted to put chaincode data twice for %s/%s", ccname, ccversion)
93+
}
94+
95+
ccpack, err := c.ccfs.PutChaincode(depSpec)
96+
if err != nil || ccpack == nil {
97+
return nil, fmt.Errorf("PutChaincodeIntoFS failed, error %s", err)
98+
}
99+
100+
// we have a non-nil CCPackage, put it in the cache
101+
c.Lock()
102+
c.cache[key] = ccpack
103+
c.Unlock()
104+
105+
return ccpack, nil
106+
}
+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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+
package ccprovider
18+
19+
import (
20+
"archive/tar"
21+
"bytes"
22+
"compress/gzip"
23+
"testing"
24+
25+
"os"
26+
27+
"github.com/golang/protobuf/proto"
28+
"github.com/hyperledger/fabric/core/container/util"
29+
"github.com/hyperledger/fabric/protos/peer"
30+
"github.com/stretchr/testify/assert"
31+
)
32+
33+
func getDepSpec(name string, path string, version string, initArgs [][]byte) (*peer.ChaincodeDeploymentSpec, error) {
34+
spec := &peer.ChaincodeSpec{Type: 1, ChaincodeId: &peer.ChaincodeID{Name: name, Path: path, Version: version}, Input: &peer.ChaincodeInput{Args: initArgs}}
35+
36+
codePackageBytes := bytes.NewBuffer(nil)
37+
gz := gzip.NewWriter(codePackageBytes)
38+
tw := tar.NewWriter(gz)
39+
40+
err := util.WriteBytesToPackage("src/garbage.go", []byte(name+path+version), tw)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
tw.Close()
46+
gz.Close()
47+
48+
return &peer.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes.Bytes()}, nil
49+
}
50+
51+
func buildPackage(name string, path string, version string, initArgs [][]byte) (CCPackage, error) {
52+
depSpec, err := getDepSpec(name, path, version, initArgs)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
buf, err := proto.Marshal(depSpec)
58+
if err != nil {
59+
return nil, err
60+
}
61+
cccdspack := &CDSPackage{}
62+
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
63+
return nil, err
64+
}
65+
66+
return cccdspack, nil
67+
}
68+
69+
type mockCCInfoFSStorageMgrImpl struct {
70+
CCMap map[string]CCPackage
71+
}
72+
73+
func (m *mockCCInfoFSStorageMgrImpl) PutChaincode(depSpec *peer.ChaincodeDeploymentSpec) (CCPackage, error) {
74+
buf, err := proto.Marshal(depSpec)
75+
if err != nil {
76+
return nil, err
77+
}
78+
cccdspack := &CDSPackage{}
79+
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
80+
return nil, err
81+
}
82+
83+
m.CCMap[depSpec.ChaincodeSpec.ChaincodeId.Name+depSpec.ChaincodeSpec.ChaincodeId.Version] = cccdspack
84+
85+
return cccdspack, nil
86+
}
87+
88+
func (m *mockCCInfoFSStorageMgrImpl) GetChaincode(ccname string, ccversion string) (CCPackage, error) {
89+
return m.CCMap[ccname+ccversion], nil
90+
}
91+
92+
// here we test the cache implementation itself
93+
func TestCCInfoCache(t *testing.T) {
94+
ccname := "foo"
95+
ccver := "1.0"
96+
ccpath := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02"
97+
98+
ccinfoFs := &mockCCInfoFSStorageMgrImpl{CCMap: map[string]CCPackage{}}
99+
cccache := NewCCInfoCache(ccinfoFs)
100+
101+
// test the get side
102+
103+
// the cc data is not yet in the cache
104+
_, err := cccache.GetChaincode(ccname, ccver)
105+
assert.Error(t, err)
106+
107+
// put it in the file system
108+
pack, err := buildPackage(ccname, ccpath, ccver, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")})
109+
assert.NoError(t, err)
110+
ccinfoFs.CCMap[ccname+ccver] = pack
111+
112+
// expect it to be in the cache now
113+
cd1, err := cccache.GetChaincode(ccname, ccver)
114+
assert.NoError(t, err)
115+
116+
// it should still be in the cache
117+
cd2, err := cccache.GetChaincode(ccname, ccver)
118+
assert.NoError(t, err)
119+
120+
// they are not null
121+
assert.NotNil(t, cd1)
122+
assert.NotNil(t, cd2)
123+
124+
// test the put side now..
125+
ccver = "2.0"
126+
127+
// create a dep spec to put
128+
ds, err := getDepSpec(ccname, ccpath, ccver, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")})
129+
assert.NoError(t, err)
130+
131+
// put it
132+
_, err = cccache.PutChaincode(ds)
133+
assert.NoError(t, err)
134+
135+
// expect it to be in the cache
136+
cd1, err = cccache.GetChaincode(ccname, ccver)
137+
assert.NoError(t, err)
138+
139+
// it should still be in the cache
140+
cd2, err = cccache.GetChaincode(ccname, ccver)
141+
assert.NoError(t, err)
142+
143+
// they are not null
144+
assert.NotNil(t, cd1)
145+
assert.NotNil(t, cd2)
146+
}
147+
148+
// here we test the peer's built-in cache after enabling it
149+
func TestCCInfoCachePeerInstance(t *testing.T) {
150+
// enable the cache first: it's disabled by default
151+
EnableCCInfoCache()
152+
153+
ccname := "foo"
154+
ccver := "1.0"
155+
ccpath := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02"
156+
157+
// the cc data is not yet in the cache
158+
_, err := GetChaincodeFromFS(ccname, ccver)
159+
assert.Error(t, err)
160+
161+
// create a dep spec to put
162+
ds, err := getDepSpec(ccname, ccpath, ccver, [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")})
163+
assert.NoError(t, err)
164+
165+
// put it
166+
err = PutChaincodeIntoFS(ds)
167+
assert.NoError(t, err)
168+
169+
// expect it to be in the cache
170+
cd, err := GetChaincodeFromFS(ccname, ccver)
171+
assert.NoError(t, err)
172+
assert.NotNil(t, cd)
173+
}
174+
175+
var ccinfocachetestpath = "/tmp/ccinfocachetest"
176+
177+
func TestMain(m *testing.M) {
178+
os.RemoveAll(ccinfocachetestpath)
179+
defer os.RemoveAll(ccinfocachetestpath)
180+
181+
SetChaincodesPath(ccinfocachetestpath)
182+
os.Exit(m.Run())
183+
}

core/common/ccprovider/ccprovider.go

+80-4
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,24 @@ func ChaincodePackageExists(ccname string, ccversion string) (bool, error) {
110110
return false, err
111111
}
112112

113+
// CCInfoProvider is responsible to provide backend storage for information
114+
// about chaincodes. Multiple implementations can persist data to a file system
115+
// or store them in a cache
116+
type CCInfoProvider interface {
117+
// GetChaincode returns information for the chaincode with the
118+
// supplied name and version
119+
GetChaincode(ccname string, ccversion string) (CCPackage, error)
120+
121+
// PutChaincode stores the supplied chaincode info
122+
PutChaincode(depSpec *pb.ChaincodeDeploymentSpec) (CCPackage, error)
123+
}
124+
125+
// CCInfoFSStorageMgr is an implementation of CCInfoProvider
126+
// backed by the file system
127+
type CCInfoFSImpl struct{}
128+
113129
// GetChaincodeFromFS this is a wrapper for hiding package implementation.
114-
func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) {
130+
func (*CCInfoFSImpl) GetChaincode(ccname string, ccversion string) (CCPackage, error) {
115131
//try raw CDS
116132
cccdspack := &CDSPackage{}
117133
_, _, err := cccdspack.InitFromFS(ccname, ccversion)
@@ -129,16 +145,76 @@ func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) {
129145

130146
// PutChaincodeIntoFS is a wrapper for putting raw ChaincodeDeploymentSpec
131147
//using CDSPackage. This is only used in UTs
132-
func PutChaincodeIntoFS(depSpec *pb.ChaincodeDeploymentSpec) error {
148+
func (*CCInfoFSImpl) PutChaincode(depSpec *pb.ChaincodeDeploymentSpec) (CCPackage, error) {
133149
buf, err := proto.Marshal(depSpec)
134150
if err != nil {
135-
return err
151+
return nil, err
136152
}
137153
cccdspack := &CDSPackage{}
138154
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
155+
return nil, err
156+
}
157+
err = cccdspack.PutChaincodeToFS()
158+
if err != nil {
159+
return nil, err
160+
}
161+
162+
return cccdspack, nil
163+
}
164+
165+
// The following lines create the cache of CCPackage data that sits
166+
// on top of the file system and avoids a trip to the file system
167+
// every time. The cache is disabled by default and only enabled
168+
// if EnableCCInfoCache is called. This is an unfortunate hack
169+
// required by some legacy tests that remove chaincode packages
170+
// from the file system as a means of simulating particular test
171+
// conditions. This way of testing is incompatible with the
172+
// immutable nature of chaincode packages that is assumed by hlf v1
173+
// and implemented by this cache. For this reason, tests are for now
174+
// allowed to run with the cache disabled (unless they enable it)
175+
// until a later time in which they are fixed. The peer process on
176+
// the other hand requires the benefits of this cache and therefore
177+
// enables it.
178+
// TODO: (post v1) enable cache by default as soon as https://jira.hyperledger.org/browse/FAB-3785 is completed
179+
180+
// ccInfoFSStorageMgr is the storage manager used either by the cache or if the
181+
// cache is bypassed
182+
var ccInfoFSProvider = &CCInfoFSImpl{}
183+
184+
// ccInfoCache is the cache instance itself
185+
var ccInfoCache = NewCCInfoCache(ccInfoFSProvider)
186+
187+
// ccInfoCacheEnabled keeps track of whether the cache is enable
188+
// (it is disabled by default)
189+
var ccInfoCacheEnabled bool
190+
191+
// EnableCCInfoCache can be called to enable the cache
192+
func EnableCCInfoCache() {
193+
ccInfoCacheEnabled = true
194+
}
195+
196+
// GetChaincodeFromFS retrieves chaincode information from the cache (or the
197+
// file system in case of a cache miss) if the cache is enabled, or directly
198+
// from the file system otherwise
199+
func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) {
200+
if ccInfoCacheEnabled {
201+
return ccInfoCache.GetChaincode(ccname, ccversion)
202+
} else {
203+
return ccInfoFSProvider.GetChaincode(ccname, ccversion)
204+
}
205+
}
206+
207+
// PutChaincodeIntoFS puts chaincode information in the file system (and
208+
// also in the cache to prime it) if the cache is enabled, or directly
209+
// from the file system otherwise
210+
func PutChaincodeIntoFS(depSpec *pb.ChaincodeDeploymentSpec) error {
211+
if ccInfoCacheEnabled {
212+
_, err := ccInfoCache.PutChaincode(depSpec)
213+
return err
214+
} else {
215+
_, err := ccInfoFSProvider.PutChaincode(depSpec)
139216
return err
140217
}
141-
return cccdspack.PutChaincodeToFS()
142218
}
143219

144220
// GetCCPackage tries each known package implementation one by one

0 commit comments

Comments
 (0)