diff --git a/core/chaincode/chaincode_support.go b/core/chaincode/chaincode_support.go index d7f13019fb7..91c5e9e86d7 100644 --- a/core/chaincode/chaincode_support.go +++ b/core/chaincode/chaincode_support.go @@ -537,6 +537,7 @@ func (chaincodeSupport *ChaincodeSupport) Launch(context context.Context, cccid var depPayload []byte //hopefully we are restarting from existing image and the deployed transaction exists + //this will also validate the ID from the LCCC depPayload, err = GetCDSFromLCCC(context, cccid.TxID, cccid.SignedProposal, cccid.Proposal, cccid.ChainID, cID.Name) if err != nil { return cID, cMsg, fmt.Errorf("Could not get deployment transaction from LCCC for %s - %s", canName, err) @@ -558,21 +559,24 @@ func (chaincodeSupport *ChaincodeSupport) Launch(context context.Context, cccid //launch container if it is a System container or not in dev mode if (!chaincodeSupport.userRunsCC || cds.ExecEnv == pb.ChaincodeDeploymentSpec_SYSTEM) && (chrte == nil || chrte.handler == nil) { - //whether we deploying, upgrading or launching a chaincode we now have a - //deployment package. If lauching, we got it from LCCC and has gone through - //ccprovider.GetChaincodeFromFS + //NOTE-We need to streamline code a bit so the data from LCCC gets passed to this thus + //avoiding the need to go to the FS. In particular, we should use cdsfs completely. It is + //just a vestige of old protocol that we continue to use ChaincodeDeploymentSpec for + //anything other than Install. In particular, instantiate, invoke, upgrade should be using + //just some form of ChaincodeInvocationSpec. + // + //But for now, if we are invoking we have gone through the LCCC path above. If instantiating + //or upgrading currently we send a CDS with nil CodePackage. In this case the codepath + //in the endorser has gone through LCCC validation. Just get the code from the FS. if cds.CodePackage == nil { //no code bytes for these situations if !(chaincodeSupport.userRunsCC || cds.ExecEnv == pb.ChaincodeDeploymentSpec_SYSTEM) { - _, cdsfs, err := ccprovider.GetChaincodeFromFS(cID.Name, cID.Version) + ccpack, err := ccprovider.GetChaincodeFromFS(cID.Name, cID.Version) if err != nil { return cID, cMsg, err } - //we should use cdsfs completely. It is just a vestige of old protocol that we - //continue to use ChaincodeDeploymentSpec for anything other than Install. In - //particular, instantiate, invoke, upgrade should be using just some form of - //ChaincodeInvocationSpec. - cds = cdsfs + + cds = ccpack.GetDepSpec() chaincodeLogger.Debugf("launchAndWaitForRegister fetched %d from file system", len(cds.CodePackage), err) } } diff --git a/core/chaincode/upgrade_test.go b/core/chaincode/upgrade_test.go index df4cb203f1a..808a764c407 100644 --- a/core/chaincode/upgrade_test.go +++ b/core/chaincode/upgrade_test.go @@ -80,6 +80,9 @@ func upgrade2(ctx context.Context, cccid *ccprovider.CCContext, } }() + //ignore existence errors + ccprovider.PutChaincodeIntoFS(chaincodeDeploymentSpec) + sysCCVers := util.GetSysCCVersion() lcccid := ccprovider.NewCCContext(cccid.ChainID, cis.ChaincodeSpec.ChaincodeId.Name, sysCCVers, uuid, true, nil, nil) diff --git a/core/common/ccprovider/ccprovider.go b/core/common/ccprovider/ccprovider.go index e4a46e5535c..fc2cc376d04 100644 --- a/core/common/ccprovider/ccprovider.go +++ b/core/common/ccprovider/ccprovider.go @@ -47,19 +47,28 @@ type CCPackage interface { // 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 - // PutChaincodeToFS writes the chaincode to the filesystem - PutChaincodeToFS() error + // 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) (*pb.ChaincodeDeploymentSpec, error) + 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 @@ -91,19 +100,20 @@ func GetChaincodePackage(ccname string, ccversion string) ([]byte, error) { } // GetChaincodeFromFS this is a wrapper for hiding package implementation. -func GetChaincodeFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) { +func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) { //try raw CDS cccdspack := &CDSPackage{} - b, depSpec, err := cccdspack.InitFromFS(ccname, ccversion) + _, _, err := cccdspack.InitFromFS(ccname, ccversion) if err != nil { //try signed CDS ccscdspack := &SignedCDSPackage{} - b, depSpec, err = ccscdspack.InitFromFS(ccname, ccversion) + _, _, err = ccscdspack.InitFromFS(ccname, ccversion) if err != nil { - return nil, nil, err + return nil, err } + return ccscdspack, nil } - return b, depSpec, nil + return cccdspack, nil } // PutChaincodeIntoFS is a wrapper for putting raw ChaincodeDeploymentSpec @@ -158,7 +168,7 @@ func GetInstalledChaincodes() (*pb.ChaincodeQueryResponse, error) { if len(fileNameArray) == 2 { ccname := fileNameArray[0] ccversion := fileNameArray[1] - _, cdsfs, err := GetChaincodeFromFS(ccname, ccversion) + 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 @@ -166,6 +176,8 @@ func GetInstalledChaincodes() (*pb.ChaincodeQueryResponse, error) { continue } + cdsfs := ccpack.GetDepSpec() + name := cdsfs.GetChaincodeSpec().GetChaincodeId().Name version := cdsfs.GetChaincodeSpec().GetChaincodeId().Version if name != ccname || version != ccversion { @@ -273,6 +285,11 @@ type ChaincodeData struct { //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"` } //implement functions needed from proto.Message for proto's mar/unmarshal functions diff --git a/core/common/ccprovider/cdspackage.go b/core/common/ccprovider/cdspackage.go index 4a9e1e2e200..6028bf141ac 100644 --- a/core/common/ccprovider/cdspackage.go +++ b/core/common/ccprovider/cdspackage.go @@ -66,19 +66,61 @@ type CDSPackage struct { buf []byte depSpec *pb.ChaincodeDeploymentSpec data *CDSData + datab []byte + id []byte +} + +// resets data +func (ccpack *CDSPackage) reset() { + *ccpack = CDSPackage{} +} + +// GetId gets the fingerprint of the chaincode based on package computation +func (ccpack *CDSPackage) GetId() []byte { + //this has to be after creating a package and initializing it + //If those steps fail, GetId() should never be called + if ccpack.id == nil { + panic("GetId called on uninitialized package") + } + return ccpack.id } // GetDepSpec gets the ChaincodeDeploymentSpec from the package func (ccpack *CDSPackage) GetDepSpec() *pb.ChaincodeDeploymentSpec { + //this has to be after creating a package and initializing it + //If those steps fail, GetDepSpec() should never be called + if ccpack.depSpec == nil { + panic("GetDepSpec called on uninitialized package") + } return ccpack.depSpec } +// GetDepSpecBytes gets the serialized ChaincodeDeploymentSpec from the package +func (ccpack *CDSPackage) GetDepSpecBytes() []byte { + //this has to be after creating a package and initializing it + //If those steps fail, GetDepSpecBytes() should never be called + if ccpack.buf == nil { + panic("GetDepSpecBytes called on uninitialized package") + } + return ccpack.buf +} + // GetPackageObject gets the ChaincodeDeploymentSpec as proto.Message func (ccpack *CDSPackage) GetPackageObject() proto.Message { return ccpack.depSpec } -func (ccpack *CDSPackage) getCDSData(cds *pb.ChaincodeDeploymentSpec) ([]byte, *CDSData, error) { +// GetChaincodeData gets the ChaincodeData +func (ccpack *CDSPackage) GetChaincodeData() *ChaincodeData { + //this has to be after creating a package and initializing it + //If those steps fail, GetChaincodeData() should never be called + if ccpack.depSpec == nil || ccpack.datab == nil || ccpack.id == nil { + panic("GetChaincodeData called on uninitialized package") + } + return &ChaincodeData{Name: ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name, Version: ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version, Data: ccpack.datab, Id: ccpack.id} +} + +func (ccpack *CDSPackage) getCDSData(cds *pb.ChaincodeDeploymentSpec) ([]byte, []byte, *CDSData, error) { // check for nil argument. It is an assertion that getCDSData // is never called on a package that did not go through/succeed // package initialization. @@ -88,17 +130,17 @@ func (ccpack *CDSPackage) getCDSData(cds *pb.ChaincodeDeploymentSpec) ([]byte, * b, err := proto.Marshal(cds) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if err = factory.InitFactories(nil); err != nil { - return nil, nil, fmt.Errorf("Internal error, BCCSP could not be initialized : %s", err) + return nil, nil, nil, fmt.Errorf("Internal error, BCCSP could not be initialized : %s", err) } //compute hashes now hash, err := factory.GetDefault().GetHash(&bccsp.SHAOpts{}) if err != nil { - return nil, nil, err + return nil, nil, nil, err } cdsdata := &CDSData{} @@ -116,46 +158,52 @@ func (ccpack *CDSPackage) getCDSData(cds *pb.ChaincodeDeploymentSpec) ([]byte, * b, err = proto.Marshal(cdsdata) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return b, cdsdata, nil + hash.Reset() + + //compute the id + hash.Write(cdsdata.CodeHash) + hash.Write(cdsdata.MetaDataHash) + + id := hash.Sum(nil) + + return b, id, cdsdata, nil } // ValidateCC returns error if the chaincode is not found or if its not a // ChaincodeDeploymentSpec -func (ccpack *CDSPackage) ValidateCC(ccdata *ChaincodeData) (*pb.ChaincodeDeploymentSpec, error) { +func (ccpack *CDSPackage) ValidateCC(ccdata *ChaincodeData) error { if ccpack.depSpec == nil { - return nil, fmt.Errorf("uninitialized package") + return fmt.Errorf("uninitialized package") } if ccpack.data == nil { - return nil, fmt.Errorf("nil data") + return fmt.Errorf("nil data") } if ccdata.Name != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name || ccdata.Version != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version { - return nil, fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId) + return fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId) } otherdata := &CDSData{} err := proto.Unmarshal(ccdata.Data, otherdata) if err != nil { - return nil, err + return err } if !ccpack.data.Equals(otherdata) { - return nil, fmt.Errorf("data mismatch") + return fmt.Errorf("data mismatch") } - return ccpack.depSpec, nil + return nil } //InitFromBuffer sets the buffer if valid and returns ChaincodeData func (ccpack *CDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, error) { //incase ccpack is reused - ccpack.buf = nil - ccpack.depSpec = nil - ccpack.data = nil + ccpack.reset() depSpec := &pb.ChaincodeDeploymentSpec{} err := proto.Unmarshal(buf, depSpec) @@ -163,7 +211,7 @@ func (ccpack *CDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, error) { return nil, fmt.Errorf("failed to unmarshal deployment spec from bytes") } - databytes, data, err := ccpack.getCDSData(depSpec) + databytes, id, data, err := ccpack.getCDSData(depSpec) if err != nil { return nil, err } @@ -171,38 +219,27 @@ func (ccpack *CDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, error) { ccpack.buf = buf ccpack.depSpec = depSpec ccpack.data = data + ccpack.datab = databytes + ccpack.id = id - return &ChaincodeData{Name: depSpec.ChaincodeSpec.ChaincodeId.Name, Version: depSpec.ChaincodeSpec.ChaincodeId.Version, Data: databytes}, nil + return ccpack.GetChaincodeData(), nil } //InitFromFS returns the chaincode and its package from the file system func (ccpack *CDSPackage) InitFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) { //incase ccpack is reused - ccpack.buf = nil - ccpack.depSpec = nil - ccpack.data = nil + ccpack.reset() buf, err := GetChaincodePackage(ccname, ccversion) if err != nil { return nil, nil, err } - depSpec := &pb.ChaincodeDeploymentSpec{} - err = proto.Unmarshal(buf, depSpec) - if err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal fs deployment spec for %s, %s", ccname, ccversion) - } - - _, data, err := ccpack.getCDSData(depSpec) - if err != nil { + if _, err = ccpack.InitFromBuffer(buf); err != nil { return nil, nil, err } - ccpack.buf = buf - ccpack.depSpec = depSpec - ccpack.data = data - - return buf, depSpec, nil + return ccpack.buf, ccpack.depSpec, nil } //PutChaincodeToFS - serializes chaincode to a package on the file system @@ -211,6 +248,10 @@ func (ccpack *CDSPackage) PutChaincodeToFS() error { return fmt.Errorf("uninitialized package") } + if ccpack.id == nil { + return fmt.Errorf("id cannot be nil if buf is not nil") + } + if ccpack.depSpec == nil { return fmt.Errorf("depspec cannot be nil if buf is not nil") } @@ -219,6 +260,10 @@ func (ccpack *CDSPackage) PutChaincodeToFS() error { return fmt.Errorf("nil data") } + if ccpack.datab == nil { + return fmt.Errorf("nil data bytes") + } + ccname := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name ccversion := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version diff --git a/core/common/ccprovider/cdspackage_test.go b/core/common/ccprovider/cdspackage_test.go index a8cf21a4966..1d4772d3fbe 100644 --- a/core/common/ccprovider/cdspackage_test.go +++ b/core/common/ccprovider/cdspackage_test.go @@ -65,7 +65,7 @@ func TestPutCDSCC(t *testing.T) { return } - if _, err = ccpack.ValidateCC(cd); err != nil { + if err = ccpack.ValidateCC(cd); err != nil { t.Fatalf("error validating package %s", err) return } @@ -84,7 +84,7 @@ func TestPutCDSErrorPaths(t *testing.T) { } //validate with invalid name - if _, err = ccpack.ValidateCC(&ChaincodeData{Name: "invalname", Version: "0"}); err == nil { + if err = ccpack.ValidateCC(&ChaincodeData{Name: "invalname", Version: "0"}); err == nil { t.Fatalf("expected error validating package") return } @@ -169,7 +169,7 @@ func TestCDSSwitchChaincodes(t *testing.T) { return } - if _, err = badccpack.ValidateCC(goodcd); err == nil { + if err = badccpack.ValidateCC(goodcd); err == nil { t.Fatalf("expected goodcd to fail against bad package but succeeded!") return } diff --git a/core/common/ccprovider/sigcdspackage.go b/core/common/ccprovider/sigcdspackage.go index 363a8f5fec3..e163bfa65a0 100644 --- a/core/common/ccprovider/sigcdspackage.go +++ b/core/common/ccprovider/sigcdspackage.go @@ -71,19 +71,61 @@ type SignedCDSPackage struct { sDepSpec *pb.SignedChaincodeDeploymentSpec env *common.Envelope data *SignedCDSData + datab []byte + id []byte +} + +// resets data +func (ccpack *SignedCDSPackage) reset() { + *ccpack = SignedCDSPackage{} +} + +// GetId gets the fingerprint of the chaincode based on package computation +func (ccpack *SignedCDSPackage) GetId() []byte { + //this has to be after creating a package and initializing it + //If those steps fail, GetId() should never be called + if ccpack.id == nil { + panic("GetId called on uninitialized package") + } + return ccpack.id } // GetDepSpec gets the ChaincodeDeploymentSpec from the package func (ccpack *SignedCDSPackage) GetDepSpec() *pb.ChaincodeDeploymentSpec { + //this has to be after creating a package and initializing it + //If those steps fail, GetDepSpec() should never be called + if ccpack.depSpec == nil { + panic("GetDepSpec called on uninitialized package") + } return ccpack.depSpec } +// GetDepSpecBytes gets the serialized ChaincodeDeploymentSpec from the package +func (ccpack *SignedCDSPackage) GetDepSpecBytes() []byte { + //this has to be after creating a package and initializing it + //If those steps fail, GetDepSpecBytes() should never be called + if ccpack.sDepSpec == nil || ccpack.sDepSpec.ChaincodeDeploymentSpec == nil { + panic("GetDepSpecBytes called on uninitialized package") + } + return ccpack.sDepSpec.ChaincodeDeploymentSpec +} + // GetPackageObject gets the ChaincodeDeploymentSpec as proto.Message func (ccpack *SignedCDSPackage) GetPackageObject() proto.Message { return ccpack.env } -func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpec) ([]byte, *SignedCDSData, error) { +// GetChaincodeData gets the ChaincodeData +func (ccpack *SignedCDSPackage) GetChaincodeData() *ChaincodeData { + //this has to be after creating a package and initializing it + //If those steps fail, GetChaincodeData() should never be called + if ccpack.depSpec == nil || ccpack.datab == nil || ccpack.id == nil { + panic("GetChaincodeData called on uninitialized package") + } + return &ChaincodeData{Name: ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name, Version: ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version, Data: ccpack.datab, Id: ccpack.id} +} + +func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpec) ([]byte, []byte, *SignedCDSData, error) { // check for nil argument. It is an assertion that getCDSData // is never called on a package that did not go through/succeed // package initialization. @@ -94,17 +136,17 @@ func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpe cds := &pb.ChaincodeDeploymentSpec{} err := proto.Unmarshal(scds.ChaincodeDeploymentSpec, cds) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if err = factory.InitFactories(nil); err != nil { - return nil, nil, fmt.Errorf("Internal error, BCCSP could not be initialized : %s", err) + return nil, nil, nil, fmt.Errorf("Internal error, BCCSP could not be initialized : %s", err) } //get the hash object hash, err := factory.GetDefault().GetHash(&bccsp.SHAOpts{}) if err != nil { - return nil, nil, err + return nil, nil, nil, err } scdsdata := &SignedCDSData{} @@ -124,7 +166,7 @@ func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpe //get the signature hashes if scds.InstantiationPolicy == nil { - return nil, nil, fmt.Errorf("instantiation policy cannot be nil for chaincode (%s:%s)", cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version) + return nil, nil, nil, fmt.Errorf("instantiation policy cannot be nil for chaincode (%s:%s)", cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version) } hash.Write(scds.InstantiationPolicy) @@ -136,52 +178,57 @@ func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpe //marshall data b, err := proto.Marshal(scdsdata) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return b, scdsdata, nil + hash.Reset() + + //compute the id + hash.Write(scdsdata.CodeHash) + hash.Write(scdsdata.MetaDataHash) + hash.Write(scdsdata.SignatureHash) + + id := hash.Sum(nil) + + return b, id, scdsdata, nil } // ValidateCC returns error if the chaincode is not found or if its not a // ChaincodeDeploymentSpec -func (ccpack *SignedCDSPackage) ValidateCC(ccdata *ChaincodeData) (*pb.ChaincodeDeploymentSpec, error) { +func (ccpack *SignedCDSPackage) ValidateCC(ccdata *ChaincodeData) error { if ccpack.sDepSpec == nil { - return nil, fmt.Errorf("uninitialized package") + return fmt.Errorf("uninitialized package") } if ccpack.sDepSpec.ChaincodeDeploymentSpec == nil { - return nil, fmt.Errorf("signed chaincode deployment spec cannot be nil in a package") + return fmt.Errorf("signed chaincode deployment spec cannot be nil in a package") } if ccpack.depSpec == nil { - return nil, fmt.Errorf("chaincode deployment spec cannot be nil in a package") + return fmt.Errorf("chaincode deployment spec cannot be nil in a package") } if ccdata.Name != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name || ccdata.Version != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version { - return nil, fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId) + return fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId) } otherdata := &SignedCDSData{} err := proto.Unmarshal(ccdata.Data, otherdata) if err != nil { - return nil, err + return err } if !ccpack.data.Equals(otherdata) { - return nil, fmt.Errorf("data mismatch") + return fmt.Errorf("data mismatch") } - return ccpack.depSpec, nil + return nil } //InitFromBuffer sets the buffer if valid and returns ChaincodeData func (ccpack *SignedCDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, error) { //incase ccpack is reused - ccpack.buf = nil - ccpack.sDepSpec = nil - ccpack.depSpec = nil - ccpack.env = nil - ccpack.data = nil + ccpack.reset() env := &common.Envelope{} err := proto.Unmarshal(buf, env) @@ -202,7 +249,7 @@ func (ccpack *SignedCDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, erro return nil, fmt.Errorf("error getting deployment spec") } - databytes, data, err := ccpack.getCDSData(sDepSpec) + databytes, id, data, err := ccpack.getCDSData(sDepSpec) if err != nil { return nil, err } @@ -212,17 +259,16 @@ func (ccpack *SignedCDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, erro ccpack.depSpec = depSpec ccpack.env = env ccpack.data = data + ccpack.datab = databytes + ccpack.id = id - return &ChaincodeData{Name: depSpec.ChaincodeSpec.ChaincodeId.Name, Version: depSpec.ChaincodeSpec.ChaincodeId.Version, Data: databytes}, nil + return ccpack.GetChaincodeData(), nil } //InitFromFS returns the chaincode and its package from the file system func (ccpack *SignedCDSPackage) InitFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) { //incase ccpack is reused - ccpack.buf = nil - ccpack.sDepSpec = nil - ccpack.depSpec = nil - ccpack.env = nil + ccpack.reset() buf, err := GetChaincodePackage(ccname, ccversion) if err != nil { @@ -242,6 +288,10 @@ func (ccpack *SignedCDSPackage) PutChaincodeToFS() error { return fmt.Errorf("uninitialized package") } + if ccpack.id == nil { + return fmt.Errorf("id cannot be nil if buf is not nil") + } + if ccpack.sDepSpec == nil || ccpack.depSpec == nil { return fmt.Errorf("depspec cannot be nil if buf is not nil") } @@ -254,6 +304,10 @@ func (ccpack *SignedCDSPackage) PutChaincodeToFS() error { return fmt.Errorf("nil data") } + if ccpack.datab == nil { + return fmt.Errorf("nil data bytes") + } + ccname := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name ccversion := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version diff --git a/core/common/ccprovider/sigcdspackage_test.go b/core/common/ccprovider/sigcdspackage_test.go index 00ec5ce425f..c332b61e11d 100644 --- a/core/common/ccprovider/sigcdspackage_test.go +++ b/core/common/ccprovider/sigcdspackage_test.go @@ -62,7 +62,7 @@ func TestPutSigCDSCC(t *testing.T) { return } - if _, err = ccpack.ValidateCC(cd); err != nil { + if err = ccpack.ValidateCC(cd); err != nil { t.Fatalf("error validating package %s", err) return } @@ -81,7 +81,7 @@ func TestPutSignedCDSErrorPaths(t *testing.T) { } //validate with invalid name - if _, err = ccpack.ValidateCC(&ChaincodeData{Name: "invalname", Version: "0"}); err == nil { + if err = ccpack.ValidateCC(&ChaincodeData{Name: "invalname", Version: "0"}); err == nil { t.Fatalf("expected error validating package") return } @@ -197,7 +197,7 @@ func TestSignedCDSSwitchChaincodes(t *testing.T) { return } - if _, err = badccpack.ValidateCC(goodcd); err == nil { + if err = badccpack.ValidateCC(goodcd); err == nil { t.Fatalf("expected goodcd to fail against bad package but succeeded!") return } diff --git a/core/scc/lccc/lccc.go b/core/scc/lccc/lccc.go index f11082c6149..57deead83e3 100644 --- a/core/scc/lccc/lccc.go +++ b/core/scc/lccc/lccc.go @@ -180,6 +180,13 @@ func (f InvalidVersionErr) Error() string { return fmt.Sprintf("invalid chaincode version %s", string(f)) } +//ChaincodeMismatchErr chaincode name from two places don't match +type ChaincodeMismatchErr string + +func (f ChaincodeMismatchErr) Error() string { + return fmt.Sprintf("chaincode name mismatch %s", string(f)) +} + //EmptyVersionErr empty version error type EmptyVersionErr string @@ -201,69 +208,101 @@ func (f IdenticalVersionErr) Error() string { return fmt.Sprintf("chaincode with the same version exists %s", string(f)) } +//InvalidCCOnFSError error due to mismatch between fingerprint on lccc and installed CC +type InvalidCCOnFSError string + +func (f InvalidCCOnFSError) Error() string { + return fmt.Sprintf("chaincode fingerprint mismatch %s", string(f)) +} + //-------------- helper functions ------------------ //create the chaincode on the given chain -func (lccc *LifeCycleSysCC) createChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string, version string, policy []byte, escc []byte, vscc []byte) (*ccprovider.ChaincodeData, error) { - return lccc.putChaincodeData(stub, chainname, ccname, version, policy, escc, vscc) +func (lccc *LifeCycleSysCC) createChaincode(stub shim.ChaincodeStubInterface, cd *ccprovider.ChaincodeData) error { + return lccc.putChaincodeData(stub, cd) } //upgrade the chaincode on the given chain -func (lccc *LifeCycleSysCC) upgradeChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string, version string, policy []byte, escc []byte, vscc []byte) (*ccprovider.ChaincodeData, error) { - return lccc.putChaincodeData(stub, chainname, ccname, version, policy, escc, vscc) +func (lccc *LifeCycleSysCC) upgradeChaincode(stub shim.ChaincodeStubInterface, cd *ccprovider.ChaincodeData) error { + return lccc.putChaincodeData(stub, cd) } //create the chaincode on the given chain -func (lccc *LifeCycleSysCC) putChaincodeData(stub shim.ChaincodeStubInterface, chainname string, ccname string, version string, policy []byte, escc []byte, vscc []byte) (*ccprovider.ChaincodeData, error) { +func (lccc *LifeCycleSysCC) putChaincodeData(stub shim.ChaincodeStubInterface, cd *ccprovider.ChaincodeData) error { // check that escc and vscc are real system chaincodes - if !lccc.sccprovider.IsSysCC(string(escc)) { - return nil, fmt.Errorf("%s is not a valid endorsement system chaincode", string(escc)) + if !lccc.sccprovider.IsSysCC(string(cd.Escc)) { + return fmt.Errorf("%s is not a valid endorsement system chaincode", string(cd.Escc)) } - if !lccc.sccprovider.IsSysCC(string(vscc)) { - return nil, fmt.Errorf("%s is not a valid validation system chaincode", string(vscc)) + if !lccc.sccprovider.IsSysCC(string(cd.Vscc)) { + return fmt.Errorf("%s is not a valid validation system chaincode", string(cd.Vscc)) } - cd := &ccprovider.ChaincodeData{Name: ccname, Version: version, Policy: policy, Escc: string(escc), Vscc: string(vscc)} cdbytes, err := proto.Marshal(cd) if err != nil { - return nil, err + return err } if cdbytes == nil { - return nil, MarshallErr(ccname) + return MarshallErr(cd.Name) } - err = stub.PutState(ccname, cdbytes) + err = stub.PutState(cd.Name, cdbytes) - return cd, err + return err } -//checks for existence of chaincode on the given chain -func (lccc *LifeCycleSysCC) getChaincode(stub shim.ChaincodeStubInterface, ccname string, checkFS bool) (*ccprovider.ChaincodeData, []byte, *pb.ChaincodeDeploymentSpec, []byte, error) { +//checks for existence of chaincode on the given channel +func (lccc *LifeCycleSysCC) getCCInstance(stub shim.ChaincodeStubInterface, ccname string) ([]byte, error) { cdbytes, err := stub.GetState(ccname) if err != nil { - return nil, nil, nil, nil, err + return nil, TXNotFoundErr(err.Error()) + } + if cdbytes == nil { + return nil, NotFoundErr(ccname) } - if cdbytes != nil { - cd := &ccprovider.ChaincodeData{} - err = proto.Unmarshal(cdbytes, cd) - if err != nil { - return nil, nil, nil, nil, MarshallErr(ccname) - } + return cdbytes, nil +} - if checkFS { - depspecbytes, depspec, err := ccprovider.GetChaincodeFromFS(ccname, cd.Version) - if err != nil { - return cd, nil, nil, nil, InvalidDeploymentSpecErr(err.Error()) - } +//gets the cd out of the bytes +func (lccc *LifeCycleSysCC) getChaincodeData(ccname string, cdbytes []byte) (*ccprovider.ChaincodeData, error) { + cd := &ccprovider.ChaincodeData{} + err := proto.Unmarshal(cdbytes, cd) + if err != nil { + return nil, MarshallErr(ccname) + } - return cd, cdbytes, depspec, depspecbytes, nil - } + //this should not happen but still a sanity check is not a bad thing + if cd.Name != ccname { + return nil, ChaincodeMismatchErr(fmt.Sprintf("%s!=%s", ccname, cd.Name)) + } - return cd, cdbytes, nil, nil, nil + return cd, nil +} + +//checks for existence of chaincode on the given chain +func (lccc *LifeCycleSysCC) getCCCode(ccname string, cdbytes []byte) (*ccprovider.ChaincodeData, *pb.ChaincodeDeploymentSpec, []byte, error) { + cd, err := lccc.getChaincodeData(ccname, cdbytes) + if err != nil { + return nil, nil, nil, err } - return nil, nil, nil, nil, NotFoundErr(ccname) + ccpack, err := ccprovider.GetChaincodeFromFS(ccname, cd.Version) + if err != nil { + return nil, nil, nil, InvalidDeploymentSpecErr(err.Error()) + } + + //this is the big test and the reason every launch should go through + //getChaincode call. We validate the chaincode entry against the + //the chaincode in FS + if err = ccpack.ValidateCC(cd); err != nil { + return nil, nil, nil, InvalidCCOnFSError(err.Error()) + } + + //these are guaranteed to be non-nil because we got a valid ccpack + depspec := ccpack.GetDepSpec() + depspecbytes := ccpack.GetDepSpecBytes() + + return cd, depspec, depspecbytes, nil } // getChaincodes returns all chaincodes instantiated on this LCCC's channel @@ -295,10 +334,10 @@ func (lccc *LifeCycleSysCC) getChaincodes(stub shim.ChaincodeStubInterface) pb.R //if chaincode is not installed on the system we won't have //data beyond name and version - _, ccdepspec, err := ccprovider.GetChaincodeFromFS(ccdata.Name, ccdata.Version) + ccpack, err := ccprovider.GetChaincodeFromFS(ccdata.Name, ccdata.Version) if err == nil { - path = ccdepspec.GetChaincodeSpec().ChaincodeId.Path - input = ccdepspec.GetChaincodeSpec().Input.String() + path = ccpack.GetDepSpec().GetChaincodeSpec().ChaincodeId.Path + input = ccpack.GetDepSpec().GetChaincodeSpec().Input.String() } ccInfo := &pb.ChaincodeInfo{Name: ccdata.Name, Version: ccdata.Version, Path: path, Input: input, Escc: ccdata.Escc, Vscc: ccdata.Vscc} @@ -438,12 +477,27 @@ func (lccc *LifeCycleSysCC) executeDeploy(stub shim.ChaincodeStubInterface, chai return nil, err } - cd, _, _, _, err := lccc.getChaincode(stub, cds.ChaincodeSpec.ChaincodeId.Name, true) - if cd != nil { + //just test for existence of the chaincode in the LCCC + _, err = lccc.getCCInstance(stub, cds.ChaincodeSpec.ChaincodeId.Name) + if err == nil { return nil, ExistsErr(cds.ChaincodeSpec.ChaincodeId.Name) } - cd, err = lccc.createChaincode(stub, chainname, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, policy, escc, vscc) + //get the chaincode from the FS + ccpack, err := ccprovider.GetChaincodeFromFS(cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version) + if err != nil { + return nil, fmt.Errorf("cannot get package for the chaincode to be instantiated (%s:%s)-%s", cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, err) + } + + //this is guranteed to be not nil + cd := ccpack.GetChaincodeData() + + //retain chaincode specific data and fill channel specific ones + cd.Escc = string(escc) + cd.Vscc = string(vscc) + cd.Policy = policy + + err = lccc.createChaincode(stub, cd) return cd, err } @@ -468,24 +522,44 @@ func (lccc *LifeCycleSysCC) executeUpgrade(stub shim.ChaincodeStubInterface, cha return nil, err } - // check for existence of chaincode - cd, _, _, _, err := lccc.getChaincode(stub, chaincodeName, true) - if cd == nil { + // check for existence of chaincode instance only (it has to exist on the channel) + // we dont care about the old chaincode on the FS. In particular, user may even + // have deleted it + cdbytes, _ := lccc.getCCInstance(stub, chaincodeName) + if cdbytes == nil { return nil, NotFoundErr(chainName) } + //we need the cd to compare the version + cd, err := lccc.getChaincodeData(chaincodeName, cdbytes) + if err != nil { + return nil, err + } + + //do not upgrade if same version if cd.Version == cds.ChaincodeSpec.ChaincodeId.Version { return nil, IdenticalVersionErr(cds.ChaincodeSpec.ChaincodeId.Name) } - ver := cds.ChaincodeSpec.ChaincodeId.Version + ccpack, err := ccprovider.GetChaincodeFromFS(chaincodeName, cds.ChaincodeSpec.ChaincodeId.Version) + if err != nil { + return nil, fmt.Errorf("cannot get package for the chaincode to be upgraded (%s:%s)-%s", chaincodeName, cds.ChaincodeSpec.ChaincodeId.Version, err) + } - newCD, err := lccc.upgradeChaincode(stub, chainName, chaincodeName, ver, policy, escc, vscc) + //get the new cd to upgrade to this is guaranteed to be not nil + cd = ccpack.GetChaincodeData() + + //retain chaincode specific data and fill channel specific ones + cd.Escc = string(escc) + cd.Vscc = string(vscc) + cd.Policy = policy + + err = lccc.upgradeChaincode(stub, cd) if err != nil { return nil, err } - return newCD, nil + return cd, nil } //-------------- the chaincode stub interface implementation ---------- @@ -653,24 +727,26 @@ func (lccc *LifeCycleSysCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response chain := string(args[1]) ccname := string(args[2]) - //check the FS only for deployment spec - //other calls are looking for LCCC entries only - checkFS := false - if function == GETDEPSPEC { - checkFS = true - } - cd, cdbytes, _, depspecbytes, err := lccc.getChaincode(stub, ccname, checkFS) - if cd == nil || cdbytes == nil { - logger.Errorf("ChaincodeId: %s does not exist on channel: %s(err:%s)", ccname, chain, err) - return shim.Error(TXNotFoundErr(ccname + "/" + chain).Error()) + cdbytes, err := lccc.getCCInstance(stub, ccname) + if err != nil { + logger.Errorf("error getting chaincode %s on channel: %s(err:%s)", ccname, chain, err) + return shim.Error(err.Error()) } switch function { case GETCCINFO: + cd, err := lccc.getChaincodeData(ccname, cdbytes) + if err != nil { + return shim.Error(err.Error()) + } return shim.Success([]byte(cd.Name)) case GETCCDATA: return shim.Success(cdbytes) default: + _, _, depspecbytes, err := lccc.getCCCode(ccname, cdbytes) + if err != nil { + return shim.Error(err.Error()) + } return shim.Success(depspecbytes) } case GETCHAINCODES: diff --git a/core/scc/lccc/lccc_test.go b/core/scc/lccc/lccc_test.go index 407bebb9485..259111b49d4 100644 --- a/core/scc/lccc/lccc_test.go +++ b/core/scc/lccc/lccc_test.go @@ -17,9 +17,10 @@ package lccc import ( "fmt" - "testing" - + "io/ioutil" "os" + "strings" + "testing" "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric/core/chaincode/shim" @@ -454,6 +455,87 @@ func TestRetryFailedDeploy(t *testing.T) { } } +//TestTamperChaincode modifies the chaincode on the FS after deploy +func TestTamperChaincode(t *testing.T) { + scc := new(LifeCycleSysCC) + stub := shim.NewMockStub("lccc", scc) + + if res := stub.MockInit("1", nil); res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + + //deploy 01 + cds, err := constructDeploymentSpec("example01", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example01", "0", [][]byte{[]byte("init"), []byte("a"), []byte("1"), []byte("b"), []byte("2")}, true) + if err != nil { + t.Logf("Could not construct example01.0") + t.FailNow() + } + + defer os.Remove(lccctestpath + "/example01.0") + + var b []byte + if b, err = proto.Marshal(cds); err != nil || b == nil { + t.Logf("Could not construct example01.0") + t.FailNow() + } + + args := [][]byte{[]byte(DEPLOY), []byte("test"), b} + res := stub.MockInvoke("1", args) + if res.Status != shim.OK { + t.Logf("Could not deploy example01.0") + t.FailNow() + } + + //deploy 02 + cds, err = constructDeploymentSpec("example02", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", "0", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, true) + if err != nil { + t.FailNow() + } + + defer os.Remove(lccctestpath + "/example02.0") + + if b, err = proto.Marshal(cds); err != nil || b == nil { + t.FailNow() + } + + //deploy correctly now + args = [][]byte{[]byte(DEPLOY), []byte("test"), b} + if res = stub.MockInvoke("1", args); res.Status != shim.OK { + t.Logf("Could not deploy example02.0") + t.FailNow() + } + + //remove the old file... + os.Remove(lccctestpath + "/example02.0") + + //read 01 and ... + if b, err = ioutil.ReadFile(lccctestpath + "/example01.0"); err != nil { + t.Logf("Could not read back example01.0") + t.FailNow() + } + + //...brute force replace 02 with bytes from 01 + if err = ioutil.WriteFile(lccctestpath+"/example02.0", b, 0644); err != nil { + t.Logf("Could not write to example02.0") + t.FailNow() + } + + //get the deploymentspec + args = [][]byte{[]byte(GETDEPSPEC), []byte("test"), []byte(cds.ChaincodeSpec.ChaincodeId.Name)} + if res = stub.MockInvoke("1", args); res.Status == shim.OK { + t.Logf("Expected error on tampering files but succeeded") + t.FailNow() + } + + //look specifically for Invalid error + expectedErr := InvalidCCOnFSError("").Error() + if strings.Index(res.Message, expectedErr) < 0 { + t.Logf("Expected prefix %s on error but appeared to have got a different error : %+v", expectedErr, res) + t.FailNow() + } +} + // TestUpgrade tests the upgrade function with various inputs for basic use cases func TestUpgrade(t *testing.T) { path := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02"