github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/core/ledger/kvledger/txmgmt/validation/validator.go (about)

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