github.com/ewagmig/fabric@v2.1.1+incompatible/core/committer/txvalidator/v14/vscc_validator.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package txvalidator
     8  
     9  import (
    10  	"fmt"
    11  
    12  	"github.com/golang/protobuf/proto"
    13  	"github.com/hyperledger/fabric-protos-go/common"
    14  	"github.com/hyperledger/fabric-protos-go/peer"
    15  	commonerrors "github.com/hyperledger/fabric/common/errors"
    16  	"github.com/hyperledger/fabric/common/policydsl"
    17  	"github.com/hyperledger/fabric/core/common/ccprovider"
    18  	"github.com/hyperledger/fabric/core/common/sysccprovider"
    19  	validation "github.com/hyperledger/fabric/core/handlers/validation/api"
    20  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil"
    21  	"github.com/hyperledger/fabric/protoutil"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  // VsccValidatorImpl is the implementation used to call
    26  // the vscc chaincode and validate block transactions
    27  type VsccValidatorImpl struct {
    28  	channelID       string
    29  	cr              ChannelResources
    30  	pluginValidator *PluginValidator
    31  }
    32  
    33  // newVSCCValidator creates new vscc validator
    34  func newVSCCValidator(channelID string, cr ChannelResources, pluginValidator *PluginValidator) *VsccValidatorImpl {
    35  	return &VsccValidatorImpl{
    36  		channelID:       channelID,
    37  		cr:              cr,
    38  		pluginValidator: pluginValidator,
    39  	}
    40  }
    41  
    42  func getChaincodeHeaderExtension(hdr *common.Header) (*peer.ChaincodeHeaderExtension, error) {
    43  	chdr, err := protoutil.UnmarshalChannelHeader(hdr.ChannelHeader)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	chaincodeHdrExt := &peer.ChaincodeHeaderExtension{}
    49  	err = proto.Unmarshal(chdr.Extension, chaincodeHdrExt)
    50  	return chaincodeHdrExt, errors.Wrap(err, "error unmarshaling ChaincodeHeaderExtension")
    51  }
    52  
    53  // VSCCValidateTx executes vscc validation for transaction
    54  func (v *VsccValidatorImpl) VSCCValidateTx(seq int, payload *common.Payload, envBytes []byte, block *common.Block) (error, peer.TxValidationCode) {
    55  	chainID := v.channelID
    56  	logger.Debugf("[%s] VSCCValidateTx starts for bytes %p", chainID, envBytes)
    57  
    58  	// get header extensions so we have the chaincode ID
    59  	hdrExt, err := getChaincodeHeaderExtension(payload.Header)
    60  	if err != nil {
    61  		return err, peer.TxValidationCode_BAD_HEADER_EXTENSION
    62  	}
    63  
    64  	// get channel header
    65  	chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader)
    66  	if err != nil {
    67  		return err, peer.TxValidationCode_BAD_CHANNEL_HEADER
    68  	}
    69  
    70  	/* obtain the list of namespaces we're writing stuff to;
    71  	   at first, we establish a few facts about this invocation:
    72  	   1) which namespaces does it write to?
    73  	   2) does it write to LSCC's namespace?
    74  	   3) does it write to any cc that cannot be invoked? */
    75  	writesToLSCC := false
    76  	writesToNonInvokableSCC := false
    77  	respPayload, err := protoutil.GetActionFromEnvelope(envBytes)
    78  	if err != nil {
    79  		return errors.WithMessage(err, "GetActionFromEnvelope failed"), peer.TxValidationCode_BAD_RESPONSE_PAYLOAD
    80  	}
    81  	txRWSet := &rwsetutil.TxRwSet{}
    82  	if err = txRWSet.FromProtoBytes(respPayload.Results); err != nil {
    83  		return errors.WithMessage(err, "txRWSet.FromProtoBytes failed"), peer.TxValidationCode_BAD_RWSET
    84  	}
    85  
    86  	// Verify the header extension and response payload contain the ChaincodeId
    87  	if hdrExt.ChaincodeId == nil {
    88  		return errors.New("nil ChaincodeId in header extension"), peer.TxValidationCode_INVALID_OTHER_REASON
    89  	}
    90  
    91  	if respPayload.ChaincodeId == nil {
    92  		return errors.New("nil ChaincodeId in ChaincodeAction"), peer.TxValidationCode_INVALID_OTHER_REASON
    93  	}
    94  
    95  	// get name and version of the cc we invoked
    96  	ccID := hdrExt.ChaincodeId.Name
    97  	ccVer := respPayload.ChaincodeId.Version
    98  
    99  	// sanity check on ccID
   100  	if ccID == "" {
   101  		err = errors.New("invalid chaincode ID")
   102  		logger.Errorf("%+v", err)
   103  		return err, peer.TxValidationCode_INVALID_OTHER_REASON
   104  	}
   105  	if ccID != respPayload.ChaincodeId.Name {
   106  		err = errors.Errorf("inconsistent ccid info (%s/%s)", ccID, respPayload.ChaincodeId.Name)
   107  		logger.Errorf("%+v", err)
   108  		return err, peer.TxValidationCode_INVALID_OTHER_REASON
   109  	}
   110  	// sanity check on ccver
   111  	if ccVer == "" {
   112  		err = errors.New("invalid chaincode version")
   113  		logger.Errorf("%+v", err)
   114  		return err, peer.TxValidationCode_INVALID_OTHER_REASON
   115  	}
   116  
   117  	var wrNamespace []string
   118  	alwaysEnforceOriginalNamespace := v.cr.Capabilities().V1_2Validation()
   119  	if alwaysEnforceOriginalNamespace {
   120  		wrNamespace = append(wrNamespace, ccID)
   121  		if respPayload.Events != nil {
   122  			ccEvent := &peer.ChaincodeEvent{}
   123  			if err = proto.Unmarshal(respPayload.Events, ccEvent); err != nil {
   124  				return errors.Wrapf(err, "invalid chaincode event"), peer.TxValidationCode_INVALID_OTHER_REASON
   125  			}
   126  			if ccEvent.ChaincodeId != ccID {
   127  				return errors.Errorf("chaincode event chaincode id does not match chaincode action chaincode id"), peer.TxValidationCode_INVALID_OTHER_REASON
   128  			}
   129  		}
   130  	}
   131  
   132  	namespaces := make(map[string]struct{})
   133  	for _, ns := range txRWSet.NsRwSets {
   134  		// check to make sure there is no duplicate namespace in txRWSet
   135  		if _, ok := namespaces[ns.NameSpace]; ok {
   136  			return errors.Errorf("duplicate namespace '%s' in txRWSet", ns.NameSpace),
   137  				peer.TxValidationCode_ILLEGAL_WRITESET
   138  		}
   139  		namespaces[ns.NameSpace] = struct{}{}
   140  
   141  		if !v.txWritesToNamespace(ns) {
   142  			continue
   143  		}
   144  
   145  		// Check to make sure we did not already populate this chaincode
   146  		// name to avoid checking the same namespace twice
   147  		if ns.NameSpace != ccID || !alwaysEnforceOriginalNamespace {
   148  			wrNamespace = append(wrNamespace, ns.NameSpace)
   149  		}
   150  
   151  		if !writesToLSCC && ns.NameSpace == "lscc" {
   152  			writesToLSCC = true
   153  		}
   154  
   155  		if !writesToNonInvokableSCC && IsSysCCAndNotInvokableCC2CC(ns.NameSpace) {
   156  			writesToNonInvokableSCC = true
   157  		}
   158  
   159  		if !writesToNonInvokableSCC && IsSysCCAndNotInvokableExternal(ns.NameSpace) {
   160  			writesToNonInvokableSCC = true
   161  		}
   162  	}
   163  
   164  	// we've gathered all the info required to proceed to validation;
   165  	// validation will behave differently depending on the type of
   166  	// chaincode (system vs. application)
   167  
   168  	if !IsSysCC(ccID) {
   169  		// if we're here, we know this is an invocation of an application chaincode;
   170  		// first of all, we make sure that:
   171  		// 1) we don't write to LSCC - an application chaincode is free to invoke LSCC
   172  		//    for instance to get information about itself or another chaincode; however
   173  		//    these legitimate invocations only ready from LSCC's namespace; currently
   174  		//    only two functions of LSCC write to its namespace: deploy and upgrade and
   175  		//    neither should be used by an application chaincode
   176  		if writesToLSCC {
   177  			return errors.Errorf("chaincode %s attempted to write to the namespace of LSCC", ccID),
   178  				peer.TxValidationCode_ILLEGAL_WRITESET
   179  		}
   180  		// 2) we don't write to the namespace of a chaincode that we cannot invoke - if
   181  		//    the chaincode cannot be invoked in the first place, there's no legitimate
   182  		//    way in which a transaction has a write set that writes to it; additionally
   183  		//    we don't have any means of verifying whether the transaction had the rights
   184  		//    to perform that write operation because in v1, system chaincodes do not have
   185  		//    any endorsement policies to speak of. So if the chaincode can't be invoked
   186  		//    it can't be written to by an invocation of an application chaincode
   187  		if writesToNonInvokableSCC {
   188  			return errors.Errorf("chaincode %s attempted to write to the namespace of a system chaincode that cannot be invoked", ccID),
   189  				peer.TxValidationCode_ILLEGAL_WRITESET
   190  		}
   191  
   192  		// validate *EACH* read write set according to its chaincode's endorsement policy
   193  		for _, ns := range wrNamespace {
   194  			// Get latest chaincode version, vscc and validate policy
   195  			txcc, vscc, policy, err := v.GetInfoForValidate(chdr, ns)
   196  			if err != nil {
   197  				logger.Errorf("GetInfoForValidate for txId = %s returned error: %+v", chdr.TxId, err)
   198  				return err, peer.TxValidationCode_INVALID_OTHER_REASON
   199  			}
   200  
   201  			// if the namespace corresponds to the cc that was originally
   202  			// invoked, we check that the version of the cc that was
   203  			// invoked corresponds to the version that lscc has returned
   204  			if ns == ccID && txcc.ChaincodeVersion != ccVer {
   205  				err = errors.Errorf("chaincode %s:%s/%s didn't match %s:%s/%s in lscc", ccID, ccVer, chdr.ChannelId, txcc.ChaincodeName, txcc.ChaincodeVersion, chdr.ChannelId)
   206  				logger.Errorf("%+v", err)
   207  				return err, peer.TxValidationCode_EXPIRED_CHAINCODE
   208  			}
   209  
   210  			// do VSCC validation
   211  			ctx := &Context{
   212  				Seq:       seq,
   213  				Envelope:  envBytes,
   214  				Block:     block,
   215  				TxID:      chdr.TxId,
   216  				Channel:   chdr.ChannelId,
   217  				Namespace: ns,
   218  				Policy:    policy,
   219  				VSCCName:  vscc.ChaincodeName,
   220  			}
   221  			if err = v.VSCCValidateTxForCC(ctx); err != nil {
   222  				switch err.(type) {
   223  				case *commonerrors.VSCCEndorsementPolicyError:
   224  					return err, peer.TxValidationCode_ENDORSEMENT_POLICY_FAILURE
   225  				default:
   226  					return err, peer.TxValidationCode_INVALID_OTHER_REASON
   227  				}
   228  			}
   229  		}
   230  	} else {
   231  		// make sure that we can invoke this system chaincode - if the chaincode
   232  		// cannot be invoked through a proposal to this peer, we have to drop the
   233  		// transaction; if we didn't, we wouldn't know how to decide whether it's
   234  		// valid or not because in v1, system chaincodes have no endorsement policy
   235  		if IsSysCCAndNotInvokableExternal(ccID) {
   236  			return errors.Errorf("committing an invocation of cc %s is illegal", ccID),
   237  				peer.TxValidationCode_ILLEGAL_WRITESET
   238  		}
   239  
   240  		// Get latest chaincode version, vscc and validate policy
   241  		_, vscc, policy, err := v.GetInfoForValidate(chdr, ccID)
   242  		if err != nil {
   243  			logger.Errorf("GetInfoForValidate for txId = %s returned error: %+v", chdr.TxId, err)
   244  			return err, peer.TxValidationCode_INVALID_OTHER_REASON
   245  		}
   246  
   247  		// validate the transaction as an invocation of this system chaincode;
   248  		// vscc will have to do custom validation for this system chaincode
   249  		// currently, VSCC does custom validation for LSCC only; if an hlf
   250  		// user creates a new system chaincode which is invokable from the outside
   251  		// they have to modify VSCC to provide appropriate validation
   252  		ctx := &Context{
   253  			Seq:       seq,
   254  			Envelope:  envBytes,
   255  			Block:     block,
   256  			TxID:      chdr.TxId,
   257  			Channel:   chdr.ChannelId,
   258  			Namespace: ccID,
   259  			Policy:    policy,
   260  			VSCCName:  vscc.ChaincodeName,
   261  		}
   262  		if err = v.VSCCValidateTxForCC(ctx); err != nil {
   263  			switch err.(type) {
   264  			case *commonerrors.VSCCEndorsementPolicyError:
   265  				return err, peer.TxValidationCode_ENDORSEMENT_POLICY_FAILURE
   266  			default:
   267  				return err, peer.TxValidationCode_INVALID_OTHER_REASON
   268  			}
   269  		}
   270  	}
   271  	logger.Debugf("[%s] VSCCValidateTx completes env bytes %p", chainID, envBytes)
   272  	return nil, peer.TxValidationCode_VALID
   273  }
   274  
   275  func (v *VsccValidatorImpl) VSCCValidateTxForCC(ctx *Context) error {
   276  	logger.Debug("Validating", ctx, "with plugin")
   277  	err := v.pluginValidator.ValidateWithPlugin(ctx)
   278  	if err == nil {
   279  		return nil
   280  	}
   281  	// If the error is a pluggable validation execution error, cast it to the common errors ExecutionFailureError.
   282  	if e, isExecutionError := err.(*validation.ExecutionFailureError); isExecutionError {
   283  		return &commonerrors.VSCCExecutionFailureError{Err: e}
   284  	}
   285  	// Else, treat it as an endorsement error.
   286  	return &commonerrors.VSCCEndorsementPolicyError{Err: err}
   287  }
   288  
   289  func (v *VsccValidatorImpl) getCDataForCC(chid, ccid string) (*ccprovider.ChaincodeData, error) {
   290  	l := v.cr.Ledger()
   291  	if l == nil {
   292  		return nil, errors.New("nil ledger instance")
   293  	}
   294  
   295  	qe, err := l.NewQueryExecutor()
   296  	if err != nil {
   297  		return nil, errors.WithMessage(err, "could not retrieve QueryExecutor")
   298  	}
   299  	defer qe.Done()
   300  
   301  	bytes, err := qe.GetState("lscc", ccid)
   302  	if err != nil {
   303  		return nil, &commonerrors.VSCCInfoLookupFailureError{
   304  			Reason: fmt.Sprintf("Could not retrieve state for chaincode %s, error %s", ccid, err),
   305  		}
   306  	}
   307  
   308  	if bytes == nil {
   309  		return nil, errors.Errorf("lscc's state for [%s] not found.", ccid)
   310  	}
   311  
   312  	cd := &ccprovider.ChaincodeData{}
   313  	err = proto.Unmarshal(bytes, cd)
   314  	if err != nil {
   315  		return nil, errors.Wrap(err, "unmarshalling ChaincodeQueryResponse failed")
   316  	}
   317  
   318  	if cd.Vscc == "" {
   319  		return nil, errors.Errorf("lscc's state for [%s] is invalid, vscc field must be set", ccid)
   320  	}
   321  
   322  	if len(cd.Policy) == 0 {
   323  		return nil, errors.Errorf("lscc's state for [%s] is invalid, policy field must be set", ccid)
   324  	}
   325  
   326  	return cd, err
   327  }
   328  
   329  // GetInfoForValidate gets the ChaincodeInstance(with latest version) of tx, vscc and policy from lscc
   330  func (v *VsccValidatorImpl) GetInfoForValidate(chdr *common.ChannelHeader, ccID string) (*sysccprovider.ChaincodeInstance, *sysccprovider.ChaincodeInstance, []byte, error) {
   331  	cc := &sysccprovider.ChaincodeInstance{
   332  		ChannelID:     chdr.ChannelId,
   333  		ChaincodeName: ccID,
   334  	}
   335  	vscc := &sysccprovider.ChaincodeInstance{
   336  		ChannelID:     chdr.ChannelId,
   337  		ChaincodeName: "vscc", // default vscc for system chaincodes
   338  	}
   339  	var policy []byte
   340  	var err error
   341  	if !IsSysCC(ccID) {
   342  		// when we are validating a chaincode that is not a
   343  		// system CC, we need to ask the CC to give us the name
   344  		// of VSCC and of the policy that should be used
   345  
   346  		// obtain name of the VSCC and the policy
   347  		cd, err := v.getCDataForCC(chdr.ChannelId, ccID)
   348  		if err != nil {
   349  			msg := fmt.Sprintf("Unable to get chaincode data from ledger for txid %s, due to %s", chdr.TxId, err)
   350  			logger.Errorf(msg)
   351  			return nil, nil, nil, err
   352  		}
   353  		cc.ChaincodeName = cd.Name
   354  		cc.ChaincodeVersion = cd.Version
   355  		vscc.ChaincodeName, policy = cd.Vscc, cd.Policy
   356  	} else {
   357  		// when we are validating a system CC, we use the default
   358  		// VSCC and a default policy that requires one signature
   359  		// from any of the members of the channel
   360  		p := policydsl.SignedByAnyMember(v.cr.GetMSPIDs())
   361  		policy, err = protoutil.Marshal(p)
   362  		if err != nil {
   363  			return nil, nil, nil, err
   364  		}
   365  	}
   366  
   367  	return cc, vscc, policy, nil
   368  }
   369  
   370  // txWritesToNamespace returns true if the supplied NsRwSet
   371  // performs a ledger write
   372  func (v *VsccValidatorImpl) txWritesToNamespace(ns *rwsetutil.NsRwSet) bool {
   373  	// check for public writes first
   374  	if ns.KvRwSet != nil && len(ns.KvRwSet.Writes) > 0 {
   375  		return true
   376  	}
   377  
   378  	// only look at collection data if we support that capability
   379  	if v.cr.Capabilities().PrivateChannelData() {
   380  		// check for private writes for all collections
   381  		for _, c := range ns.CollHashedRwSets {
   382  			if c.HashedRwSet != nil && len(c.HashedRwSet.HashedWrites) > 0 {
   383  				return true
   384  			}
   385  
   386  			// only look at private metadata writes if we support that capability
   387  			if v.cr.Capabilities().KeyLevelEndorsement() {
   388  				// private metadata updates
   389  				if c.HashedRwSet != nil && len(c.HashedRwSet.MetadataWrites) > 0 {
   390  					return true
   391  				}
   392  			}
   393  		}
   394  	}
   395  
   396  	// only look at metadata writes if we support that capability
   397  	if v.cr.Capabilities().KeyLevelEndorsement() {
   398  		// public metadata updates
   399  		if ns.KvRwSet != nil && len(ns.KvRwSet.MetadataWrites) > 0 {
   400  			return true
   401  		}
   402  	}
   403  
   404  	return false
   405  }
   406  
   407  func IsSysCCAndNotInvokableExternal(name string) bool {
   408  	return name == "vscc" || name == "escc"
   409  }
   410  
   411  func IsSysCC(name string) bool {
   412  	return name == "vscc" || name == "escc" || name == "lscc" || name == "qscc" || name == "cscc"
   413  }
   414  
   415  func IsSysCCAndNotInvokableCC2CC(name string) bool {
   416  	return name == "vscc" || name == "escc" || name == "cscc"
   417  }