github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/internal/peer/chaincode/common.go (about)

     1  /*
     2  Copyright hechain. 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  	"encoding/json"
    13  	"fmt"
    14  	"io/ioutil"
    15  	"math"
    16  	"strings"
    17  	"sync"
    18  
    19  	"github.com/golang/protobuf/proto"
    20  	"github.com/hechain20/hechain/bccsp"
    21  	"github.com/hechain20/hechain/common/policydsl"
    22  	"github.com/hechain20/hechain/common/util"
    23  	"github.com/hechain20/hechain/internal/peer/common"
    24  	"github.com/hechain20/hechain/internal/pkg/identity"
    25  	"github.com/hechain20/hechain/protoutil"
    26  	"github.com/hyperledger/fabric-chaincode-go/shim"
    27  	pcommon "github.com/hyperledger/fabric-protos-go/common"
    28  	ab "github.com/hyperledger/fabric-protos-go/orderer"
    29  	pb "github.com/hyperledger/fabric-protos-go/peer"
    30  	"github.com/pkg/errors"
    31  	"github.com/spf13/cobra"
    32  	"github.com/spf13/viper"
    33  )
    34  
    35  // checkSpec to see if chaincode resides within current package capture for language.
    36  func checkSpec(spec *pb.ChaincodeSpec) error {
    37  	// Don't allow nil value
    38  	if spec == nil {
    39  		return errors.New("expected chaincode specification, nil received")
    40  	}
    41  	if spec.ChaincodeId == nil {
    42  		return errors.New("expected chaincode ID, nil received")
    43  	}
    44  
    45  	return platformRegistry.ValidateSpec(spec.Type.String(), spec.ChaincodeId.Path)
    46  }
    47  
    48  // getChaincodeDeploymentSpec get chaincode deployment spec given the chaincode spec
    49  func getChaincodeDeploymentSpec(spec *pb.ChaincodeSpec, crtPkg bool) (*pb.ChaincodeDeploymentSpec, error) {
    50  	var codePackageBytes []byte
    51  	if crtPkg {
    52  		var err error
    53  		if err = checkSpec(spec); err != nil {
    54  			return nil, err
    55  		}
    56  
    57  		codePackageBytes, err = platformRegistry.GetDeploymentPayload(spec.Type.String(), spec.ChaincodeId.Path)
    58  		if err != nil {
    59  			return nil, errors.WithMessage(err, "error getting chaincode package bytes")
    60  		}
    61  		chaincodePath, err := platformRegistry.NormalizePath(spec.Type.String(), spec.ChaincodeId.Path)
    62  		if err != nil {
    63  			return nil, errors.WithMessage(err, "failed to normalize chaincode path")
    64  		}
    65  		spec.ChaincodeId.Path = chaincodePath
    66  	}
    67  
    68  	return &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}, nil
    69  }
    70  
    71  // getChaincodeSpec get chaincode spec from the cli cmd parameters
    72  func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) {
    73  	spec := &pb.ChaincodeSpec{}
    74  	if err := checkChaincodeCmdParams(cmd); err != nil {
    75  		// unset usage silence because it's a command line usage error
    76  		cmd.SilenceUsage = false
    77  		return spec, err
    78  	}
    79  
    80  	// Build the spec
    81  	input := chaincodeInput{}
    82  	if err := json.Unmarshal([]byte(chaincodeCtorJSON), &input); err != nil {
    83  		return spec, errors.Wrap(err, "chaincode argument error")
    84  	}
    85  	input.IsInit = isInit
    86  
    87  	chaincodeLang = strings.ToUpper(chaincodeLang)
    88  	spec = &pb.ChaincodeSpec{
    89  		Type:        pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value[chaincodeLang]),
    90  		ChaincodeId: &pb.ChaincodeID{Path: chaincodePath, Name: chaincodeName, Version: chaincodeVersion},
    91  		Input:       &input.ChaincodeInput,
    92  	}
    93  	return spec, nil
    94  }
    95  
    96  // chaincodeInput is wrapper around the proto defined ChaincodeInput message that
    97  // is decorated with a custom JSON unmarshaller.
    98  type chaincodeInput struct {
    99  	pb.ChaincodeInput
   100  }
   101  
   102  // UnmarshalJSON converts the string-based REST/JSON input to
   103  // the []byte-based current ChaincodeInput structure.
   104  func (c *chaincodeInput) UnmarshalJSON(b []byte) error {
   105  	sa := struct {
   106  		Function string
   107  		Args     []string
   108  	}{}
   109  	err := json.Unmarshal(b, &sa)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	allArgs := sa.Args
   114  	if sa.Function != "" {
   115  		allArgs = append([]string{sa.Function}, sa.Args...)
   116  	}
   117  	c.Args = util.ToChaincodeArgs(allArgs...)
   118  	return nil
   119  }
   120  
   121  func chaincodeInvokeOrQuery(cmd *cobra.Command, invoke bool, cf *ChaincodeCmdFactory) (err error) {
   122  	spec, err := getChaincodeSpec(cmd)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	// call with empty txid to ensure production code generates a txid.
   128  	// otherwise, tests can explicitly set their own txid
   129  	txID := ""
   130  
   131  	proposalResp, err := ChaincodeInvokeOrQuery(
   132  		spec,
   133  		channelID,
   134  		txID,
   135  		invoke,
   136  		cf.Signer,
   137  		cf.Certificate,
   138  		cf.EndorserClients,
   139  		cf.DeliverClients,
   140  		cf.BroadcastClient,
   141  	)
   142  	if err != nil {
   143  		return errors.Errorf("%s - proposal response: %v", err, proposalResp)
   144  	}
   145  
   146  	if invoke {
   147  		logger.Debugf("ESCC invoke result: %v", proposalResp)
   148  		pRespPayload, err := protoutil.UnmarshalProposalResponsePayload(proposalResp.Payload)
   149  		if err != nil {
   150  			return errors.WithMessage(err, "error while unmarshalling proposal response payload")
   151  		}
   152  		ca, err := protoutil.UnmarshalChaincodeAction(pRespPayload.Extension)
   153  		if err != nil {
   154  			return errors.WithMessage(err, "error while unmarshalling chaincode action")
   155  		}
   156  		if proposalResp.Endorsement == nil {
   157  			return errors.Errorf("endorsement failure during invoke. response: %v", proposalResp.Response)
   158  		}
   159  		logger.Infof("Chaincode invoke successful. result: %v", ca.Response)
   160  	} else {
   161  		if proposalResp == nil {
   162  			return errors.New("error during query: received nil proposal response")
   163  		}
   164  		if proposalResp.Endorsement == nil {
   165  			return errors.Errorf("endorsement failure during query. response: %v", proposalResp.Response)
   166  		}
   167  
   168  		if chaincodeQueryRaw && chaincodeQueryHex {
   169  			return fmt.Errorf("options --raw (-r) and --hex (-x) are not compatible")
   170  		}
   171  		if chaincodeQueryRaw {
   172  			fmt.Println(proposalResp.Response.Payload)
   173  			return nil
   174  		}
   175  		if chaincodeQueryHex {
   176  			fmt.Printf("%x\n", proposalResp.Response.Payload)
   177  			return nil
   178  		}
   179  		fmt.Println(string(proposalResp.Response.Payload))
   180  	}
   181  	return nil
   182  }
   183  
   184  type endorsementPolicy struct {
   185  	ChannelConfigPolicy string `json:"channelConfigPolicy,omitempty"`
   186  	SignaturePolicy     string `json:"signaturePolicy,omitempty"`
   187  }
   188  
   189  type collectionConfigJson struct {
   190  	Name              string             `json:"name"`
   191  	Policy            string             `json:"policy"`
   192  	RequiredPeerCount *int32             `json:"requiredPeerCount"`
   193  	MaxPeerCount      *int32             `json:"maxPeerCount"`
   194  	BlockToLive       uint64             `json:"blockToLive"`
   195  	MemberOnlyRead    bool               `json:"memberOnlyRead"`
   196  	MemberOnlyWrite   bool               `json:"memberOnlyWrite"`
   197  	EndorsementPolicy *endorsementPolicy `json:"endorsementPolicy,omitempty"`
   198  }
   199  
   200  // GetCollectionConfigFromFile retrieves the collection configuration
   201  // from the supplied file; the supplied file must contain a
   202  // json-formatted array of collectionConfigJson elements
   203  func GetCollectionConfigFromFile(ccFile string) (*pb.CollectionConfigPackage, []byte, error) {
   204  	fileBytes, err := ioutil.ReadFile(ccFile)
   205  	if err != nil {
   206  		return nil, nil, errors.Wrapf(err, "could not read file '%s'", ccFile)
   207  	}
   208  
   209  	return getCollectionConfigFromBytes(fileBytes)
   210  }
   211  
   212  // getCollectionConfig retrieves the collection configuration
   213  // from the supplied byte array; the byte array must contain a
   214  // json-formatted array of collectionConfigJson elements
   215  func getCollectionConfigFromBytes(cconfBytes []byte) (*pb.CollectionConfigPackage, []byte, error) {
   216  	cconf := &[]collectionConfigJson{}
   217  	err := json.Unmarshal(cconfBytes, cconf)
   218  	if err != nil {
   219  		return nil, nil, errors.Wrap(err, "could not parse the collection configuration")
   220  	}
   221  
   222  	ccarray := make([]*pb.CollectionConfig, 0, len(*cconf))
   223  	for _, cconfitem := range *cconf {
   224  		p, err := policydsl.FromString(cconfitem.Policy)
   225  		if err != nil {
   226  			return nil, nil, errors.WithMessagef(err, "invalid policy %s", cconfitem.Policy)
   227  		}
   228  
   229  		cpc := &pb.CollectionPolicyConfig{
   230  			Payload: &pb.CollectionPolicyConfig_SignaturePolicy{
   231  				SignaturePolicy: p,
   232  			},
   233  		}
   234  
   235  		var ep *pb.ApplicationPolicy
   236  		if cconfitem.EndorsementPolicy != nil {
   237  			signaturePolicy := cconfitem.EndorsementPolicy.SignaturePolicy
   238  			channelConfigPolicy := cconfitem.EndorsementPolicy.ChannelConfigPolicy
   239  			ep, err = getApplicationPolicy(signaturePolicy, channelConfigPolicy)
   240  			if err != nil {
   241  				return nil, nil, errors.WithMessagef(err, "invalid endorsement policy [%#v]", cconfitem.EndorsementPolicy)
   242  			}
   243  		}
   244  
   245  		// Set default requiredPeerCount and MaxPeerCount if not specified in json
   246  		requiredPeerCount := int32(0)
   247  		maxPeerCount := int32(1)
   248  		if cconfitem.RequiredPeerCount != nil {
   249  			requiredPeerCount = *cconfitem.RequiredPeerCount
   250  		}
   251  		if cconfitem.MaxPeerCount != nil {
   252  			maxPeerCount = *cconfitem.MaxPeerCount
   253  		}
   254  
   255  		cc := &pb.CollectionConfig{
   256  			Payload: &pb.CollectionConfig_StaticCollectionConfig{
   257  				StaticCollectionConfig: &pb.StaticCollectionConfig{
   258  					Name:              cconfitem.Name,
   259  					MemberOrgsPolicy:  cpc,
   260  					RequiredPeerCount: requiredPeerCount,
   261  					MaximumPeerCount:  maxPeerCount,
   262  					BlockToLive:       cconfitem.BlockToLive,
   263  					MemberOnlyRead:    cconfitem.MemberOnlyRead,
   264  					MemberOnlyWrite:   cconfitem.MemberOnlyWrite,
   265  					EndorsementPolicy: ep,
   266  				},
   267  			},
   268  		}
   269  
   270  		ccarray = append(ccarray, cc)
   271  	}
   272  
   273  	ccp := &pb.CollectionConfigPackage{Config: ccarray}
   274  	ccpBytes, err := proto.Marshal(ccp)
   275  	return ccp, ccpBytes, err
   276  }
   277  
   278  func getApplicationPolicy(signaturePolicy, channelConfigPolicy string) (*pb.ApplicationPolicy, error) {
   279  	if signaturePolicy == "" && channelConfigPolicy == "" {
   280  		// no policy, no problem
   281  		return nil, nil
   282  	}
   283  
   284  	if signaturePolicy != "" && channelConfigPolicy != "" {
   285  		// mo policies, mo problems
   286  		return nil, errors.New(`cannot specify both "--signature-policy" and "--channel-config-policy"`)
   287  	}
   288  
   289  	var applicationPolicy *pb.ApplicationPolicy
   290  	if signaturePolicy != "" {
   291  		signaturePolicyEnvelope, err := policydsl.FromString(signaturePolicy)
   292  		if err != nil {
   293  			return nil, errors.Errorf("invalid signature policy: %s", signaturePolicy)
   294  		}
   295  
   296  		applicationPolicy = &pb.ApplicationPolicy{
   297  			Type: &pb.ApplicationPolicy_SignaturePolicy{
   298  				SignaturePolicy: signaturePolicyEnvelope,
   299  			},
   300  		}
   301  	}
   302  
   303  	if channelConfigPolicy != "" {
   304  		applicationPolicy = &pb.ApplicationPolicy{
   305  			Type: &pb.ApplicationPolicy_ChannelConfigPolicyReference{
   306  				ChannelConfigPolicyReference: channelConfigPolicy,
   307  			},
   308  		}
   309  	}
   310  
   311  	return applicationPolicy, nil
   312  }
   313  
   314  func checkChaincodeCmdParams(cmd *cobra.Command) error {
   315  	// we need chaincode name for everything, including deploy
   316  	if chaincodeName == common.UndefinedParamValue {
   317  		return errors.Errorf("must supply value for %s name parameter", chainFuncName)
   318  	}
   319  
   320  	if cmd.Name() == instantiateCmdName || cmd.Name() == installCmdName ||
   321  		cmd.Name() == upgradeCmdName || cmd.Name() == packageCmdName {
   322  		if chaincodeVersion == common.UndefinedParamValue {
   323  			return errors.Errorf("chaincode version is not provided for %s", cmd.Name())
   324  		}
   325  
   326  		if escc != common.UndefinedParamValue {
   327  			logger.Infof("Using escc %s", escc)
   328  		} else {
   329  			logger.Info("Using default escc")
   330  			escc = "escc"
   331  		}
   332  
   333  		if vscc != common.UndefinedParamValue {
   334  			logger.Infof("Using vscc %s", vscc)
   335  		} else {
   336  			logger.Info("Using default vscc")
   337  			vscc = "vscc"
   338  		}
   339  
   340  		if policy != common.UndefinedParamValue {
   341  			p, err := policydsl.FromString(policy)
   342  			if err != nil {
   343  				return errors.Errorf("invalid policy %s", policy)
   344  			}
   345  			policyMarshalled = protoutil.MarshalOrPanic(p)
   346  		}
   347  
   348  		if collectionsConfigFile != common.UndefinedParamValue {
   349  			var err error
   350  			_, collectionConfigBytes, err = GetCollectionConfigFromFile(collectionsConfigFile)
   351  			if err != nil {
   352  				return errors.WithMessagef(err, "invalid collection configuration in file %s", collectionsConfigFile)
   353  			}
   354  		}
   355  	}
   356  
   357  	// Check that non-empty chaincode parameters contain only Args as a key.
   358  	// Type checking is done later when the JSON is actually unmarshaled
   359  	// into a pb.ChaincodeInput. To better understand what's going
   360  	// on here with JSON parsing see http://blog.golang.org/json-and-go -
   361  	// Generic JSON with interface{}
   362  	if chaincodeCtorJSON != "{}" {
   363  		var f interface{}
   364  		err := json.Unmarshal([]byte(chaincodeCtorJSON), &f)
   365  		if err != nil {
   366  			return errors.Wrap(err, "chaincode argument error")
   367  		}
   368  		m := f.(map[string]interface{})
   369  		sm := make(map[string]interface{})
   370  		for k := range m {
   371  			sm[strings.ToLower(k)] = m[k]
   372  		}
   373  		_, argsPresent := sm["args"]
   374  		_, funcPresent := sm["function"]
   375  		if !argsPresent || (len(m) == 2 && !funcPresent) || len(m) > 2 {
   376  			return errors.New("non-empty JSON chaincode parameters must contain the following keys: 'Args' or 'Function' and 'Args'")
   377  		}
   378  	} else {
   379  		if cmd == nil || (cmd != chaincodeInstallCmd && cmd != chaincodePackageCmd) {
   380  			return errors.New("empty JSON chaincode parameters must contain the following keys: 'Args' or 'Function' and 'Args'")
   381  		}
   382  	}
   383  
   384  	return nil
   385  }
   386  
   387  func validatePeerConnectionParameters(cmdName string) error {
   388  	if connectionProfile != common.UndefinedParamValue {
   389  		networkConfig, err := common.GetConfig(connectionProfile)
   390  		if err != nil {
   391  			return err
   392  		}
   393  		if len(networkConfig.Channels[channelID].Peers) != 0 {
   394  			peerAddresses = []string{}
   395  			tlsRootCertFiles = []string{}
   396  			for peer, peerChannelConfig := range networkConfig.Channels[channelID].Peers {
   397  				if peerChannelConfig.EndorsingPeer {
   398  					peerConfig, ok := networkConfig.Peers[peer]
   399  					if !ok {
   400  						return errors.Errorf("peer '%s' is defined in the channel config but doesn't have associated peer config", peer)
   401  					}
   402  					peerAddresses = append(peerAddresses, peerConfig.URL)
   403  					tlsRootCertFiles = append(tlsRootCertFiles, peerConfig.TLSCACerts.Path)
   404  				}
   405  			}
   406  		}
   407  	}
   408  
   409  	// currently only support multiple peer addresses for invoke
   410  	multiplePeersAllowed := map[string]bool{
   411  		"invoke": true,
   412  	}
   413  	_, ok := multiplePeersAllowed[cmdName]
   414  	if !ok && len(peerAddresses) > 1 {
   415  		return errors.Errorf("'%s' command can only be executed against one peer. received %d", cmdName, len(peerAddresses))
   416  	}
   417  
   418  	if len(tlsRootCertFiles) > len(peerAddresses) {
   419  		logger.Warningf("received more TLS root cert files (%d) than peer addresses (%d)", len(tlsRootCertFiles), len(peerAddresses))
   420  	}
   421  
   422  	if viper.GetBool("peer.tls.enabled") {
   423  		if len(tlsRootCertFiles) != len(peerAddresses) {
   424  			return errors.Errorf("number of peer addresses (%d) does not match the number of TLS root cert files (%d)", len(peerAddresses), len(tlsRootCertFiles))
   425  		}
   426  	} else {
   427  		tlsRootCertFiles = nil
   428  	}
   429  
   430  	return nil
   431  }
   432  
   433  // ChaincodeCmdFactory holds the clients used by ChaincodeCmd
   434  type ChaincodeCmdFactory struct {
   435  	EndorserClients []pb.EndorserClient
   436  	DeliverClients  []pb.DeliverClient
   437  	Certificate     tls.Certificate
   438  	Signer          identity.SignerSerializer
   439  	BroadcastClient common.BroadcastClient
   440  }
   441  
   442  // InitCmdFactory init the ChaincodeCmdFactory with default clients
   443  func InitCmdFactory(cmdName string, isEndorserRequired, isOrdererRequired bool, cryptoProvider bccsp.BCCSP) (*ChaincodeCmdFactory, error) {
   444  	var err error
   445  	var endorserClients []pb.EndorserClient
   446  	var deliverClients []pb.DeliverClient
   447  	if isEndorserRequired {
   448  		if err = validatePeerConnectionParameters(cmdName); err != nil {
   449  			return nil, errors.WithMessage(err, "error validating peer connection parameters")
   450  		}
   451  		for i, address := range peerAddresses {
   452  			var tlsRootCertFile string
   453  			if tlsRootCertFiles != nil {
   454  				tlsRootCertFile = tlsRootCertFiles[i]
   455  			}
   456  			endorserClient, err := common.GetEndorserClientFnc(address, tlsRootCertFile)
   457  			if err != nil {
   458  				return nil, errors.WithMessagef(err, "error getting endorser client for %s", cmdName)
   459  			}
   460  			endorserClients = append(endorserClients, endorserClient)
   461  			deliverClient, err := common.GetPeerDeliverClientFnc(address, tlsRootCertFile)
   462  			if err != nil {
   463  				return nil, errors.WithMessagef(err, "error getting deliver client for %s", cmdName)
   464  			}
   465  			deliverClients = append(deliverClients, deliverClient)
   466  		}
   467  		if len(endorserClients) == 0 {
   468  			return nil, errors.New("no endorser clients retrieved - this might indicate a bug")
   469  		}
   470  	}
   471  	certificate, err := common.GetClientCertificateFnc()
   472  	if err != nil {
   473  		return nil, errors.WithMessage(err, "error getting client certificate")
   474  	}
   475  
   476  	signer, err := common.GetDefaultSignerFnc()
   477  	if err != nil {
   478  		return nil, errors.WithMessage(err, "error getting default signer")
   479  	}
   480  
   481  	var broadcastClient common.BroadcastClient
   482  	if isOrdererRequired {
   483  		if len(common.OrderingEndpoint) == 0 {
   484  			if len(endorserClients) == 0 {
   485  				return nil, errors.New("orderer is required, but no ordering endpoint or endorser client supplied")
   486  			}
   487  			endorserClient := endorserClients[0]
   488  
   489  			orderingEndpoints, err := common.GetOrdererEndpointOfChainFnc(channelID, signer, endorserClient, cryptoProvider)
   490  			if err != nil {
   491  				return nil, errors.WithMessagef(err, "error getting channel (%s) orderer endpoint", channelID)
   492  			}
   493  			if len(orderingEndpoints) == 0 {
   494  				return nil, errors.Errorf("no orderer endpoints retrieved for channel %s, pass orderer endpoint with -o flag instead", channelID)
   495  			}
   496  			logger.Infof("Retrieved channel (%s) orderer endpoint: %s", channelID, orderingEndpoints[0])
   497  			// override viper env
   498  			viper.Set("orderer.address", orderingEndpoints[0])
   499  		}
   500  
   501  		broadcastClient, err = common.GetBroadcastClientFnc()
   502  		if err != nil {
   503  			return nil, errors.WithMessage(err, "error getting broadcast client")
   504  		}
   505  	}
   506  	return &ChaincodeCmdFactory{
   507  		EndorserClients: endorserClients,
   508  		DeliverClients:  deliverClients,
   509  		Signer:          signer,
   510  		BroadcastClient: broadcastClient,
   511  		Certificate:     certificate,
   512  	}, nil
   513  }
   514  
   515  // processProposals sends a signed proposal to a set of peers, and gathers all the responses.
   516  func processProposals(endorserClients []pb.EndorserClient, signedProposal *pb.SignedProposal) ([]*pb.ProposalResponse, error) {
   517  	responsesCh := make(chan *pb.ProposalResponse, len(endorserClients))
   518  	errorCh := make(chan error, len(endorserClients))
   519  	wg := sync.WaitGroup{}
   520  	for _, endorser := range endorserClients {
   521  		wg.Add(1)
   522  		go func(endorser pb.EndorserClient) {
   523  			defer wg.Done()
   524  			proposalResp, err := endorser.ProcessProposal(context.Background(), signedProposal)
   525  			if err != nil {
   526  				errorCh <- err
   527  				return
   528  			}
   529  			responsesCh <- proposalResp
   530  		}(endorser)
   531  	}
   532  	wg.Wait()
   533  	close(responsesCh)
   534  	close(errorCh)
   535  	for err := range errorCh {
   536  		return nil, err
   537  	}
   538  	var responses []*pb.ProposalResponse
   539  	for response := range responsesCh {
   540  		responses = append(responses, response)
   541  	}
   542  	return responses, nil
   543  }
   544  
   545  // ChaincodeInvokeOrQuery invokes or queries the chaincode. If successful, the
   546  // INVOKE form prints the ProposalResponse to STDOUT, and the QUERY form prints
   547  // the query result on STDOUT. A command-line flag (-r, --raw) determines
   548  // whether the query result is output as raw bytes, or as a printable string.
   549  // The printable form is optionally (-x, --hex) a hexadecimal representation
   550  // of the query response. If the query response is NIL, nothing is output.
   551  //
   552  // NOTE - Query will likely go away as all interactions with the endorser are
   553  // Proposal and ProposalResponses
   554  func ChaincodeInvokeOrQuery(
   555  	spec *pb.ChaincodeSpec,
   556  	cID string,
   557  	txID string,
   558  	invoke bool,
   559  	signer identity.SignerSerializer,
   560  	certificate tls.Certificate,
   561  	endorserClients []pb.EndorserClient,
   562  	deliverClients []pb.DeliverClient,
   563  	bc common.BroadcastClient,
   564  ) (*pb.ProposalResponse, error) {
   565  	// Build the ChaincodeInvocationSpec message
   566  	invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec}
   567  
   568  	creator, err := signer.Serialize()
   569  	if err != nil {
   570  		return nil, errors.WithMessage(err, "error serializing identity")
   571  	}
   572  
   573  	funcName := "invoke"
   574  	if !invoke {
   575  		funcName = "query"
   576  	}
   577  
   578  	// extract the transient field if it exists
   579  	var tMap map[string][]byte
   580  	if transient != "" {
   581  		if err := json.Unmarshal([]byte(transient), &tMap); err != nil {
   582  			return nil, errors.Wrap(err, "error parsing transient string")
   583  		}
   584  	}
   585  
   586  	prop, txid, err := protoutil.CreateChaincodeProposalWithTxIDAndTransient(pcommon.HeaderType_ENDORSER_TRANSACTION, cID, invocation, creator, txID, tMap)
   587  	if err != nil {
   588  		return nil, errors.WithMessagef(err, "error creating proposal for %s", funcName)
   589  	}
   590  
   591  	signedProp, err := protoutil.GetSignedProposal(prop, signer)
   592  	if err != nil {
   593  		return nil, errors.WithMessagef(err, "error creating signed proposal for %s", funcName)
   594  	}
   595  
   596  	responses, err := processProposals(endorserClients, signedProp)
   597  	if err != nil {
   598  		return nil, errors.WithMessagef(err, "error endorsing %s", funcName)
   599  	}
   600  
   601  	if len(responses) == 0 {
   602  		// this should only happen if some new code has introduced a bug
   603  		return nil, errors.New("no proposal responses received - this might indicate a bug")
   604  	}
   605  	// all responses will be checked when the signed transaction is created.
   606  	// for now, just set this so we check the first response's status
   607  	proposalResp := responses[0]
   608  
   609  	if invoke {
   610  		if proposalResp != nil {
   611  			if proposalResp.Response.Status >= shim.ERRORTHRESHOLD {
   612  				return proposalResp, nil
   613  			}
   614  			// assemble a signed transaction (it's an Envelope message)
   615  			env, err := protoutil.CreateSignedTx(prop, signer, responses...)
   616  			if err != nil {
   617  				return proposalResp, errors.WithMessage(err, "could not assemble transaction")
   618  			}
   619  			var dg *DeliverGroup
   620  			var ctx context.Context
   621  			if waitForEvent {
   622  				var cancelFunc context.CancelFunc
   623  				ctx, cancelFunc = context.WithTimeout(context.Background(), waitForEventTimeout)
   624  				defer cancelFunc()
   625  
   626  				dg = NewDeliverGroup(
   627  					deliverClients,
   628  					peerAddresses,
   629  					signer,
   630  					certificate,
   631  					channelID,
   632  					txid,
   633  				)
   634  				// connect to deliver service on all peers
   635  				err := dg.Connect(ctx)
   636  				if err != nil {
   637  					return nil, err
   638  				}
   639  			}
   640  
   641  			// send the envelope for ordering
   642  			if err = bc.Send(env); err != nil {
   643  				return proposalResp, errors.WithMessagef(err, "error sending transaction for %s", funcName)
   644  			}
   645  
   646  			if dg != nil && ctx != nil {
   647  				// wait for event that contains the txid from all peers
   648  				err = dg.Wait(ctx)
   649  				if err != nil {
   650  					return nil, err
   651  				}
   652  			}
   653  		}
   654  	}
   655  
   656  	return proposalResp, nil
   657  }
   658  
   659  // DeliverGroup holds all of the information needed to connect
   660  // to a set of peers to wait for the interested txid to be
   661  // committed to the ledgers of all peers. This functionality
   662  // is currently implemented via the peer's DeliverFiltered service.
   663  // An error from any of the peers/deliver clients will result in
   664  // the invoke command returning an error. Only the first error that
   665  // occurs will be set
   666  type DeliverGroup struct {
   667  	Clients     []*DeliverClient
   668  	Certificate tls.Certificate
   669  	ChannelID   string
   670  	TxID        string
   671  	Signer      identity.SignerSerializer
   672  	mutex       sync.Mutex
   673  	Error       error
   674  	wg          sync.WaitGroup
   675  }
   676  
   677  // DeliverClient holds the client/connection related to a specific
   678  // peer. The address is included for logging purposes
   679  type DeliverClient struct {
   680  	Client     pb.DeliverClient
   681  	Connection pb.Deliver_DeliverClient
   682  	Address    string
   683  }
   684  
   685  func NewDeliverGroup(
   686  	deliverClients []pb.DeliverClient,
   687  	peerAddresses []string,
   688  	signer identity.SignerSerializer,
   689  	certificate tls.Certificate,
   690  	channelID string,
   691  	txid string,
   692  ) *DeliverGroup {
   693  	clients := make([]*DeliverClient, len(deliverClients))
   694  	for i, client := range deliverClients {
   695  		address := peerAddresses[i]
   696  		if address == "" {
   697  			address = viper.GetString("peer.address")
   698  		}
   699  		dc := &DeliverClient{
   700  			Client:  client,
   701  			Address: address,
   702  		}
   703  		clients[i] = dc
   704  	}
   705  
   706  	dg := &DeliverGroup{
   707  		Clients:     clients,
   708  		Certificate: certificate,
   709  		ChannelID:   channelID,
   710  		TxID:        txid,
   711  		Signer:      signer,
   712  	}
   713  
   714  	return dg
   715  }
   716  
   717  // Connect waits for all deliver clients in the group to connect to
   718  // the peer's deliver service, receive an error, or for the context
   719  // to timeout. An error will be returned whenever even a single
   720  // deliver client fails to connect to its peer
   721  func (dg *DeliverGroup) Connect(ctx context.Context) error {
   722  	dg.wg.Add(len(dg.Clients))
   723  	for _, client := range dg.Clients {
   724  		go dg.ClientConnect(ctx, client)
   725  	}
   726  	readyCh := make(chan struct{})
   727  	go dg.WaitForWG(readyCh)
   728  
   729  	select {
   730  	case <-readyCh:
   731  		if dg.Error != nil {
   732  			err := errors.WithMessage(dg.Error, "failed to connect to deliver on all peers")
   733  			return err
   734  		}
   735  	case <-ctx.Done():
   736  		err := errors.New("timed out waiting for connection to deliver on all peers")
   737  		return err
   738  	}
   739  
   740  	return nil
   741  }
   742  
   743  // ClientConnect sends a deliver seek info envelope using the
   744  // provided deliver client, setting the deliverGroup's Error
   745  // field upon any error
   746  func (dg *DeliverGroup) ClientConnect(ctx context.Context, dc *DeliverClient) {
   747  	defer dg.wg.Done()
   748  	df, err := dc.Client.DeliverFiltered(ctx)
   749  	if err != nil {
   750  		err = errors.WithMessagef(err, "error connecting to deliver filtered at %s", dc.Address)
   751  		dg.setError(err)
   752  		return
   753  	}
   754  	defer df.CloseSend()
   755  	dc.Connection = df
   756  
   757  	envelope := createDeliverEnvelope(dg.ChannelID, dg.Certificate, dg.Signer)
   758  	err = df.Send(envelope)
   759  	if err != nil {
   760  		err = errors.WithMessagef(err, "error sending deliver seek info envelope to %s", dc.Address)
   761  		dg.setError(err)
   762  		return
   763  	}
   764  }
   765  
   766  // Wait waits for all deliver client connections in the group to
   767  // either receive a block with the txid, an error, or for the
   768  // context to timeout
   769  func (dg *DeliverGroup) Wait(ctx context.Context) error {
   770  	if len(dg.Clients) == 0 {
   771  		return nil
   772  	}
   773  
   774  	dg.wg.Add(len(dg.Clients))
   775  	for _, client := range dg.Clients {
   776  		go dg.ClientWait(client)
   777  	}
   778  	readyCh := make(chan struct{})
   779  	go dg.WaitForWG(readyCh)
   780  
   781  	select {
   782  	case <-readyCh:
   783  		if dg.Error != nil {
   784  			return dg.Error
   785  		}
   786  	case <-ctx.Done():
   787  		err := errors.New("timed out waiting for txid on all peers")
   788  		return err
   789  	}
   790  
   791  	return nil
   792  }
   793  
   794  // ClientWait waits for the specified deliver client to receive
   795  // a block event with the requested txid
   796  func (dg *DeliverGroup) ClientWait(dc *DeliverClient) {
   797  	defer dg.wg.Done()
   798  	for {
   799  		resp, err := dc.Connection.Recv()
   800  		if err != nil {
   801  			err = errors.WithMessagef(err, "error receiving from deliver filtered at %s", dc.Address)
   802  			dg.setError(err)
   803  			return
   804  		}
   805  		switch r := resp.Type.(type) {
   806  		case *pb.DeliverResponse_FilteredBlock:
   807  			filteredTransactions := r.FilteredBlock.FilteredTransactions
   808  			for _, tx := range filteredTransactions {
   809  				if tx.Txid == dg.TxID {
   810  					logger.Infof("txid [%s] committed with status (%s) at %s", dg.TxID, tx.TxValidationCode, dc.Address)
   811  					if tx.TxValidationCode != pb.TxValidationCode_VALID {
   812  						err = errors.Errorf("transaction invalidated with status (%s)", tx.TxValidationCode)
   813  						dg.setError(err)
   814  					}
   815  					return
   816  				}
   817  			}
   818  		case *pb.DeliverResponse_Status:
   819  			err = errors.Errorf("deliver completed with status (%s) before txid received", r.Status)
   820  			dg.setError(err)
   821  			return
   822  		default:
   823  			err = errors.Errorf("received unexpected response type (%T) from %s", r, dc.Address)
   824  			dg.setError(err)
   825  			return
   826  		}
   827  	}
   828  }
   829  
   830  // WaitForWG waits for the deliverGroup's wait group and closes
   831  // the channel when ready
   832  func (dg *DeliverGroup) WaitForWG(readyCh chan struct{}) {
   833  	dg.wg.Wait()
   834  	close(readyCh)
   835  }
   836  
   837  // setError serializes an error for the deliverGroup
   838  func (dg *DeliverGroup) setError(err error) {
   839  	dg.mutex.Lock()
   840  	dg.Error = err
   841  	dg.mutex.Unlock()
   842  }
   843  
   844  func createDeliverEnvelope(
   845  	channelID string,
   846  	certificate tls.Certificate,
   847  	signer identity.SignerSerializer,
   848  ) *pcommon.Envelope {
   849  	var tlsCertHash []byte
   850  	// check for client certificate and create hash if present
   851  	if len(certificate.Certificate) > 0 {
   852  		tlsCertHash = util.ComputeSHA256(certificate.Certificate[0])
   853  	}
   854  
   855  	start := &ab.SeekPosition{
   856  		Type: &ab.SeekPosition_Newest{
   857  			Newest: &ab.SeekNewest{},
   858  		},
   859  	}
   860  
   861  	stop := &ab.SeekPosition{
   862  		Type: &ab.SeekPosition_Specified{
   863  			Specified: &ab.SeekSpecified{
   864  				Number: math.MaxUint64,
   865  			},
   866  		},
   867  	}
   868  
   869  	seekInfo := &ab.SeekInfo{
   870  		Start:    start,
   871  		Stop:     stop,
   872  		Behavior: ab.SeekInfo_BLOCK_UNTIL_READY,
   873  	}
   874  
   875  	env, err := protoutil.CreateSignedEnvelopeWithTLSBinding(
   876  		pcommon.HeaderType_DELIVER_SEEK_INFO,
   877  		channelID,
   878  		signer,
   879  		seekInfo,
   880  		int32(0),
   881  		uint64(0),
   882  		tlsCertHash,
   883  	)
   884  	if err != nil {
   885  		logger.Errorf("Error signing envelope: %s", err)
   886  		return nil
   887  	}
   888  
   889  	return env
   890  }