go_study/fabric-main/internal/peer/lifecycle/chaincode/checkcommitreadiness.go

283 lines
8.5 KiB
Go

/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package chaincode
import (
"context"
"fmt"
"io"
"os"
"sort"
"strings"
"github.com/golang/protobuf/proto"
cb "github.com/hyperledger/fabric-protos-go/common"
pb "github.com/hyperledger/fabric-protos-go/peer"
lb "github.com/hyperledger/fabric-protos-go/peer/lifecycle"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/internal/pkg/identity"
"github.com/hyperledger/fabric/protoutil"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// CommitReadinessChecker holds the dependencies needed to
// check whether a chaincode definition is ready to be committed
// on a channel (i.e. has approvals from enough organizations to satisfy
// the lifecycle endorsement policy) and receive the list of orgs that
// have approved the definition.
type CommitReadinessChecker struct {
Command *cobra.Command
EndorserClient pb.EndorserClient
Input *CommitReadinessCheckInput
Signer identity.SignerSerializer
Writer io.Writer
}
// CommitReadinessCheckInput holds all of the input parameters for checking
// whether a chaincode definition is ready to be committed ValidationParameter
// bytes is the (marshalled) endorsement policy when using the default
// endorsement and validation plugins.
type CommitReadinessCheckInput struct {
ChannelID string
Name string
Version string
PackageID string
Sequence int64
EndorsementPlugin string
ValidationPlugin string
ValidationParameterBytes []byte
CollectionConfigPackage *pb.CollectionConfigPackage
InitRequired bool
PeerAddresses []string
TxID string
OutputFormat string
}
// Validate the input for a CheckCommitReadiness proposal
func (c *CommitReadinessCheckInput) Validate() error {
if c.ChannelID == "" {
return errors.New("The required parameter 'channelID' is empty. Rerun the command with -C flag")
}
if c.Name == "" {
return errors.New("The required parameter 'name' is empty. Rerun the command with -n flag")
}
if c.Version == "" {
return errors.New("The required parameter 'version' is empty. Rerun the command with -v flag")
}
if c.Sequence == 0 {
return errors.New("The required parameter 'sequence' is empty. Rerun the command with --sequence flag")
}
return nil
}
// CheckCommitReadinessCmd returns the cobra command for the
// CheckCommitReadiness lifecycle operation
func CheckCommitReadinessCmd(c *CommitReadinessChecker, cryptoProvider bccsp.BCCSP) *cobra.Command {
chaincodeCheckCommitReadinessCmd := &cobra.Command{
Use: "checkcommitreadiness",
Short: "Check whether a chaincode definition is ready to be committed on a channel.",
Long: "Check whether a chaincode definition is ready to be committed on a channel.",
RunE: func(cmd *cobra.Command, args []string) error {
if c == nil {
// set input from CLI flags
input, err := c.createInput()
if err != nil {
return err
}
ccInput := &ClientConnectionsInput{
CommandName: cmd.Name(),
EndorserRequired: true,
ChannelID: channelID,
PeerAddresses: peerAddresses,
TLSRootCertFiles: tlsRootCertFiles,
ConnectionProfilePath: connectionProfilePath,
TLSEnabled: viper.GetBool("peer.tls.enabled"),
}
cc, err := NewClientConnections(ccInput, cryptoProvider)
if err != nil {
return err
}
c = &CommitReadinessChecker{
Command: cmd,
Input: input,
EndorserClient: cc.EndorserClients[0],
Signer: cc.Signer,
Writer: os.Stdout,
}
}
return c.ReadinessCheck()
},
}
flagList := []string{
"channelID",
"name",
"version",
"sequence",
"endorsement-plugin",
"validation-plugin",
"signature-policy",
"channel-config-policy",
"init-required",
"collections-config",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
"output",
}
attachFlags(chaincodeCheckCommitReadinessCmd, flagList)
return chaincodeCheckCommitReadinessCmd
}
// ReadinessCheck submits a CheckCommitReadiness proposal
// and prints the result.
func (c *CommitReadinessChecker) ReadinessCheck() error {
err := c.Input.Validate()
if err != nil {
return err
}
if c.Command != nil {
// Parsing of the command line is done so silence cmd usage
c.Command.SilenceUsage = true
}
proposal, err := c.createProposal(c.Input.TxID)
if err != nil {
return errors.WithMessage(err, "failed to create proposal")
}
signedProposal, err := signProposal(proposal, c.Signer)
if err != nil {
return errors.WithMessage(err, "failed to create signed proposal")
}
// checkcommitreadiness currently only supports a single peer
proposalResponse, err := c.EndorserClient.ProcessProposal(context.Background(), signedProposal)
if err != nil {
return errors.WithMessage(err, "failed to endorse proposal")
}
if proposalResponse == nil {
return errors.New("received nil proposal response")
}
if proposalResponse.Response == nil {
return errors.New("received proposal response with nil response")
}
if proposalResponse.Response.Status != int32(cb.Status_SUCCESS) {
return errors.Errorf("query failed with status: %d - %s", proposalResponse.Response.Status, proposalResponse.Response.Message)
}
if strings.ToLower(c.Input.OutputFormat) == "json" {
return printResponseAsJSON(proposalResponse, &lb.CheckCommitReadinessResult{}, c.Writer)
}
return c.printResponse(proposalResponse)
}
// printResponse prints the information included in the response
// from the server as human readable plain-text.
func (c *CommitReadinessChecker) printResponse(proposalResponse *pb.ProposalResponse) error {
result := &lb.CheckCommitReadinessResult{}
err := proto.Unmarshal(proposalResponse.Response.Payload, result)
if err != nil {
return errors.Wrap(err, "failed to unmarshal proposal response's response payload")
}
orgs := []string{}
for org := range result.Approvals {
orgs = append(orgs, org)
}
sort.Strings(orgs)
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)
for _, org := range orgs {
fmt.Fprintf(c.Writer, "%s: %t\n", org, result.Approvals[org])
}
return nil
}
// setInput creates the input struct based on the CLI flags
func (c *CommitReadinessChecker) createInput() (*CommitReadinessCheckInput, error) {
policyBytes, err := createPolicyBytes(signaturePolicy, channelConfigPolicy)
if err != nil {
return nil, err
}
ccp, err := createCollectionConfigPackage(collectionsConfigFile)
if err != nil {
return nil, err
}
input := &CommitReadinessCheckInput{
ChannelID: channelID,
Name: chaincodeName,
Version: chaincodeVersion,
PackageID: packageID,
Sequence: int64(sequence),
EndorsementPlugin: endorsementPlugin,
ValidationPlugin: validationPlugin,
ValidationParameterBytes: policyBytes,
InitRequired: initRequired,
CollectionConfigPackage: ccp,
PeerAddresses: peerAddresses,
OutputFormat: output,
}
return input, nil
}
func (c *CommitReadinessChecker) createProposal(inputTxID string) (*pb.Proposal, error) {
args := &lb.CheckCommitReadinessArgs{
Name: c.Input.Name,
Version: c.Input.Version,
Sequence: c.Input.Sequence,
EndorsementPlugin: c.Input.EndorsementPlugin,
ValidationPlugin: c.Input.ValidationPlugin,
ValidationParameter: c.Input.ValidationParameterBytes,
InitRequired: c.Input.InitRequired,
Collections: c.Input.CollectionConfigPackage,
}
argsBytes, err := proto.Marshal(args)
if err != nil {
return nil, err
}
ccInput := &pb.ChaincodeInput{Args: [][]byte{[]byte(checkCommitReadinessFuncName), argsBytes}}
cis := &pb.ChaincodeInvocationSpec{
ChaincodeSpec: &pb.ChaincodeSpec{
ChaincodeId: &pb.ChaincodeID{Name: lifecycleName},
Input: ccInput,
},
}
creatorBytes, err := c.Signer.Serialize()
if err != nil {
return nil, errors.WithMessage(err, "failed to serialize identity")
}
proposal, _, err := protoutil.CreateChaincodeProposalWithTxIDAndTransient(cb.HeaderType_ENDORSER_TRANSACTION, c.Input.ChannelID, cis, creatorBytes, inputTxID, nil)
if err != nil {
return nil, errors.WithMessage(err, "failed to create ChaincodeInvocationSpec proposal")
}
return proposal, nil
}