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 }