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 }