github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/chaincode/persistence/chaincode_package.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package persistence
     8  
     9  import (
    10  	"archive/tar"
    11  	"bytes"
    12  	"compress/gzip"
    13  	"encoding/json"
    14  	"io"
    15  	"io/ioutil"
    16  	"os"
    17  	"path/filepath"
    18  	"regexp"
    19  
    20  	pb "github.com/hyperledger/fabric-protos-go/peer"
    21  
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  // The chaincode package is simply a .tar.gz file.  For the time being, we
    26  // assume that the package contains a metadata.json file which contains a
    27  // 'type', a 'path', and a 'label'.  In the future, it would be nice if we
    28  // move to a more buildpack type system, rather than the below presented
    29  // JAR+manifest type system, but for expediency and incremental changes,
    30  // moving to a tar format over the proto format for a user-inspectable
    31  // artifact seems like a good step.
    32  
    33  const (
    34  	// MetadataFile is the expected location of the metadata json document
    35  	// in the top level of the chaincode package.
    36  	MetadataFile = "metadata.json"
    37  
    38  	// CodePackageFile is the expected location of the code package in the
    39  	// top level of the chaincode package
    40  	CodePackageFile = "code.tar.gz"
    41  )
    42  
    43  //go:generate counterfeiter -o mock/legacy_cc_package_locator.go --fake-name LegacyCCPackageLocator . LegacyCCPackageLocator
    44  
    45  type LegacyCCPackageLocator interface {
    46  	GetChaincodeDepSpec(nameVersion string) (*pb.ChaincodeDeploymentSpec, error)
    47  }
    48  
    49  type FallbackPackageLocator struct {
    50  	ChaincodePackageLocator *ChaincodePackageLocator
    51  	LegacyCCPackageLocator  LegacyCCPackageLocator
    52  }
    53  
    54  func (fpl *FallbackPackageLocator) GetChaincodePackage(packageID string) (*ChaincodePackageMetadata, []byte, io.ReadCloser, error) {
    55  	// XXX, this path has too many return parameters.  We could split it into two calls,
    56  	// or, we could deserialize the metadata where it's needed.  But, as written was the
    57  	// fastest path to fixing a bug around the mutation of metadata.
    58  	streamer := fpl.ChaincodePackageLocator.ChaincodePackageStreamer(packageID)
    59  	if streamer.Exists() {
    60  		metadata, err := streamer.Metadata()
    61  		if err != nil {
    62  			return nil, nil, nil, errors.WithMessagef(err, "error retrieving chaincode package metadata '%s'", packageID)
    63  		}
    64  
    65  		mdBytes, err := streamer.MetadataBytes()
    66  		if err != nil {
    67  			return nil, nil, nil, errors.WithMessagef(err, "error retrieving chaincode package metadata bytes '%s'", packageID)
    68  		}
    69  
    70  		tarStream, err := streamer.Code()
    71  		if err != nil {
    72  			return nil, nil, nil, errors.WithMessagef(err, "error retrieving chaincode package code '%s'", packageID)
    73  		}
    74  
    75  		return metadata, mdBytes, tarStream, nil
    76  	}
    77  
    78  	cds, err := fpl.LegacyCCPackageLocator.GetChaincodeDepSpec(string(packageID))
    79  	if err != nil {
    80  		return nil, nil, nil, errors.WithMessagef(err, "could not get legacy chaincode package '%s'", packageID)
    81  	}
    82  
    83  	md := &ChaincodePackageMetadata{
    84  		Path:  cds.ChaincodeSpec.ChaincodeId.Path,
    85  		Type:  cds.ChaincodeSpec.Type.String(),
    86  		Label: cds.ChaincodeSpec.ChaincodeId.Name,
    87  	}
    88  
    89  	mdBytes, err := json.Marshal(md)
    90  	if err != nil {
    91  		return nil, nil, nil, errors.WithMessagef(err, "could not marshal metadata for chaincode package '%s'", packageID)
    92  	}
    93  
    94  	return md,
    95  		mdBytes,
    96  		ioutil.NopCloser(bytes.NewBuffer(cds.CodePackage)),
    97  		nil
    98  }
    99  
   100  type ChaincodePackageLocator struct {
   101  	ChaincodeDir string
   102  }
   103  
   104  func (cpl *ChaincodePackageLocator) ChaincodePackageStreamer(packageID string) *ChaincodePackageStreamer {
   105  	return &ChaincodePackageStreamer{
   106  		PackagePath: filepath.Join(cpl.ChaincodeDir, CCFileName(packageID)),
   107  	}
   108  }
   109  
   110  type ChaincodePackageStreamer struct {
   111  	PackagePath string
   112  }
   113  
   114  func (cps *ChaincodePackageStreamer) Exists() bool {
   115  	_, err := os.Stat(cps.PackagePath)
   116  	return err == nil
   117  }
   118  
   119  func (cps *ChaincodePackageStreamer) Metadata() (*ChaincodePackageMetadata, error) {
   120  	tarFileStream, err := cps.File(MetadataFile)
   121  	if err != nil {
   122  		return nil, errors.WithMessage(err, "could not get metadata file")
   123  	}
   124  
   125  	defer tarFileStream.Close()
   126  
   127  	metadata := &ChaincodePackageMetadata{}
   128  	err = json.NewDecoder(tarFileStream).Decode(metadata)
   129  	if err != nil {
   130  		return nil, errors.WithMessage(err, "could not parse metadata file")
   131  	}
   132  
   133  	return metadata, nil
   134  }
   135  
   136  func (cps *ChaincodePackageStreamer) MetadataBytes() ([]byte, error) {
   137  	tarFileStream, err := cps.File(MetadataFile)
   138  	if err != nil {
   139  		return nil, errors.WithMessage(err, "could not get metadata file")
   140  	}
   141  
   142  	defer tarFileStream.Close()
   143  
   144  	md, err := ioutil.ReadAll(tarFileStream)
   145  	if err != nil {
   146  		return nil, errors.WithMessage(err, "could read metadata file")
   147  	}
   148  
   149  	return md, nil
   150  }
   151  
   152  func (cps *ChaincodePackageStreamer) Code() (*TarFileStream, error) {
   153  	tarFileStream, err := cps.File(CodePackageFile)
   154  	if err != nil {
   155  		return nil, errors.WithMessage(err, "could not get code package")
   156  	}
   157  
   158  	return tarFileStream, nil
   159  }
   160  
   161  func (cps *ChaincodePackageStreamer) File(name string) (tarFileStream *TarFileStream, err error) {
   162  	file, err := os.Open(cps.PackagePath)
   163  	if err != nil {
   164  		return nil, errors.WithMessagef(err, "could not open chaincode package at '%s'", cps.PackagePath)
   165  	}
   166  
   167  	defer func() {
   168  		if err != nil {
   169  			file.Close()
   170  		}
   171  	}()
   172  
   173  	gzReader, err := gzip.NewReader(file)
   174  	if err != nil {
   175  		return nil, errors.Wrapf(err, "error reading as gzip stream")
   176  	}
   177  
   178  	tarReader := tar.NewReader(gzReader)
   179  
   180  	for {
   181  		header, err := tarReader.Next()
   182  		if err == io.EOF {
   183  			break
   184  		}
   185  
   186  		if err != nil {
   187  			return nil, errors.Wrapf(err, "error inspecting next tar header")
   188  		}
   189  
   190  		if header.Name != name {
   191  			continue
   192  		}
   193  
   194  		if header.Typeflag != tar.TypeReg {
   195  			return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag)
   196  		}
   197  
   198  		return &TarFileStream{
   199  			TarFile:    tarReader,
   200  			FileStream: file,
   201  		}, nil
   202  	}
   203  
   204  	return nil, errors.Errorf("did not find file '%s' in package", name)
   205  }
   206  
   207  type TarFileStream struct {
   208  	TarFile    io.Reader
   209  	FileStream io.Closer
   210  }
   211  
   212  func (tfs *TarFileStream) Read(p []byte) (int, error) {
   213  	return tfs.TarFile.Read(p)
   214  }
   215  
   216  func (tfs *TarFileStream) Close() error {
   217  	return tfs.FileStream.Close()
   218  }
   219  
   220  // ChaincodePackage represents the un-tar-ed format of the chaincode package.
   221  type ChaincodePackage struct {
   222  	Metadata    *ChaincodePackageMetadata
   223  	CodePackage []byte
   224  	DBArtifacts []byte
   225  }
   226  
   227  // ChaincodePackageMetadata contains the information necessary to understand
   228  // the embedded code package.
   229  type ChaincodePackageMetadata struct {
   230  	Type  string `json:"type"`
   231  	Path  string `json:"path"`
   232  	Label string `json:"label"`
   233  }
   234  
   235  // MetadataProvider provides the means to retrieve metadata
   236  // information (for instance the DB indexes) from a code package.
   237  type MetadataProvider interface {
   238  	GetDBArtifacts(codePackage []byte) ([]byte, error)
   239  }
   240  
   241  // ChaincodePackageParser provides the ability to parse chaincode packages.
   242  type ChaincodePackageParser struct {
   243  	MetadataProvider MetadataProvider
   244  }
   245  
   246  // LabelRegexp is the regular expression controlling the allowed characters
   247  // for the package label.
   248  var LabelRegexp = regexp.MustCompile(`^[[:alnum:]][[:alnum:]_.+-]*$`)
   249  
   250  // ValidateLabel return an error if the provided label contains any invalid
   251  // characters, as determined by LabelRegexp.
   252  func ValidateLabel(label string) error {
   253  	if !LabelRegexp.MatchString(label) {
   254  		return errors.Errorf("invalid label '%s'. Label must be non-empty, can only consist of alphanumerics, symbols from '.+-_', and can only begin with alphanumerics", label)
   255  	}
   256  
   257  	return nil
   258  }
   259  
   260  // Parse parses a set of bytes as a chaincode package
   261  // and returns the parsed package as a struct
   262  func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, error) {
   263  	ccPackageMetadata, codePackage, err := ParseChaincodePackage(source)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	dbArtifacts, err := ccpp.MetadataProvider.GetDBArtifacts(codePackage)
   269  	if err != nil {
   270  		return nil, errors.WithMessage(err, "error retrieving DB artifacts from code package")
   271  	}
   272  
   273  	return &ChaincodePackage{
   274  		Metadata:    ccPackageMetadata,
   275  		CodePackage: codePackage,
   276  		DBArtifacts: dbArtifacts,
   277  	}, nil
   278  }
   279  
   280  // ParseChaincodePackage parses a set of bytes as a chaincode package
   281  // and returns the parsed package as a metadata struct and a code package
   282  func ParseChaincodePackage(source []byte) (*ChaincodePackageMetadata, []byte, error) {
   283  	gzReader, err := gzip.NewReader(bytes.NewBuffer(source))
   284  	if err != nil {
   285  		return &ChaincodePackageMetadata{}, nil, errors.Wrapf(err, "error reading as gzip stream")
   286  	}
   287  
   288  	tarReader := tar.NewReader(gzReader)
   289  
   290  	var codePackage []byte
   291  	var ccPackageMetadata *ChaincodePackageMetadata
   292  	for {
   293  		header, err := tarReader.Next()
   294  		if err == io.EOF {
   295  			break
   296  		}
   297  
   298  		if err != nil {
   299  			return ccPackageMetadata, nil, errors.Wrapf(err, "error inspecting next tar header")
   300  		}
   301  
   302  		if header.Typeflag != tar.TypeReg {
   303  			return ccPackageMetadata, nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag)
   304  		}
   305  
   306  		fileBytes, err := ioutil.ReadAll(tarReader)
   307  		if err != nil {
   308  			return ccPackageMetadata, nil, errors.Wrapf(err, "could not read %s from tar", header.Name)
   309  		}
   310  
   311  		switch header.Name {
   312  
   313  		case MetadataFile:
   314  			ccPackageMetadata = &ChaincodePackageMetadata{}
   315  			err := json.Unmarshal(fileBytes, ccPackageMetadata)
   316  			if err != nil {
   317  				return ccPackageMetadata, nil, errors.Wrapf(err, "could not unmarshal %s as json", MetadataFile)
   318  			}
   319  
   320  		case CodePackageFile:
   321  			codePackage = fileBytes
   322  		default:
   323  			logger.Warningf("Encountered unexpected file '%s' in top level of chaincode package", header.Name)
   324  		}
   325  	}
   326  
   327  	if codePackage == nil {
   328  		return ccPackageMetadata, nil, errors.Errorf("did not find a code package inside the package")
   329  	}
   330  
   331  	if ccPackageMetadata == nil {
   332  		return ccPackageMetadata, nil, errors.Errorf("did not find any package metadata (missing %s)", MetadataFile)
   333  	}
   334  
   335  	if err := ValidateLabel(ccPackageMetadata.Label); err != nil {
   336  		return ccPackageMetadata, nil, err
   337  	}
   338  
   339  	return ccPackageMetadata, codePackage, nil
   340  }