-
Notifications
You must be signed in to change notification settings - Fork 0
/
ccprovider.go
504 lines (416 loc) · 17.7 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
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package ccprovider
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/core/ledger"
pb "github.com/hyperledger/fabric/protos/peer"
)
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)
// InitFromFS gets the chaincode from the filesystem (includes the raw bytes too)
InitFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, 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
}
func GetChaincodePackage(ccname string, ccversion string) ([]byte, error) {
return GetChaincodePackageFromPath(ccname, ccversion, chaincodeInstallPath)
}
// GetChaincodePackage returns the chaincode package from the file system
func GetChaincodePackageFromPath(ccname string, ccversion string, ccInstallPath string) ([]byte, error) {
path := fmt.Sprintf("%s/%s.%s", ccInstallPath, ccname, ccversion)
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(ccname string, ccversion string) (CCPackage, error)
}
// CCInfoFSImpl provides the implementation for CC on the FS and the access to it
// It implements CCCacheSupport
type CCInfoFSImpl struct{}
// GetChaincodeFromFS this is a wrapper for hiding package implementation.
// It calls GetChaincodeFromPath with the chaincodeInstallPath
func (cifs *CCInfoFSImpl) GetChaincode(ccname string, ccversion string) (CCPackage, error) {
return cifs.GetChaincodeFromPath(ccname, ccversion, chaincodeInstallPath)
}
// GetChaincodeFromPath this is a wrapper for hiding package implementation.
func (*CCInfoFSImpl) GetChaincodeFromPath(ccname string, ccversion string, path string) (CCPackage, error) {
// try raw CDS
cccdspack := &CDSPackage{}
_, _, err := cccdspack.InitFromPath(ccname, ccversion, path)
if err != nil {
// try signed CDS
ccscdspack := &SignedCDSPackage{}
_, _, err = ccscdspack.InitFromPath(ccname, ccversion, path)
if err != nil {
return nil, err
}
return ccscdspack, nil
}
return cccdspack, nil
}
// PutChaincodeIntoFS is a wrapper for putting raw ChaincodeDeploymentSpec
//using CDSPackage. This is only used in UTs
func (*CCInfoFSImpl) PutChaincode(depSpec *pb.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
}
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 file system
func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) {
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 {
_, err := ccInfoFSProvider.PutChaincode(depSpec)
return err
}
// GetChaincodeData gets chaincode data from cache if there's one
func GetChaincodeData(ccname string, ccversion string) (*ChaincodeData, error) {
if ccInfoCacheEnabled {
ccproviderLogger.Debugf("Getting chaincode data for <%s, %s> from cache", ccname, ccversion)
return ccInfoCache.GetChaincodeData(ccname, ccversion)
}
if ccpack, err := ccInfoFSProvider.GetChaincode(ccname, ccversion); err != nil {
return nil, err
} else {
ccproviderLogger.Infof("Putting chaincode data for <%s, %s> into cache", ccname, ccversion)
return ccpack.GetChaincodeData(), nil
}
}
func CheckInstantiationPolicy(name, version string, cdLedger *ChaincodeData) error {
ccdata, err := GetChaincodeData(name, version)
if err != nil {
return err
}
// we have the info from the fs, check that the policy
// matches the one on the file system if one was specified;
// this check is required because the admin of this peer
// might have specified instantiation policies for their
// chaincode, for example to make sure that the chaincode
// is only instantiated on certain channels; a malicious
// peer on the other hand might have created a deploy
// transaction that attempts to bypass the instantiation
// policy. This check is there to ensure that this will not
// happen, i.e. that the peer will refuse to invoke the
// chaincode under these conditions. More info on
// https://jira.hyperledger.org/browse/FAB-3156
if ccdata.InstantiationPolicy != nil {
if !bytes.Equal(ccdata.InstantiationPolicy, cdLedger.InstantiationPolicy) {
return fmt.Errorf("Instantiation policy mismatch for cc %s/%s", name, version)
}
}
return nil
}
// GetCCPackage tries each known package implementation one by one
// till the right package is found
func GetCCPackage(buf []byte) (CCPackage, error) {
// try raw CDS
cccdspack := &CDSPackage{}
if _, err := cccdspack.InitFromBuffer(buf); err != nil {
// try signed CDS
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
// 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
// a non-chaincode file has been found in the chaincodes directory
ccproviderLogger.Errorf("Unreadable chaincode file found on filesystem: %s", file.Name())
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
}
// CCContext pass this around instead of string of args
type CCContext struct {
// ChainID chain id
ChainID string
// Name chaincode name
Name string
// Version used to construct the chaincode image and register
Version string
// TxID is the transaction id for the proposal (if any)
TxID string
// Syscc is this a system chaincode
Syscc bool
// SignedProposal for this invoke (if any) this is kept here for access
// control and in case we need to pass something from this to the chaincode
SignedProposal *pb.SignedProposal
// Proposal for this invoke (if any) this is kept here just in case we need to
// pass something from this to the chaincode
Proposal *pb.Proposal
// canonicalName is not set but computed
canonicalName string
// this is additional data passed to the chaincode
ProposalDecorations map[string][]byte
}
// NewCCContext just construct a new struct with whatever args
func NewCCContext(cname, name, version, txid string, syscc bool, signedProp *pb.SignedProposal, prop *pb.Proposal) *CCContext {
cccid := &CCContext{
ChainID: cname,
Name: name,
Version: version,
TxID: txid,
Syscc: syscc,
SignedProposal: signedProp,
Proposal: prop,
canonicalName: name + ":" + version,
ProposalDecorations: nil,
}
// The version CANNOT be empty. The chaincode namespace has to use version and chain name.
// Note that neither channel name nor version are stored on the ledger.
if version == "" {
panic(fmt.Sprintf("---empty version---(%s)", cccid))
}
ccproviderLogger.Debugf("NewCCCC(%s)", cccid)
return cccid
}
func (cccid *CCContext) String() string {
return fmt.Sprintf("chain=%s,chaincode=%s,version=%s,txid=%s,syscc=%t,proposal=%p,canname=%s",
cccid.ChainID, cccid.Name, cccid.Version, cccid.TxID, cccid.Syscc, cccid.Proposal, cccid.canonicalName)
}
// GetCanonicalName returns the canonical name associated with the proposal context
func (cccid *CCContext) GetCanonicalName() string {
if cccid.canonicalName == "" {
panic(fmt.Sprintf("missing canonical name: %s", cccid))
}
return cccid.canonicalName
}
//-------- ChaincodeDefinition - interface for ChaincodeData ------
// ChaincodeDefinition describes all of the necessary information for a peer to decide whether to endorse
// a proposal and whether to validate a transaction, for a particular chaincode.
type ChaincodeDefinition interface {
// CCName returns the name of this chaincode (the name it was put in the ChaincodeRegistry with).
CCName() string
// Hash returns the hash of the chaincode.
Hash() []byte
// CCVersion returns the version of the chaincode.
CCVersion() string
// Validation returns how to validate transactions for this chaincode.
// The string returned is the name of the validation method (usually 'vscc')
// and the bytes returned are the argument to the validation (in the case of
// 'vscc', this is a marshaled pb.VSCCArgs message).
Validation() (string, []byte)
// Endorsement returns how to endorse proposals for this chaincode.
// The string returns is the name of the endorsement method (usually 'escc').
Endorsement() string
}
//-------- 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 specifc (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"`
}
// CCName returns the name of this chaincode (the name it was put in the ChaincodeRegistry with).
func (cd *ChaincodeData) CCName() string {
return cd.Name
}
// Hash returns the hash of the chaincode.
func (cd *ChaincodeData) Hash() []byte {
return cd.Id
}
// CCVersion returns the version of the chaincode.
func (cd *ChaincodeData) CCVersion() string {
return cd.Version
}
// Validation returns how to validate transactions for this chaincode.
// The string returned is the name of the validation method (usually 'vscc')
// and the bytes returned are the argument to the validation (in the case of
// 'vscc', this is a marshaled pb.VSCCArgs message).
func (cd *ChaincodeData) Validation() (string, []byte) {
return cd.Vscc, cd.Policy
}
// Endorsement returns how to endorse proposals for this chaincode.
// The string returns is the name of the endorsement method (usually 'escc').
func (cd *ChaincodeData) Endorsement() string {
return cd.Escc
}
// 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() {}
// ChaincodeSpecGetter normalizes getting a chaincode spec from an
// ChaincodeInvocationSpec or a ChaincodeDeploymentSpec.
type ChaincodeSpecGetter interface {
GetChaincodeSpec() *pb.ChaincodeSpec
}
// ChaincodeProvider provides an abstraction layer that is
// used for different packages to interact with code in the
// chaincode package without importing it; more methods
// should be added below if necessary
type ChaincodeProvider interface {
// GetContext returns a ledger context and a tx simulator; it's the
// caller's responsability to release the simulator by calling its
// done method once it is no longer useful
GetContext(ledger ledger.PeerLedger, txid string) (context.Context, ledger.TxSimulator, error)
// ExecuteChaincode executes the chaincode given context and args
ExecuteChaincode(ctxt context.Context, cccid *CCContext, args [][]byte) (*pb.Response, *pb.ChaincodeEvent, error)
// Execute executes the chaincode given context and spec (invocation or deploy)
Execute(ctxt context.Context, cccid *CCContext, spec ChaincodeSpecGetter) (*pb.Response, *pb.ChaincodeEvent, error)
// Stop stops the chaincode given context and deployment spec
Stop(ctxt context.Context, cccid *CCContext, spec *pb.ChaincodeDeploymentSpec) error
}