github.com/defanghe/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 }