github.com/yimialmonte/fabric@v2.1.1+incompatible/core/endorser/endorser.go (about)

     1  /*
     2  Copyright IBM Corp. 2016 All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package endorser
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"strconv"
    13  	"time"
    14  
    15  	"github.com/golang/protobuf/proto"
    16  	"github.com/hyperledger/fabric-chaincode-go/shim"
    17  	pb "github.com/hyperledger/fabric-protos-go/peer"
    18  	"github.com/hyperledger/fabric-protos-go/transientstore"
    19  	"github.com/hyperledger/fabric/common/flogging"
    20  	"github.com/hyperledger/fabric/common/util"
    21  	"github.com/hyperledger/fabric/core/chaincode/lifecycle"
    22  	"github.com/hyperledger/fabric/core/common/ccprovider"
    23  	"github.com/hyperledger/fabric/core/ledger"
    24  	"github.com/hyperledger/fabric/internal/pkg/identity"
    25  	"github.com/hyperledger/fabric/msp"
    26  	"github.com/hyperledger/fabric/protoutil"
    27  	"github.com/pkg/errors"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  var endorserLogger = flogging.MustGetLogger("endorser")
    32  
    33  // The Jira issue that documents Endorser flow along with its relationship to
    34  // the lifecycle chaincode - https://jira.hyperledger.org/browse/FAB-181
    35  
    36  //go:generate counterfeiter -o fake/prvt_data_distributor.go --fake-name PrivateDataDistributor . PrivateDataDistributor
    37  
    38  type PrivateDataDistributor interface {
    39  	DistributePrivateData(channel string, txID string, privateData *transientstore.TxPvtReadWriteSetWithConfigInfo, blkHt uint64) error
    40  }
    41  
    42  // Support contains functions that the endorser requires to execute its tasks
    43  type Support interface {
    44  	identity.SignerSerializer
    45  	// GetTxSimulator returns the transaction simulator for the specified ledger
    46  	// a client may obtain more than one such simulator; they are made unique
    47  	// by way of the supplied txid
    48  	GetTxSimulator(ledgername string, txid string) (ledger.TxSimulator, error)
    49  
    50  	// GetHistoryQueryExecutor gives handle to a history query executor for the
    51  	// specified ledger
    52  	GetHistoryQueryExecutor(ledgername string) (ledger.HistoryQueryExecutor, error)
    53  
    54  	// GetTransactionByID retrieves a transaction by id
    55  	GetTransactionByID(chid, txID string) (*pb.ProcessedTransaction, error)
    56  
    57  	// IsSysCC returns true if the name matches a system chaincode's
    58  	// system chaincode names are system, chain wide
    59  	IsSysCC(name string) bool
    60  
    61  	// Execute - execute proposal, return original response of chaincode
    62  	Execute(txParams *ccprovider.TransactionParams, name string, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error)
    63  
    64  	// ExecuteLegacyInit - executes a deployment proposal, return original response of chaincode
    65  	ExecuteLegacyInit(txParams *ccprovider.TransactionParams, name, version string, spec *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error)
    66  
    67  	// ChaincodeEndorsementInfo returns the information from lifecycle required to endorse the chaincode.
    68  	ChaincodeEndorsementInfo(channelID, chaincodeID string, txsim ledger.QueryExecutor) (*lifecycle.ChaincodeEndorsementInfo, error)
    69  
    70  	// CheckACL checks the ACL for the resource for the channel using the
    71  	// SignedProposal from which an id can be extracted for testing against a policy
    72  	CheckACL(channelID string, signedProp *pb.SignedProposal) error
    73  
    74  	// EndorseWithPlugin endorses the response with a plugin
    75  	EndorseWithPlugin(pluginName, channnelID string, prpBytes []byte, signedProposal *pb.SignedProposal) (*pb.Endorsement, []byte, error)
    76  
    77  	// GetLedgerHeight returns ledger height for given channelID
    78  	GetLedgerHeight(channelID string) (uint64, error)
    79  
    80  	// GetDeployedCCInfoProvider returns ledger.DeployedChaincodeInfoProvider
    81  	GetDeployedCCInfoProvider() ledger.DeployedChaincodeInfoProvider
    82  }
    83  
    84  //go:generate counterfeiter -o fake/channel_fetcher.go --fake-name ChannelFetcher . ChannelFetcher
    85  
    86  // ChannelFetcher fetches the channel context for a given channel ID.
    87  type ChannelFetcher interface {
    88  	Channel(channelID string) *Channel
    89  }
    90  
    91  type Channel struct {
    92  	IdentityDeserializer msp.IdentityDeserializer
    93  }
    94  
    95  // Endorser provides the Endorser service ProcessProposal
    96  type Endorser struct {
    97  	ChannelFetcher         ChannelFetcher
    98  	LocalMSP               msp.IdentityDeserializer
    99  	PrivateDataDistributor PrivateDataDistributor
   100  	Support                Support
   101  	PvtRWSetAssembler      PvtRWSetAssembler
   102  	Metrics                *Metrics
   103  }
   104  
   105  // call specified chaincode (system or user)
   106  func (e *Endorser) callChaincode(txParams *ccprovider.TransactionParams, input *pb.ChaincodeInput, chaincodeName string) (*pb.Response, *pb.ChaincodeEvent, error) {
   107  	defer func(start time.Time) {
   108  		logger := endorserLogger.WithOptions(zap.AddCallerSkip(1))
   109  		logger = decorateLogger(logger, txParams)
   110  		elapsedMillisec := time.Since(start).Milliseconds()
   111  		logger.Infof("finished chaincode: %s duration: %dms", chaincodeName, elapsedMillisec)
   112  	}(time.Now())
   113  
   114  	meterLabels := []string{
   115  		"channel", txParams.ChannelID,
   116  		"chaincode", chaincodeName,
   117  	}
   118  
   119  	res, ccevent, err := e.Support.Execute(txParams, chaincodeName, input)
   120  	if err != nil {
   121  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   122  		return nil, nil, err
   123  	}
   124  
   125  	// per doc anything < 400 can be sent as TX.
   126  	// fabric errors will always be >= 400 (ie, unambiguous errors )
   127  	// "lscc" will respond with status 200 or 500 (ie, unambiguous OK or ERROR)
   128  	if res.Status >= shim.ERRORTHRESHOLD {
   129  		return res, nil, nil
   130  	}
   131  
   132  	// Unless this is the weirdo LSCC case, just return
   133  	if chaincodeName != "lscc" || len(input.Args) < 3 || (string(input.Args[0]) != "deploy" && string(input.Args[0]) != "upgrade") {
   134  		return res, ccevent, nil
   135  	}
   136  
   137  	// ----- BEGIN -  SECTION THAT MAY NEED TO BE DONE IN LSCC ------
   138  	// if this a call to deploy a chaincode, We need a mechanism
   139  	// to pass TxSimulator into LSCC. Till that is worked out this
   140  	// special code does the actual deploy, upgrade here so as to collect
   141  	// all state under one TxSimulator
   142  	//
   143  	// NOTE that if there's an error all simulation, including the chaincode
   144  	// table changes in lscc will be thrown away
   145  	cds, err := protoutil.UnmarshalChaincodeDeploymentSpec(input.Args[2])
   146  	if err != nil {
   147  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   148  		return nil, nil, err
   149  	}
   150  
   151  	// this should not be a system chaincode
   152  	if e.Support.IsSysCC(cds.ChaincodeSpec.ChaincodeId.Name) {
   153  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   154  		return nil, nil, errors.Errorf("attempting to deploy a system chaincode %s/%s", cds.ChaincodeSpec.ChaincodeId.Name, txParams.ChannelID)
   155  	}
   156  
   157  	if len(cds.CodePackage) != 0 {
   158  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   159  		return nil, nil, errors.Errorf("lscc upgrade/deploy should not include a code packages")
   160  	}
   161  
   162  	_, _, err = e.Support.ExecuteLegacyInit(txParams, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, cds.ChaincodeSpec.Input)
   163  	if err != nil {
   164  		// increment the failure to indicate instantion/upgrade failures
   165  		meterLabels = []string{
   166  			"channel", txParams.ChannelID,
   167  			"chaincode", cds.ChaincodeSpec.ChaincodeId.Name,
   168  		}
   169  		e.Metrics.InitFailed.With(meterLabels...).Add(1)
   170  		return nil, nil, err
   171  	}
   172  
   173  	return res, ccevent, err
   174  
   175  }
   176  
   177  // SimulateProposal simulates the proposal by calling the chaincode
   178  func (e *Endorser) SimulateProposal(txParams *ccprovider.TransactionParams, chaincodeName string, chaincodeInput *pb.ChaincodeInput) (*pb.Response, []byte, *pb.ChaincodeEvent, error) {
   179  	logger := decorateLogger(endorserLogger, txParams)
   180  
   181  	meterLabels := []string{
   182  		"channel", txParams.ChannelID,
   183  		"chaincode", chaincodeName,
   184  	}
   185  
   186  	// ---3. execute the proposal and get simulation results
   187  	res, ccevent, err := e.callChaincode(txParams, chaincodeInput, chaincodeName)
   188  	if err != nil {
   189  		logger.Errorf("failed to invoke chaincode %s, error: %+v", chaincodeName, err)
   190  		return nil, nil, nil, err
   191  	}
   192  
   193  	if txParams.TXSimulator == nil {
   194  		return res, nil, ccevent, nil
   195  	}
   196  
   197  	// Note, this is a little goofy, as if there is private data, Done() gets called
   198  	// early, so this is invoked multiple times, but that is how the code worked before
   199  	// this change, so, should be safe.  Long term, let's move the Done up to the create.
   200  	defer txParams.TXSimulator.Done()
   201  
   202  	simResult, err := txParams.TXSimulator.GetTxSimulationResults()
   203  	if err != nil {
   204  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   205  		return nil, nil, nil, err
   206  	}
   207  
   208  	if simResult.PvtSimulationResults != nil {
   209  		if chaincodeName == "lscc" {
   210  			// TODO: remove once we can store collection configuration outside of LSCC
   211  			e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   212  			return nil, nil, nil, errors.New("Private data is forbidden to be used in instantiate")
   213  		}
   214  		pvtDataWithConfig, err := AssemblePvtRWSet(txParams.ChannelID, simResult.PvtSimulationResults, txParams.TXSimulator, e.Support.GetDeployedCCInfoProvider())
   215  		// To read collection config need to read collection updates before
   216  		// releasing the lock, hence txParams.TXSimulator.Done()  moved down here
   217  		txParams.TXSimulator.Done()
   218  
   219  		if err != nil {
   220  			e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   221  			return nil, nil, nil, errors.WithMessage(err, "failed to obtain collections config")
   222  		}
   223  		endorsedAt, err := e.Support.GetLedgerHeight(txParams.ChannelID)
   224  		if err != nil {
   225  			e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   226  			return nil, nil, nil, errors.WithMessage(err, fmt.Sprintf("failed to obtain ledger height for channel '%s'", txParams.ChannelID))
   227  		}
   228  		// Add ledger height at which transaction was endorsed,
   229  		// `endorsedAt` is obtained from the block storage and at times this could be 'endorsement Height + 1'.
   230  		// However, since we use this height only to select the configuration (3rd parameter in distributePrivateData) and
   231  		// manage transient store purge for orphaned private writesets (4th parameter in distributePrivateData), this works for now.
   232  		// Ideally, ledger should add support in the simulator as a first class function `GetHeight()`.
   233  		pvtDataWithConfig.EndorsedAt = endorsedAt
   234  		if err := e.PrivateDataDistributor.DistributePrivateData(txParams.ChannelID, txParams.TxID, pvtDataWithConfig, endorsedAt); err != nil {
   235  			e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   236  			return nil, nil, nil, err
   237  		}
   238  	}
   239  
   240  	pubSimResBytes, err := simResult.GetPubSimulationBytes()
   241  	if err != nil {
   242  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   243  		return nil, nil, nil, err
   244  	}
   245  
   246  	return res, pubSimResBytes, ccevent, nil
   247  }
   248  
   249  // preProcess checks the tx proposal headers, uniqueness and ACL
   250  func (e *Endorser) preProcess(up *UnpackedProposal, channel *Channel) error {
   251  	// at first, we check whether the message is valid
   252  
   253  	err := up.Validate(channel.IdentityDeserializer)
   254  	if err != nil {
   255  		e.Metrics.ProposalValidationFailed.Add(1)
   256  		return errors.WithMessage(err, "error validating proposal")
   257  	}
   258  
   259  	if up.ChannelHeader.ChannelId == "" {
   260  		// chainless proposals do not/cannot affect ledger and cannot be submitted as transactions
   261  		// ignore uniqueness checks; also, chainless proposals are not validated using the policies
   262  		// of the chain since by definition there is no chain; they are validated against the local
   263  		// MSP of the peer instead by the call to ValidateUnpackProposal above
   264  		return nil
   265  	}
   266  
   267  	// labels that provide context for failure metrics
   268  	meterLabels := []string{
   269  		"channel", up.ChannelHeader.ChannelId,
   270  		"chaincode", up.ChaincodeName,
   271  	}
   272  
   273  	// Here we handle uniqueness check and ACLs for proposals targeting a chain
   274  	// Notice that ValidateProposalMessage has already verified that TxID is computed properly
   275  	if _, err = e.Support.GetTransactionByID(up.ChannelHeader.ChannelId, up.ChannelHeader.TxId); err == nil {
   276  		// increment failure due to duplicate transactions. Useful for catching replay attacks in
   277  		// addition to benign retries
   278  		e.Metrics.DuplicateTxsFailure.With(meterLabels...).Add(1)
   279  		return errors.Errorf("duplicate transaction found [%s]. Creator [%x]", up.ChannelHeader.TxId, up.SignatureHeader.Creator)
   280  	}
   281  
   282  	// check ACL only for application chaincodes; ACLs
   283  	// for system chaincodes are checked elsewhere
   284  	if !e.Support.IsSysCC(up.ChaincodeName) {
   285  		// check that the proposal complies with the Channel's writers
   286  		if err = e.Support.CheckACL(up.ChannelHeader.ChannelId, up.SignedProposal); err != nil {
   287  			e.Metrics.ProposalACLCheckFailed.With(meterLabels...).Add(1)
   288  			return err
   289  		}
   290  	}
   291  
   292  	return nil
   293  }
   294  
   295  // ProcessProposal process the Proposal
   296  func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
   297  	// start time for computing elapsed time metric for successfully endorsed proposals
   298  	startTime := time.Now()
   299  	e.Metrics.ProposalsReceived.Add(1)
   300  
   301  	addr := util.ExtractRemoteAddress(ctx)
   302  	endorserLogger.Debug("request from", addr)
   303  
   304  	// variables to capture proposal duration metric
   305  	success := false
   306  
   307  	up, err := UnpackProposal(signedProp)
   308  	if err != nil {
   309  		e.Metrics.ProposalValidationFailed.Add(1)
   310  		return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
   311  	}
   312  
   313  	var channel *Channel
   314  	if up.ChannelID() != "" {
   315  		channel = e.ChannelFetcher.Channel(up.ChannelID())
   316  		if channel == nil {
   317  			return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: fmt.Sprintf("channel '%s' not found", up.ChannelHeader.ChannelId)}}, nil
   318  		}
   319  	} else {
   320  		channel = &Channel{
   321  			IdentityDeserializer: e.LocalMSP,
   322  		}
   323  	}
   324  
   325  	// 0 -- check and validate
   326  	err = e.preProcess(up, channel)
   327  	if err != nil {
   328  		return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
   329  	}
   330  
   331  	defer func() {
   332  		meterLabels := []string{
   333  			"channel", up.ChannelHeader.ChannelId,
   334  			"chaincode", up.ChaincodeName,
   335  			"success", strconv.FormatBool(success),
   336  		}
   337  		e.Metrics.ProposalDuration.With(meterLabels...).Observe(time.Since(startTime).Seconds())
   338  	}()
   339  
   340  	pResp, err := e.ProcessProposalSuccessfullyOrError(up)
   341  	if err != nil {
   342  		return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil
   343  	}
   344  
   345  	if pResp.Endorsement != nil || up.ChannelHeader.ChannelId == "" {
   346  		// We mark the tx as successfull only if it was successfully endorsed, or
   347  		// if it was a system chaincode on a channel-less channel and therefore
   348  		// cannot be endorsed.
   349  		success = true
   350  
   351  		// total failed proposals = ProposalsReceived-SuccessfulProposals
   352  		e.Metrics.SuccessfulProposals.Add(1)
   353  	}
   354  	return pResp, nil
   355  }
   356  
   357  func (e *Endorser) ProcessProposalSuccessfullyOrError(up *UnpackedProposal) (*pb.ProposalResponse, error) {
   358  	txParams := &ccprovider.TransactionParams{
   359  		ChannelID:  up.ChannelHeader.ChannelId,
   360  		TxID:       up.ChannelHeader.TxId,
   361  		SignedProp: up.SignedProposal,
   362  		Proposal:   up.Proposal,
   363  	}
   364  
   365  	logger := decorateLogger(endorserLogger, txParams)
   366  
   367  	if acquireTxSimulator(up.ChannelHeader.ChannelId, up.ChaincodeName) {
   368  		txSim, err := e.Support.GetTxSimulator(up.ChannelID(), up.TxID())
   369  		if err != nil {
   370  			return nil, err
   371  		}
   372  
   373  		// txsim acquires a shared lock on the stateDB. As this would impact the block commits (i.e., commit
   374  		// of valid write-sets to the stateDB), we must release the lock as early as possible.
   375  		// Hence, this txsim object is closed in simulateProposal() as soon as the tx is simulated and
   376  		// rwset is collected before gossip dissemination if required for privateData. For safety, we
   377  		// add the following defer statement and is useful when an error occur. Note that calling
   378  		// txsim.Done() more than once does not cause any issue. If the txsim is already
   379  		// released, the following txsim.Done() simply returns.
   380  		defer txSim.Done()
   381  
   382  		hqe, err := e.Support.GetHistoryQueryExecutor(up.ChannelID())
   383  		if err != nil {
   384  			return nil, err
   385  		}
   386  
   387  		txParams.TXSimulator = txSim
   388  		txParams.HistoryQueryExecutor = hqe
   389  	}
   390  
   391  	cdLedger, err := e.Support.ChaincodeEndorsementInfo(up.ChannelID(), up.ChaincodeName, txParams.TXSimulator)
   392  	if err != nil {
   393  		return nil, errors.WithMessagef(err, "make sure the chaincode %s has been successfully defined on channel %s and try again", up.ChaincodeName, up.ChannelID())
   394  	}
   395  
   396  	// 1 -- simulate
   397  	res, simulationResult, ccevent, err := e.SimulateProposal(txParams, up.ChaincodeName, up.Input)
   398  	if err != nil {
   399  		return nil, errors.WithMessage(err, "error in simulation")
   400  	}
   401  
   402  	cceventBytes, err := CreateCCEventBytes(ccevent)
   403  	if err != nil {
   404  		return nil, errors.Wrap(err, "failed to marshal chaincode event")
   405  	}
   406  
   407  	prpBytes, err := protoutil.GetBytesProposalResponsePayload(up.ProposalHash, res, simulationResult, cceventBytes, &pb.ChaincodeID{
   408  		Name:    up.ChaincodeName,
   409  		Version: cdLedger.Version,
   410  	})
   411  	if err != nil {
   412  		logger.Warning("Failed marshaling the proposal response payload to bytes", err)
   413  		return nil, errors.WithMessage(err, "failed to create the proposal response")
   414  	}
   415  
   416  	// if error, capture endorsement failure metric
   417  	meterLabels := []string{
   418  		"channel", up.ChannelID(),
   419  		"chaincode", up.ChaincodeName,
   420  	}
   421  
   422  	switch {
   423  	case res.Status >= shim.ERROR:
   424  		return &pb.ProposalResponse{
   425  			Response: res,
   426  			Payload:  prpBytes,
   427  		}, nil
   428  	case up.ChannelID() == "":
   429  		// Chaincode invocations without a channel ID is a broken concept
   430  		// that should be removed in the future.  For now, return unendorsed
   431  		// success.
   432  		return &pb.ProposalResponse{
   433  			Response: res,
   434  		}, nil
   435  	case res.Status >= shim.ERRORTHRESHOLD:
   436  		meterLabels = append(meterLabels, "chaincodeerror", strconv.FormatBool(true))
   437  		e.Metrics.EndorsementsFailed.With(meterLabels...).Add(1)
   438  		logger.Debugf("chaincode error %d", res.Status)
   439  		return &pb.ProposalResponse{
   440  			Response: res,
   441  		}, nil
   442  	}
   443  
   444  	escc := cdLedger.EndorsementPlugin
   445  
   446  	logger.Debugf("escc for chaincode %s is %s", up.ChaincodeName, escc)
   447  
   448  	// Note, mPrpBytes is the same as prpBytes by default endorsement plugin, but others could change it.
   449  	endorsement, mPrpBytes, err := e.Support.EndorseWithPlugin(escc, up.ChannelID(), prpBytes, up.SignedProposal)
   450  	if err != nil {
   451  		meterLabels = append(meterLabels, "chaincodeerror", strconv.FormatBool(false))
   452  		e.Metrics.EndorsementsFailed.With(meterLabels...).Add(1)
   453  		return nil, errors.WithMessage(err, "endorsing with plugin failed")
   454  	}
   455  
   456  	return &pb.ProposalResponse{
   457  		Version:     1,
   458  		Endorsement: endorsement,
   459  		Payload:     mPrpBytes,
   460  		Response:    res,
   461  	}, nil
   462  }
   463  
   464  // determine whether or not a transaction simulator should be
   465  // obtained for a proposal.
   466  func acquireTxSimulator(chainID string, chaincodeName string) bool {
   467  	if chainID == "" {
   468  		return false
   469  	}
   470  
   471  	// ¯\_(ツ)_/¯ locking.
   472  	// Don't get a simulator for the query and config system chaincode.
   473  	// These don't need the simulator and its read lock results in deadlocks.
   474  	switch chaincodeName {
   475  	case "qscc", "cscc":
   476  		return false
   477  	default:
   478  		return true
   479  	}
   480  }
   481  
   482  // shorttxid replicates the chaincode package function to shorten txids.
   483  // ~~TODO utilize a common shorttxid utility across packages.~~
   484  // TODO use a formal type for transaction ID and make it a stringer
   485  func shorttxid(txid string) string {
   486  	if len(txid) < 8 {
   487  		return txid
   488  	}
   489  	return txid[0:8]
   490  }
   491  
   492  func CreateCCEventBytes(ccevent *pb.ChaincodeEvent) ([]byte, error) {
   493  	if ccevent == nil {
   494  		return nil, nil
   495  	}
   496  
   497  	return proto.Marshal(ccevent)
   498  }
   499  
   500  func decorateLogger(logger *flogging.FabricLogger, txParams *ccprovider.TransactionParams) *flogging.FabricLogger {
   501  	return logger.With("channel", txParams.ChannelID, "txID", shorttxid(txParams.TxID))
   502  }