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