forked from hyperledger/fabric
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ccprovider.go
418 lines (348 loc) · 13.5 KB
/
ccprovider.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package ccprovider
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"unicode"
"github.com/golang/protobuf/proto"
pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/bccsp/factory"
"github.com/hyperledger/fabric/common/chaincode"
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/core/common/privdata"
"github.com/hyperledger/fabric/core/ledger"
"github.com/pkg/errors"
)
var ccproviderLogger = flogging.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)
// PutChaincodeToFS writes the chaincode to the filesystem
PutChaincodeToFS() error
// GetDepSpec gets the ChaincodeDeploymentSpec from the package
GetDepSpec() *pb.ChaincodeDeploymentSpec
// GetDepSpecBytes gets the serialized ChaincodeDeploymentSpec from the package
GetDepSpecBytes() []byte
// 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) error
// GetPackageObject gets the object as a proto.Message
GetPackageObject() proto.Message
// GetChaincodeData gets the ChaincodeData
GetChaincodeData() *ChaincodeData
// GetId gets the fingerprint of the chaincode based on package computation
GetId() []byte
}
// SetChaincodesPath sets the chaincode path for this peer
func SetChaincodesPath(path string) {
if s, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
if err := os.Mkdir(path, 0755); err != nil {
panic(fmt.Sprintf("Could not create chaincodes install path: %s", err))
}
} else {
panic(fmt.Sprintf("Could not stat chaincodes install path: %s", err))
}
} else if !s.IsDir() {
panic(fmt.Errorf("chaincode path exists but not a dir: %s", path))
}
chaincodeInstallPath = path
}
// isPrintable is used by CDSPackage and SignedCDSPackage validation to
// detect garbage strings in unmarshaled proto fields where printable
// characters are expected.
func isPrintable(name string) bool {
notASCII := func(r rune) bool {
return !unicode.IsPrint(r)
}
return strings.IndexFunc(name, notASCII) == -1
}
// GetChaincodePackage returns the chaincode package from the file system
func GetChaincodePackageFromPath(ccNameVersion string, ccInstallPath string) ([]byte, error) {
path := fmt.Sprintf("%s/%s", ccInstallPath, strings.ReplaceAll(ccNameVersion, ":", "."))
var ccbytes []byte
var err error
if ccbytes, err = ioutil.ReadFile(path); err != nil {
return nil, err
}
return ccbytes, nil
}
// ChaincodePackageExists returns whether the chaincode package exists in the file system
func ChaincodePackageExists(ccname string, ccversion string) (bool, error) {
path := filepath.Join(chaincodeInstallPath, ccname+"."+ccversion)
_, err := os.Stat(path)
if err == nil {
// chaincodepackage already exists
return true, nil
}
return false, err
}
type CCCacheSupport interface {
// GetChaincode is needed by the cache to get chaincode data
GetChaincode(ccNameVersion string) (CCPackage, error)
}
// CCInfoFSImpl provides the implementation for CC on the FS and the access to it
// It implements CCCacheSupport
type CCInfoFSImpl struct {
GetHasher GetHasher
}
// GetChaincodeFromFS this is a wrapper for hiding package implementation.
// It calls GetChaincodeFromPath with the chaincodeInstallPath
func (cifs *CCInfoFSImpl) GetChaincode(ccNameVersion string) (CCPackage, error) {
return cifs.GetChaincodeFromPath(ccNameVersion, chaincodeInstallPath)
}
func (cifs *CCInfoFSImpl) GetChaincodeCodePackage(ccNameVersion string) ([]byte, error) {
ccpack, err := cifs.GetChaincode(ccNameVersion)
if err != nil {
return nil, err
}
return ccpack.GetDepSpec().CodePackage, nil
}
func (cifs *CCInfoFSImpl) GetChaincodeDepSpec(ccNameVersion string) (*pb.ChaincodeDeploymentSpec, error) {
ccpack, err := cifs.GetChaincode(ccNameVersion)
if err != nil {
return nil, err
}
return ccpack.GetDepSpec(), nil
}
// GetChaincodeFromPath this is a wrapper for hiding package implementation.
func (cifs *CCInfoFSImpl) GetChaincodeFromPath(ccNameVersion string, path string) (CCPackage, error) {
// try raw CDS
cccdspack := &CDSPackage{GetHasher: cifs.GetHasher}
_, _, err := cccdspack.InitFromPath(ccNameVersion, path)
if err != nil {
// try signed CDS
ccscdspack := &SignedCDSPackage{GetHasher: cifs.GetHasher}
_, _, err = ccscdspack.InitFromPath(ccNameVersion, path)
if err != nil {
return nil, err
}
return ccscdspack, nil
}
return cccdspack, nil
}
// GetChaincodeInstallPath returns the path to the installed chaincodes
func (*CCInfoFSImpl) GetChaincodeInstallPath() string {
return chaincodeInstallPath
}
// PutChaincode is a wrapper for putting raw ChaincodeDeploymentSpec
//using CDSPackage. This is only used in UTs
func (cifs *CCInfoFSImpl) PutChaincode(depSpec *pb.ChaincodeDeploymentSpec) (CCPackage, error) {
buf, err := proto.Marshal(depSpec)
if err != nil {
return nil, err
}
cccdspack := &CDSPackage{GetHasher: cifs.GetHasher}
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
return nil, err
}
err = cccdspack.PutChaincodeToFS()
if err != nil {
return nil, err
}
return cccdspack, nil
}
// DirEnumerator enumerates directories
type DirEnumerator func(string) ([]os.FileInfo, error)
// ChaincodeExtractor extracts chaincode from a given path
type ChaincodeExtractor func(ccNameVersion string, path string, getHasher GetHasher) (CCPackage, error)
// ListInstalledChaincodes retrieves the installed chaincodes
func (cifs *CCInfoFSImpl) ListInstalledChaincodes(dir string, ls DirEnumerator, ccFromPath ChaincodeExtractor) ([]chaincode.InstalledChaincode, error) {
var chaincodes []chaincode.InstalledChaincode
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
return nil, nil
}
files, err := ls(dir)
if err != nil {
return nil, errors.Wrapf(err, "failed reading directory %s", dir)
}
for _, f := range files {
// Skip directories, we're only interested in normal files
if f.IsDir() {
continue
}
// A chaincode file name is of the type "name.version"
// We're only interested in the name.
// Skip files that don't adhere to the file naming convention of "A.B"
i := strings.Index(f.Name(), ".")
if i == -1 {
ccproviderLogger.Info("Skipping", f.Name(), "because of missing separator '.'")
continue
}
ccName := f.Name()[:i] // Everything before the separator
ccVersion := f.Name()[i+1:] // Everything after the separator
ccPackage, err := ccFromPath(ccName+":"+ccVersion, dir, cifs.GetHasher)
if err != nil {
ccproviderLogger.Warning("Failed obtaining chaincode information about", ccName, ccVersion, ":", err)
return nil, errors.Wrapf(err, "failed obtaining information about %s, version %s", ccName, ccVersion)
}
chaincodes = append(chaincodes, chaincode.InstalledChaincode{
Name: ccName,
Version: ccVersion,
Hash: ccPackage.GetId(),
})
}
ccproviderLogger.Debug("Returning", chaincodes)
return chaincodes, nil
}
// ccInfoFSStorageMgr is the storage manager used either by the cache or if the
// cache is bypassed
var ccInfoFSProvider = &CCInfoFSImpl{GetHasher: factory.GetDefault()}
// ccInfoCache is the cache instance itself
var ccInfoCache = NewCCInfoCache(ccInfoFSProvider)
// GetChaincodeFromFS retrieves chaincode information from the file system
func GetChaincodeFromFS(ccNameVersion string) (CCPackage, error) {
return ccInfoFSProvider.GetChaincode(ccNameVersion)
}
// GetChaincodeData gets chaincode data from cache if there's one
func GetChaincodeData(ccNameVersion string) (*ChaincodeData, error) {
ccproviderLogger.Debugf("Getting chaincode data for <%s> from cache", ccNameVersion)
return ccInfoCache.GetChaincodeData(ccNameVersion)
}
// GetCCPackage tries each known package implementation one by one
// till the right package is found
func GetCCPackage(buf []byte, bccsp bccsp.BCCSP) (CCPackage, error) {
// try raw CDS
cds := &CDSPackage{GetHasher: bccsp}
if ccdata, err := cds.InitFromBuffer(buf); err != nil {
cds = nil
} else {
err = cds.ValidateCC(ccdata)
if err != nil {
cds = nil
}
}
// try signed CDS
scds := &SignedCDSPackage{GetHasher: bccsp}
if ccdata, err := scds.InitFromBuffer(buf); err != nil {
scds = nil
} else {
err = scds.ValidateCC(ccdata)
if err != nil {
scds = nil
}
}
if cds != nil && scds != nil {
// Both were unmarshaled successfully, this is exactly why the approach of
// hoping proto fails for bad inputs is fatally flawed.
ccproviderLogger.Errorf("Could not determine chaincode package type, guessing SignedCDS")
return scds, nil
}
if cds != nil {
return cds, nil
}
if scds != nil {
return scds, nil
}
return nil, errors.New("could not unmarshal chaincode package to CDS or SignedCDS")
}
// GetInstalledChaincodes returns a map whose key is the chaincode id and
// value is the ChaincodeDeploymentSpec struct for that chaincodes that have
// been installed (but not necessarily instantiated) on the peer by searching
// the chaincode install path
func GetInstalledChaincodes() (*pb.ChaincodeQueryResponse, error) {
files, err := ioutil.ReadDir(chaincodeInstallPath)
if err != nil {
return nil, err
}
// array to store info for all chaincode entries from LSCC
var ccInfoArray []*pb.ChaincodeInfo
for _, file := range files {
// split at first period as chaincode versions can contain periods while
// chaincode names cannot
fileNameArray := strings.SplitN(file.Name(), ".", 2)
// check that length is 2 as expected, otherwise skip to next cc file
if len(fileNameArray) == 2 {
ccname := fileNameArray[0]
ccversion := fileNameArray[1]
ccpack, err := GetChaincodeFromFS(ccname + ":" + ccversion)
if err != nil {
// either chaincode on filesystem has been tampered with or
// _lifecycle chaincode files exist in the chaincodes directory.
continue
}
cdsfs := ccpack.GetDepSpec()
name := cdsfs.GetChaincodeSpec().GetChaincodeId().Name
version := cdsfs.GetChaincodeSpec().GetChaincodeId().Version
if name != ccname || version != ccversion {
// chaincode name/version in the chaincode file name has been modified
// by an external entity
ccproviderLogger.Errorf("Chaincode file's name/version has been modified on the filesystem: %s", file.Name())
continue
}
path := cdsfs.GetChaincodeSpec().ChaincodeId.Path
// since this is just an installed chaincode these should be blank
input, escc, vscc := "", "", ""
ccInfo := &pb.ChaincodeInfo{Name: name, Version: version, Path: path, Input: input, Escc: escc, Vscc: vscc, Id: ccpack.GetId()}
// 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}
return cqr, nil
}
//-------- ChaincodeData is stored on the LSCC -------
// ChaincodeData defines the datastructure for chaincodes to be serialized by proto
// Type provides an additional check by directing to use a specific package after instantiation
// Data is Type specific (see CDSPackage and SignedCDSPackage)
type ChaincodeData struct {
// Name of the chaincode
Name string `protobuf:"bytes,1,opt,name=name"`
// Version of the chaincode
Version string `protobuf:"bytes,2,opt,name=version"`
// Escc for the chaincode instance
Escc string `protobuf:"bytes,3,opt,name=escc"`
// Vscc for the chaincode instance
Vscc string `protobuf:"bytes,4,opt,name=vscc"`
// Policy endorsement policy for the chaincode instance
Policy []byte `protobuf:"bytes,5,opt,name=policy,proto3"`
// Data data specific to the package
Data []byte `protobuf:"bytes,6,opt,name=data,proto3"`
// Id of the chaincode that's the unique fingerprint for the CC This is not
// currently used anywhere but serves as a good eyecatcher
Id []byte `protobuf:"bytes,7,opt,name=id,proto3"`
// InstantiationPolicy for the chaincode
InstantiationPolicy []byte `protobuf:"bytes,8,opt,name=instantiation_policy,proto3"`
}
// ChaincodeID is the name by which the chaincode will register itself.
func (cd *ChaincodeData) ChaincodeID() string {
return cd.Name + ":" + cd.Version
}
// implement functions needed from proto.Message for proto's mar/unmarshal functions
// Reset resets
func (cd *ChaincodeData) Reset() { *cd = ChaincodeData{} }
// String converts to string
func (cd *ChaincodeData) String() string { return proto.CompactTextString(cd) }
// ProtoMessage just exists to make proto happy
func (*ChaincodeData) ProtoMessage() {}
// TransactionParams are parameters which are tied to a particular transaction
// and which are required for invoking chaincode.
type TransactionParams struct {
TxID string
ChannelID string
NamespaceID string
SignedProp *pb.SignedProposal
Proposal *pb.Proposal
TXSimulator ledger.TxSimulator
HistoryQueryExecutor ledger.HistoryQueryExecutor
CollectionStore privdata.CollectionStore
IsInitTransaction bool
// this is additional data passed to the chaincode
ProposalDecorations map[string][]byte
}