github.com/tenywen/fabric@v1.0.0-beta.0.20170620030522-a5b1ed380643/core/ledger/kvledger/txmgmt/validator/statebasedval/state_based_validator.go (about)

     1  /*
     2  Copyright IBM Corp. 2016 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  		 http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package statebasedval
    18  
    19  import (
    20  	"github.com/hyperledger/fabric/common/flogging"
    21  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil"
    22  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb"
    23  	"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
    24  	"github.com/hyperledger/fabric/core/ledger/util"
    25  	"github.com/hyperledger/fabric/protos/common"
    26  	"github.com/hyperledger/fabric/protos/ledger/rwset/kvrwset"
    27  	"github.com/hyperledger/fabric/protos/peer"
    28  	putils "github.com/hyperledger/fabric/protos/utils"
    29  )
    30  
    31  var logger = flogging.MustGetLogger("statevalidator")
    32  
    33  // Validator validates a tx against the latest committed state
    34  // and preceding valid transactions with in the same block
    35  type Validator struct {
    36  	db statedb.VersionedDB
    37  }
    38  
    39  // NewValidator constructs StateValidator
    40  func NewValidator(db statedb.VersionedDB) *Validator {
    41  	return &Validator{db}
    42  }
    43  
    44  //validate endorser transaction
    45  func (v *Validator) validateEndorserTX(envBytes []byte, doMVCCValidation bool, updates *statedb.UpdateBatch) (*rwsetutil.TxRwSet, peer.TxValidationCode, error) {
    46  	// extract actions from the envelope message
    47  	respPayload, err := putils.GetActionFromEnvelope(envBytes)
    48  	if err != nil {
    49  		return nil, peer.TxValidationCode_NIL_TXACTION, nil
    50  	}
    51  
    52  	//preparation for extracting RWSet from transaction
    53  	txRWSet := &rwsetutil.TxRwSet{}
    54  
    55  	// Get the Result from the Action
    56  	// and then Unmarshal it into a TxReadWriteSet using custom unmarshalling
    57  
    58  	if err = txRWSet.FromProtoBytes(respPayload.Results); err != nil {
    59  		return nil, peer.TxValidationCode_INVALID_OTHER_REASON, nil
    60  	}
    61  
    62  	txResult := peer.TxValidationCode_VALID
    63  
    64  	//mvccvalidation, may invalidate transaction
    65  	if doMVCCValidation {
    66  		if txResult, err = v.validateTx(txRWSet, updates); err != nil {
    67  			return nil, txResult, err
    68  		} else if txResult != peer.TxValidationCode_VALID {
    69  			txRWSet = nil
    70  		}
    71  	}
    72  
    73  	return txRWSet, txResult, err
    74  }
    75  
    76  // ValidateAndPrepareBatch implements method in Validator interface
    77  func (v *Validator) ValidateAndPrepareBatch(block *common.Block, doMVCCValidation bool) (*statedb.UpdateBatch, error) {
    78  	logger.Debugf("New block arrived for validation:%#v, doMVCCValidation=%t", block, doMVCCValidation)
    79  	updates := statedb.NewUpdateBatch()
    80  	logger.Debugf("Validating a block with [%d] transactions", len(block.Data.Data))
    81  
    82  	// Committer validator has already set validation flags based on well formed tran checks
    83  	txsFilter := util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])
    84  
    85  	// Precaution in case committer validator has not added validation flags yet
    86  	if len(txsFilter) == 0 {
    87  		txsFilter = util.NewTxValidationFlags(len(block.Data.Data))
    88  		block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsFilter
    89  	}
    90  
    91  	for txIndex, envBytes := range block.Data.Data {
    92  		if txsFilter.IsInvalid(txIndex) {
    93  			// Skiping invalid transaction
    94  			logger.Warningf("Block [%d] Transaction index [%d] marked as invalid by committer. Reason code [%d]",
    95  				block.Header.Number, txIndex, txsFilter.Flag(txIndex))
    96  			continue
    97  		}
    98  
    99  		env, err := putils.GetEnvelopeFromBlock(envBytes)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  
   104  		payload, err := putils.GetPayload(env)
   105  		if err != nil {
   106  			return nil, err
   107  		}
   108  
   109  		chdr, err := putils.UnmarshalChannelHeader(payload.Header.ChannelHeader)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  
   114  		txType := common.HeaderType(chdr.Type)
   115  
   116  		if txType != common.HeaderType_ENDORSER_TRANSACTION {
   117  			logger.Debugf("Skipping mvcc validation for Block [%d] Transaction index [%d] because, the transaction type is [%s]",
   118  				block.Header.Number, txIndex, txType)
   119  			continue
   120  		}
   121  
   122  		txRWSet, txResult, err := v.validateEndorserTX(envBytes, doMVCCValidation, updates)
   123  
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  
   128  		txsFilter.SetFlag(txIndex, txResult)
   129  
   130  		//txRWSet != nil => t is valid
   131  		if txRWSet != nil {
   132  			committingTxHeight := version.NewHeight(block.Header.Number, uint64(txIndex))
   133  			addWriteSetToBatch(txRWSet, committingTxHeight, updates)
   134  			txsFilter.SetFlag(txIndex, peer.TxValidationCode_VALID)
   135  		}
   136  
   137  		if txsFilter.IsValid(txIndex) {
   138  			logger.Debugf("Block [%d] Transaction index [%d] TxId [%s] marked as valid by state validator",
   139  				block.Header.Number, txIndex, chdr.TxId)
   140  		} else {
   141  			logger.Warningf("Block [%d] Transaction index [%d] TxId [%s] marked as invalid by state validator. Reason code [%d]",
   142  				block.Header.Number, txIndex, chdr.TxId, txsFilter.Flag(txIndex))
   143  		}
   144  	}
   145  	block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsFilter
   146  	return updates, nil
   147  }
   148  
   149  func addWriteSetToBatch(txRWSet *rwsetutil.TxRwSet, txHeight *version.Height, batch *statedb.UpdateBatch) {
   150  	for _, nsRWSet := range txRWSet.NsRwSets {
   151  		ns := nsRWSet.NameSpace
   152  		for _, kvWrite := range nsRWSet.KvRwSet.Writes {
   153  			if kvWrite.IsDelete {
   154  				batch.Delete(ns, kvWrite.Key, txHeight)
   155  			} else {
   156  				batch.Put(ns, kvWrite.Key, kvWrite.Value, txHeight)
   157  			}
   158  		}
   159  	}
   160  }
   161  
   162  func (v *Validator) validateTx(txRWSet *rwsetutil.TxRwSet, updates *statedb.UpdateBatch) (peer.TxValidationCode, error) {
   163  	for _, nsRWSet := range txRWSet.NsRwSets {
   164  		ns := nsRWSet.NameSpace
   165  
   166  		if valid, err := v.validateReadSet(ns, nsRWSet.KvRwSet.Reads, updates); !valid || err != nil {
   167  			if err != nil {
   168  				return peer.TxValidationCode(-1), err
   169  			}
   170  			return peer.TxValidationCode_MVCC_READ_CONFLICT, nil
   171  		}
   172  		if valid, err := v.validateRangeQueries(ns, nsRWSet.KvRwSet.RangeQueriesInfo, updates); !valid || err != nil {
   173  			if err != nil {
   174  				return peer.TxValidationCode(-1), err
   175  			}
   176  			return peer.TxValidationCode_PHANTOM_READ_CONFLICT, nil
   177  		}
   178  	}
   179  	return peer.TxValidationCode_VALID, nil
   180  }
   181  
   182  func (v *Validator) validateReadSet(ns string, kvReads []*kvrwset.KVRead, updates *statedb.UpdateBatch) (bool, error) {
   183  	for _, kvRead := range kvReads {
   184  		if valid, err := v.validateKVRead(ns, kvRead, updates); !valid || err != nil {
   185  			return valid, err
   186  		}
   187  	}
   188  	return true, nil
   189  }
   190  
   191  // validateKVRead performs mvcc check for a key read during transaction simulation.
   192  // i.e., it checks whether a key/version combination is already updated in the statedb (by an already committed block)
   193  // or in the updates (by a preceding valid transaction in the current block)
   194  func (v *Validator) validateKVRead(ns string, kvRead *kvrwset.KVRead, updates *statedb.UpdateBatch) (bool, error) {
   195  	if updates.Exists(ns, kvRead.Key) {
   196  		return false, nil
   197  	}
   198  	versionedValue, err := v.db.GetState(ns, kvRead.Key)
   199  	if err != nil {
   200  		return false, nil
   201  	}
   202  	var committedVersion *version.Height
   203  	if versionedValue != nil {
   204  		committedVersion = versionedValue.Version
   205  	}
   206  
   207  	if !version.AreSame(committedVersion, rwsetutil.NewVersion(kvRead.Version)) {
   208  		logger.Debugf("Version mismatch for key [%s:%s]. Committed version = [%s], Version in readSet [%s]",
   209  			ns, kvRead.Key, committedVersion, kvRead.Version)
   210  		return false, nil
   211  	}
   212  	return true, nil
   213  }
   214  
   215  func (v *Validator) validateRangeQueries(ns string, rangeQueriesInfo []*kvrwset.RangeQueryInfo, updates *statedb.UpdateBatch) (bool, error) {
   216  	for _, rqi := range rangeQueriesInfo {
   217  		if valid, err := v.validateRangeQuery(ns, rqi, updates); !valid || err != nil {
   218  			return valid, err
   219  		}
   220  	}
   221  	return true, nil
   222  }
   223  
   224  // validateRangeQuery performs a phatom read check i.e., it
   225  // checks whether the results of the range query are still the same when executed on the
   226  // statedb (latest state as of last committed block) + updates (prepared by the writes of preceding valid transactions
   227  // in the current block and yet to be committed as part of group commit at the end of the validation of the block)
   228  func (v *Validator) validateRangeQuery(ns string, rangeQueryInfo *kvrwset.RangeQueryInfo, updates *statedb.UpdateBatch) (bool, error) {
   229  	logger.Debugf("validateRangeQuery: ns=%s, rangeQueryInfo=%s", ns, rangeQueryInfo)
   230  
   231  	// If during simulation, the caller had not exhausted the iterator so
   232  	// rangeQueryInfo.EndKey is not actual endKey given by the caller in the range query
   233  	// but rather it is the last key seen by the caller and hence the combinedItr should include the endKey in the results.
   234  	includeEndKey := !rangeQueryInfo.ItrExhausted
   235  
   236  	combinedItr, err := newCombinedIterator(v.db, updates,
   237  		ns, rangeQueryInfo.StartKey, rangeQueryInfo.EndKey, includeEndKey)
   238  	if err != nil {
   239  		return false, err
   240  	}
   241  	defer combinedItr.Close()
   242  	var validator rangeQueryValidator
   243  	if rangeQueryInfo.GetReadsMerkleHashes() != nil {
   244  		logger.Debug(`Hashing results are present in the range query info hence, initiating hashing based validation`)
   245  		validator = &rangeQueryHashValidator{}
   246  	} else {
   247  		logger.Debug(`Hashing results are not present in the range query info hence, initiating raw KVReads based validation`)
   248  		validator = &rangeQueryResultsValidator{}
   249  	}
   250  	validator.init(rangeQueryInfo, combinedItr)
   251  	return validator.validate()
   252  }