336 lines
9.5 KiB
Go
336 lines
9.5 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package chaincode
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
cb "github.com/hyperledger/fabric-protos-go/common"
|
|
pb "github.com/hyperledger/fabric-protos-go/peer"
|
|
lb "github.com/hyperledger/fabric-protos-go/peer/lifecycle"
|
|
"github.com/hyperledger/fabric/bccsp"
|
|
"github.com/hyperledger/fabric/internal/peer/chaincode"
|
|
"github.com/hyperledger/fabric/internal/peer/common"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// ApproverForMyOrg holds the dependencies needed to approve
|
|
// a chaincode definition for an organization
|
|
type ApproverForMyOrg struct {
|
|
Certificate tls.Certificate
|
|
Command *cobra.Command
|
|
BroadcastClient common.BroadcastClient
|
|
DeliverClients []pb.DeliverClient
|
|
EndorserClients []EndorserClient
|
|
Input *ApproveForMyOrgInput
|
|
Signer Signer
|
|
}
|
|
|
|
// ApproveForMyOrgInput holds all of the input parameters for approving a
|
|
// chaincode definition for an organization. ValidationParameter bytes is
|
|
// the (marshalled) endorsement policy when using the default endorsement
|
|
// and validation plugins
|
|
type ApproveForMyOrgInput struct {
|
|
ChannelID string
|
|
Name string
|
|
Version string
|
|
PackageID string
|
|
Sequence int64
|
|
EndorsementPlugin string
|
|
ValidationPlugin string
|
|
ValidationParameterBytes []byte
|
|
CollectionConfigPackage *pb.CollectionConfigPackage
|
|
InitRequired bool
|
|
PeerAddresses []string
|
|
WaitForEvent bool
|
|
WaitForEventTimeout time.Duration
|
|
TxID string
|
|
}
|
|
|
|
// Validate the input for an ApproveChaincodeDefinitionForMyOrg proposal
|
|
func (a *ApproveForMyOrgInput) Validate() error {
|
|
if a.ChannelID == "" {
|
|
return errors.New("The required parameter 'channelID' is empty. Rerun the command with -C flag")
|
|
}
|
|
|
|
if a.Name == "" {
|
|
return errors.New("The required parameter 'name' is empty. Rerun the command with -n flag")
|
|
}
|
|
|
|
if a.Version == "" {
|
|
return errors.New("The required parameter 'version' is empty. Rerun the command with -v flag")
|
|
}
|
|
|
|
if a.Sequence == 0 {
|
|
return errors.New("The required parameter 'sequence' is empty. Rerun the command with --sequence flag")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ApproveForMyOrgCmd returns the cobra command for chaincode ApproveForMyOrg
|
|
func ApproveForMyOrgCmd(a *ApproverForMyOrg, cryptoProvider bccsp.BCCSP) *cobra.Command {
|
|
chaincodeApproveForMyOrgCmd := &cobra.Command{
|
|
Use: "approveformyorg",
|
|
Short: "Approve the chaincode definition for my org.",
|
|
Long: "Approve the chaincode definition for my organization.",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if a == nil {
|
|
// set input from CLI flags
|
|
input, err := a.createInput()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ccInput := &ClientConnectionsInput{
|
|
CommandName: cmd.Name(),
|
|
EndorserRequired: true,
|
|
OrdererRequired: true,
|
|
ChannelID: channelID,
|
|
PeerAddresses: peerAddresses,
|
|
TLSRootCertFiles: tlsRootCertFiles,
|
|
ConnectionProfilePath: connectionProfilePath,
|
|
TLSEnabled: viper.GetBool("peer.tls.enabled"),
|
|
}
|
|
|
|
cc, err := NewClientConnections(ccInput, cryptoProvider)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
endorserClients := make([]EndorserClient, len(cc.EndorserClients))
|
|
for i, e := range cc.EndorserClients {
|
|
endorserClients[i] = e
|
|
}
|
|
|
|
a = &ApproverForMyOrg{
|
|
Command: cmd,
|
|
Input: input,
|
|
Certificate: cc.Certificate,
|
|
BroadcastClient: cc.BroadcastClient,
|
|
DeliverClients: cc.DeliverClients,
|
|
EndorserClients: endorserClients,
|
|
Signer: cc.Signer,
|
|
}
|
|
}
|
|
return a.Approve()
|
|
},
|
|
}
|
|
flagList := []string{
|
|
"channelID",
|
|
"name",
|
|
"version",
|
|
"package-id",
|
|
"sequence",
|
|
"endorsement-plugin",
|
|
"validation-plugin",
|
|
"signature-policy",
|
|
"channel-config-policy",
|
|
"init-required",
|
|
"collections-config",
|
|
"peerAddresses",
|
|
"tlsRootCertFiles",
|
|
"connectionProfile",
|
|
"waitForEvent",
|
|
"waitForEventTimeout",
|
|
}
|
|
attachFlags(chaincodeApproveForMyOrgCmd, flagList)
|
|
|
|
return chaincodeApproveForMyOrgCmd
|
|
}
|
|
|
|
// Approve submits a ApproveChaincodeDefinitionForMyOrg
|
|
// proposal
|
|
func (a *ApproverForMyOrg) Approve() error {
|
|
err := a.Input.Validate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if a.Command != nil {
|
|
// Parsing of the command line is done so silence cmd usage
|
|
a.Command.SilenceUsage = true
|
|
}
|
|
|
|
proposal, txID, err := a.createProposal(a.Input.TxID)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to create proposal")
|
|
}
|
|
|
|
signedProposal, err := signProposal(proposal, a.Signer)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to create signed proposal")
|
|
}
|
|
|
|
var responses []*pb.ProposalResponse
|
|
for _, endorser := range a.EndorserClients {
|
|
proposalResponse, err := endorser.ProcessProposal(context.Background(), signedProposal)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to endorse proposal")
|
|
}
|
|
responses = append(responses, proposalResponse)
|
|
}
|
|
|
|
if len(responses) == 0 {
|
|
// this should only be empty due to a programming bug
|
|
return errors.New("no proposal responses received")
|
|
}
|
|
|
|
// all responses will be checked when the signed transaction is created.
|
|
// for now, just set this so we check the first response's status
|
|
proposalResponse := responses[0]
|
|
|
|
if proposalResponse == nil {
|
|
return errors.New("received nil proposal response")
|
|
}
|
|
|
|
if proposalResponse.Response == nil {
|
|
return errors.Errorf("received proposal response with nil response")
|
|
}
|
|
|
|
if proposalResponse.Response.Status != int32(cb.Status_SUCCESS) {
|
|
return errors.Errorf("proposal failed with status: %d - %s", proposalResponse.Response.Status, proposalResponse.Response.Message)
|
|
}
|
|
// assemble a signed transaction (it's an Envelope message)
|
|
env, err := protoutil.CreateSignedTx(proposal, a.Signer, responses...)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to create signed transaction")
|
|
}
|
|
var dg *chaincode.DeliverGroup
|
|
var ctx context.Context
|
|
if a.Input.WaitForEvent {
|
|
var cancelFunc context.CancelFunc
|
|
ctx, cancelFunc = context.WithTimeout(context.Background(), a.Input.WaitForEventTimeout)
|
|
defer cancelFunc()
|
|
|
|
dg = chaincode.NewDeliverGroup(
|
|
a.DeliverClients,
|
|
a.Input.PeerAddresses,
|
|
a.Signer,
|
|
a.Certificate,
|
|
a.Input.ChannelID,
|
|
txID,
|
|
)
|
|
// connect to deliver service on all peers
|
|
err := dg.Connect(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err = a.BroadcastClient.Send(env); err != nil {
|
|
return errors.WithMessage(err, "failed to send transaction")
|
|
}
|
|
|
|
if dg != nil && ctx != nil {
|
|
// wait for event that contains the txID from all peers
|
|
err = dg.Wait(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// createInput creates the input struct based on the CLI flags
|
|
func (a *ApproverForMyOrg) createInput() (*ApproveForMyOrgInput, error) {
|
|
policyBytes, err := createPolicyBytes(signaturePolicy, channelConfigPolicy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ccp, err := createCollectionConfigPackage(collectionsConfigFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
input := &ApproveForMyOrgInput{
|
|
ChannelID: channelID,
|
|
Name: chaincodeName,
|
|
Version: chaincodeVersion,
|
|
PackageID: packageID,
|
|
Sequence: int64(sequence),
|
|
EndorsementPlugin: endorsementPlugin,
|
|
ValidationPlugin: validationPlugin,
|
|
ValidationParameterBytes: policyBytes,
|
|
InitRequired: initRequired,
|
|
CollectionConfigPackage: ccp,
|
|
PeerAddresses: peerAddresses,
|
|
WaitForEvent: waitForEvent,
|
|
WaitForEventTimeout: waitForEventTimeout,
|
|
}
|
|
|
|
return input, nil
|
|
}
|
|
|
|
func (a *ApproverForMyOrg) createProposal(inputTxID string) (proposal *pb.Proposal, txID string, err error) {
|
|
if a.Signer == nil {
|
|
return nil, "", errors.New("nil signer provided")
|
|
}
|
|
|
|
var ccsrc *lb.ChaincodeSource
|
|
if a.Input.PackageID != "" {
|
|
ccsrc = &lb.ChaincodeSource{
|
|
Type: &lb.ChaincodeSource_LocalPackage{
|
|
LocalPackage: &lb.ChaincodeSource_Local{
|
|
PackageId: a.Input.PackageID,
|
|
},
|
|
},
|
|
}
|
|
} else {
|
|
ccsrc = &lb.ChaincodeSource{
|
|
Type: &lb.ChaincodeSource_Unavailable_{
|
|
Unavailable: &lb.ChaincodeSource_Unavailable{},
|
|
},
|
|
}
|
|
}
|
|
|
|
args := &lb.ApproveChaincodeDefinitionForMyOrgArgs{
|
|
Name: a.Input.Name,
|
|
Version: a.Input.Version,
|
|
Sequence: a.Input.Sequence,
|
|
EndorsementPlugin: a.Input.EndorsementPlugin,
|
|
ValidationPlugin: a.Input.ValidationPlugin,
|
|
ValidationParameter: a.Input.ValidationParameterBytes,
|
|
InitRequired: a.Input.InitRequired,
|
|
Collections: a.Input.CollectionConfigPackage,
|
|
Source: ccsrc,
|
|
}
|
|
|
|
argsBytes, err := proto.Marshal(args)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
ccInput := &pb.ChaincodeInput{Args: [][]byte{[]byte(approveFuncName), argsBytes}}
|
|
|
|
cis := &pb.ChaincodeInvocationSpec{
|
|
ChaincodeSpec: &pb.ChaincodeSpec{
|
|
ChaincodeId: &pb.ChaincodeID{Name: lifecycleName},
|
|
Input: ccInput,
|
|
},
|
|
}
|
|
|
|
creatorBytes, err := a.Signer.Serialize()
|
|
if err != nil {
|
|
return nil, "", errors.WithMessage(err, "failed to serialize identity")
|
|
}
|
|
|
|
proposal, txID, err = protoutil.CreateChaincodeProposalWithTxIDAndTransient(cb.HeaderType_ENDORSER_TRANSACTION, a.Input.ChannelID, cis, creatorBytes, inputTxID, nil)
|
|
if err != nil {
|
|
return nil, "", errors.WithMessage(err, "failed to create ChaincodeInvocationSpec proposal")
|
|
}
|
|
|
|
return proposal, txID, nil
|
|
}
|