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 }