github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/endorser/endorser.go (about)

     1  /*
     2  Copyright hechain. 2022 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/hechain20/hechain/common/flogging"
    17  	"github.com/hechain20/hechain/common/util"
    18  	"github.com/hechain20/hechain/core/chaincode/lifecycle"
    19  	"github.com/hechain20/hechain/core/common/ccprovider"
    20  	"github.com/hechain20/hechain/core/ledger"
    21  	"github.com/hechain20/hechain/internal/pkg/identity"
    22  	"github.com/hechain20/hechain/msp"
    23  	"github.com/hechain20/hechain/protoutil"
    24  	"github.com/hyperledger/fabric-chaincode-go/shim"
    25  	"github.com/hyperledger/fabric-protos-go/common"
    26  	pb "github.com/hyperledger/fabric-protos-go/peer"
    27  	"github.com/hyperledger/fabric-protos-go/transientstore"
    28  	"github.com/pkg/errors"
    29  	"go.uber.org/zap"
    30  )
    31  
    32  var endorserLogger = flogging.MustGetLogger("endorser")
    33  
    34  // The Jira issue that documents Endorser flow along with its relationship to
    35  // the lifecycle chaincode - https://jira.hyperledger.org/browse/FAB-181
    36  
    37  //go:generate counterfeiter -o fake/prvt_data_distributor.go --fake-name PrivateDataDistributor . PrivateDataDistributor
    38  
    39  type PrivateDataDistributor interface {
    40  	DistributePrivateData(channel string, txID string, privateData *transientstore.TxPvtReadWriteSetWithConfigInfo, blkHt uint64) error
    41  }
    42  
    43  // Support contains functions that the endorser requires to execute its tasks
    44  type Support interface {
    45  	identity.SignerSerializer
    46  	// GetTxSimulator returns the transaction simulator for the specified ledger
    47  	// a client may obtain more than one such simulator; they are made unique
    48  	// by way of the supplied txid
    49  	GetTxSimulator(ledgername string, txid string) (ledger.TxSimulator, error)
    50  
    51  	// GetHistoryQueryExecutor gives handle to a history query executor for the
    52  	// specified ledger
    53  	GetHistoryQueryExecutor(ledgername string) (ledger.HistoryQueryExecutor, error)
    54  
    55  	// GetTransactionByID retrieves a transaction by id
    56  	GetTransactionByID(chid, txID string) (*pb.ProcessedTransaction, error)
    57  
    58  	// IsSysCC returns true if the name matches a system chaincode's
    59  	// system chaincode names are system, chain wide
    60  	IsSysCC(name string) bool
    61  
    62  	// Execute - execute proposal, return original response of chaincode
    63  	Execute(txParams *ccprovider.TransactionParams, name string, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error)
    64  
    65  	// ExecuteLegacyInit - executes a deployment proposal, return original response of chaincode
    66  	ExecuteLegacyInit(txParams *ccprovider.TransactionParams, name, version string, spec *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error)
    67  
    68  	// ChaincodeEndorsementInfo returns the information from lifecycle required to endorse the chaincode.
    69  	ChaincodeEndorsementInfo(channelID, chaincodeID string, txsim ledger.QueryExecutor) (*lifecycle.ChaincodeEndorsementInfo, error)
    70  
    71  	// CheckACL checks the ACL for the resource for the channel using the
    72  	// SignedProposal from which an id can be extracted for testing against a policy
    73  	CheckACL(channelID string, signedProp *pb.SignedProposal) error
    74  
    75  	// EndorseWithPlugin endorses the response with a plugin
    76  	EndorseWithPlugin(pluginName, channnelID string, prpBytes []byte, signedProposal *pb.SignedProposal) (*pb.Endorsement, []byte, error)
    77  
    78  	// GetLedgerHeight returns ledger height for given channelID
    79  	GetLedgerHeight(channelID string) (uint64, error)
    80  
    81  	// GetDeployedCCInfoProvider returns ledger.DeployedChaincodeInfoProvider
    82  	GetDeployedCCInfoProvider() ledger.DeployedChaincodeInfoProvider
    83  }
    84  
    85  //go:generate counterfeiter -o fake/channel_fetcher.go --fake-name ChannelFetcher . ChannelFetcher
    86  
    87  // ChannelFetcher fetches the channel context for a given channel ID.
    88  type ChannelFetcher interface {
    89  	Channel(channelID string) *Channel
    90  }
    91  
    92  type Channel struct {
    93  	IdentityDeserializer msp.IdentityDeserializer
    94  }
    95  
    96  // Endorser provides the Endorser service ProcessProposal
    97  type Endorser struct {
    98  	ChannelFetcher         ChannelFetcher
    99  	LocalMSP               msp.IdentityDeserializer
   100  	PrivateDataDistributor PrivateDataDistributor
   101  	Support                Support
   102  	PvtRWSetAssembler      PvtRWSetAssembler
   103  	Metrics                *Metrics
   104  }
   105  
   106  // call specified chaincode (system or user)
   107  func (e *Endorser) callChaincode(txParams *ccprovider.TransactionParams, input *pb.ChaincodeInput, chaincodeName string) (*pb.Response, *pb.ChaincodeEvent, error) {
   108  	defer func(start time.Time) {
   109  		logger := endorserLogger.WithOptions(zap.AddCallerSkip(1))
   110  		logger = decorateLogger(logger, txParams)
   111  		elapsedMillisec := time.Since(start).Milliseconds()
   112  		logger.Infof("finished chaincode: %s duration: %dms", chaincodeName, elapsedMillisec)
   113  	}(time.Now())
   114  
   115  	meterLabels := []string{
   116  		"channel", txParams.ChannelID,
   117  		"chaincode", chaincodeName,
   118  	}
   119  
   120  	res, ccevent, err := e.Support.Execute(txParams, chaincodeName, input)
   121  	if err != nil {
   122  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   123  		return nil, nil, err
   124  	}
   125  
   126  	// per doc anything < 400 can be sent as TX.
   127  	// fabric errors will always be >= 400 (ie, unambiguous errors )
   128  	// "lscc" will respond with status 200 or 500 (ie, unambiguous OK or ERROR)
   129  	if res.Status >= shim.ERRORTHRESHOLD {
   130  		return res, nil, nil
   131  	}
   132  
   133  	// Unless this is the weirdo LSCC case, just return
   134  	if chaincodeName != "lscc" || len(input.Args) < 3 || (string(input.Args[0]) != "deploy" && string(input.Args[0]) != "upgrade") {
   135  		return res, ccevent, nil
   136  	}
   137  
   138  	// ----- BEGIN -  SECTION THAT MAY NEED TO BE DONE IN LSCC ------
   139  	// if this a call to deploy a chaincode, We need a mechanism
   140  	// to pass TxSimulator into LSCC. Till that is worked out this
   141  	// special code does the actual deploy, upgrade here so as to collect
   142  	// all state under one TxSimulator
   143  	//
   144  	// NOTE that if there's an error all simulation, including the chaincode
   145  	// table changes in lscc will be thrown away
   146  	cds, err := protoutil.UnmarshalChaincodeDeploymentSpec(input.Args[2])
   147  	if err != nil {
   148  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   149  		return nil, nil, err
   150  	}
   151  
   152  	// this should not be a system chaincode
   153  	if e.Support.IsSysCC(cds.ChaincodeSpec.ChaincodeId.Name) {
   154  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   155  		return nil, nil, errors.Errorf("attempting to deploy a system chaincode %s/%s", cds.ChaincodeSpec.ChaincodeId.Name, txParams.ChannelID)
   156  	}
   157  
   158  	if len(cds.CodePackage) != 0 {
   159  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   160  		return nil, nil, errors.Errorf("lscc upgrade/deploy should not include a code packages")
   161  	}
   162  
   163  	_, _, err = e.Support.ExecuteLegacyInit(txParams, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, cds.ChaincodeSpec.Input)
   164  	if err != nil {
   165  		// increment the failure to indicate instantion/upgrade failures
   166  		meterLabels = []string{
   167  			"channel", txParams.ChannelID,
   168  			"chaincode", cds.ChaincodeSpec.ChaincodeId.Name,
   169  		}
   170  		e.Metrics.InitFailed.With(meterLabels...).Add(1)
   171  		return nil, nil, err
   172  	}
   173  
   174  	return res, ccevent, err
   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, *pb.ChaincodeInterest, 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, nil, err
   191  	}
   192  
   193  	if txParams.TXSimulator == nil {
   194  		return res, nil, ccevent, nil, 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, 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, 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, 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, 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, nil, err
   237  		}
   238  	}
   239  
   240  	ccInterest, err := e.buildChaincodeInterest(simResult)
   241  	if err != nil {
   242  		return nil, nil, nil, nil, err
   243  	}
   244  
   245  	pubSimResBytes, err := simResult.GetPubSimulationBytes()
   246  	if err != nil {
   247  		e.Metrics.SimulationFailure.With(meterLabels...).Add(1)
   248  		return nil, nil, nil, nil, err
   249  	}
   250  
   251  	return res, pubSimResBytes, ccevent, ccInterest, nil
   252  }
   253  
   254  // preProcess checks the tx proposal headers, uniqueness and ACL
   255  func (e *Endorser) preProcess(up *UnpackedProposal, channel *Channel) error {
   256  	// at first, we check whether the message is valid
   257  
   258  	err := up.Validate(channel.IdentityDeserializer)
   259  	if err != nil {
   260  		e.Metrics.ProposalValidationFailed.Add(1)
   261  		return errors.WithMessage(err, "error validating proposal")
   262  	}
   263  
   264  	if up.ChannelHeader.ChannelId == "" {
   265  		// chainless proposals do not/cannot affect ledger and cannot be submitted as transactions
   266  		// ignore uniqueness checks; also, chainless proposals are not validated using the policies
   267  		// of the chain since by definition there is no chain; they are validated against the local
   268  		// MSP of the peer instead by the call to ValidateUnpackProposal above
   269  		return nil
   270  	}
   271  
   272  	// labels that provide context for failure metrics
   273  	meterLabels := []string{
   274  		"channel", up.ChannelHeader.ChannelId,
   275  		"chaincode", up.ChaincodeName,
   276  	}
   277  
   278  	// Here we handle uniqueness check and ACLs for proposals targeting a chain
   279  	// Notice that ValidateProposalMessage has already verified that TxID is computed properly
   280  	if _, err = e.Support.GetTransactionByID(up.ChannelHeader.ChannelId, up.ChannelHeader.TxId); err == nil {
   281  		// increment failure due to duplicate transactions. Useful for catching replay attacks in
   282  		// addition to benign retries
   283  		e.Metrics.DuplicateTxsFailure.With(meterLabels...).Add(1)
   284  		return errors.Errorf("duplicate transaction found [%s]. Creator [%x]", up.ChannelHeader.TxId, up.SignatureHeader.Creator)
   285  	}
   286  
   287  	// check ACL only for application chaincodes; ACLs
   288  	// for system chaincodes are checked elsewhere
   289  	if !e.Support.IsSysCC(up.ChaincodeName) {
   290  		// check that the proposal complies with the Channel's writers
   291  		if err = e.Support.CheckACL(up.ChannelHeader.ChannelId, up.SignedProposal); err != nil {
   292  			e.Metrics.ProposalACLCheckFailed.With(meterLabels...).Add(1)
   293  			return err
   294  		}
   295  	}
   296  
   297  	return nil
   298  }
   299  
   300  // ProcessProposal process the Proposal
   301  // Errors related to the proposal itself are returned with an error that results in a grpc error.
   302  // Errors related to proposal processing (either infrastructure errors or chaincode errors) are returned with a nil error,
   303  // clients are expected to look at the ProposalResponse response status code (e.g. 500) and message.
   304  func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
   305  	// start time for computing elapsed time metric for successfully endorsed proposals
   306  	startTime := time.Now()
   307  	e.Metrics.ProposalsReceived.Add(1)
   308  
   309  	addr := util.ExtractRemoteAddress(ctx)
   310  	endorserLogger.Debug("request from", addr)
   311  
   312  	// variables to capture proposal duration metric
   313  	success := false
   314  
   315  	up, err := UnpackProposal(signedProp)
   316  	if err != nil {
   317  		e.Metrics.ProposalValidationFailed.Add(1)
   318  		endorserLogger.Warnw("Failed to unpack proposal", "error", err.Error())
   319  		return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
   320  	}
   321  
   322  	var channel *Channel
   323  	if up.ChannelID() != "" {
   324  		channel = e.ChannelFetcher.Channel(up.ChannelID())
   325  		if channel == nil {
   326  			return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: fmt.Sprintf("channel '%s' not found", up.ChannelHeader.ChannelId)}}, nil
   327  		}
   328  	} else {
   329  		channel = &Channel{
   330  			IdentityDeserializer: e.LocalMSP,
   331  		}
   332  	}
   333  
   334  	// 0 -- check and validate
   335  	err = e.preProcess(up, channel)
   336  	if err != nil {
   337  		endorserLogger.Warnw("Failed to preProcess proposal", "error", err.Error())
   338  		return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
   339  	}
   340  
   341  	defer func() {
   342  		meterLabels := []string{
   343  			"channel", up.ChannelHeader.ChannelId,
   344  			"chaincode", up.ChaincodeName,
   345  			"success", strconv.FormatBool(success),
   346  		}
   347  		e.Metrics.ProposalDuration.With(meterLabels...).Observe(time.Since(startTime).Seconds())
   348  	}()
   349  
   350  	pResp, err := e.ProcessProposalSuccessfullyOrError(up)
   351  	if err != nil {
   352  		endorserLogger.Warnw("Failed to invoke chaincode", "channel", up.ChannelHeader.ChannelId, "chaincode", up.ChaincodeName, "error", err.Error())
   353  		// Return a nil error since clients are expected to look at the ProposalResponse response status code (500) and message.
   354  		return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil
   355  	}
   356  
   357  	if pResp.Endorsement != nil || up.ChannelHeader.ChannelId == "" {
   358  		// We mark the tx as successful only if it was successfully endorsed, or
   359  		// if it was a system chaincode on a channel-less channel and therefore
   360  		// cannot be endorsed.
   361  		success = true
   362  
   363  		// total failed proposals = ProposalsReceived-SuccessfulProposals
   364  		e.Metrics.SuccessfulProposals.Add(1)
   365  	}
   366  	return pResp, nil
   367  }
   368  
   369  func (e *Endorser) ProcessProposalSuccessfullyOrError(up *UnpackedProposal) (*pb.ProposalResponse, error) {
   370  	txParams := &ccprovider.TransactionParams{
   371  		ChannelID:  up.ChannelHeader.ChannelId,
   372  		TxID:       up.ChannelHeader.TxId,
   373  		SignedProp: up.SignedProposal,
   374  		Proposal:   up.Proposal,
   375  	}
   376  
   377  	logger := decorateLogger(endorserLogger, txParams)
   378  
   379  	if acquireTxSimulator(up.ChannelHeader.ChannelId, up.ChaincodeName) {
   380  		txSim, err := e.Support.GetTxSimulator(up.ChannelID(), up.TxID())
   381  		if err != nil {
   382  			return nil, err
   383  		}
   384  
   385  		// txsim acquires a shared lock on the stateDB. As this would impact the block commits (i.e., commit
   386  		// of valid write-sets to the stateDB), we must release the lock as early as possible.
   387  		// Hence, this txsim object is closed in simulateProposal() as soon as the tx is simulated and
   388  		// rwset is collected before gossip dissemination if required for privateData. For safety, we
   389  		// add the following defer statement and is useful when an error occur. Note that calling
   390  		// txsim.Done() more than once does not cause any issue. If the txsim is already
   391  		// released, the following txsim.Done() simply returns.
   392  		defer txSim.Done()
   393  
   394  		hqe, err := e.Support.GetHistoryQueryExecutor(up.ChannelID())
   395  		if err != nil {
   396  			return nil, err
   397  		}
   398  
   399  		txParams.TXSimulator = txSim
   400  		txParams.HistoryQueryExecutor = hqe
   401  	}
   402  
   403  	cdLedger, err := e.Support.ChaincodeEndorsementInfo(up.ChannelID(), up.ChaincodeName, txParams.TXSimulator)
   404  	if err != nil {
   405  		return nil, errors.WithMessagef(err, "make sure the chaincode %s has been successfully defined on channel %s and try again", up.ChaincodeName, up.ChannelID())
   406  	}
   407  
   408  	// 1 -- simulate
   409  	res, simulationResult, ccevent, ccInterest, err := e.simulateProposal(txParams, up.ChaincodeName, up.Input)
   410  	if err != nil {
   411  		return nil, errors.WithMessage(err, "error in simulation")
   412  	}
   413  
   414  	cceventBytes, err := CreateCCEventBytes(ccevent)
   415  	if err != nil {
   416  		return nil, errors.Wrap(err, "failed to marshal chaincode event")
   417  	}
   418  
   419  	prpBytes, err := protoutil.GetBytesProposalResponsePayload(up.ProposalHash, res, simulationResult, cceventBytes, &pb.ChaincodeID{
   420  		Name:    up.ChaincodeName,
   421  		Version: cdLedger.Version,
   422  	})
   423  	if err != nil {
   424  		logger.Warning("Failed marshaling the proposal response payload to bytes", err)
   425  		return nil, errors.WithMessage(err, "failed to create the proposal response")
   426  	}
   427  
   428  	// if error, capture endorsement failure metric
   429  	meterLabels := []string{
   430  		"channel", up.ChannelID(),
   431  		"chaincode", up.ChaincodeName,
   432  	}
   433  
   434  	switch {
   435  	case res.Status >= shim.ERROR:
   436  		return &pb.ProposalResponse{
   437  			Response: res,
   438  			Payload:  prpBytes,
   439  			Interest: ccInterest,
   440  		}, nil
   441  	case up.ChannelID() == "":
   442  		// Chaincode invocations without a channel ID is a broken concept
   443  		// that should be removed in the future.  For now, return unendorsed
   444  		// success.
   445  		return &pb.ProposalResponse{
   446  			Response: res,
   447  		}, nil
   448  	case res.Status >= shim.ERRORTHRESHOLD:
   449  		meterLabels = append(meterLabels, "chaincodeerror", strconv.FormatBool(true))
   450  		e.Metrics.EndorsementsFailed.With(meterLabels...).Add(1)
   451  		logger.Debugf("chaincode error %d", res.Status)
   452  		return &pb.ProposalResponse{
   453  			Response: res,
   454  		}, nil
   455  	}
   456  
   457  	escc := cdLedger.EndorsementPlugin
   458  
   459  	logger.Debugf("escc for chaincode %s is %s", up.ChaincodeName, escc)
   460  
   461  	// Note, mPrpBytes is the same as prpBytes by default endorsement plugin, but others could change it.
   462  	endorsement, mPrpBytes, err := e.Support.EndorseWithPlugin(escc, up.ChannelID(), prpBytes, up.SignedProposal)
   463  	if err != nil {
   464  		meterLabels = append(meterLabels, "chaincodeerror", strconv.FormatBool(false))
   465  		e.Metrics.EndorsementsFailed.With(meterLabels...).Add(1)
   466  		return nil, errors.WithMessage(err, "endorsing with plugin failed")
   467  	}
   468  
   469  	return &pb.ProposalResponse{
   470  		Version:     1,
   471  		Endorsement: endorsement,
   472  		Payload:     mPrpBytes,
   473  		Response:    res,
   474  		Interest:    ccInterest,
   475  	}, nil
   476  }
   477  
   478  // Using the simulation results, build the ChaincodeInterest structure that the client can pass to the discovery service
   479  // to get the correct endorsement policy for the chaincode(s) and any collections encountered.
   480  func (e *Endorser) buildChaincodeInterest(simResult *ledger.TxSimulationResults) (*pb.ChaincodeInterest, error) {
   481  	// build a structure that collates all the information needed for the chaincode interest:
   482  	policies, err := parseWritesetMetadata(simResult.WritesetMetadata)
   483  	if err != nil {
   484  		return nil, err
   485  	}
   486  
   487  	// There might be public states that are read and not written.  Need to add these to the policyRequired structure.
   488  	// This will also include private reads, because the hashed read will appear in the public RWset.
   489  	for _, nsrws := range simResult.PubSimulationResults.GetNsRwset() {
   490  		if e.Support.IsSysCC(nsrws.Namespace) {
   491  			// skip system chaincodes
   492  			continue
   493  		}
   494  		if _, ok := policies.policyRequired[nsrws.Namespace]; !ok {
   495  			// There's a public RWset for this namespace, but no public or private writes, so chaincode policy is required.
   496  			policies.add(nsrws.Namespace, "", true)
   497  		}
   498  	}
   499  
   500  	for chaincode, collections := range simResult.PrivateReads {
   501  		for collection := range collections {
   502  			policies.add(chaincode, collection, true)
   503  		}
   504  	}
   505  
   506  	ccInterest := &pb.ChaincodeInterest{}
   507  	for chaincode, collections := range policies.policyRequired {
   508  		if e.Support.IsSysCC(chaincode) {
   509  			// skip system chaincodes
   510  			continue
   511  		}
   512  		for collection := range collections {
   513  			ccCall := &pb.ChaincodeCall{
   514  				Name: chaincode,
   515  			}
   516  			if collection == "" { // the empty collection name here represents the public RWset
   517  				keyPolicies := policies.sbePolicies[chaincode]
   518  				if len(keyPolicies) > 0 {
   519  					// For simplicity, we'll always add the SBE policies to the public ChaincodeCall, and set the disregard flag if the chaincode policy is not required.
   520  					ccCall.KeyPolicies = keyPolicies
   521  					if !policies.requireChaincodePolicy(chaincode) {
   522  						ccCall.DisregardNamespacePolicy = true
   523  					}
   524  				} else if !policies.requireChaincodePolicy(chaincode) {
   525  					continue
   526  				}
   527  			} else {
   528  				// Since each collection in a chaincode could have different values of the NoPrivateReads flag, create a new Chaincode entry for each.
   529  				ccCall.CollectionNames = []string{collection}
   530  				ccCall.NoPrivateReads = !simResult.PrivateReads.Exists(chaincode, collection)
   531  			}
   532  			ccInterest.Chaincodes = append(ccInterest.Chaincodes, ccCall)
   533  		}
   534  	}
   535  
   536  	endorserLogger.Debug("ccInterest", ccInterest)
   537  	return ccInterest, nil
   538  }
   539  
   540  type metadataPolicies struct {
   541  	// Map of SBE policies: namespace -> array of policies.
   542  	sbePolicies map[string][]*common.SignaturePolicyEnvelope
   543  	// Whether the chaincode/collection policy is required for endorsement: namespace -> collection -> isRequired
   544  	// Empty collection name represents the public rwset
   545  	// Each entry in this map represents a ChaincodeCall structure in the final ChaincodeInterest.  The boolean
   546  	// flag isRequired is used to control whether the DisregardNamespacePolicy flag should be set.
   547  	policyRequired map[string]map[string]bool
   548  }
   549  
   550  func parseWritesetMetadata(metadata ledger.WritesetMetadata) (*metadataPolicies, error) {
   551  	mp := &metadataPolicies{
   552  		sbePolicies:    map[string][]*common.SignaturePolicyEnvelope{},
   553  		policyRequired: map[string]map[string]bool{},
   554  	}
   555  	for ns, cmap := range metadata {
   556  		mp.policyRequired[ns] = map[string]bool{"": false}
   557  		for coll, kmap := range cmap {
   558  			// look through each of the states that were written to
   559  			for _, stateMetadata := range kmap {
   560  				if policyBytes, sbeExists := stateMetadata[pb.MetaDataKeys_VALIDATION_PARAMETER.String()]; sbeExists {
   561  					policy, err := protoutil.UnmarshalSignaturePolicy(policyBytes)
   562  					if err != nil {
   563  						return nil, err
   564  					}
   565  					mp.sbePolicies[ns] = append(mp.sbePolicies[ns], policy)
   566  				} else {
   567  					// the state metadata doesn't contain data relating to SBE policy, so the chaincode/collection policy is required
   568  					mp.policyRequired[ns][coll] = true
   569  				}
   570  			}
   571  		}
   572  	}
   573  
   574  	return mp, nil
   575  }
   576  
   577  func (mp *metadataPolicies) add(ns string, coll string, required bool) {
   578  	if entry, ok := mp.policyRequired[ns]; ok {
   579  		entry[coll] = required
   580  	} else {
   581  		mp.policyRequired[ns] = map[string]bool{coll: required}
   582  	}
   583  }
   584  
   585  func (mp *metadataPolicies) requireChaincodePolicy(ns string) bool {
   586  	// if any of the states (keys) were written to without those states having a SBE policy, then the chaincode policy will be required for this namespace
   587  	return mp.policyRequired[ns][""]
   588  }
   589  
   590  // determine whether or not a transaction simulator should be
   591  // obtained for a proposal.
   592  func acquireTxSimulator(chainID string, chaincodeName string) bool {
   593  	if chainID == "" {
   594  		return false
   595  	}
   596  
   597  	// ¯\_(ツ)_/¯ locking.
   598  	// Don't get a simulator for the query and config system chaincode.
   599  	// These don't need the simulator and its read lock results in deadlocks.
   600  	switch chaincodeName {
   601  	case "qscc", "cscc":
   602  		return false
   603  	default:
   604  		return true
   605  	}
   606  }
   607  
   608  // shorttxid replicates the chaincode package function to shorten txids.
   609  // ~~TODO utilize a common shorttxid utility across packages.~~
   610  // TODO use a formal type for transaction ID and make it a stringer
   611  func shorttxid(txid string) string {
   612  	if len(txid) < 8 {
   613  		return txid
   614  	}
   615  	return txid[0:8]
   616  }
   617  
   618  func CreateCCEventBytes(ccevent *pb.ChaincodeEvent) ([]byte, error) {
   619  	if ccevent == nil {
   620  		return nil, nil
   621  	}
   622  
   623  	return proto.Marshal(ccevent)
   624  }
   625  
   626  func decorateLogger(logger *flogging.FabricLogger, txParams *ccprovider.TransactionParams) *flogging.FabricLogger {
   627  	return logger.With("channel", txParams.ChannelID, "txID", shorttxid(txParams.TxID))
   628  }