github.com/inklabsfoundation/inkchain@v0.17.1-0.20181025012015-c3cef8062f19/core/common/ccprovider/ccprovider.go (about) 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 "context" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "strings" 26 27 "github.com/golang/protobuf/proto" 28 29 "bytes" 30 31 "github.com/inklabsfoundation/inkchain/common/flogging" 32 "github.com/inklabsfoundation/inkchain/core/ledger" 33 pb "github.com/inklabsfoundation/inkchain/protos/peer" 34 ) 35 36 var ccproviderLogger = flogging.MustGetLogger("ccprovider") 37 38 var chaincodeInstallPath string 39 40 //CCPackage encapsulates a chaincode package which can be 41 // raw ChaincodeDeploymentSpec 42 // SignedChaincodeDeploymentSpec 43 // Attempt to keep the interface at a level with minimal 44 // interface for possible generalization. 45 type CCPackage interface { 46 //InitFromBuffer initialize the package from bytes 47 InitFromBuffer(buf []byte) (*ChaincodeData, error) 48 49 // InitFromFS gets the chaincode from the filesystem (includes the raw bytes too) 50 InitFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) 51 52 // PutChaincodeToFS writes the chaincode to the filesystem 53 PutChaincodeToFS() error 54 55 // GetDepSpec gets the ChaincodeDeploymentSpec from the package 56 GetDepSpec() *pb.ChaincodeDeploymentSpec 57 58 // GetDepSpecBytes gets the serialized ChaincodeDeploymentSpec from the package 59 GetDepSpecBytes() []byte 60 61 // ValidateCC validates and returns the chaincode deployment spec corresponding to 62 // ChaincodeData. The validation is based on the metadata from ChaincodeData 63 // One use of this method is to validate the chaincode before launching 64 ValidateCC(ccdata *ChaincodeData) error 65 66 // GetPackageObject gets the object as a proto.Message 67 GetPackageObject() proto.Message 68 69 // GetChaincodeData gets the ChaincodeData 70 GetChaincodeData() *ChaincodeData 71 72 // GetId gets the fingerprint of the chaincode based on package computation 73 GetId() []byte 74 } 75 76 //SetChaincodesPath sets the chaincode path for this peer 77 func SetChaincodesPath(path string) { 78 if s, err := os.Stat(path); err != nil { 79 if os.IsNotExist(err) { 80 if err := os.Mkdir(path, 0755); err != nil { 81 panic(fmt.Sprintf("Could not create chaincodes install path: %s", err)) 82 } 83 } else { 84 panic(fmt.Sprintf("Could not stat chaincodes install path: %s", err)) 85 } 86 } else if !s.IsDir() { 87 panic(fmt.Errorf("chaincode path exists but not a dir: %s", path)) 88 } 89 90 chaincodeInstallPath = path 91 } 92 93 //GetChaincodePackage returns the chaincode package from the file system 94 func GetChaincodePackage(ccname string, ccversion string) ([]byte, error) { 95 path := fmt.Sprintf("%s/%s.%s", chaincodeInstallPath, ccname, ccversion) 96 var ccbytes []byte 97 var err error 98 if ccbytes, err = ioutil.ReadFile(path); err != nil { 99 return nil, err 100 } 101 return ccbytes, nil 102 } 103 104 //ChaincodePackageExists returns whether the chaincode package exists in the file system 105 func ChaincodePackageExists(ccname string, ccversion string) (bool, error) { 106 path := filepath.Join(chaincodeInstallPath, ccname+"."+ccversion) 107 _, err := os.Stat(path) 108 if err == nil { 109 // chaincodepackage already exists 110 return true, nil 111 } 112 return false, err 113 } 114 115 type CCCacheSupport interface { 116 //GetChaincode is needed by the cache to get chaincode data 117 GetChaincode(ccname string, ccversion string) (CCPackage, error) 118 } 119 120 // CCInfoFSImpl provides the implementation for CC on the FS and the access to it 121 // It implements CCCacheSupport 122 type CCInfoFSImpl struct{} 123 124 // GetChaincodeFromFS this is a wrapper for hiding package implementation. 125 func (*CCInfoFSImpl) GetChaincode(ccname string, ccversion string) (CCPackage, error) { 126 //try raw CDS 127 cccdspack := &CDSPackage{} 128 _, _, err := cccdspack.InitFromFS(ccname, ccversion) 129 if err != nil { 130 //try signed CDS 131 ccscdspack := &SignedCDSPackage{} 132 _, _, err = ccscdspack.InitFromFS(ccname, ccversion) 133 if err != nil { 134 return nil, err 135 } 136 return ccscdspack, nil 137 } 138 return cccdspack, nil 139 } 140 141 // PutChaincodeIntoFS is a wrapper for putting raw ChaincodeDeploymentSpec 142 //using CDSPackage. This is only used in UTs 143 func (*CCInfoFSImpl) PutChaincode(depSpec *pb.ChaincodeDeploymentSpec) (CCPackage, error) { 144 buf, err := proto.Marshal(depSpec) 145 if err != nil { 146 return nil, err 147 } 148 cccdspack := &CDSPackage{} 149 if _, err := cccdspack.InitFromBuffer(buf); err != nil { 150 return nil, err 151 } 152 err = cccdspack.PutChaincodeToFS() 153 if err != nil { 154 return nil, err 155 } 156 157 return cccdspack, nil 158 } 159 160 // The following lines create the cache of CCPackage data that sits 161 // on top of the file system and avoids a trip to the file system 162 // every time. The cache is disabled by default and only enabled 163 // if EnableCCInfoCache is called. This is an unfortunate hack 164 // required by some legacy tests that remove chaincode packages 165 // from the file system as a means of simulating particular test 166 // conditions. This way of testing is incompatible with the 167 // immutable nature of chaincode packages that is assumed by hlf v1 168 // and implemented by this cache. For this reason, tests are for now 169 // allowed to run with the cache disabled (unless they enable it) 170 // until a later time in which they are fixed. The peer process on 171 // the other hand requires the benefits of this cache and therefore 172 // enables it. 173 174 // ccInfoFSStorageMgr is the storage manager used either by the cache or if the 175 // cache is bypassed 176 var ccInfoFSProvider = &CCInfoFSImpl{} 177 178 // ccInfoCache is the cache instance itself 179 var ccInfoCache = NewCCInfoCache(ccInfoFSProvider) 180 181 // ccInfoCacheEnabled keeps track of whether the cache is enable 182 // (it is disabled by default) 183 var ccInfoCacheEnabled bool 184 185 // EnableCCInfoCache can be called to enable the cache 186 func EnableCCInfoCache() { 187 ccInfoCacheEnabled = true 188 } 189 190 // GetChaincodeFromFS retrieves chaincode information from the file system 191 func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) { 192 return ccInfoFSProvider.GetChaincode(ccname, ccversion) 193 } 194 195 // PutChaincodeIntoFS puts chaincode information in the file system (and 196 // also in the cache to prime it) if the cache is enabled, or directly 197 // from the file system otherwise 198 func PutChaincodeIntoFS(depSpec *pb.ChaincodeDeploymentSpec) error { 199 _, err := ccInfoFSProvider.PutChaincode(depSpec) 200 return err 201 } 202 203 // GetChaincodeData gets chaincode data from cache if there's one 204 func GetChaincodeData(ccname string, ccversion string) (*ChaincodeData, error) { 205 if ccInfoCacheEnabled { 206 ccproviderLogger.Debugf("Getting chaincode data for <%s, %s> from cache", ccname, ccversion) 207 return ccInfoCache.GetChaincodeData(ccname, ccversion) 208 } 209 if ccpack, err := ccInfoFSProvider.GetChaincode(ccname, ccversion); err != nil { 210 return nil, err 211 } else { 212 ccproviderLogger.Infof("Putting chaincode data for <%s, %s> into cache", ccname, ccversion) 213 return ccpack.GetChaincodeData(), nil 214 } 215 } 216 217 func CheckInsantiationPolicy(name, version string, cdLedger *ChaincodeData) error { 218 ccdata, err := GetChaincodeData(name, version) 219 if err != nil { 220 return err 221 } 222 223 // we have the info from the fs, check that the policy 224 // matches the one on the file system if one was specified; 225 // this check is required because the admin of this peer 226 // might have specified instantiation policies for their 227 // chaincode, for example to make sure that the chaincode 228 // is only instantiated on certain channels; a malicious 229 // peer on the other hand might have created a deploy 230 // transaction that attempts to bypass the instantiation 231 // policy. This check is there to ensure that this will not 232 // happen, i.e. that the peer will refuse to invoke the 233 // chaincode under these conditions. More info on 234 if ccdata.InstantiationPolicy != nil { 235 if !bytes.Equal(ccdata.InstantiationPolicy, cdLedger.InstantiationPolicy) { 236 return fmt.Errorf("Instantiation policy mismatch for cc %s/%s", name, version) 237 } 238 } 239 240 return nil 241 } 242 243 // GetCCPackage tries each known package implementation one by one 244 // till the right package is found 245 func GetCCPackage(buf []byte) (CCPackage, error) { 246 //try raw CDS 247 cccdspack := &CDSPackage{} 248 if _, err := cccdspack.InitFromBuffer(buf); err != nil { 249 //try signed CDS 250 ccscdspack := &SignedCDSPackage{} 251 if _, err := ccscdspack.InitFromBuffer(buf); err != nil { 252 return nil, err 253 } 254 return ccscdspack, nil 255 } 256 return cccdspack, nil 257 } 258 259 // GetInstalledChaincodes returns a map whose key is the chaincode id and 260 // value is the ChaincodeDeploymentSpec struct for that chaincodes that have 261 // been installed (but not necessarily instantiated) on the peer by searching 262 // the chaincode install path 263 func GetInstalledChaincodes() (*pb.ChaincodeQueryResponse, error) { 264 files, err := ioutil.ReadDir(chaincodeInstallPath) 265 if err != nil { 266 return nil, err 267 } 268 269 // array to store info for all chaincode entries from LSCC 270 var ccInfoArray []*pb.ChaincodeInfo 271 272 for _, file := range files { 273 // split at first period as chaincode versions can contain periods while 274 // chaincode names cannot 275 fileNameArray := strings.SplitN(file.Name(), ".", 2) 276 277 // check that length is 2 as expected, otherwise skip to next cc file 278 if len(fileNameArray) == 2 { 279 ccname := fileNameArray[0] 280 ccversion := fileNameArray[1] 281 ccpack, err := GetChaincodeFromFS(ccname, ccversion) 282 if err != nil { 283 // either chaincode on filesystem has been tampered with or 284 // a non-chaincode file has been found in the chaincodes directory 285 ccproviderLogger.Errorf("Unreadable chaincode file found on filesystem: %s", file.Name()) 286 continue 287 } 288 289 cdsfs := ccpack.GetDepSpec() 290 291 name := cdsfs.GetChaincodeSpec().GetChaincodeId().Name 292 version := cdsfs.GetChaincodeSpec().GetChaincodeId().Version 293 if name != ccname || version != ccversion { 294 // chaincode name/version in the chaincode file name has been modified 295 // by an external entity 296 ccproviderLogger.Errorf("Chaincode file's name/version has been modified on the filesystem: %s", file.Name()) 297 continue 298 } 299 300 path := cdsfs.GetChaincodeSpec().ChaincodeId.Path 301 // since this is just an installed chaincode these should be blank 302 input, escc, vscc := "", "", "" 303 304 ccInfo := &pb.ChaincodeInfo{Name: name, Version: version, Path: path, Input: input, Escc: escc, Vscc: vscc} 305 306 // add this specific chaincode's metadata to the array of all chaincodes 307 ccInfoArray = append(ccInfoArray, ccInfo) 308 } 309 } 310 // add array with info about all instantiated chaincodes to the query 311 // response proto 312 cqr := &pb.ChaincodeQueryResponse{Chaincodes: ccInfoArray} 313 314 return cqr, nil 315 } 316 317 //CCContext pass this around instead of string of args 318 type CCContext struct { 319 //ChainID chain id 320 ChainID string 321 322 //Name chaincode name 323 Name string 324 325 //Version used to construct the chaincode image and register 326 Version string 327 328 //TxID is the transaction id for the proposal (if any) 329 TxID string 330 331 //Syscc is this a system chaincode 332 Syscc bool 333 334 //SignedProposal for this invoke (if any) 335 //this is kept here for access control and in case we need to pass something 336 //from this to the chaincode 337 SignedProposal *pb.SignedProposal 338 339 //Proposal for this invoke (if any) 340 //this is kept here just in case we need to pass something 341 //from this to the chaincode 342 Proposal *pb.Proposal 343 344 //this is not set but computed (note that this is not exported. use GetCanonicalName) 345 canonicalName string 346 } 347 348 //NewCCContext just construct a new struct with whatever args 349 func NewCCContext(cid, name, version, txid string, syscc bool, signedProp *pb.SignedProposal, prop *pb.Proposal) *CCContext { 350 //version CANNOT be empty. The chaincode namespace has to use version and chain name. 351 //All system chaincodes share the same version given by utils.GetSysCCVersion. Note 352 //that neither Chain Name or Version are stored in a chaincodes state on the ledger 353 if version == "" { 354 panic(fmt.Sprintf("---empty version---(chain=%s,chaincode=%s,version=%s,txid=%s,syscc=%t,proposal=%p", cid, name, version, txid, syscc, prop)) 355 } 356 357 canName := name + ":" + version 358 359 cccid := &CCContext{cid, name, version, txid, syscc, signedProp, prop, canName} 360 361 ccproviderLogger.Debugf("NewCCCC (chain=%s,chaincode=%s,version=%s,txid=%s,syscc=%t,proposal=%p,canname=%s", cid, name, version, txid, syscc, prop, cccid.canonicalName) 362 363 return cccid 364 } 365 366 //GetCanonicalName returns the canonical name associated with the proposal context 367 func (cccid *CCContext) GetCanonicalName() string { 368 if cccid.canonicalName == "" { 369 panic(fmt.Sprintf("cccid not constructed using NewCCContext(chain=%s,chaincode=%s,version=%s,txid=%s,syscc=%t)", cccid.ChainID, cccid.Name, cccid.Version, cccid.TxID, cccid.Syscc)) 370 } 371 372 return cccid.canonicalName 373 } 374 375 //-------- ChaincodeData is stored on the LSCC ------- 376 377 //ChaincodeData defines the datastructure for chaincodes to be serialized by proto 378 //Type provides an additional check by directing to use a specific package after instantiation 379 //Data is Type specifc (see CDSPackage and SignedCDSPackage) 380 type ChaincodeData struct { 381 //Name of the chaincode 382 Name string `protobuf:"bytes,1,opt,name=name"` 383 384 //Version of the chaincode 385 Version string `protobuf:"bytes,2,opt,name=version"` 386 387 //Escc for the chaincode instance 388 Escc string `protobuf:"bytes,3,opt,name=escc"` 389 390 //Vscc for the chaincode instance 391 Vscc string `protobuf:"bytes,4,opt,name=vscc"` 392 393 //Policy endorsement policy for the chaincode instance 394 Policy []byte `protobuf:"bytes,5,opt,name=policy,proto3"` 395 396 //Data data specific to the package 397 Data []byte `protobuf:"bytes,6,opt,name=data,proto3"` 398 399 //Id of the chaincode that's the unique fingerprint for the CC 400 //This is not currently used anywhere but serves as a good 401 //eyecatcher 402 Id []byte `protobuf:"bytes,7,opt,name=id,proto3"` 403 404 //InstantiationPolicy for the chaincode 405 InstantiationPolicy []byte `protobuf:"bytes,8,opt,name=instantiation_policy,proto3"` 406 } 407 408 //implement functions needed from proto.Message for proto's mar/unmarshal functions 409 410 //Reset resets 411 func (cd *ChaincodeData) Reset() { *cd = ChaincodeData{} } 412 413 //String converts to string 414 func (cd *ChaincodeData) String() string { return proto.CompactTextString(cd) } 415 416 //ProtoMessage just exists to make proto happy 417 func (*ChaincodeData) ProtoMessage() {} 418 419 // ChaincodeProvider provides an abstraction layer that is 420 // used for different packages to interact with code in the 421 // chaincode package without importing it; more methods 422 // should be added below if necessary 423 type ChaincodeProvider interface { 424 // GetContext returns a ledger context 425 GetContext(ledger ledger.PeerLedger) (context.Context, error) 426 // GetCCContext returns an opaque chaincode context 427 GetCCContext(cid, name, version, txid string, syscc bool, signedProp *pb.SignedProposal, prop *pb.Proposal) interface{} 428 // GetCCValidationInfoFromLSCC returns the VSCC and the policy listed by LSCC for the supplied chaincode 429 GetCCValidationInfoFromLSCC(ctxt context.Context, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, chainID string, chaincodeID string) (string, []byte, error) 430 // ExecuteChaincode executes the chaincode given context and args 431 ExecuteChaincode(ctxt context.Context, cccid interface{}, args [][]byte) (*pb.Response, *pb.ChaincodeEvent, error) 432 // Execute executes the chaincode given context and spec (invocation or deploy) 433 Execute(ctxt context.Context, cccid interface{}, spec interface{}) (*pb.Response, *pb.ChaincodeEvent, error) 434 // ExecuteWithErrorFilder executes the chaincode given context and spec and returns payload 435 ExecuteWithErrorFilter(ctxt context.Context, cccid interface{}, spec interface{}) ([]byte, *pb.ChaincodeEvent, error) 436 // Stop stops the chaincode given context and deployment spec 437 Stop(ctxt context.Context, cccid interface{}, spec *pb.ChaincodeDeploymentSpec) error 438 // ReleaseContext releases the context returned previously by GetContext 439 ReleaseContext() 440 } 441 442 var ccFactory ChaincodeProviderFactory 443 444 // ChaincodeProviderFactory defines a factory interface so 445 // that the actual implementation can be injected 446 type ChaincodeProviderFactory interface { 447 NewChaincodeProvider() ChaincodeProvider 448 } 449 450 // RegisterChaincodeProviderFactory is to be called once to set 451 // the factory that will be used to obtain instances of ChaincodeProvider 452 func RegisterChaincodeProviderFactory(ccfact ChaincodeProviderFactory) { 453 ccFactory = ccfact 454 } 455 456 // GetChaincodeProvider returns instances of ChaincodeProvider; 457 // the actual implementation is controlled by the factory that 458 // is registered via RegisterChaincodeProviderFactory 459 func GetChaincodeProvider() ChaincodeProvider { 460 if ccFactory == nil { 461 panic("The factory must be set first via RegisterChaincodeProviderFactory") 462 } 463 return ccFactory.NewChaincodeProvider() 464 }