github.com/anjalikarhana/fabric@v2.1.1+incompatible/internal/peer/lifecycle/chaincode/commit.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package chaincode
     8  
     9  import (
    10  	"context"
    11  	"crypto/tls"
    12  	"fmt"
    13  	"time"
    14  
    15  	"github.com/golang/protobuf/proto"
    16  	cb "github.com/hyperledger/fabric-protos-go/common"
    17  	pb "github.com/hyperledger/fabric-protos-go/peer"
    18  	lb "github.com/hyperledger/fabric-protos-go/peer/lifecycle"
    19  	"github.com/hyperledger/fabric/bccsp"
    20  	"github.com/hyperledger/fabric/internal/peer/chaincode"
    21  	"github.com/hyperledger/fabric/internal/peer/common"
    22  	"github.com/hyperledger/fabric/protoutil"
    23  	"github.com/pkg/errors"
    24  	"github.com/spf13/cobra"
    25  	"github.com/spf13/viper"
    26  )
    27  
    28  // Committer holds the dependencies needed to commit
    29  // a chaincode
    30  type Committer struct {
    31  	Certificate     tls.Certificate
    32  	Command         *cobra.Command
    33  	BroadcastClient common.BroadcastClient
    34  	EndorserClients []EndorserClient
    35  	DeliverClients  []pb.DeliverClient
    36  	Input           *CommitInput
    37  	Signer          Signer
    38  }
    39  
    40  // CommitInput holds all of the input parameters for committing a
    41  // chaincode definition. ValidationParameter bytes is the (marshalled)
    42  // endorsement policy when using the default endorsement and validation
    43  // plugins
    44  type CommitInput struct {
    45  	ChannelID                string
    46  	Name                     string
    47  	Version                  string
    48  	Hash                     []byte
    49  	Sequence                 int64
    50  	EndorsementPlugin        string
    51  	ValidationPlugin         string
    52  	ValidationParameterBytes []byte
    53  	CollectionConfigPackage  *pb.CollectionConfigPackage
    54  	InitRequired             bool
    55  	PeerAddresses            []string
    56  	WaitForEvent             bool
    57  	WaitForEventTimeout      time.Duration
    58  	TxID                     string
    59  }
    60  
    61  // Validate the input for a CommitChaincodeDefinition proposal
    62  func (c *CommitInput) Validate() error {
    63  	if c.ChannelID == "" {
    64  		return errors.New("The required parameter 'channelID' is empty. Rerun the command with -C flag")
    65  	}
    66  
    67  	if c.Name == "" {
    68  		return errors.New("The required parameter 'name' is empty. Rerun the command with -n flag")
    69  	}
    70  
    71  	if c.Version == "" {
    72  		return errors.New("The required parameter 'version' is empty. Rerun the command with -v flag")
    73  	}
    74  
    75  	if c.Sequence == 0 {
    76  		return errors.New("The required parameter 'sequence' is empty. Rerun the command with --sequence flag")
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  // CommitCmd returns the cobra command for chaincode Commit
    83  func CommitCmd(c *Committer, cryptoProvider bccsp.BCCSP) *cobra.Command {
    84  	chaincodeCommitCmd := &cobra.Command{
    85  		Use:   "commit",
    86  		Short: fmt.Sprintf("Commit the chaincode definition on the channel."),
    87  		Long:  fmt.Sprintf("Commit the chaincode definition on the channel."),
    88  		RunE: func(cmd *cobra.Command, args []string) error {
    89  			if c == nil {
    90  				// set input from CLI flags
    91  				input, err := c.createInput()
    92  				if err != nil {
    93  					return err
    94  				}
    95  
    96  				ccInput := &ClientConnectionsInput{
    97  					CommandName:           cmd.Name(),
    98  					EndorserRequired:      true,
    99  					OrdererRequired:       true,
   100  					ChannelID:             channelID,
   101  					PeerAddresses:         peerAddresses,
   102  					TLSRootCertFiles:      tlsRootCertFiles,
   103  					ConnectionProfilePath: connectionProfilePath,
   104  					TLSEnabled:            viper.GetBool("peer.tls.enabled"),
   105  				}
   106  
   107  				cc, err := NewClientConnections(ccInput, cryptoProvider)
   108  				if err != nil {
   109  					return err
   110  				}
   111  
   112  				endorserClients := make([]EndorserClient, len(cc.EndorserClients))
   113  				for i, e := range cc.EndorserClients {
   114  					endorserClients[i] = e
   115  				}
   116  
   117  				c = &Committer{
   118  					Command:         cmd,
   119  					Input:           input,
   120  					Certificate:     cc.Certificate,
   121  					BroadcastClient: cc.BroadcastClient,
   122  					DeliverClients:  cc.DeliverClients,
   123  					EndorserClients: endorserClients,
   124  					Signer:          cc.Signer,
   125  				}
   126  			}
   127  			return c.Commit()
   128  		},
   129  	}
   130  	flagList := []string{
   131  		"channelID",
   132  		"name",
   133  		"version",
   134  		"sequence",
   135  		"endorsement-plugin",
   136  		"validation-plugin",
   137  		"signature-policy",
   138  		"channel-config-policy",
   139  		"init-required",
   140  		"collections-config",
   141  		"peerAddresses",
   142  		"tlsRootCertFiles",
   143  		"connectionProfile",
   144  		"waitForEvent",
   145  		"waitForEventTimeout",
   146  	}
   147  	attachFlags(chaincodeCommitCmd, flagList)
   148  
   149  	return chaincodeCommitCmd
   150  }
   151  
   152  // Commit submits a CommitChaincodeDefinition proposal
   153  func (c *Committer) Commit() error {
   154  	err := c.Input.Validate()
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	if c.Command != nil {
   160  		// Parsing of the command line is done so silence cmd usage
   161  		c.Command.SilenceUsage = true
   162  	}
   163  
   164  	proposal, txID, err := c.createProposal(c.Input.TxID)
   165  	if err != nil {
   166  		return errors.WithMessage(err, "failed to create proposal")
   167  	}
   168  
   169  	signedProposal, err := signProposal(proposal, c.Signer)
   170  	if err != nil {
   171  		return errors.WithMessage(err, "failed to create signed proposal")
   172  	}
   173  
   174  	var responses []*pb.ProposalResponse
   175  	for _, endorser := range c.EndorserClients {
   176  		proposalResponse, err := endorser.ProcessProposal(context.Background(), signedProposal)
   177  		if err != nil {
   178  			return errors.WithMessage(err, "failed to endorse proposal")
   179  		}
   180  		responses = append(responses, proposalResponse)
   181  	}
   182  
   183  	if len(responses) == 0 {
   184  		// this should only be empty due to a programming bug
   185  		return errors.New("no proposal responses received")
   186  	}
   187  
   188  	// all responses will be checked when the signed transaction is created.
   189  	// for now, just set this so we check the first response's status
   190  	proposalResponse := responses[0]
   191  
   192  	if proposalResponse == nil {
   193  		return errors.New("received nil proposal response")
   194  	}
   195  
   196  	if proposalResponse.Response == nil {
   197  		return errors.New("received proposal response with nil response")
   198  	}
   199  
   200  	if proposalResponse.Response.Status != int32(cb.Status_SUCCESS) {
   201  		return errors.Errorf("proposal failed with status: %d - %s", proposalResponse.Response.Status, proposalResponse.Response.Message)
   202  	}
   203  	// assemble a signed transaction (it's an Envelope message)
   204  	env, err := protoutil.CreateSignedTx(proposal, c.Signer, responses...)
   205  	if err != nil {
   206  		return errors.WithMessage(err, "failed to create signed transaction")
   207  	}
   208  
   209  	var dg *chaincode.DeliverGroup
   210  	var ctx context.Context
   211  	if c.Input.WaitForEvent {
   212  		var cancelFunc context.CancelFunc
   213  		ctx, cancelFunc = context.WithTimeout(context.Background(), c.Input.WaitForEventTimeout)
   214  		defer cancelFunc()
   215  
   216  		dg = chaincode.NewDeliverGroup(
   217  			c.DeliverClients,
   218  			c.Input.PeerAddresses,
   219  			c.Signer,
   220  			c.Certificate,
   221  			c.Input.ChannelID,
   222  			txID,
   223  		)
   224  		// connect to deliver service on all peers
   225  		err := dg.Connect(ctx)
   226  		if err != nil {
   227  			return err
   228  		}
   229  	}
   230  
   231  	if err = c.BroadcastClient.Send(env); err != nil {
   232  		return errors.WithMessage(err, "failed to send transaction")
   233  	}
   234  
   235  	if dg != nil && ctx != nil {
   236  		// wait for event that contains the txID from all peers
   237  		err = dg.Wait(ctx)
   238  		if err != nil {
   239  			return err
   240  		}
   241  	}
   242  	return err
   243  }
   244  
   245  // createInput creates the input struct based on the CLI flags
   246  func (c *Committer) createInput() (*CommitInput, error) {
   247  	policyBytes, err := createPolicyBytes(signaturePolicy, channelConfigPolicy)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	ccp, err := createCollectionConfigPackage(collectionsConfigFile)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	input := &CommitInput{
   258  		ChannelID:                channelID,
   259  		Name:                     chaincodeName,
   260  		Version:                  chaincodeVersion,
   261  		Sequence:                 int64(sequence),
   262  		EndorsementPlugin:        endorsementPlugin,
   263  		ValidationPlugin:         validationPlugin,
   264  		ValidationParameterBytes: policyBytes,
   265  		InitRequired:             initRequired,
   266  		CollectionConfigPackage:  ccp,
   267  		PeerAddresses:            peerAddresses,
   268  		WaitForEvent:             waitForEvent,
   269  		WaitForEventTimeout:      waitForEventTimeout,
   270  	}
   271  
   272  	return input, nil
   273  }
   274  
   275  func (c *Committer) createProposal(inputTxID string) (proposal *pb.Proposal, txID string, err error) {
   276  	args := &lb.CommitChaincodeDefinitionArgs{
   277  		Name:                c.Input.Name,
   278  		Version:             c.Input.Version,
   279  		Sequence:            c.Input.Sequence,
   280  		EndorsementPlugin:   c.Input.EndorsementPlugin,
   281  		ValidationPlugin:    c.Input.ValidationPlugin,
   282  		ValidationParameter: c.Input.ValidationParameterBytes,
   283  		InitRequired:        c.Input.InitRequired,
   284  		Collections:         c.Input.CollectionConfigPackage,
   285  	}
   286  
   287  	argsBytes, err := proto.Marshal(args)
   288  	if err != nil {
   289  		return nil, "", err
   290  	}
   291  	ccInput := &pb.ChaincodeInput{Args: [][]byte{[]byte(commitFuncName), argsBytes}}
   292  
   293  	cis := &pb.ChaincodeInvocationSpec{
   294  		ChaincodeSpec: &pb.ChaincodeSpec{
   295  			ChaincodeId: &pb.ChaincodeID{Name: lifecycleName},
   296  			Input:       ccInput,
   297  		},
   298  	}
   299  
   300  	creatorBytes, err := c.Signer.Serialize()
   301  	if err != nil {
   302  		return nil, "", errors.WithMessage(err, "failed to serialize identity")
   303  	}
   304  
   305  	proposal, txID, err = protoutil.CreateChaincodeProposalWithTxIDAndTransient(cb.HeaderType_ENDORSER_TRANSACTION, c.Input.ChannelID, cis, creatorBytes, inputTxID, nil)
   306  	if err != nil {
   307  		return nil, "", errors.WithMessage(err, "failed to create ChaincodeInvocationSpec proposal")
   308  	}
   309  
   310  	return proposal, txID, nil
   311  }