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 }