github.com/ewagmig/fabric@v2.1.1+incompatible/core/ledger/kvledger/txmgmt/validator/statebasedval/state_based_validator.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package statebasedval
     8  
     9  import (
    10  	"github.com/hyperledger/fabric-protos-go/ledger/rwset/kvrwset"
    11  	"github.com/hyperledger/fabric-protos-go/peer"
    12  	"github.com/hyperledger/fabric/common/flogging"
    13  	"github.com/hyperledger/fabric/core/ledger"
    14  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/privacyenabledstate"
    15  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil"
    16  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb"
    17  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/validator/internal"
    18  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
    19  )
    20  
    21  var logger = flogging.MustGetLogger("statebasedval")
    22  
    23  // Validator validates a tx against the latest committed state
    24  // and preceding valid transactions with in the same block
    25  type Validator struct {
    26  	DB     privacyenabledstate.DB
    27  	Hasher ledger.Hasher
    28  }
    29  
    30  // preLoadCommittedVersionOfRSet loads committed version of all keys in each
    31  // transaction's read set into a cache.
    32  func (v *Validator) preLoadCommittedVersionOfRSet(block *internal.Block) error {
    33  
    34  	// Collect both public and hashed keys in read sets of all transactions in a given block
    35  	var pubKeys []*statedb.CompositeKey
    36  	var hashedKeys []*privacyenabledstate.HashedCompositeKey
    37  
    38  	// pubKeysMap and hashedKeysMap are used to avoid duplicate entries in the
    39  	// pubKeys and hashedKeys. Though map alone can be used to collect keys in
    40  	// read sets and pass as an argument in LoadCommittedVersionOfPubAndHashedKeys(),
    41  	// array is used for better code readability. On the negative side, this approach
    42  	// might use some extra memory.
    43  	pubKeysMap := make(map[statedb.CompositeKey]interface{})
    44  	hashedKeysMap := make(map[privacyenabledstate.HashedCompositeKey]interface{})
    45  
    46  	for _, tx := range block.Txs {
    47  		for _, nsRWSet := range tx.RWSet.NsRwSets {
    48  			for _, kvRead := range nsRWSet.KvRwSet.Reads {
    49  				compositeKey := statedb.CompositeKey{
    50  					Namespace: nsRWSet.NameSpace,
    51  					Key:       kvRead.Key,
    52  				}
    53  				if _, ok := pubKeysMap[compositeKey]; !ok {
    54  					pubKeysMap[compositeKey] = nil
    55  					pubKeys = append(pubKeys, &compositeKey)
    56  				}
    57  
    58  			}
    59  			for _, colHashedRwSet := range nsRWSet.CollHashedRwSets {
    60  				for _, kvHashedRead := range colHashedRwSet.HashedRwSet.HashedReads {
    61  					hashedCompositeKey := privacyenabledstate.HashedCompositeKey{
    62  						Namespace:      nsRWSet.NameSpace,
    63  						CollectionName: colHashedRwSet.CollectionName,
    64  						KeyHash:        string(kvHashedRead.KeyHash),
    65  					}
    66  					if _, ok := hashedKeysMap[hashedCompositeKey]; !ok {
    67  						hashedKeysMap[hashedCompositeKey] = nil
    68  						hashedKeys = append(hashedKeys, &hashedCompositeKey)
    69  					}
    70  				}
    71  			}
    72  		}
    73  	}
    74  
    75  	// Load committed version of all keys into a cache
    76  	if len(pubKeys) > 0 || len(hashedKeys) > 0 {
    77  		err := v.DB.LoadCommittedVersionsOfPubAndHashedKeys(pubKeys, hashedKeys)
    78  		if err != nil {
    79  			return err
    80  		}
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  // ValidateAndPrepareBatch implements method in Validator interface
    87  func (v *Validator) ValidateAndPrepareBatch(block *internal.Block, doMVCCValidation bool) (*internal.PubAndHashUpdates, error) {
    88  	// Check whether statedb implements BulkOptimizable interface. For now,
    89  	// only CouchDB implements BulkOptimizable to reduce the number of REST
    90  	// API calls from peer to CouchDB instance.
    91  	if v.DB.IsBulkOptimizable() {
    92  		err := v.preLoadCommittedVersionOfRSet(block)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  	}
    97  
    98  	updates := internal.NewPubAndHashUpdates()
    99  	for _, tx := range block.Txs {
   100  		var validationCode peer.TxValidationCode
   101  		var err error
   102  		if validationCode, err = v.validateEndorserTX(tx.RWSet, doMVCCValidation, updates); err != nil {
   103  			return nil, err
   104  		}
   105  
   106  		tx.ValidationCode = validationCode
   107  		if validationCode == peer.TxValidationCode_VALID {
   108  			logger.Debugf("Block [%d] Transaction index [%d] TxId [%s] marked as valid by state validator. ContainsPostOrderWrites [%t]", block.Num, tx.IndexInBlock, tx.ID, tx.ContainsPostOrderWrites)
   109  			committingTxHeight := version.NewHeight(block.Num, uint64(tx.IndexInBlock))
   110  			updates.ApplyWriteSet(tx.RWSet, committingTxHeight, v.DB, tx.ContainsPostOrderWrites)
   111  		} else {
   112  			logger.Warningf("Block [%d] Transaction index [%d] TxId [%s] marked as invalid by state validator. Reason code [%s]",
   113  				block.Num, tx.IndexInBlock, tx.ID, validationCode.String())
   114  		}
   115  	}
   116  	return updates, nil
   117  }
   118  
   119  // validateEndorserTX validates endorser transaction
   120  func (v *Validator) validateEndorserTX(
   121  	txRWSet *rwsetutil.TxRwSet,
   122  	doMVCCValidation bool,
   123  	updates *internal.PubAndHashUpdates) (peer.TxValidationCode, error) {
   124  
   125  	var validationCode = peer.TxValidationCode_VALID
   126  	var err error
   127  	//mvcc validation, may invalidate transaction
   128  	if doMVCCValidation {
   129  		validationCode, err = v.validateTx(txRWSet, updates)
   130  	}
   131  	return validationCode, err
   132  }
   133  
   134  func (v *Validator) validateTx(txRWSet *rwsetutil.TxRwSet, updates *internal.PubAndHashUpdates) (peer.TxValidationCode, error) {
   135  	// Uncomment the following only for local debugging. Don't want to print data in the logs in production
   136  	//logger.Debugf("validateTx - validating txRWSet: %s", spew.Sdump(txRWSet))
   137  	for _, nsRWSet := range txRWSet.NsRwSets {
   138  		ns := nsRWSet.NameSpace
   139  		// Validate public reads
   140  		if valid, err := v.validateReadSet(ns, nsRWSet.KvRwSet.Reads, updates.PubUpdates); !valid || err != nil {
   141  			if err != nil {
   142  				return peer.TxValidationCode(-1), err
   143  			}
   144  			return peer.TxValidationCode_MVCC_READ_CONFLICT, nil
   145  		}
   146  		// Validate range queries for phantom items
   147  		if valid, err := v.validateRangeQueries(ns, nsRWSet.KvRwSet.RangeQueriesInfo, updates.PubUpdates); !valid || err != nil {
   148  			if err != nil {
   149  				return peer.TxValidationCode(-1), err
   150  			}
   151  			return peer.TxValidationCode_PHANTOM_READ_CONFLICT, nil
   152  		}
   153  		// Validate hashes for private reads
   154  		if valid, err := v.validateNsHashedReadSets(ns, nsRWSet.CollHashedRwSets, updates.HashUpdates); !valid || err != nil {
   155  			if err != nil {
   156  				return peer.TxValidationCode(-1), err
   157  			}
   158  			return peer.TxValidationCode_MVCC_READ_CONFLICT, nil
   159  		}
   160  	}
   161  	return peer.TxValidationCode_VALID, nil
   162  }
   163  
   164  ////////////////////////////////////////////////////////////////////////////////
   165  /////                 Validation of public read-set
   166  ////////////////////////////////////////////////////////////////////////////////
   167  func (v *Validator) validateReadSet(ns string, kvReads []*kvrwset.KVRead, updates *privacyenabledstate.PubUpdateBatch) (bool, error) {
   168  	for _, kvRead := range kvReads {
   169  		if valid, err := v.validateKVRead(ns, kvRead, updates); !valid || err != nil {
   170  			return valid, err
   171  		}
   172  	}
   173  	return true, nil
   174  }
   175  
   176  // validateKVRead performs mvcc check for a key read during transaction simulation.
   177  // i.e., it checks whether a key/version combination is already updated in the statedb (by an already committed block)
   178  // or in the updates (by a preceding valid transaction in the current block)
   179  func (v *Validator) validateKVRead(ns string, kvRead *kvrwset.KVRead, updates *privacyenabledstate.PubUpdateBatch) (bool, error) {
   180  	if updates.Exists(ns, kvRead.Key) {
   181  		return false, nil
   182  	}
   183  	committedVersion, err := v.DB.GetVersion(ns, kvRead.Key)
   184  	if err != nil {
   185  		return false, err
   186  	}
   187  
   188  	logger.Debugf("Comparing versions for key [%s]: committed version=%#v and read version=%#v",
   189  		kvRead.Key, committedVersion, rwsetutil.NewVersion(kvRead.Version))
   190  	if !version.AreSame(committedVersion, rwsetutil.NewVersion(kvRead.Version)) {
   191  		logger.Debugf("Version mismatch for key [%s:%s]. Committed version = [%#v], Version in readSet [%#v]",
   192  			ns, kvRead.Key, committedVersion, kvRead.Version)
   193  		return false, nil
   194  	}
   195  	return true, nil
   196  }
   197  
   198  ////////////////////////////////////////////////////////////////////////////////
   199  /////                 Validation of range queries
   200  ////////////////////////////////////////////////////////////////////////////////
   201  func (v *Validator) validateRangeQueries(ns string, rangeQueriesInfo []*kvrwset.RangeQueryInfo, updates *privacyenabledstate.PubUpdateBatch) (bool, error) {
   202  	for _, rqi := range rangeQueriesInfo {
   203  		if valid, err := v.validateRangeQuery(ns, rqi, updates); !valid || err != nil {
   204  			return valid, err
   205  		}
   206  	}
   207  	return true, nil
   208  }
   209  
   210  // validateRangeQuery performs a phantom read check i.e., it
   211  // checks whether the results of the range query are still the same when executed on the
   212  // statedb (latest state as of last committed block) + updates (prepared by the writes of preceding valid transactions
   213  // in the current block and yet to be committed as part of group commit at the end of the validation of the block)
   214  func (v *Validator) validateRangeQuery(ns string, rangeQueryInfo *kvrwset.RangeQueryInfo, updates *privacyenabledstate.PubUpdateBatch) (bool, error) {
   215  	logger.Debugf("validateRangeQuery: ns=%s, rangeQueryInfo=%s", ns, rangeQueryInfo)
   216  
   217  	// If during simulation, the caller had not exhausted the iterator so
   218  	// rangeQueryInfo.EndKey is not actual endKey given by the caller in the range query
   219  	// but rather it is the last key seen by the caller and hence the combinedItr should include the endKey in the results.
   220  	includeEndKey := !rangeQueryInfo.ItrExhausted
   221  
   222  	combinedItr, err := newCombinedIterator(v.DB, updates.UpdateBatch,
   223  		ns, rangeQueryInfo.StartKey, rangeQueryInfo.EndKey, includeEndKey)
   224  	if err != nil {
   225  		return false, err
   226  	}
   227  	defer combinedItr.Close()
   228  	var validator rangeQueryValidator
   229  	if rangeQueryInfo.GetReadsMerkleHashes() != nil {
   230  		logger.Debug(`Hashing results are present in the range query info hence, initiating hashing based validation`)
   231  		validator = &rangeQueryHashValidator{hasher: v.Hasher}
   232  	} else {
   233  		logger.Debug(`Hashing results are not present in the range query info hence, initiating raw KVReads based validation`)
   234  		validator = &rangeQueryResultsValidator{}
   235  	}
   236  	validator.init(rangeQueryInfo, combinedItr)
   237  	return validator.validate()
   238  }
   239  
   240  ////////////////////////////////////////////////////////////////////////////////
   241  /////                 Validation of hashed read-set
   242  ////////////////////////////////////////////////////////////////////////////////
   243  func (v *Validator) validateNsHashedReadSets(ns string, collHashedRWSets []*rwsetutil.CollHashedRwSet,
   244  	updates *privacyenabledstate.HashedUpdateBatch) (bool, error) {
   245  	for _, collHashedRWSet := range collHashedRWSets {
   246  		if valid, err := v.validateCollHashedReadSet(ns, collHashedRWSet.CollectionName, collHashedRWSet.HashedRwSet.HashedReads, updates); !valid || err != nil {
   247  			return valid, err
   248  		}
   249  	}
   250  	return true, nil
   251  }
   252  
   253  func (v *Validator) validateCollHashedReadSet(ns, coll string, kvReadHashes []*kvrwset.KVReadHash,
   254  	updates *privacyenabledstate.HashedUpdateBatch) (bool, error) {
   255  	for _, kvReadHash := range kvReadHashes {
   256  		if valid, err := v.validateKVReadHash(ns, coll, kvReadHash, updates); !valid || err != nil {
   257  			return valid, err
   258  		}
   259  	}
   260  	return true, nil
   261  }
   262  
   263  // validateKVReadHash performs mvcc check for a hash of a key that is present in the private data space
   264  // i.e., it checks whether a key/version combination is already updated in the statedb (by an already committed block)
   265  // or in the updates (by a preceding valid transaction in the current block)
   266  func (v *Validator) validateKVReadHash(ns, coll string, kvReadHash *kvrwset.KVReadHash,
   267  	updates *privacyenabledstate.HashedUpdateBatch) (bool, error) {
   268  	if updates.Contains(ns, coll, kvReadHash.KeyHash) {
   269  		return false, nil
   270  	}
   271  	committedVersion, err := v.DB.GetKeyHashVersion(ns, coll, kvReadHash.KeyHash)
   272  	if err != nil {
   273  		return false, err
   274  	}
   275  
   276  	if !version.AreSame(committedVersion, rwsetutil.NewVersion(kvReadHash.Version)) {
   277  		logger.Debugf("Version mismatch for key hash [%s:%s:%#v]. Committed version = [%s], Version in hashedReadSet [%s]",
   278  			ns, coll, kvReadHash.KeyHash, committedVersion, kvReadHash.Version)
   279  		return false, nil
   280  	}
   281  	return true, nil
   282  }