Skip to content

Commit

Permalink
[FABG-993] Implemented lifecycle approve chaincode (#112)
Browse files Browse the repository at this point in the history
Implement LifecycleApproveCC which approves a chaincode for an organization.

Signed-off-by: Bob Stasyszyn <Bob.Stasyszyn@securekey.com>
  • Loading branch information
bstasyszyn committed Jul 23, 2020
1 parent c0312be commit ec04055
Show file tree
Hide file tree
Showing 9 changed files with 1,107 additions and 6 deletions.
29 changes: 29 additions & 0 deletions pkg/client/resmgmt/lifecycleclient.go
Expand Up @@ -9,6 +9,8 @@ package resmgmt
import (
reqContext "context"

"github.com/hyperledger/fabric-protos-go/common"
pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/pkg/errors"

"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/multi"
Expand Down Expand Up @@ -46,6 +48,20 @@ type LifecycleInstalledCC struct {
References map[string][]CCReference
}

// LifecycleApproveCCRequest contains the parameters for approving a chaincode for an org.
type LifecycleApproveCCRequest struct {
Name string
Version string
PackageID string
Sequence int64
EndorsementPlugin string
ValidationPlugin string
SignaturePolicy *common.SignaturePolicyEnvelope
ChannelConfigPolicy string
CollectionConfig []*pb.CollectionConfig
InitRequired bool
}

// LifecycleInstallCC installs a chaincode package using Fabric 2.0 chaincode lifecycle.
func (rc *Client) LifecycleInstallCC(req LifecycleInstallCCRequest, options ...RequestOption) ([]LifecycleInstallCCResponse, error) {
err := rc.lifecycleProcessor.verifyInstallParams(req)
Expand Down Expand Up @@ -175,3 +191,16 @@ func (rc *Client) LifecycleGetInstalledCCPackage(packageID string, options ...Re

return response, errs.ToError()
}

// LifecycleApproveCC approves a chaincode for an organization.
func (rc *Client) LifecycleApproveCC(channelID string, req LifecycleApproveCCRequest, options ...RequestOption) (fab.TransactionID, error) {
opts, err := rc.prepareRequestOpts(options...)
if err != nil {
return "", errors.WithMessage(err, "failed to get opts for ApproveCC")
}

reqCtx, cancel := rc.createRequestContext(opts, fab.ResMgmt)
defer cancel()

return rc.lifecycleProcessor.approve(reqCtx, channelID, req, opts)
}
137 changes: 137 additions & 0 deletions pkg/client/resmgmt/lifecycleclient_test.go
Expand Up @@ -215,3 +215,140 @@ func TestClient_LifecycleQueryInstalled(t *testing.T) {
require.Empty(t, resp)
})
}

func TestClient_LifecycleApproveCC(t *testing.T) {
const channelID = "channel1"

respBytes, err := proto.Marshal(&lb.ApproveChaincodeDefinitionForMyOrgResult{})
require.NoError(t, err)

peer1 := &fcmocks.MockPeer{Payload: respBytes}
rc := setupDefaultResMgmtClient(t)

req := LifecycleApproveCCRequest{
Name: "cc1",
Version: "v1",
PackageID: "pkg1",
Sequence: 1,
}

t.Run("Success", func(t *testing.T) {
txnID, err := rc.LifecycleApproveCC(channelID, req, WithTargets(peer1))
require.NoError(t, err)
require.NotEmpty(t, txnID)
})

t.Run("No channel ID -> error", func(t *testing.T) {
txnID, err := rc.LifecycleApproveCC("", req, WithTargets(peer1))
require.EqualError(t, err, "channel ID is required")
require.Empty(t, txnID)
})

t.Run("No name -> error", func(t *testing.T) {
req := LifecycleApproveCCRequest{
Version: "v1",
PackageID: "pkg1",
Sequence: 1,
}

txnID, err := rc.LifecycleApproveCC(channelID, req, WithTargets(peer1))
require.EqualError(t, err, "name is required")
require.Empty(t, txnID)
})

t.Run("No version -> error", func(t *testing.T) {
req := LifecycleApproveCCRequest{
Name: "cc1",
PackageID: "pkg1",
Sequence: 1,
}

txnID, err := rc.LifecycleApproveCC(channelID, req, WithTargets(peer1))
require.EqualError(t, err, "version is required")
require.Empty(t, txnID)
})

t.Run("Get targets -> error", func(t *testing.T) {
errExpected := fmt.Errorf("injected targets error")

rc := setupDefaultResMgmtClient(t)
rc.lifecycleProcessor.getCCProposalTargets = func(string, requestOptions) ([]fab.Peer, error) { return nil, errExpected }

txnID, err := rc.LifecycleApproveCC(channelID, req, WithTargets(peer1))
require.EqualError(t, err, errExpected.Error())
require.Empty(t, txnID)
})

t.Run("CreateProposal -> error", func(t *testing.T) {
errExpected := fmt.Errorf("injected proposal error")

rc := setupDefaultResMgmtClient(t)

lifecycleResource := &MockLifecycleResource{}
lifecycleResource.CreateApproveProposalReturns(nil, errExpected)
rc.lifecycleProcessor.lifecycleResource = lifecycleResource

txnID, err := rc.LifecycleApproveCC(channelID, req, WithTargets(peer1))
require.Error(t, err, errExpected.Error())
require.Contains(t, err.Error(), errExpected.Error())
require.Empty(t, txnID)
})

t.Run("VerifySignature -> error", func(t *testing.T) {
errExpected := fmt.Errorf("injected signature error")

rc := setupDefaultResMgmtClient(t)

rc.lifecycleProcessor.verifyTPSignature = func(fab.ChannelService, []*fab.TransactionProposalResponse) error { return errExpected }

txnID, err := rc.LifecycleApproveCC(channelID, req, WithTargets(peer1))
require.Error(t, err, errExpected.Error())
require.Contains(t, err.Error(), errExpected.Error())
require.NotEmpty(t, txnID)
})

t.Run("Channel provider -> error", func(t *testing.T) {
ctx := setupTestContext("test", "Org1MSP")
ctx.SetEndpointConfig(getNetworkConfig(t))
cp := &MockChannelProvider{}

ctx.SetCustomChannelProvider(cp)

rc := setupResMgmtClient(t, ctx, getDefaultTargetFilterOption())
rc.lifecycleProcessor.getCCProposalTargets = func(string, requestOptions) ([]fab.Peer, error) { return []fab.Peer{peer1}, nil }

t.Run("ChannelService error", func(t *testing.T) {
errExpected := fmt.Errorf("injected provider error")
cp.ChannelServiceReturns(nil, errExpected)

txnID, err := rc.LifecycleApproveCC(channelID, req, WithTargets(peer1))
require.Error(t, err)
require.Contains(t, err.Error(), errExpected.Error())
require.Empty(t, txnID)
})

t.Run("Transactor error", func(t *testing.T) {
errExpected := fmt.Errorf("injected transactor error")
cs := &MockChannelService{}
cs.TransactorReturns(nil, errExpected)
cp.ChannelServiceReturns(cs, nil)

txnID, err := rc.LifecycleApproveCC(channelID, req, WithTargets(peer1))
require.Error(t, err)
require.Contains(t, err.Error(), errExpected.Error())
require.Empty(t, txnID)
})

t.Run("EventService error", func(t *testing.T) {
errExpected := fmt.Errorf("injected event service error")
cs := &MockChannelService{}
cs.EventServiceReturns(nil, errExpected)
cp.ChannelServiceReturns(cs, nil)

txnID, err := rc.LifecycleApproveCC(channelID, req, WithTargets(peer1))
require.Error(t, err)
require.Contains(t, err.Error(), errExpected.Error())
require.Empty(t, txnID)
})
})
}
92 changes: 88 additions & 4 deletions pkg/client/resmgmt/lifecycleprocessor.go
Expand Up @@ -21,25 +21,39 @@ import (
lifecyclepkg "github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/lifecycle"
"github.com/hyperledger/fabric-sdk-go/pkg/fab/peer"
"github.com/hyperledger/fabric-sdk-go/pkg/fab/resource"
"github.com/hyperledger/fabric-sdk-go/pkg/fab/txn"
)

//go:generate counterfeiter -o mocklifecycleresource.gen.go -fake-name MockLifecycleResource . lifecycleResource
//go:generate counterfeiter -o mockchannelprovider.gen.go -fake-name MockChannelProvider ../../common/providers/fab ChannelProvider
//go:generate counterfeiter -o mockchannelservice.gen.go -fake-name MockChannelService ../../common/providers/fab ChannelService

type lifecycleResource interface {
Install(reqCtx reqContext.Context, installPkg []byte, targets []fab.ProposalProcessor, opts ...resource.Opt) ([]*resource.LifecycleInstallProposalResponse, error)
GetInstalledPackage(reqCtx reqContext.Context, packageID string, target fab.ProposalProcessor, opts ...resource.Opt) ([]byte, error)
QueryInstalled(reqCtx reqContext.Context, target fab.ProposalProcessor, opts ...resource.Opt) (*resource.LifecycleQueryInstalledCCResponse, error)
CreateApproveProposal(txh fab.TransactionHeader, req *resource.ApproveChaincodeRequest) (*fab.TransactionProposal, error)
}

type targetProvider func(channelID string, opts requestOptions) ([]fab.Peer, error)
type signatureVerifier func(channelService fab.ChannelService, txProposalResponse []*fab.TransactionProposalResponse) error
type committer func(eventService fab.EventService, tp *fab.TransactionProposal, txProposalResponse []*fab.TransactionProposalResponse, transac fab.Transactor, reqCtx reqContext.Context) (fab.TransactionID, error)

type lifecycleProcessor struct {
lifecycleResource
ctx context.Client
ctx context.Client
getCCProposalTargets targetProvider
verifyTPSignature signatureVerifier
commitTransaction committer
}

func newLifecycleProcessor(ctx context.Client) *lifecycleProcessor {
func newLifecycleProcessor(ctx context.Client, targetProvider targetProvider, signatureVerifier signatureVerifier, committer committer) *lifecycleProcessor {
return &lifecycleProcessor{
lifecycleResource: resource.NewLifecycle(),
ctx: ctx,
lifecycleResource: resource.NewLifecycle(),
ctx: ctx,
getCCProposalTargets: targetProvider,
verifyTPSignature: signatureVerifier,
commitTransaction: committer,
}
}

Expand Down Expand Up @@ -75,6 +89,60 @@ func (p *lifecycleProcessor) queryInstalled(reqCtx reqContext.Context, target fa
return p.toInstalledChaincodes(r.InstalledChaincodes), nil
}

func (p *lifecycleProcessor) approve(reqCtx reqContext.Context, channelID string, req LifecycleApproveCCRequest, opts requestOptions) (fab.TransactionID, error) {
if err := p.verifyApproveParams(channelID, req); err != nil {
return fab.EmptyTransactionID, err
}

targets, err := p.getCCProposalTargets(channelID, opts)
if err != nil {
return fab.EmptyTransactionID, err
}

// Get transactor on the channel to create and send the deploy proposal
channelService, err := p.ctx.ChannelProvider().ChannelService(p.ctx, channelID)
if err != nil {
return fab.EmptyTransactionID, errors.WithMessage(err, "Unable to get channel service")
}

transactor, err := channelService.Transactor(reqCtx)
if err != nil {
return fab.EmptyTransactionID, errors.WithMessage(err, "get channel transactor failed")
}

eventService, err := channelService.EventService()
if err != nil {
return fab.EmptyTransactionID, errors.WithMessage(err, "unable to get event service")
}

txh, err := txn.NewHeader(p.ctx, channelID)
if err != nil {
return fab.EmptyTransactionID, errors.WithMessage(err, "create transaction ID failed")
}

var acr = resource.ApproveChaincodeRequest(req)

tp, err := p.lifecycleResource.CreateApproveProposal(txh, &acr)
if err != nil {
return fab.EmptyTransactionID, errors.WithMessage(err, "creation of approve chaincode proposal failed")
}

// Process and send transaction proposal
txProposalResponse, err := transactor.SendTransactionProposal(tp, peersToTxnProcessors(targets))
if err != nil {
return tp.TxnID, errors.WithMessage(err, "sending approve transaction proposal failed")
}

// Verify signature(s)
err = p.verifyTPSignature(channelService, txProposalResponse)
if err != nil {
return tp.TxnID, errors.WithMessage(err, "sending approve transaction proposal failed to verify signature")
}

// send transaction and check event
return p.commitTransaction(eventService, tp, txProposalResponse, transactor, reqCtx)
}

func (p *lifecycleProcessor) adjustTargetsForInstall(targets []fab.Peer, req LifecycleInstallCCRequest, retry retry.Opts, parentReqCtx reqContext.Context) ([]fab.Peer, multi.Errors) {
errs := multi.Errors{}

Expand Down Expand Up @@ -112,6 +180,22 @@ func (p *lifecycleProcessor) verifyInstallParams(req LifecycleInstallCCRequest)
return nil
}

func (p *lifecycleProcessor) verifyApproveParams(channelID string, req LifecycleApproveCCRequest) error {
if channelID == "" {
return errors.New("channel ID is required")
}

if req.Name == "" {
return errors.New("name is required")
}

if req.Version == "" {
return errors.New("version is required")
}

return nil
}

func (p *lifecycleProcessor) isInstalled(reqCtx reqContext.Context, req LifecycleInstallCCRequest, peer fab.ProposalProcessor, retryOpts retry.Opts) (bool, error) {
packageID := lifecyclepkg.ComputePackageID(req.Label, req.Package)

Expand Down

0 comments on commit ec04055

Please sign in to comment.