github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/committer/txvalidator/v20/plugindispatcher/dispatcher.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package plugindispatcher
     8  
     9  import (
    10  	"fmt"
    11  
    12  	"github.com/golang/protobuf/proto"
    13  	commonerrors "github.com/hechain20/hechain/common/errors"
    14  	"github.com/hechain20/hechain/common/flogging"
    15  	validation "github.com/hechain20/hechain/core/handlers/validation/api"
    16  	s "github.com/hechain20/hechain/core/handlers/validation/api/state"
    17  	"github.com/hechain20/hechain/core/ledger"
    18  	"github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/rwsetutil"
    19  	"github.com/hechain20/hechain/protoutil"
    20  	"github.com/hyperledger/fabric-protos-go/common"
    21  	"github.com/hyperledger/fabric-protos-go/peer"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  // ChannelResources provides access to channel artefacts or
    26  // functions to interact with them
    27  type ChannelResources interface {
    28  	// GetMSPIDs returns the IDs for the application MSPs
    29  	// that have been defined in the channel
    30  	GetMSPIDs() []string
    31  }
    32  
    33  // LedgerResources provides access to ledger artefacts or
    34  // functions to interact with them
    35  type LedgerResources interface {
    36  	// NewQueryExecutor gives handle to a query executor.
    37  	// A client can obtain more than one 'QueryExecutor's for parallel execution.
    38  	// Any synchronization should be performed at the implementation level if required
    39  	NewQueryExecutor() (ledger.QueryExecutor, error)
    40  }
    41  
    42  // LifecycleResources provides access to chaincode lifecycle artefacts or
    43  // functions to interact with them
    44  type LifecycleResources interface {
    45  	// ValidationInfo returns the name and arguments of the validation plugin for the supplied
    46  	// chaincode. The function returns two types of errors, unexpected errors and validation
    47  	// errors. The reason for this is that this function is called from the validation code,
    48  	// which needs to differentiate the two types of error to halt processing on the channel
    49  	// if the unexpected error is not nil and mark the transaction as invalid if the validation
    50  	// error is not nil.
    51  	ValidationInfo(channelID, chaincodeName string, qe ledger.SimpleQueryExecutor) (plugin string, args []byte, unexpectedErr error, validationErr error)
    52  }
    53  
    54  // CollectionResources provides access to collection artefacts
    55  type CollectionResources interface {
    56  	// CollectionValidationInfo returns collection-level endorsement policy for the supplied chaincode.
    57  	// The function returns two types of errors, unexpected errors and validation errors. The
    58  	// reason for this is that this function is to be called from the validation code, which
    59  	// needs to tell apart the two types of error to halt processing on the channel if the
    60  	// unexpected error is not nil and mark the transaction as invalid if the validation error
    61  	// is not nil.
    62  	CollectionValidationInfo(chaincodeName, collectionName string, state s.State) (args []byte, unexpectedErr error, validationErr error)
    63  }
    64  
    65  // CollectionAndLifecycleResources provides access to resources
    66  // about chaincodes and their lifecycle and collections and their
    67  // policies
    68  type CollectionAndLifecycleResources interface {
    69  	LifecycleResources
    70  
    71  	// CollectionValidationInfo is exactly like the method defined in CollectionResources but
    72  	// also takes the channel ID.  This is necessary to determine if the org collection names are valid.
    73  	CollectionValidationInfo(channelID, chaincodeName, collectionName string, state s.State) (args []byte, unexpectedErr error, validationErr error)
    74  }
    75  
    76  //go:generate mockery -dir . -name LifecycleResources -case underscore -output mocks/
    77  
    78  var logger = flogging.MustGetLogger("committer.txvalidator")
    79  
    80  // dispatcherImpl is the implementation used to call
    81  // the validation plugin and validate block transactions
    82  type dispatcherImpl struct {
    83  	chainID         string
    84  	cr              ChannelResources
    85  	ler             LedgerResources
    86  	lcr             LifecycleResources
    87  	pluginValidator *PluginValidator
    88  }
    89  
    90  // New creates new plugin dispatcher
    91  func New(chainID string, cr ChannelResources, ler LedgerResources, lcr LifecycleResources, pluginValidator *PluginValidator) *dispatcherImpl {
    92  	return &dispatcherImpl{
    93  		chainID:         chainID,
    94  		cr:              cr,
    95  		ler:             ler,
    96  		lcr:             lcr,
    97  		pluginValidator: pluginValidator,
    98  	}
    99  }
   100  
   101  // Dispatch executes the validation plugin(s) for transaction
   102  func (v *dispatcherImpl) Dispatch(seq int, payload *common.Payload, envBytes []byte, block *common.Block) (peer.TxValidationCode, error) {
   103  	chainID := v.chainID
   104  	logger.Debugf("[%s] Dispatch starts for bytes %p", chainID, envBytes)
   105  
   106  	// get channel header
   107  	chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader)
   108  	if err != nil {
   109  		return peer.TxValidationCode_BAD_CHANNEL_HEADER, err
   110  	}
   111  
   112  	// get header extensions so we have the chaincode ID
   113  	hdrExt, err := protoutil.UnmarshalChaincodeHeaderExtension(chdr.Extension)
   114  	if err != nil {
   115  		return peer.TxValidationCode_BAD_HEADER_EXTENSION, err
   116  	}
   117  
   118  	/* obtain the list of namespaces we're writing to */
   119  	respPayload, err := protoutil.GetActionFromEnvelope(envBytes)
   120  	if err != nil {
   121  		return peer.TxValidationCode_BAD_RESPONSE_PAYLOAD, errors.WithMessage(err, "GetActionFromEnvelope failed")
   122  	}
   123  	txRWSet := &rwsetutil.TxRwSet{}
   124  	if err = txRWSet.FromProtoBytes(respPayload.Results); err != nil {
   125  		return peer.TxValidationCode_BAD_RWSET, errors.WithMessage(err, "txRWSet.FromProtoBytes failed")
   126  	}
   127  
   128  	// Verify the header extension and response payload contain the ChaincodeId
   129  	if hdrExt.ChaincodeId == nil {
   130  		return peer.TxValidationCode_INVALID_OTHER_REASON, errors.New("nil ChaincodeId in header extension")
   131  	}
   132  
   133  	if respPayload.ChaincodeId == nil {
   134  		return peer.TxValidationCode_INVALID_OTHER_REASON, errors.New("nil ChaincodeId in ChaincodeAction")
   135  	}
   136  
   137  	// get name and version of the cc we invoked
   138  	ccID := hdrExt.ChaincodeId.Name
   139  	ccVer := respPayload.ChaincodeId.Version
   140  
   141  	// sanity check on ccID
   142  	if ccID == "" {
   143  		err = errors.New("invalid chaincode ID")
   144  		logger.Errorf("%+v", err)
   145  		return peer.TxValidationCode_INVALID_CHAINCODE, err
   146  	}
   147  	if ccID != respPayload.ChaincodeId.Name {
   148  		err = errors.Errorf("inconsistent ccid info (%s/%s)", ccID, respPayload.ChaincodeId.Name)
   149  		logger.Errorf("%+v", err)
   150  		return peer.TxValidationCode_INVALID_CHAINCODE, err
   151  	}
   152  	// sanity check on ccver
   153  	if ccVer == "" {
   154  		err = errors.New("invalid chaincode version")
   155  		logger.Errorf("%+v", err)
   156  		return peer.TxValidationCode_INVALID_CHAINCODE, err
   157  	}
   158  
   159  	wrNamespace := map[string]bool{}
   160  	wrNamespace[ccID] = true
   161  	if respPayload.Events != nil {
   162  		ccEvent := &peer.ChaincodeEvent{}
   163  		if err = proto.Unmarshal(respPayload.Events, ccEvent); err != nil {
   164  			return peer.TxValidationCode_INVALID_OTHER_REASON, errors.Wrapf(err, "invalid chaincode event")
   165  		}
   166  		if ccEvent.ChaincodeId != ccID {
   167  			return peer.TxValidationCode_INVALID_OTHER_REASON, errors.Errorf("chaincode event chaincode id does not match chaincode action chaincode id")
   168  		}
   169  	}
   170  
   171  	namespaces := make(map[string]struct{})
   172  	for _, ns := range txRWSet.NsRwSets {
   173  		// check to make sure there is no duplicate namespace in txRWSet
   174  		if _, ok := namespaces[ns.NameSpace]; ok {
   175  			logger.Errorf("duplicate namespace '%s' in txRWSet", ns.NameSpace)
   176  			return peer.TxValidationCode_ILLEGAL_WRITESET,
   177  				errors.Errorf("duplicate namespace '%s' in txRWSet", ns.NameSpace)
   178  		}
   179  		namespaces[ns.NameSpace] = struct{}{}
   180  
   181  		if v.txWritesToNamespace(ns) {
   182  			wrNamespace[ns.NameSpace] = true
   183  		}
   184  	}
   185  
   186  	// we've gathered all the info required to proceed to validation;
   187  	// validation will behave differently depending on the chaincode
   188  
   189  	// validate *EACH* read write set according to its chaincode's endorsement policy
   190  	for ns := range wrNamespace {
   191  		// Get latest chaincode validation plugin name and policy
   192  		validationPlugin, args, err := v.GetInfoForValidate(chdr, ns)
   193  		if err != nil {
   194  			logger.Errorf("GetInfoForValidate for txId = %s returned error: %+v", chdr.TxId, err)
   195  			return peer.TxValidationCode_INVALID_CHAINCODE, err
   196  		}
   197  
   198  		// invoke the plugin
   199  		ctx := &Context{
   200  			Seq:        seq,
   201  			Envelope:   envBytes,
   202  			Block:      block,
   203  			TxID:       chdr.TxId,
   204  			Channel:    chdr.ChannelId,
   205  			Namespace:  ns,
   206  			Policy:     args,
   207  			PluginName: validationPlugin,
   208  		}
   209  		if err = v.invokeValidationPlugin(ctx); err != nil {
   210  			switch err.(type) {
   211  			case *commonerrors.VSCCEndorsementPolicyError:
   212  				return peer.TxValidationCode_ENDORSEMENT_POLICY_FAILURE, err
   213  			default:
   214  				return peer.TxValidationCode_INVALID_OTHER_REASON, err
   215  			}
   216  		}
   217  	}
   218  
   219  	logger.Debugf("[%s] Dispatch completes env bytes %p", chainID, envBytes)
   220  	return peer.TxValidationCode_VALID, nil
   221  }
   222  
   223  func (v *dispatcherImpl) invokeValidationPlugin(ctx *Context) error {
   224  	logger.Debug("Validating", ctx, "with plugin")
   225  	err := v.pluginValidator.ValidateWithPlugin(ctx)
   226  	if err == nil {
   227  		return nil
   228  	}
   229  	// If the error is a pluggable validation execution error, cast it to the common errors ExecutionFailureError.
   230  	if e, isExecutionError := err.(*validation.ExecutionFailureError); isExecutionError {
   231  		return &commonerrors.VSCCExecutionFailureError{Err: e}
   232  	}
   233  	// Else, treat it as an endorsement error.
   234  	return &commonerrors.VSCCEndorsementPolicyError{Err: err}
   235  }
   236  
   237  func (v *dispatcherImpl) getCDataForCC(channelID, ccid string) (string, []byte, error) {
   238  	qe, err := v.ler.NewQueryExecutor()
   239  	if err != nil {
   240  		return "", nil, errors.WithMessage(err, "could not retrieve QueryExecutor")
   241  	}
   242  	defer qe.Done()
   243  
   244  	plugin, args, unexpectedErr, validationErr := v.lcr.ValidationInfo(channelID, ccid, qe)
   245  	if unexpectedErr != nil {
   246  		return "", nil, &commonerrors.VSCCInfoLookupFailureError{
   247  			Reason: fmt.Sprintf("Could not retrieve state for chaincode %s, error %s", ccid, unexpectedErr),
   248  		}
   249  	}
   250  	if validationErr != nil {
   251  		return "", nil, validationErr
   252  	}
   253  
   254  	if plugin == "" {
   255  		return "", nil, errors.Errorf("chaincode definition for [%s] is invalid, plugin field must be set", ccid)
   256  	}
   257  
   258  	if len(args) == 0 {
   259  		return "", nil, errors.Errorf("chaincode definition for [%s] is invalid, policy field must be set", ccid)
   260  	}
   261  
   262  	return plugin, args, nil
   263  }
   264  
   265  // GetInfoForValidate gets the ChaincodeInstance(with latest version) of tx, validation plugin and policy
   266  func (v *dispatcherImpl) GetInfoForValidate(chdr *common.ChannelHeader, ccID string) (string, []byte, error) {
   267  	// obtain name of the validation plugin and the policy
   268  	plugin, args, err := v.getCDataForCC(chdr.ChannelId, ccID)
   269  	if err != nil {
   270  		msg := fmt.Sprintf("Unable to get chaincode data from ledger for txid %s, due to %s", chdr.TxId, err)
   271  		logger.Errorf(msg)
   272  		return "", nil, err
   273  	}
   274  	return plugin, args, nil
   275  }
   276  
   277  // txWritesToNamespace returns true if the supplied NsRwSet
   278  // performs a ledger write
   279  func (v *dispatcherImpl) txWritesToNamespace(ns *rwsetutil.NsRwSet) bool {
   280  	// check for public writes first
   281  	if ns.KvRwSet != nil && len(ns.KvRwSet.Writes) > 0 {
   282  		return true
   283  	}
   284  
   285  	// check for private writes for all collections
   286  	for _, c := range ns.CollHashedRwSets {
   287  		if c.HashedRwSet != nil && len(c.HashedRwSet.HashedWrites) > 0 {
   288  			return true
   289  		}
   290  
   291  		// private metadata updates
   292  		if c.HashedRwSet != nil && len(c.HashedRwSet.MetadataWrites) > 0 {
   293  			return true
   294  		}
   295  	}
   296  
   297  	if ns.KvRwSet != nil && len(ns.KvRwSet.MetadataWrites) > 0 {
   298  		return true
   299  	}
   300  
   301  	return false
   302  }