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