Skip to content

Commit

Permalink
[FABG-736] Supported java chaincode (hyperledger#37)
Browse files Browse the repository at this point in the history
* [FABG-736] Support for java chaincode

Support for installing, instantiating and upgrading java chaincode

Signed-off-by: yakumioto <[email protected]>
  • Loading branch information
yakumioto authored and alikic committed Dec 29, 2019
1 parent e1055f3 commit 4f8dd0d
Show file tree
Hide file tree
Showing 23 changed files with 948 additions and 4 deletions.
4 changes: 3 additions & 1 deletion pkg/client/resmgmt/lscc.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type chaincodeDeployRequest struct {
Name string
Path string
Version string
Lang pb.ChaincodeSpec_Type
Args [][]byte
Policy *common.SignaturePolicyEnvelope
CollConfig []*pb.CollectionConfig
Expand All @@ -52,7 +53,7 @@ func createChaincodeDeployProposal(txh fab.TransactionHeader, deploy chaincodePr
args = append(args, []byte(channelID))

ccds := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{
Type: pb.ChaincodeSpec_GOLANG, ChaincodeId: &pb.ChaincodeID{Name: chaincode.Name, Path: chaincode.Path, Version: chaincode.Version},
Type: chaincode.Lang, ChaincodeId: &pb.ChaincodeID{Name: chaincode.Name, Path: chaincode.Path, Version: chaincode.Version},
Input: &pb.ChaincodeInput{Args: chaincode.Args}}}
ccdsBytes, err := protoutil.Marshal(ccds)
if err != nil {
Expand Down Expand Up @@ -90,6 +91,7 @@ func createChaincodeDeployProposal(txh fab.TransactionHeader, deploy chaincodePr

cir := fab.ChaincodeInvokeRequest{
ChaincodeID: lscc,
Lang: chaincode.Lang,
Fcn: fcn,
Args: args,
}
Expand Down
12 changes: 10 additions & 2 deletions pkg/client/resmgmt/resmgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type InstantiateCCRequest struct {
Name string
Path string
Version string
Lang pb.ChaincodeSpec_Type
Args [][]byte
Policy *common.SignaturePolicyEnvelope
CollConfig []*pb.CollectionConfig
Expand All @@ -87,6 +88,7 @@ type UpgradeCCRequest struct {
Name string
Path string
Version string
Lang pb.ChaincodeSpec_Type
Args [][]byte
Policy *common.SignaturePolicyEnvelope
CollConfig []*pb.CollectionConfig
Expand Down Expand Up @@ -791,7 +793,7 @@ func (rc *Client) verifyTPSignature(channelService fab.ChannelService, txProposa

// sendCCProposal sends proposal for type Instantiate, Upgrade
func (rc *Client) sendCCProposal(reqCtx reqContext.Context, ccProposalType chaincodeProposalType, channelID string, req InstantiateCCRequest, opts requestOptions) (fab.TransactionID, error) {
if err := checkRequiredCCProposalParams(channelID, req); err != nil {
if err := checkRequiredCCProposalParams(channelID, &req); err != nil {
return fab.EmptyTransactionID, err
}

Expand Down Expand Up @@ -866,7 +868,7 @@ func (rc *Client) sendTransactionAndCheckEvent(eventService fab.EventService, tp
}
}

func checkRequiredCCProposalParams(channelID string, req InstantiateCCRequest) error {
func checkRequiredCCProposalParams(channelID string, req *InstantiateCCRequest) error {

if channelID == "" {
return errors.New("must provide channel ID")
Expand All @@ -875,6 +877,12 @@ func checkRequiredCCProposalParams(channelID string, req InstantiateCCRequest) e
if req.Name == "" || req.Version == "" || req.Path == "" || req.Policy == nil {
return errors.New("Chaincode name, version, path and policy are required")
}

// Forward compatibility, set Lang to golang by default
if req.Lang == 0 || pb.ChaincodeSpec_Type_name[int32(req.Lang)] == "" {
req.Lang = pb.ChaincodeSpec_GOLANG
}

return nil
}

Expand Down
33 changes: 33 additions & 0 deletions pkg/client/resmgmt/resmgmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,39 @@ func TestGetConfigSignaturesFromIdentities(t *testing.T) {
assert.NotNil(t, signature, "signatures must not be empty")
}

func TestCheckRequiredCCProposalParams(t *testing.T) {
// Valid request
ccPolicy := cauthdsl.SignedByMspMember("Org1MSP")
req := InstantiateCCRequest{Name: "name", Version: "version", Path: "path", Policy: ccPolicy}

// Test empty channel lang
checkRequiredCCProposalParams("mychannel", &req)
if req.Lang != pb.ChaincodeSpec_GOLANG {
t.Fatal("Lang must be equal to golang", req.Lang)
}

// Test channel lang with golang
req = InstantiateCCRequest{Name: "name", Version: "version", Lang: pb.ChaincodeSpec_GOLANG, Path: "path", Policy: ccPolicy}
checkRequiredCCProposalParams("mychannel", &req)
if req.Lang != pb.ChaincodeSpec_GOLANG {
t.Fatal("Lang must be equal to golang")
}

// Test channel lang with java
req = InstantiateCCRequest{Name: "name", Version: "version", Lang: pb.ChaincodeSpec_JAVA, Path: "path", Policy: ccPolicy}
checkRequiredCCProposalParams("mychannel", &req)
if req.Lang != pb.ChaincodeSpec_JAVA {
t.Fatal("Lang must be equal to java")
}

// Test channel lang with unknown
req = InstantiateCCRequest{Name: "name", Version: "version", Lang: 11, Path: "path", Policy: ccPolicy}
checkRequiredCCProposalParams("mychannel", &req)
if req.Lang != pb.ChaincodeSpec_GOLANG {
t.Fatal("Lang must be equal to golang", req.Lang)
}
}

func createClientContext(fabCtx context.Client) context.ClientProvider {
return func() (context.Client, error) {
return fabCtx, nil
Expand Down
1 change: 1 addition & 0 deletions pkg/common/providers/fab/proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type TransactionHeader interface {
// ChaincodeInvokeRequest contains the parameters for sending a transaction proposal.
type ChaincodeInvokeRequest struct {
ChaincodeID string
Lang pb.ChaincodeSpec_Type
TransientMap map[string][]byte
Fcn string
Args [][]byte
Expand Down
209 changes: 209 additions & 0 deletions pkg/fab/ccpackager/javapackager/packager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
Copyright Mioto Yaku All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package javapackager

import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"

pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric-sdk-go/pkg/common/logging"
"github.com/hyperledger/fabric-sdk-go/pkg/fab/resource"
"github.com/pkg/errors"
)

// Descriptor ...
type Descriptor struct {
name string
fqp string
}

var keep = []string{".c", ".h", ".s", ".java", ".yaml", ".json", ".xml", ".gradle"}

var logger = logging.NewLogger("fabsdk/fab")

// NewCCPackage creates new go lang chaincode package
func NewCCPackage(chaincodePath string) (*resource.CCPackage, error) {

if chaincodePath == "" {
return nil, errors.New("chaincode path must be provided")
}

logger.Debugf("projDir variable=%s", chaincodePath)

// We generate the tar in two phases: First grab a list of descriptors,
// and then pack them into an archive. While the two phases aren't
// strictly necessary yet, they pave the way for the future where we
// will need to assemble sources from multiple packages
descriptors, err := findSource(chaincodePath)
if err != nil {
return nil, err
}
tarBytes, err := generateTarGz(descriptors)
if err != nil {
return nil, err
}

ccPkg := &resource.CCPackage{Type: pb.ChaincodeSpec_JAVA, Code: tarBytes}

return ccPkg, nil
}

// -------------------------------------------------------------------------
// findSource(goPath, filePath)
// -------------------------------------------------------------------------
// Given an input 'filePath', recursively parse the filesystem for any files
// that fit the criteria for being valid golang source (ISREG + (*.(go|c|h)))
// As a convenience, we also formulate a tar-friendly "name" for each file
// based on relative position to 'goPath'.
// -------------------------------------------------------------------------
func findSource(filePath string) ([]*Descriptor, error) {
var descriptors []*Descriptor

folder := filePath
// trim trailing slash if it exists
if folder[len(folder)-1] == '/' {
folder = folder[:len(folder)-1]
}

var abs bool
if abs = filepath.IsAbs(folder); !abs {
var err error
folder, err = filepath.Rel("", folder)
if err != nil {
return nil, err
}
}

err := filepath.Walk(folder,
func(path string, fileInfo os.FileInfo, err error) error {
if err != nil {
return err
}
if fileInfo.Mode().IsRegular() && isSource(path) {
relPath := path
if strings.Contains(path, "/META-INF/") {
relPath = path[strings.Index(path, "/META-INF/")+1:]
}
if len(relPath) > len(folder) {
relPath = relPath[len(folder)+1:]
}
fmt.Println(relPath, relPath)
descriptors = append(descriptors, &Descriptor{name: relPath, fqp: path})
}
return nil

})

return descriptors, err
}

// -------------------------------------------------------------------------
// isSource(path)
// -------------------------------------------------------------------------
// predicate function for determining whether a given path should be
// considered valid source code, based entirely on the extension. It is
// assumed that other checks for file type have already been performed.
// -------------------------------------------------------------------------
func isSource(filePath string) bool {
var extension = filepath.Ext(filePath)
for _, v := range keep {
if v == extension {
return true
}
}
return false
}

// -------------------------------------------------------------------------
// generateTarGz(descriptors)
// -------------------------------------------------------------------------
// creates an .tar.gz stream from the provided descriptor entries
// -------------------------------------------------------------------------
func generateTarGz(descriptors []*Descriptor) ([]byte, error) {
// set up the gzip writer
var codePackage bytes.Buffer
gw := gzip.NewWriter(&codePackage)
tw := tar.NewWriter(gw)
for _, v := range descriptors {
logger.Debugf("generateTarGz for %s", v.fqp)
err := packEntry(tw, gw, v)
if err != nil {
err1 := closeStream(tw, gw)
if err1 != nil {
return nil, errors.Wrap(err, fmt.Sprintf("packEntry failed and close error %s", err1))
}
return nil, errors.Wrap(err, "packEntry failed")
}
}
err := closeStream(tw, gw)
if err != nil {
return nil, errors.Wrap(err, "closeStream failed")
}
return codePackage.Bytes(), nil

}

func closeStream(tw io.Closer, gw io.Closer) error {
err := tw.Close()
if err != nil {
return err
}
err = gw.Close()
return err
}

func packEntry(tw *tar.Writer, gw *gzip.Writer, descriptor *Descriptor) error {
file, err := os.Open(descriptor.fqp)
if err != nil {
return err
}
defer func() {
err := file.Close()
if err != nil {
logger.Warnf("error file close %s", err)
}
}()

if stat, err := file.Stat(); err == nil {

// now lets create the header as needed for this file within the tarball
header := new(tar.Header)
header.Name = descriptor.name
header.Size = stat.Size()
header.Mode = int64(stat.Mode())
// Use a deterministic "zero-time" for all date fields
header.ModTime = time.Time{}
header.AccessTime = time.Time{}
header.ChangeTime = time.Time{}
// write the header to the tarball archive
if err := tw.WriteHeader(header); err != nil {
return err
}

// copy the file data to the tarball

if _, err := io.Copy(tw, file); err != nil {
return err
}
if err := tw.Flush(); err != nil {
return err
}
if err := gw.Flush(); err != nil {
return err
}

}
return nil
}
Loading

0 comments on commit 4f8dd0d

Please sign in to comment.