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