github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/kvledger/txmgmt/validation/validator.go (about)

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