github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/kvledger/txmgmt/validation/batch_preparer.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 "bytes" 11 12 "github.com/hechain20/hechain/common/flogging" 13 "github.com/hechain20/hechain/core/ledger" 14 "github.com/hechain20/hechain/core/ledger/internal/version" 15 "github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/privacyenabledstate" 16 "github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/rwsetutil" 17 "github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/statedb" 18 "github.com/hechain20/hechain/core/ledger/util" 19 "github.com/hechain20/hechain/internal/pkg/txflags" 20 "github.com/hechain20/hechain/protoutil" 21 "github.com/hyperledger/fabric-protos-go/common" 22 "github.com/hyperledger/fabric-protos-go/ledger/rwset" 23 "github.com/hyperledger/fabric-protos-go/peer" 24 "github.com/pkg/errors" 25 ) 26 27 var logger = flogging.MustGetLogger("validation") 28 29 // PostOrderSimulatorProvider provides access to a tx simulator for executing post order non-endorser transactions 30 type PostOrderSimulatorProvider interface { 31 NewTxSimulator(txid string) (ledger.TxSimulator, error) 32 } 33 34 // CommitBatchPreparer performs validation and prepares the final batch that is to be committed to the statedb 35 type CommitBatchPreparer struct { 36 postOrderSimulatorProvider PostOrderSimulatorProvider 37 db *privacyenabledstate.DB 38 validator *validator 39 customTxProcessors map[common.HeaderType]ledger.CustomTxProcessor 40 } 41 42 // TxStatInfo encapsulates information about a transaction 43 type TxStatInfo struct { 44 TxIDFromChannelHeader string 45 ValidationCode peer.TxValidationCode 46 TxType common.HeaderType 47 ChaincodeID *peer.ChaincodeID 48 ChaincodeEventData []byte 49 NumCollections int 50 } 51 52 // NewCommitBatchPreparer constructs a validator that internally manages statebased validator and in addition 53 // handles the tasks that are agnostic to a particular validation scheme such as parsing the block and handling the pvt data 54 func NewCommitBatchPreparer( 55 postOrderSimulatorProvider PostOrderSimulatorProvider, 56 db *privacyenabledstate.DB, 57 customTxProcessors map[common.HeaderType]ledger.CustomTxProcessor, 58 hashFunc rwsetutil.HashFunc, 59 ) *CommitBatchPreparer { 60 return &CommitBatchPreparer{ 61 postOrderSimulatorProvider, 62 db, 63 &validator{ 64 db: db, 65 hashFunc: hashFunc, 66 }, 67 customTxProcessors, 68 } 69 } 70 71 // ValidateAndPrepareBatch performs validation of transactions in the block and prepares the batch of final writes 72 func (p *CommitBatchPreparer) ValidateAndPrepareBatch(blockAndPvtdata *ledger.BlockAndPvtData, 73 doMVCCValidation bool) (*privacyenabledstate.UpdateBatch, []*TxStatInfo, error) { 74 blk := blockAndPvtdata.Block 75 logger.Debugf("ValidateAndPrepareBatch() for block number = [%d]", blk.Header.Number) 76 var internalBlock *block 77 var txsStatInfo []*TxStatInfo 78 var pubAndHashUpdates *publicAndHashUpdates 79 var pvtUpdates *privacyenabledstate.PvtUpdateBatch 80 var err error 81 82 logger.Debug("preprocessing ProtoBlock...") 83 if internalBlock, txsStatInfo, err = preprocessProtoBlock( 84 p.postOrderSimulatorProvider, 85 p.db.ValidateKeyValue, 86 blk, 87 doMVCCValidation, 88 p.customTxProcessors, 89 ); err != nil { 90 return nil, nil, err 91 } 92 93 if pubAndHashUpdates, err = p.validator.validateAndPrepareBatch(internalBlock, doMVCCValidation); err != nil { 94 return nil, nil, err 95 } 96 logger.Debug("validating rwset...") 97 if pvtUpdates, err = validateAndPreparePvtBatch( 98 internalBlock, 99 p.db, 100 pubAndHashUpdates, 101 blockAndPvtdata.PvtData, 102 ); err != nil { 103 return nil, nil, err 104 } 105 logger.Debug("postprocessing ProtoBlock...") 106 postprocessProtoBlock(blk, internalBlock) 107 logger.Debug("ValidateAndPrepareBatch() complete") 108 109 txsFilter := txflags.ValidationFlags(blk.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]) 110 for i := range txsFilter { 111 txsStatInfo[i].ValidationCode = txsFilter.Flag(i) 112 } 113 return &privacyenabledstate.UpdateBatch{ 114 PubUpdates: pubAndHashUpdates.publicUpdates, 115 HashUpdates: pubAndHashUpdates.hashUpdates, 116 PvtUpdates: pvtUpdates, 117 }, txsStatInfo, nil 118 } 119 120 // validateAndPreparePvtBatch pulls out the private write-set for the transactions that are marked as valid 121 // by the internal public data validator. Finally, it validates (if not already self-endorsed) the pvt rwset against the 122 // corresponding hash present in the public rwset 123 func validateAndPreparePvtBatch( 124 blk *block, 125 db *privacyenabledstate.DB, 126 pubAndHashUpdates *publicAndHashUpdates, 127 pvtdata map[uint64]*ledger.TxPvtData, 128 ) (*privacyenabledstate.PvtUpdateBatch, error) { 129 pvtUpdates := privacyenabledstate.NewPvtUpdateBatch() 130 metadataUpdates := metadataUpdates{} 131 for _, tx := range blk.txs { 132 if tx.validationCode != peer.TxValidationCode_VALID { 133 continue 134 } 135 if !tx.containsPvtWrites() { 136 continue 137 } 138 txPvtdata := pvtdata[uint64(tx.indexInBlock)] 139 if txPvtdata == nil { 140 continue 141 } 142 if requiresPvtdataValidation(txPvtdata) { 143 if err := validatePvtdata(tx, txPvtdata); err != nil { 144 return nil, err 145 } 146 } 147 var pvtRWSet *rwsetutil.TxPvtRwSet 148 var err error 149 if pvtRWSet, err = rwsetutil.TxPvtRwSetFromProtoMsg(txPvtdata.WriteSet); err != nil { 150 return nil, err 151 } 152 addPvtRWSetToPvtUpdateBatch(pvtRWSet, pvtUpdates, version.NewHeight(blk.num, uint64(tx.indexInBlock))) 153 addEntriesToMetadataUpdates(metadataUpdates, pvtRWSet) 154 } 155 if err := incrementPvtdataVersionIfNeeded(metadataUpdates, pvtUpdates, pubAndHashUpdates, db); err != nil { 156 return nil, err 157 } 158 return pvtUpdates, nil 159 } 160 161 // requiresPvtdataValidation returns whether or not a hashes of the collection should be computed 162 // for the collections of present in the private data 163 // TODO for now always return true. Add capability of checking if this data was produced by 164 // the validating peer itself during simulation and in that case return false 165 func requiresPvtdataValidation(tx *ledger.TxPvtData) bool { 166 return true 167 } 168 169 // validPvtdata returns true if hashes of all the collections writeset present in the pvt data 170 // match with the corresponding hashes present in the public read-write set 171 func validatePvtdata(tx *transaction, pvtdata *ledger.TxPvtData) error { 172 if pvtdata.WriteSet == nil { 173 return nil 174 } 175 176 for _, nsPvtdata := range pvtdata.WriteSet.NsPvtRwset { 177 for _, collPvtdata := range nsPvtdata.CollectionPvtRwset { 178 collPvtdataHash := util.ComputeHash(collPvtdata.Rwset) 179 hashInPubdata := tx.retrieveHash(nsPvtdata.Namespace, collPvtdata.CollectionName) 180 if !bytes.Equal(collPvtdataHash, hashInPubdata) { 181 return errors.Errorf(`hash of pvt data for collection [%s:%s] does not match with the corresponding hash in the public data. public hash = [%#v], pvt data hash = [%#v]`, 182 nsPvtdata.Namespace, collPvtdata.CollectionName, hashInPubdata, collPvtdataHash) 183 } 184 } 185 } 186 return nil 187 } 188 189 // preprocessProtoBlock parses the proto instance of block into 'Block' structure. 190 // The returned 'Block' structure contains only transactions that are endorser transactions and are not already marked as invalid 191 func preprocessProtoBlock(postOrderSimulatorProvider PostOrderSimulatorProvider, 192 validateKVFunc func(key string, value []byte) error, 193 blk *common.Block, doMVCCValidation bool, 194 customTxProcessors map[common.HeaderType]ledger.CustomTxProcessor, 195 ) (*block, []*TxStatInfo, error) { 196 b := &block{num: blk.Header.Number} 197 txsStatInfo := []*TxStatInfo{} 198 // Committer validator has already set validation flags based on well formed tran checks 199 txsFilter := txflags.ValidationFlags(blk.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]) 200 for txIndex, envBytes := range blk.Data.Data { 201 var env *common.Envelope 202 var chdr *common.ChannelHeader 203 var payload *common.Payload 204 var err error 205 txStatInfo := &TxStatInfo{TxType: -1} 206 txsStatInfo = append(txsStatInfo, txStatInfo) 207 if env, err = protoutil.GetEnvelopeFromBlock(envBytes); err == nil { 208 if payload, err = protoutil.UnmarshalPayload(env.Payload); err == nil { 209 chdr, err = protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) 210 } 211 } 212 txStatInfo.TxIDFromChannelHeader = chdr.GetTxId() 213 if txsFilter.IsInvalid(txIndex) { 214 // Skipping invalid transaction 215 logger.Warningf("Channel [%s]: Block [%d] Transaction index [%d] TxId [%s]"+ 216 " marked as invalid by committer. Reason code [%s]", 217 chdr.GetChannelId(), blk.Header.Number, txIndex, chdr.GetTxId(), 218 txsFilter.Flag(txIndex).String()) 219 continue 220 } 221 if err != nil { 222 return nil, nil, err 223 } 224 225 var txRWSet *rwsetutil.TxRwSet 226 var containsPostOrderWrites bool 227 txType := common.HeaderType(chdr.Type) 228 logger.Debugf("txType=%s", txType) 229 txStatInfo.TxType = txType 230 if txType == common.HeaderType_ENDORSER_TRANSACTION { 231 // extract actions from the envelope message 232 respPayload, err := protoutil.GetActionFromEnvelope(envBytes) 233 if err != nil { 234 txsFilter.SetFlag(txIndex, peer.TxValidationCode_NIL_TXACTION) 235 continue 236 } 237 txStatInfo.ChaincodeID = respPayload.ChaincodeId 238 txStatInfo.ChaincodeEventData = respPayload.Events 239 txRWSet = &rwsetutil.TxRwSet{} 240 if err = txRWSet.FromProtoBytes(respPayload.Results); err != nil { 241 txsFilter.SetFlag(txIndex, peer.TxValidationCode_INVALID_OTHER_REASON) 242 continue 243 } 244 } else { 245 rwsetProto, err := processNonEndorserTx( 246 env, 247 chdr.TxId, 248 txType, 249 postOrderSimulatorProvider, 250 !doMVCCValidation, 251 customTxProcessors, 252 ) 253 if _, ok := err.(*ledger.InvalidTxError); ok { 254 txsFilter.SetFlag(txIndex, peer.TxValidationCode_INVALID_OTHER_REASON) 255 continue 256 } 257 if err != nil { 258 return nil, nil, err 259 } 260 if rwsetProto != nil { 261 if txRWSet, err = rwsetutil.TxRwSetFromProtoMsg(rwsetProto); err != nil { 262 return nil, nil, err 263 } 264 } 265 containsPostOrderWrites = true 266 } 267 if txRWSet != nil { 268 txStatInfo.NumCollections = txRWSet.NumCollections() 269 if err := validateWriteset(txRWSet, validateKVFunc); err != nil { 270 logger.Warningf("Channel [%s]: Block [%d] Transaction index [%d] TxId [%s]"+ 271 " marked as invalid. Reason code [%s]", 272 chdr.GetChannelId(), blk.Header.Number, txIndex, chdr.GetTxId(), peer.TxValidationCode_INVALID_WRITESET) 273 txsFilter.SetFlag(txIndex, peer.TxValidationCode_INVALID_WRITESET) 274 continue 275 } 276 b.txs = append(b.txs, &transaction{ 277 indexInBlock: txIndex, 278 id: chdr.TxId, 279 rwset: txRWSet, 280 containsPostOrderWrites: containsPostOrderWrites, 281 }) 282 } 283 } 284 return b, txsStatInfo, nil 285 } 286 287 func processNonEndorserTx( 288 txEnv *common.Envelope, 289 txid string, 290 txType common.HeaderType, 291 postOrderSimulatorProvider PostOrderSimulatorProvider, 292 synchingState bool, 293 customTxProcessors map[common.HeaderType]ledger.CustomTxProcessor, 294 ) (*rwset.TxReadWriteSet, error) { 295 logger.Debugf("Performing custom processing for transaction [txid=%s], [txType=%s]", txid, txType) 296 processor := customTxProcessors[txType] 297 logger.Debugf("Processor for custom tx processing:%#v", processor) 298 if processor == nil { 299 return nil, nil 300 } 301 302 var err error 303 var sim ledger.TxSimulator 304 var simRes *ledger.TxSimulationResults 305 if sim, err = postOrderSimulatorProvider.NewTxSimulator(txid); err != nil { 306 return nil, err 307 } 308 defer sim.Done() 309 if err = processor.GenerateSimulationResults(txEnv, sim, synchingState); err != nil { 310 return nil, err 311 } 312 if simRes, err = sim.GetTxSimulationResults(); err != nil { 313 return nil, err 314 } 315 return simRes.PubSimulationResults, nil 316 } 317 318 func validateWriteset(txRWSet *rwsetutil.TxRwSet, validateKVFunc func(key string, value []byte) error) error { 319 for _, nsRwSet := range txRWSet.NsRwSets { 320 pubWriteset := nsRwSet.KvRwSet 321 if pubWriteset == nil { 322 continue 323 } 324 for _, kvwrite := range pubWriteset.Writes { 325 if err := validateKVFunc(kvwrite.Key, kvwrite.Value); err != nil { 326 return err 327 } 328 } 329 } 330 return nil 331 } 332 333 // postprocessProtoBlock updates the proto block's validation flags (in metadata) by the results of validation process 334 func postprocessProtoBlock(blk *common.Block, validatedBlock *block) { 335 txsFilter := txflags.ValidationFlags(blk.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]) 336 for _, tx := range validatedBlock.txs { 337 txsFilter.SetFlag(tx.indexInBlock, tx.validationCode) 338 } 339 blk.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsFilter 340 } 341 342 func addPvtRWSetToPvtUpdateBatch(pvtRWSet *rwsetutil.TxPvtRwSet, pvtUpdateBatch *privacyenabledstate.PvtUpdateBatch, ver *version.Height) { 343 for _, ns := range pvtRWSet.NsPvtRwSet { 344 for _, coll := range ns.CollPvtRwSets { 345 for _, kvwrite := range coll.KvRwSet.Writes { 346 if !rwsetutil.IsKVWriteDelete(kvwrite) { 347 pvtUpdateBatch.Put(ns.NameSpace, coll.CollectionName, kvwrite.Key, kvwrite.Value, ver) 348 } else { 349 pvtUpdateBatch.Delete(ns.NameSpace, coll.CollectionName, kvwrite.Key, ver) 350 } 351 } 352 } 353 } 354 } 355 356 // incrementPvtdataVersionIfNeeded changes the versions of the private data keys if the version of the corresponding hashed key has 357 // been upgraded. A metadata-update-only type of transaction may have caused the version change of the existing value in the hashed space. 358 // Iterate through all the metadata writes and try to get these keys and increment the version in the private writes to be the same as of the hashed key version - if the latest 359 // value of the key is available. Otherwise, in this scenario, we end up having the latest value in the private state but the version 360 // gets left as stale and will cause simulation failure because of wrongly assuming that we have stale value 361 func incrementPvtdataVersionIfNeeded( 362 metadataUpdates metadataUpdates, 363 pvtUpdateBatch *privacyenabledstate.PvtUpdateBatch, 364 pubAndHashUpdates *publicAndHashUpdates, 365 db *privacyenabledstate.DB) error { 366 for collKey := range metadataUpdates { 367 ns, coll, key := collKey.ns, collKey.coll, collKey.key 368 keyHash := util.ComputeStringHash(key) 369 hashedVal := pubAndHashUpdates.hashUpdates.Get(ns, coll, string(keyHash)) 370 if hashedVal == nil { 371 // This key is finally not getting updated in the hashed space by this block - 372 // either the metadata update was on a non-existing key or the key gets deleted by a latter transaction in the block 373 // ignore the metadata update for this key 374 continue 375 } 376 latestVal, err := retrieveLatestVal(ns, coll, key, pvtUpdateBatch, db) 377 if err != nil { 378 return err 379 } 380 if latestVal == nil || // latest value not found either in db or in the pvt updates (caused by commit with missing data) 381 version.AreSame(latestVal.Version, hashedVal.Version) { // version is already same as in hashed space - No version increment because of metadata-only transaction took place 382 continue 383 } 384 // TODO - computing hash could be avoided. In the hashed updates, we can augment additional info that 385 // which original version has been renewed 386 latestValHash := util.ComputeHash(latestVal.Value) 387 if bytes.Equal(latestValHash, hashedVal.Value) { // since we allow block commits with missing pvt data, the private value available may be stale. 388 // upgrade the version only if the pvt value matches with corresponding hash in the hashed space 389 pvtUpdateBatch.Put(ns, coll, key, latestVal.Value, hashedVal.Version) 390 } 391 } 392 return nil 393 } 394 395 type collKey struct { 396 ns, coll, key string 397 } 398 399 type metadataUpdates map[collKey]bool 400 401 func addEntriesToMetadataUpdates(metadataUpdates metadataUpdates, pvtRWSet *rwsetutil.TxPvtRwSet) { 402 for _, ns := range pvtRWSet.NsPvtRwSet { 403 for _, coll := range ns.CollPvtRwSets { 404 for _, metadataWrite := range coll.KvRwSet.MetadataWrites { 405 ns, coll, key := ns.NameSpace, coll.CollectionName, metadataWrite.Key 406 metadataUpdates[collKey{ns, coll, key}] = true 407 } 408 } 409 } 410 } 411 412 func retrieveLatestVal(ns, coll, key string, pvtUpdateBatch *privacyenabledstate.PvtUpdateBatch, 413 db *privacyenabledstate.DB) (val *statedb.VersionedValue, err error) { 414 val = pvtUpdateBatch.Get(ns, coll, key) 415 if val == nil { 416 val, err = db.GetPrivateData(ns, coll, key) 417 } 418 return 419 }