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 }