github.com/defanghe/fabric@v2.1.1+incompatible/internal/peer/lifecycle/chaincode/checkcommitreadiness.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 "fmt" 12 "io" 13 "os" 14 "sort" 15 "strings" 16 17 "github.com/golang/protobuf/proto" 18 cb "github.com/hyperledger/fabric-protos-go/common" 19 pb "github.com/hyperledger/fabric-protos-go/peer" 20 lb "github.com/hyperledger/fabric-protos-go/peer/lifecycle" 21 "github.com/hyperledger/fabric/bccsp" 22 "github.com/hyperledger/fabric/internal/pkg/identity" 23 "github.com/hyperledger/fabric/protoutil" 24 "github.com/pkg/errors" 25 "github.com/spf13/cobra" 26 "github.com/spf13/viper" 27 ) 28 29 // CommitReadinessChecker holds the dependencies needed to 30 // check whether a chaincode definition is ready to be committed 31 // on a channel (i.e. has approvals from enough organizations to satisfy 32 // the lifecycle endorsement policy) and receive the list of orgs that 33 // have approved the definition. 34 type CommitReadinessChecker struct { 35 Command *cobra.Command 36 EndorserClient pb.EndorserClient 37 Input *CommitReadinessCheckInput 38 Signer identity.SignerSerializer 39 Writer io.Writer 40 } 41 42 // CommitReadinessCheckInput holds all of the input parameters for checking 43 // whether a chaincode definition is ready to be committed ValidationParameter 44 // bytes is the (marshalled) endorsement policy when using the default 45 // endorsement and validation plugins. 46 type CommitReadinessCheckInput struct { 47 ChannelID string 48 Name string 49 Version string 50 PackageID string 51 Sequence int64 52 EndorsementPlugin string 53 ValidationPlugin string 54 ValidationParameterBytes []byte 55 CollectionConfigPackage *pb.CollectionConfigPackage 56 InitRequired bool 57 PeerAddresses []string 58 TxID string 59 OutputFormat string 60 } 61 62 // Validate the input for a CheckCommitReadiness proposal 63 func (c *CommitReadinessCheckInput) Validate() error { 64 if c.ChannelID == "" { 65 return errors.New("The required parameter 'channelID' is empty. Rerun the command with -C flag") 66 } 67 68 if c.Name == "" { 69 return errors.New("The required parameter 'name' is empty. Rerun the command with -n flag") 70 } 71 72 if c.Version == "" { 73 return errors.New("The required parameter 'version' is empty. Rerun the command with -v flag") 74 } 75 76 if c.Sequence == 0 { 77 return errors.New("The required parameter 'sequence' is empty. Rerun the command with --sequence flag") 78 } 79 80 return nil 81 } 82 83 // CheckCommitReadinessCmd returns the cobra command for the 84 // CheckCommitReadiness lifecycle operation 85 func CheckCommitReadinessCmd(c *CommitReadinessChecker, cryptoProvider bccsp.BCCSP) *cobra.Command { 86 chaincodeCheckCommitReadinessCmd := &cobra.Command{ 87 Use: "checkcommitreadiness", 88 Short: fmt.Sprintf("Check whether a chaincode definition is ready to be committed on a channel."), 89 Long: fmt.Sprintf("Check whether a chaincode definition is ready to be committed on a channel."), 90 RunE: func(cmd *cobra.Command, args []string) error { 91 if c == nil { 92 // set input from CLI flags 93 input, err := c.createInput() 94 if err != nil { 95 return err 96 } 97 98 ccInput := &ClientConnectionsInput{ 99 CommandName: cmd.Name(), 100 EndorserRequired: true, 101 ChannelID: channelID, 102 PeerAddresses: peerAddresses, 103 TLSRootCertFiles: tlsRootCertFiles, 104 ConnectionProfilePath: connectionProfilePath, 105 TLSEnabled: viper.GetBool("peer.tls.enabled"), 106 } 107 108 cc, err := NewClientConnections(ccInput, cryptoProvider) 109 if err != nil { 110 return err 111 } 112 113 c = &CommitReadinessChecker{ 114 Command: cmd, 115 Input: input, 116 EndorserClient: cc.EndorserClients[0], 117 Signer: cc.Signer, 118 Writer: os.Stdout, 119 } 120 } 121 122 return c.ReadinessCheck() 123 }, 124 } 125 flagList := []string{ 126 "channelID", 127 "name", 128 "version", 129 "sequence", 130 "endorsement-plugin", 131 "validation-plugin", 132 "signature-policy", 133 "channel-config-policy", 134 "init-required", 135 "collections-config", 136 "peerAddresses", 137 "tlsRootCertFiles", 138 "connectionProfile", 139 "output", 140 } 141 attachFlags(chaincodeCheckCommitReadinessCmd, flagList) 142 143 return chaincodeCheckCommitReadinessCmd 144 } 145 146 // ReadinessCheck submits a CheckCommitReadiness proposal 147 // and prints the result. 148 func (c *CommitReadinessChecker) ReadinessCheck() error { 149 err := c.Input.Validate() 150 if err != nil { 151 return err 152 } 153 154 if c.Command != nil { 155 // Parsing of the command line is done so silence cmd usage 156 c.Command.SilenceUsage = true 157 } 158 159 proposal, err := c.createProposal(c.Input.TxID) 160 if err != nil { 161 return errors.WithMessage(err, "failed to create proposal") 162 } 163 164 signedProposal, err := signProposal(proposal, c.Signer) 165 if err != nil { 166 return errors.WithMessage(err, "failed to create signed proposal") 167 } 168 169 // checkcommitreadiness currently only supports a single peer 170 proposalResponse, err := c.EndorserClient.ProcessProposal(context.Background(), signedProposal) 171 if err != nil { 172 return errors.WithMessage(err, "failed to endorse proposal") 173 } 174 175 if proposalResponse == nil { 176 return errors.New("received nil proposal response") 177 } 178 179 if proposalResponse.Response == nil { 180 return errors.New("received proposal response with nil response") 181 } 182 183 if proposalResponse.Response.Status != int32(cb.Status_SUCCESS) { 184 return errors.Errorf("query failed with status: %d - %s", proposalResponse.Response.Status, proposalResponse.Response.Message) 185 } 186 187 if strings.ToLower(c.Input.OutputFormat) == "json" { 188 return printResponseAsJSON(proposalResponse, &lb.CheckCommitReadinessResult{}, c.Writer) 189 } 190 return c.printResponse(proposalResponse) 191 } 192 193 // printResponse prints the information included in the response 194 // from the server as human readable plain-text. 195 func (c *CommitReadinessChecker) printResponse(proposalResponse *pb.ProposalResponse) error { 196 result := &lb.CheckCommitReadinessResult{} 197 err := proto.Unmarshal(proposalResponse.Response.Payload, result) 198 if err != nil { 199 return errors.Wrap(err, "failed to unmarshal proposal response's response payload") 200 } 201 202 orgs := []string{} 203 for org := range result.Approvals { 204 orgs = append(orgs, org) 205 } 206 sort.Strings(orgs) 207 208 fmt.Fprintf(c.Writer, "Chaincode definition for chaincode '%s', version '%s', sequence '%d' on channel '%s' approval status by org:\n", c.Input.Name, c.Input.Version, c.Input.Sequence, c.Input.ChannelID) 209 for _, org := range orgs { 210 fmt.Fprintf(c.Writer, "%s: %t\n", org, result.Approvals[org]) 211 } 212 213 return nil 214 } 215 216 // setInput creates the input struct based on the CLI flags 217 func (c *CommitReadinessChecker) createInput() (*CommitReadinessCheckInput, error) { 218 policyBytes, err := createPolicyBytes(signaturePolicy, channelConfigPolicy) 219 if err != nil { 220 return nil, err 221 } 222 223 ccp, err := createCollectionConfigPackage(collectionsConfigFile) 224 if err != nil { 225 return nil, err 226 } 227 228 input := &CommitReadinessCheckInput{ 229 ChannelID: channelID, 230 Name: chaincodeName, 231 Version: chaincodeVersion, 232 PackageID: packageID, 233 Sequence: int64(sequence), 234 EndorsementPlugin: endorsementPlugin, 235 ValidationPlugin: validationPlugin, 236 ValidationParameterBytes: policyBytes, 237 InitRequired: initRequired, 238 CollectionConfigPackage: ccp, 239 PeerAddresses: peerAddresses, 240 OutputFormat: output, 241 } 242 243 return input, nil 244 } 245 246 func (c *CommitReadinessChecker) createProposal(inputTxID string) (*pb.Proposal, error) { 247 args := &lb.CheckCommitReadinessArgs{ 248 Name: c.Input.Name, 249 Version: c.Input.Version, 250 Sequence: c.Input.Sequence, 251 EndorsementPlugin: c.Input.EndorsementPlugin, 252 ValidationPlugin: c.Input.ValidationPlugin, 253 ValidationParameter: c.Input.ValidationParameterBytes, 254 InitRequired: c.Input.InitRequired, 255 Collections: c.Input.CollectionConfigPackage, 256 } 257 258 argsBytes, err := proto.Marshal(args) 259 if err != nil { 260 return nil, err 261 } 262 ccInput := &pb.ChaincodeInput{Args: [][]byte{[]byte(checkCommitReadinessFuncName), argsBytes}} 263 264 cis := &pb.ChaincodeInvocationSpec{ 265 ChaincodeSpec: &pb.ChaincodeSpec{ 266 ChaincodeId: &pb.ChaincodeID{Name: lifecycleName}, 267 Input: ccInput, 268 }, 269 } 270 271 creatorBytes, err := c.Signer.Serialize() 272 if err != nil { 273 return nil, errors.WithMessage(err, "failed to serialize identity") 274 } 275 276 proposal, _, err := protoutil.CreateChaincodeProposalWithTxIDAndTransient(cb.HeaderType_ENDORSER_TRANSACTION, c.Input.ChannelID, cis, creatorBytes, inputTxID, nil) 277 if err != nil { 278 return nil, errors.WithMessage(err, "failed to create ChaincodeInvocationSpec proposal") 279 } 280 281 return proposal, nil 282 }