Skip to content

Commit

Permalink
Add calculatepackageid command
Browse files Browse the repository at this point in the history
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 <tatsuya.sato.so@hitachi.com>
  • Loading branch information
satota2 authored and denyeart committed Oct 29, 2021
1 parent 9ef778a commit 3e433d4
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 23 deletions.
47 changes: 29 additions & 18 deletions core/chaincode/persistence/chaincode_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand All @@ -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:
Expand All @@ -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
}
9 changes: 7 additions & 2 deletions core/chaincode/persistence/persistence.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/peer/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
114 changes: 114 additions & 0 deletions internal/peer/lifecycle/chaincode/calculatepackageid.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
133 changes: 133 additions & 0 deletions internal/peer/lifecycle/chaincode/calculatepackageid_test.go
Original file line number Diff line number Diff line change
@@ -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"))
})
})
})
})
5 changes: 3 additions & 2 deletions internal/peer/lifecycle/chaincode/chaincode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
Binary file not shown.
Binary file not shown.

0 comments on commit 3e433d4

Please sign in to comment.