From 3e433d4dbafb4dd97c4a41f0ba7dd45fa9c9e6d4 Mon Sep 17 00:00:00 2001 From: Tatsuya Sato Date: Thu, 14 Oct 2021 01:44:27 +0000 Subject: [PATCH] Add `calculatepackageid` command This patch `calculatepackageid` command to calculate the package ID for a packaged chaincode rather than an installed chaincode. The new command will be useful, for example, the following cases: * When multiple chaincode packages with the same label name are installed, it is possible to identify which ID corresponds to which package later. * To check whether a particular chaincode package is installed or not on a peer without installing that package. Signed-off-by: Tatsuya Sato --- .../persistence/chaincode_package.go | 47 ++++--- core/chaincode/persistence/persistence.go | 9 +- internal/peer/common/common.go | 2 +- .../lifecycle/chaincode/calculatepackageid.go | 114 +++++++++++++++ .../chaincode/calculatepackageid_test.go | 133 ++++++++++++++++++ .../peer/lifecycle/chaincode/chaincode.go | 5 +- .../chaincode/testdata/good-package.tar.gz | Bin 0 -> 252 bytes .../testdata/unparsed-package.tar.gz | Bin 0 -> 53 bytes 8 files changed, 287 insertions(+), 23 deletions(-) create mode 100644 internal/peer/lifecycle/chaincode/calculatepackageid.go create mode 100644 internal/peer/lifecycle/chaincode/calculatepackageid_test.go create mode 100644 internal/peer/lifecycle/chaincode/testdata/good-package.tar.gz create mode 100644 internal/peer/lifecycle/chaincode/testdata/unparsed-package.tar.gz diff --git a/core/chaincode/persistence/chaincode_package.go b/core/chaincode/persistence/chaincode_package.go index 4f0916603b5..52f66c3e0af 100644 --- a/core/chaincode/persistence/chaincode_package.go +++ b/core/chaincode/persistence/chaincode_package.go @@ -260,9 +260,29 @@ func ValidateLabel(label string) error { // Parse parses a set of bytes as a chaincode package // and returns the parsed package as a struct func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, error) { + ccPackageMetadata, codePackage, err := ParseChaincodePackage(source) + if err != nil { + return nil, err + } + + dbArtifacts, err := ccpp.MetadataProvider.GetDBArtifacts(codePackage) + if err != nil { + return nil, errors.WithMessage(err, "error retrieving DB artifacts from code package") + } + + return &ChaincodePackage{ + Metadata: ccPackageMetadata, + CodePackage: codePackage, + DBArtifacts: dbArtifacts, + }, nil +} + +// ParseChaincodePackage parses a set of bytes as a chaincode package +// and returns the parsed package as a metadata struct and a code package +func ParseChaincodePackage(source []byte) (*ChaincodePackageMetadata, []byte, error) { gzReader, err := gzip.NewReader(bytes.NewBuffer(source)) if err != nil { - return nil, errors.Wrapf(err, "error reading as gzip stream") + return &ChaincodePackageMetadata{}, nil, errors.Wrapf(err, "error reading as gzip stream") } tarReader := tar.NewReader(gzReader) @@ -276,16 +296,16 @@ func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, erro } if err != nil { - return nil, errors.Wrapf(err, "error inspecting next tar header") + return ccPackageMetadata, nil, errors.Wrapf(err, "error inspecting next tar header") } if header.Typeflag != tar.TypeReg { - return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag) + return ccPackageMetadata, nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag) } fileBytes, err := ioutil.ReadAll(tarReader) if err != nil { - return nil, errors.Wrapf(err, "could not read %s from tar", header.Name) + return ccPackageMetadata, nil, errors.Wrapf(err, "could not read %s from tar", header.Name) } switch header.Name { @@ -294,7 +314,7 @@ func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, erro ccPackageMetadata = &ChaincodePackageMetadata{} err := json.Unmarshal(fileBytes, ccPackageMetadata) if err != nil { - return nil, errors.Wrapf(err, "could not unmarshal %s as json", MetadataFile) + return ccPackageMetadata, nil, errors.Wrapf(err, "could not unmarshal %s as json", MetadataFile) } case CodePackageFile: @@ -305,25 +325,16 @@ func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, erro } if codePackage == nil { - return nil, errors.Errorf("did not find a code package inside the package") + return ccPackageMetadata, nil, errors.Errorf("did not find a code package inside the package") } if ccPackageMetadata == nil { - return nil, errors.Errorf("did not find any package metadata (missing %s)", MetadataFile) + return ccPackageMetadata, nil, errors.Errorf("did not find any package metadata (missing %s)", MetadataFile) } if err := ValidateLabel(ccPackageMetadata.Label); err != nil { - return nil, err + return ccPackageMetadata, nil, err } - dbArtifacts, err := ccpp.MetadataProvider.GetDBArtifacts(codePackage) - if err != nil { - return nil, errors.WithMessage(err, "error retrieving DB artifacts from code package") - } - - return &ChaincodePackage{ - Metadata: ccPackageMetadata, - CodePackage: codePackage, - DBArtifacts: dbArtifacts, - }, nil + return ccPackageMetadata, codePackage, nil } diff --git a/core/chaincode/persistence/persistence.go b/core/chaincode/persistence/persistence.go index 2a47a40062c..6d900cdb1fb 100644 --- a/core/chaincode/persistence/persistence.go +++ b/core/chaincode/persistence/persistence.go @@ -144,8 +144,7 @@ func (s *Store) Initialize() { // Save persists chaincode install package bytes. It returns // the hash of the chaincode install package func (s *Store) Save(label string, ccInstallPkg []byte) (string, error) { - hash := util.ComputeSHA256(ccInstallPkg) - packageID := packageID(label, hash) + packageID := PackageID(label, ccInstallPkg) ccInstallPkgFileName := CCFileName(packageID) ccInstallPkgFilePath := filepath.Join(s.Path, ccInstallPkgFileName) @@ -229,6 +228,12 @@ func (s *Store) GetChaincodeInstallPath() string { return s.Path } +// PackageID returns the package ID with the label and hash of the chaincode install package +func PackageID(label string, ccInstallPkg []byte) string { + hash := util.ComputeSHA256(ccInstallPkg) + return packageID(label, hash) +} + func packageID(label string, hash []byte) string { return fmt.Sprintf("%s:%x", label, hash) } diff --git a/internal/peer/common/common.go b/internal/peer/common/common.go index 37db1baa3e7..ca13bb72896 100644 --- a/internal/peer/common/common.go +++ b/internal/peer/common/common.go @@ -347,7 +347,7 @@ func InitCmd(cmd *cobra.Command, args []string) { }) // chaincode packaging does not require material from the local MSP - if cmd.CommandPath() == "peer lifecycle chaincode package" { + if cmd.CommandPath() == "peer lifecycle chaincode package" || cmd.CommandPath() == "peer lifecycle chaincode calculatepackageid" { mainLogger.Debug("peer lifecycle chaincode package does not need to init crypto") return } diff --git a/internal/peer/lifecycle/chaincode/calculatepackageid.go b/internal/peer/lifecycle/chaincode/calculatepackageid.go new file mode 100644 index 00000000000..a79d463539e --- /dev/null +++ b/internal/peer/lifecycle/chaincode/calculatepackageid.go @@ -0,0 +1,114 @@ +/* +Copyright Hitachi, Ltd. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package chaincode + +import ( + "fmt" + "io" + "os" + + "github.com/hyperledger/fabric/core/chaincode/persistence" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// PackageIDCalculator holds the dependencies needed to calculate +// the package ID for a packaged chaincode +type PackageIDCalculator struct { + Command *cobra.Command + Input *CalculatePackageIDInput + Reader Reader + Writer io.Writer +} + +// CalculatePackageIDInput holds the input parameters for calculating +// the package ID of a packaged chaincode +type CalculatePackageIDInput struct { + PackageFile string +} + +// Validate checks that the required parameters are provided +func (i *CalculatePackageIDInput) Validate() error { + if i.PackageFile == "" { + return errors.New("chaincode install package must be provided") + } + + return nil +} + +// CalculatePackageIDCmd returns the cobra command for calculating +// the package ID for a packaged chaincode +func CalculatePackageIDCmd(p *PackageIDCalculator) *cobra.Command { + calculatePackageIDCmd := &cobra.Command{ + Use: "calculatepackageid packageFile", + Short: "Calculate the package ID for a chaincode.", + Long: "Calculate the package ID for a packaged chaincode.", + ValidArgs: []string{"1"}, + RunE: func(cmd *cobra.Command, args []string) error { + if p == nil { + p = &PackageIDCalculator{ + Reader: &persistence.FilesystemIO{}, + Writer: os.Stdout, + } + } + p.Command = cmd + + return p.CalculatePackageID(args) + }, + } + flagList := []string{ + "peerAddresses", + "tlsRootCertFiles", + "connectionProfile", + } + attachFlags(calculatePackageIDCmd, flagList) + + return calculatePackageIDCmd +} + +// PackageIDCalculator calculates the package ID for a packaged chaincode. +func (p *PackageIDCalculator) CalculatePackageID(args []string) error { + if p.Command != nil { + // Parsing of the command line is done so silence cmd usage + p.Command.SilenceUsage = true + } + + if len(args) != 1 { + return errors.New("invalid number of args. expected only the packaged chaincode file") + } + p.setInput(args[0]) + + return p.PackageID() +} + +// PackageID calculates the package ID for a packaged chaincode and print it. +func (p *PackageIDCalculator) PackageID() error { + err := p.Input.Validate() + if err != nil { + return err + } + pkgBytes, err := p.Reader.ReadFile(p.Input.PackageFile) + if err != nil { + return errors.WithMessagef(err, "failed to read chaincode package at '%s'", p.Input.PackageFile) + } + + metadata, _, err := persistence.ParseChaincodePackage(pkgBytes) + if err != nil { + return errors.WithMessage(err, "could not parse as a chaincode install package") + } + + packageID := persistence.PackageID(metadata.Label, pkgBytes) + + fmt.Fprintf(p.Writer, "Package ID: %s\n", packageID) + return nil +} + +func (p *PackageIDCalculator) setInput(packageFile string) { + p.Input = &CalculatePackageIDInput{ + PackageFile: packageFile, + } +} diff --git a/internal/peer/lifecycle/chaincode/calculatepackageid_test.go b/internal/peer/lifecycle/chaincode/calculatepackageid_test.go new file mode 100644 index 00000000000..2643c5c1649 --- /dev/null +++ b/internal/peer/lifecycle/chaincode/calculatepackageid_test.go @@ -0,0 +1,133 @@ +/* +Copyright Hitachi, Ltd. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package chaincode_test + +import ( + "io/ioutil" + + "github.com/hyperledger/fabric/internal/peer/lifecycle/chaincode" + "github.com/hyperledger/fabric/internal/peer/lifecycle/chaincode/mock" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("CalculatePackageID", func() { + Describe("PackageIDCalculator", func() { + var ( + mockReader *mock.Reader + input *chaincode.CalculatePackageIDInput + packageIDCalculator *chaincode.PackageIDCalculator + ) + + BeforeEach(func() { + input = &chaincode.CalculatePackageIDInput{ + PackageFile: "pkgFile", + } + + mockReader = &mock.Reader{} + data, err := ioutil.ReadFile("testdata/good-package.tar.gz") + Expect(err).NotTo(HaveOccurred()) + mockReader.ReadFileReturns(data, nil) + + buffer := gbytes.NewBuffer() + + packageIDCalculator = &chaincode.PackageIDCalculator{ + Input: input, + Reader: mockReader, + Writer: buffer, + } + }) + + It("calculates the package IDs for chaincodes", func() { + err := packageIDCalculator.PackageID() + Expect(err).NotTo(HaveOccurred()) + Eventually(packageIDCalculator.Writer).Should(gbytes.Say("Package ID: Real-Label:fb3edf9621c5e3d864079d8c9764205f4db09d7021cfa4124aa79f4edcc2f64a\n")) + }) + + Context("when the chaincode install package is not provided", func() { + BeforeEach(func() { + packageIDCalculator.Input.PackageFile = "" + }) + + It("returns an error", func() { + err := packageIDCalculator.PackageID() + Expect(err).To(MatchError("chaincode install package must be provided")) + }) + }) + + Context("when the package file cannot be read", func() { + BeforeEach(func() { + mockReader.ReadFileReturns(nil, errors.New("coffee")) + }) + + It("returns an error", func() { + err := packageIDCalculator.PackageID() + Expect(err).To(MatchError("failed to read chaincode package at 'pkgFile': coffee")) + }) + }) + + Context("when the package file cannot be parsed", func() { + BeforeEach(func() { + data, err := ioutil.ReadFile("testdata/unparsed-package.tar.gz") + Expect(err).NotTo(HaveOccurred()) + mockReader.ReadFileReturns(data, nil) + }) + + It("returns an error", func() { + err := packageIDCalculator.PackageID() + Expect(err).To(MatchError(ContainSubstring("could not parse as a chaincode install package"))) + }) + }) + }) + + Describe("CalculatePackageIDCmd", func() { + var calculatePackageIDCmd *cobra.Command + + BeforeEach(func() { + calculatePackageIDCmd = chaincode.CalculatePackageIDCmd(nil) + calculatePackageIDCmd.SilenceErrors = true + calculatePackageIDCmd.SilenceUsage = true + calculatePackageIDCmd.SetArgs([]string{ + "testpkg", + }) + }) + + It("sets up the calculator and attempts to calculate the package ID for the chaincode", func() { + err := calculatePackageIDCmd.Execute() + Expect(err).To(MatchError(ContainSubstring("failed to read chaincode package at 'testpkg'"))) + }) + + Context("when more than one argument is provided", func() { + BeforeEach(func() { + calculatePackageIDCmd.SetArgs([]string{ + "testpkg", + "whatthe", + }) + }) + + It("returns an error", func() { + err := calculatePackageIDCmd.Execute() + Expect(err).To(MatchError("invalid number of args. expected only the packaged chaincode file")) + }) + }) + + Context("when no argument is provided", func() { + BeforeEach(func() { + calculatePackageIDCmd.SetArgs([]string{}) + }) + + It("returns an error", func() { + err := calculatePackageIDCmd.Execute() + Expect(err).To(MatchError("invalid number of args. expected only the packaged chaincode file")) + }) + }) + }) +}) diff --git a/internal/peer/lifecycle/chaincode/chaincode.go b/internal/peer/lifecycle/chaincode/chaincode.go index 72d3f2d00ad..19126685825 100644 --- a/internal/peer/lifecycle/chaincode/chaincode.go +++ b/internal/peer/lifecycle/chaincode/chaincode.go @@ -34,6 +34,7 @@ func Cmd(cryptoProvider bccsp.BCCSP) *cobra.Command { addFlags(chaincodeCmd) chaincodeCmd.AddCommand(PackageCmd(nil)) + chaincodeCmd.AddCommand(CalculatePackageIDCmd(nil)) chaincodeCmd.AddCommand(InstallCmd(nil, cryptoProvider)) chaincodeCmd.AddCommand(QueryInstalledCmd(nil, cryptoProvider)) chaincodeCmd.AddCommand(GetInstalledPackageCmd(nil, cryptoProvider)) @@ -74,8 +75,8 @@ var ( var chaincodeCmd = &cobra.Command{ Use: "chaincode", - Short: "Perform chaincode operations: package|install|queryinstalled|getinstalledpackage|approveformyorg|queryapproved|checkcommitreadiness|commit|querycommitted", - Long: "Perform chaincode operations: package|install|queryinstalled|getinstalledpackage|approveformyorg|queryapproved|checkcommitreadiness|commit|querycommitted", + Short: "Perform chaincode operations: package|install|queryinstalled|getinstalledpackage|calculatepackageid|approveformyorg|queryapproved|checkcommitreadiness|commit|querycommitted", + Long: "Perform chaincode operations: package|install|queryinstalled|getinstalledpackage|calculatepackageid|approveformyorg|queryapproved|checkcommitreadiness|commit|querycommitted", PersistentPreRun: func(cmd *cobra.Command, args []string) { common.InitCmd(cmd, args) common.SetOrdererEnv(cmd, args) diff --git a/internal/peer/lifecycle/chaincode/testdata/good-package.tar.gz b/internal/peer/lifecycle/chaincode/testdata/good-package.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..5ca7bc3b46c6c29fea6b3fa715b615766034fbe7 GIT binary patch literal 252 zcmb2|=3uDmco)mS{PwbA_8|iawg>ZmYdf9G_MEkhwZ1`}->6Lf{;hmJ>8%+u#fSgr z2|r8hQRK0b+;d*_=RKBLa`#NDvrY!@yOnQ#(&EYjtaI3l?>ZrQkX`{s@`Yu#`bNCN8g(}n_h<5-`!pI&%btwlSloVNWUjjtj@_D`lq+V_UCkO zEB@aztg4dCk9_yhn|)OA*`H_SUw-UKxqj;8^BG4Ct5wSrX3o15{a>f%a!&NQcD5(o z-!JYfT>Iba>cao7fug&vgud8+@x}l7FIK+HIJmuzjR6h(xg