/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package chaincode import ( "context" "os" "github.com/golang/protobuf/proto" cb "github.com/hyperledger/fabric-protos-go/common" pb "github.com/hyperledger/fabric-protos-go/peer" "github.com/hyperledger/fabric/bccsp" "github.com/hyperledger/fabric/core/common/ccpackage" "github.com/hyperledger/fabric/core/common/ccprovider" "github.com/hyperledger/fabric/internal/peer/common" "github.com/hyperledger/fabric/internal/pkg/identity" "github.com/hyperledger/fabric/protoutil" "github.com/pkg/errors" "github.com/spf13/cobra" ) var chaincodeInstallCmd *cobra.Command const ( installCmdName = "install" ) // Installer holds the dependencies needed to install // a chaincode type Installer struct { Command *cobra.Command EndorserClients []pb.EndorserClient Input *InstallInput Signer identity.SignerSerializer CryptoProvider bccsp.BCCSP } // InstallInput holds the input parameters for installing // a chaincode type InstallInput struct { Name string Version string Language string PackageFile string Path string } // installCmd returns the cobra command for chaincode install func installCmd(cf *ChaincodeCmdFactory, i *Installer, cryptoProvider bccsp.BCCSP) *cobra.Command { chaincodeInstallCmd = &cobra.Command{ Use: "install", Short: "Install a chaincode.", Long: "Install a chaincode on a peer. This installs a chaincode deployment spec package (if provided) or packages the specified chaincode before subsequently installing it.", ValidArgs: []string{"1"}, RunE: func(cmd *cobra.Command, args []string) error { if i == nil { var err error if cf == nil { cf, err = InitCmdFactory(cmd.Name(), true, false, cryptoProvider) if err != nil { return err } } i = &Installer{ Command: cmd, EndorserClients: cf.EndorserClients, Signer: cf.Signer, CryptoProvider: cryptoProvider, } } return i.installChaincode(args) }, } flagList := []string{ "lang", "ctor", "path", "name", "version", "peerAddresses", "tlsRootCertFiles", "connectionProfile", } attachFlags(chaincodeInstallCmd, flagList) return chaincodeInstallCmd } // installChaincode installs the chaincode func (i *Installer) installChaincode(args []string) error { if i.Command != nil { // Parsing of the command line is done so silence cmd usage i.Command.SilenceUsage = true } i.setInput(args) // LSCC install return i.install() } func (i *Installer) setInput(args []string) { i.Input = &InstallInput{ Name: chaincodeName, Version: chaincodeVersion, Path: chaincodePath, } if len(args) > 0 { i.Input.PackageFile = args[0] } } // install installs a chaincode deployment spec to "peer.address" // for use with lscc func (i *Installer) install() error { ccPkgMsg, err := i.getChaincodePackageMessage() if err != nil { return err } proposal, err := i.createInstallProposal(ccPkgMsg) if err != nil { return err } signedProposal, err := protoutil.GetSignedProposal(proposal, i.Signer) if err != nil { return errors.WithMessagef(err, "error creating signed proposal for %s", chainFuncName) } return i.submitInstallProposal(signedProposal) } func (i *Installer) submitInstallProposal(signedProposal *pb.SignedProposal) error { // install is currently only supported for one peer proposalResponse, err := i.EndorserClients[0].ProcessProposal(context.Background(), signedProposal) if err != nil { return errors.WithMessage(err, "error endorsing chaincode install") } if proposalResponse == nil { return errors.New("error during install: received nil proposal response") } if proposalResponse.Response == nil { return errors.New("error during install: received proposal response with nil response") } if proposalResponse.Response.Status != int32(cb.Status_SUCCESS) { return errors.Errorf("install failed with status: %d - %s", proposalResponse.Response.Status, proposalResponse.Response.Message) } logger.Infof("Installed remotely: %v", proposalResponse) return nil } func (i *Installer) getChaincodePackageMessage() (proto.Message, error) { // if no package provided, create one if i.Input.PackageFile == "" { if i.Input.Path == common.UndefinedParamValue || i.Input.Version == common.UndefinedParamValue || i.Input.Name == common.UndefinedParamValue { return nil, errors.Errorf("must supply value for %s name, path and version parameters", chainFuncName) } // generate a raw ChaincodeDeploymentSpec ccPkgMsg, err := genChaincodeDeploymentSpec(i.Command, i.Input.Name, i.Input.Version) if err != nil { return nil, err } return ccPkgMsg, nil } // read in a package generated by the "package" sub-command (and perhaps signed // by multiple owners with the "signpackage" sub-command) // var cds *pb.ChaincodeDeploymentSpec ccPkgMsg, cds, err := getPackageFromFile(i.Input.PackageFile, i.CryptoProvider) if err != nil { return nil, err } // get the chaincode details from cds cName := cds.ChaincodeSpec.ChaincodeId.Name cVersion := cds.ChaincodeSpec.ChaincodeId.Version // if user provided chaincodeName, use it for validation if i.Input.Name != "" && i.Input.Name != cName { return nil, errors.Errorf("chaincode name %s does not match name %s in package", i.Input.Name, cName) } // if user provided chaincodeVersion, use it for validation if i.Input.Version != "" && i.Input.Version != cVersion { return nil, errors.Errorf("chaincode version %s does not match version %s in packages", i.Input.Version, cVersion) } return ccPkgMsg, nil } func (i *Installer) createInstallProposal(msg proto.Message) (*pb.Proposal, error) { creator, err := i.Signer.Serialize() if err != nil { return nil, errors.WithMessage(err, "error serializing identity") } prop, _, err := protoutil.CreateInstallProposalFromCDS(msg, creator) if err != nil { return nil, errors.WithMessagef(err, "error creating proposal for %s", chainFuncName) } return prop, nil } // genChaincodeDeploymentSpec creates ChaincodeDeploymentSpec as the package to install func genChaincodeDeploymentSpec(cmd *cobra.Command, chaincodeName, chaincodeVersion string) (*pb.ChaincodeDeploymentSpec, error) { if existed, _ := ccprovider.ChaincodePackageExists(chaincodeName, chaincodeVersion); existed { return nil, errors.Errorf("chaincode %s:%s already exists", chaincodeName, chaincodeVersion) } spec, err := getChaincodeSpec(cmd) if err != nil { return nil, err } cds, err := getChaincodeDeploymentSpec(spec, true) if err != nil { return nil, errors.WithMessagef(err, "error getting chaincode deployment spec for %s", chaincodeName) } return cds, nil } // getPackageFromFile get the chaincode package from file and the extracted ChaincodeDeploymentSpec func getPackageFromFile(ccPkgFile string, cryptoProvider bccsp.BCCSP) (proto.Message, *pb.ChaincodeDeploymentSpec, error) { ccPkgBytes, err := os.ReadFile(ccPkgFile) if err != nil { return nil, nil, err } // the bytes should be a valid package (CDS or SignedCDS) ccpack, err := ccprovider.GetCCPackage(ccPkgBytes, cryptoProvider) if err != nil { return nil, nil, err } // either CDS or Envelope o := ccpack.GetPackageObject() // try CDS first cds, ok := o.(*pb.ChaincodeDeploymentSpec) if !ok || cds == nil { // try Envelope next env, ok := o.(*cb.Envelope) if !ok || env == nil { return nil, nil, errors.New("error extracting valid chaincode package") } // this will check for a valid package Envelope _, sCDS, err := ccpackage.ExtractSignedCCDepSpec(env) if err != nil { return nil, nil, errors.WithMessage(err, "error extracting valid signed chaincode package") } // ...and get the CDS at last cds, err = protoutil.UnmarshalChaincodeDeploymentSpec(sCDS.ChaincodeDeploymentSpec) if err != nil { return nil, nil, errors.WithMessage(err, "error extracting chaincode deployment spec") } err = platformRegistry.ValidateDeploymentSpec(cds.ChaincodeSpec.Type.String(), cds.CodePackage) if err != nil { return nil, nil, errors.WithMessage(err, "chaincode deployment spec validation failed") } } return o, cds, nil }