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 }