github.com/Hnampk/fabric@v2.1.1+incompatible/core/ledger/kvledger/txmgmt/validator/valimpl/helper.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package valimpl 8 9 import ( 10 "bytes" 11 "fmt" 12 13 "github.com/hyperledger/fabric-protos-go/common" 14 "github.com/hyperledger/fabric-protos-go/ledger/rwset" 15 "github.com/hyperledger/fabric-protos-go/peer" 16 "github.com/hyperledger/fabric/core/ledger" 17 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/privacyenabledstate" 18 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil" 19 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb" 20 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/txmgr" 21 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/validator" 22 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/validator/internal" 23 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version" 24 "github.com/hyperledger/fabric/core/ledger/util" 25 "github.com/hyperledger/fabric/protoutil" 26 ) 27 28 // validateAndPreparePvtBatch pulls out the private write-set for the transactions that are marked as valid 29 // by the internal public data validator. Finally, it validates (if not already self-endorsed) the pvt rwset against the 30 // corresponding hash present in the public rwset 31 func validateAndPreparePvtBatch( 32 block *internal.Block, 33 db privacyenabledstate.DB, 34 pubAndHashUpdates *internal.PubAndHashUpdates, 35 pvtdata map[uint64]*ledger.TxPvtData, 36 customTxProcessors map[common.HeaderType]ledger.CustomTxProcessor, 37 ) (*privacyenabledstate.PvtUpdateBatch, error) { 38 39 pvtUpdates := privacyenabledstate.NewPvtUpdateBatch() 40 metadataUpdates := metadataUpdates{} 41 for _, tx := range block.Txs { 42 if tx.ValidationCode != peer.TxValidationCode_VALID { 43 continue 44 } 45 if !tx.ContainsPvtWrites() { 46 continue 47 } 48 txPvtdata := pvtdata[uint64(tx.IndexInBlock)] 49 if txPvtdata == nil { 50 continue 51 } 52 if requiresPvtdataValidation(txPvtdata) { 53 if err := validatePvtdata(tx, txPvtdata); err != nil { 54 return nil, err 55 } 56 } 57 var pvtRWSet *rwsetutil.TxPvtRwSet 58 var err error 59 if pvtRWSet, err = rwsetutil.TxPvtRwSetFromProtoMsg(txPvtdata.WriteSet); err != nil { 60 return nil, err 61 } 62 addPvtRWSetToPvtUpdateBatch(pvtRWSet, pvtUpdates, version.NewHeight(block.Num, uint64(tx.IndexInBlock))) 63 addEntriesToMetadataUpdates(metadataUpdates, pvtRWSet) 64 } 65 if err := incrementPvtdataVersionIfNeeded(metadataUpdates, pvtUpdates, pubAndHashUpdates, db); err != nil { 66 return nil, err 67 } 68 return pvtUpdates, nil 69 } 70 71 // requiresPvtdataValidation returns whether or not a hashes of the collection should be computed 72 // for the collections of present in the private data 73 // TODO for now always return true. Add capability of checking if this data was produced by 74 // the validating peer itself during simulation and in that case return false 75 func requiresPvtdataValidation(tx *ledger.TxPvtData) bool { 76 return true 77 } 78 79 // validPvtdata returns true if hashes of all the collections writeset present in the pvt data 80 // match with the corresponding hashes present in the public read-write set 81 func validatePvtdata(tx *internal.Transaction, pvtdata *ledger.TxPvtData) error { 82 if pvtdata.WriteSet == nil { 83 return nil 84 } 85 86 for _, nsPvtdata := range pvtdata.WriteSet.NsPvtRwset { 87 for _, collPvtdata := range nsPvtdata.CollectionPvtRwset { 88 collPvtdataHash := util.ComputeHash(collPvtdata.Rwset) 89 hashInPubdata := tx.RetrieveHash(nsPvtdata.Namespace, collPvtdata.CollectionName) 90 if !bytes.Equal(collPvtdataHash, hashInPubdata) { 91 return &validator.ErrPvtdataHashMissmatch{ 92 Msg: fmt.Sprintf(`Hash of pvt data for collection [%s:%s] does not match with the corresponding hash in the public data. 93 public hash = [%#v], pvt data hash = [%#v]`, nsPvtdata.Namespace, collPvtdata.CollectionName, hashInPubdata, collPvtdataHash), 94 } 95 } 96 } 97 } 98 return nil 99 } 100 101 // preprocessProtoBlock parses the proto instance of block into 'Block' structure. 102 // The returned 'Block' structure contains only transactions that are endorser transactions and are not already marked as invalid 103 func preprocessProtoBlock(txMgr txmgr.TxMgr, 104 validateKVFunc func(key string, value []byte) error, 105 block *common.Block, doMVCCValidation bool, 106 customTxProcessors map[common.HeaderType]ledger.CustomTxProcessor, 107 ) (*internal.Block, []*txmgr.TxStatInfo, error) { 108 b := &internal.Block{Num: block.Header.Number} 109 txsStatInfo := []*txmgr.TxStatInfo{} 110 // Committer validator has already set validation flags based on well formed tran checks 111 txsFilter := util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]) 112 for txIndex, envBytes := range block.Data.Data { 113 var env *common.Envelope 114 var chdr *common.ChannelHeader 115 var payload *common.Payload 116 var err error 117 txStatInfo := &txmgr.TxStatInfo{TxType: -1} 118 txsStatInfo = append(txsStatInfo, txStatInfo) 119 if env, err = protoutil.GetEnvelopeFromBlock(envBytes); err == nil { 120 if payload, err = protoutil.UnmarshalPayload(env.Payload); err == nil { 121 chdr, err = protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) 122 } 123 } 124 if txsFilter.IsInvalid(txIndex) { 125 // Skipping invalid transaction 126 logger.Warningf("Channel [%s]: Block [%d] Transaction index [%d] TxId [%s]"+ 127 " marked as invalid by committer. Reason code [%s]", 128 chdr.GetChannelId(), block.Header.Number, txIndex, chdr.GetTxId(), 129 txsFilter.Flag(txIndex).String()) 130 continue 131 } 132 if err != nil { 133 return nil, nil, err 134 } 135 136 var txRWSet *rwsetutil.TxRwSet 137 var containsPostOrderWrites bool 138 txType := common.HeaderType(chdr.Type) 139 logger.Debugf("txType=%s", txType) 140 txStatInfo.TxType = txType 141 if txType == common.HeaderType_ENDORSER_TRANSACTION { 142 // extract actions from the envelope message 143 respPayload, err := protoutil.GetActionFromEnvelope(envBytes) 144 if err != nil { 145 txsFilter.SetFlag(txIndex, peer.TxValidationCode_NIL_TXACTION) 146 continue 147 } 148 txStatInfo.ChaincodeID = respPayload.ChaincodeId 149 txRWSet = &rwsetutil.TxRwSet{} 150 if err = txRWSet.FromProtoBytes(respPayload.Results); err != nil { 151 txsFilter.SetFlag(txIndex, peer.TxValidationCode_INVALID_OTHER_REASON) 152 continue 153 } 154 } else { 155 rwsetProto, err := processNonEndorserTx(env, chdr.TxId, txType, txMgr, !doMVCCValidation, customTxProcessors) 156 if _, ok := err.(*ledger.InvalidTxError); ok { 157 txsFilter.SetFlag(txIndex, peer.TxValidationCode_INVALID_OTHER_REASON) 158 continue 159 } 160 if err != nil { 161 return nil, nil, err 162 } 163 if rwsetProto != nil { 164 if txRWSet, err = rwsetutil.TxRwSetFromProtoMsg(rwsetProto); err != nil { 165 return nil, nil, err 166 } 167 } 168 containsPostOrderWrites = true 169 } 170 if txRWSet != nil { 171 txStatInfo.NumCollections = txRWSet.NumCollections() 172 if err := validateWriteset(txRWSet, validateKVFunc); err != nil { 173 logger.Warningf("Channel [%s]: Block [%d] Transaction index [%d] TxId [%s]"+ 174 " marked as invalid. Reason code [%s]", 175 chdr.GetChannelId(), block.Header.Number, txIndex, chdr.GetTxId(), peer.TxValidationCode_INVALID_WRITESET) 176 txsFilter.SetFlag(txIndex, peer.TxValidationCode_INVALID_WRITESET) 177 continue 178 } 179 b.Txs = append(b.Txs, &internal.Transaction{ 180 IndexInBlock: txIndex, 181 ID: chdr.TxId, 182 RWSet: txRWSet, 183 ContainsPostOrderWrites: containsPostOrderWrites, 184 }) 185 } 186 } 187 return b, txsStatInfo, nil 188 } 189 190 func processNonEndorserTx( 191 txEnv *common.Envelope, 192 txid string, 193 txType common.HeaderType, 194 txmgr txmgr.TxMgr, 195 synchingState bool, 196 customTxProcessors map[common.HeaderType]ledger.CustomTxProcessor, 197 ) (*rwset.TxReadWriteSet, error) { 198 logger.Debugf("Performing custom processing for transaction [txid=%s], [txType=%s]", txid, txType) 199 processor := customTxProcessors[txType] 200 logger.Debugf("Processor for custom tx processing:%#v", processor) 201 if processor == nil { 202 return nil, nil 203 } 204 205 var err error 206 var sim ledger.TxSimulator 207 var simRes *ledger.TxSimulationResults 208 if sim, err = txmgr.NewTxSimulator(txid); err != nil { 209 return nil, err 210 } 211 defer sim.Done() 212 if err = processor.GenerateSimulationResults(txEnv, sim, synchingState); err != nil { 213 return nil, err 214 } 215 if simRes, err = sim.GetTxSimulationResults(); err != nil { 216 return nil, err 217 } 218 return simRes.PubSimulationResults, nil 219 } 220 221 func validateWriteset(txRWSet *rwsetutil.TxRwSet, validateKVFunc func(key string, value []byte) error) error { 222 for _, nsRwSet := range txRWSet.NsRwSets { 223 pubWriteset := nsRwSet.KvRwSet 224 if pubWriteset == nil { 225 continue 226 } 227 for _, kvwrite := range pubWriteset.Writes { 228 if err := validateKVFunc(kvwrite.Key, kvwrite.Value); err != nil { 229 return err 230 } 231 } 232 } 233 return nil 234 } 235 236 // postprocessProtoBlock updates the proto block's validation flags (in metadata) by the results of validation process 237 func postprocessProtoBlock(block *common.Block, validatedBlock *internal.Block) { 238 txsFilter := util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]) 239 for _, tx := range validatedBlock.Txs { 240 txsFilter.SetFlag(tx.IndexInBlock, tx.ValidationCode) 241 } 242 block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsFilter 243 } 244 245 func addPvtRWSetToPvtUpdateBatch(pvtRWSet *rwsetutil.TxPvtRwSet, pvtUpdateBatch *privacyenabledstate.PvtUpdateBatch, ver *version.Height) { 246 for _, ns := range pvtRWSet.NsPvtRwSet { 247 for _, coll := range ns.CollPvtRwSets { 248 for _, kvwrite := range coll.KvRwSet.Writes { 249 if !kvwrite.IsDelete { 250 pvtUpdateBatch.Put(ns.NameSpace, coll.CollectionName, kvwrite.Key, kvwrite.Value, ver) 251 } else { 252 pvtUpdateBatch.Delete(ns.NameSpace, coll.CollectionName, kvwrite.Key, ver) 253 } 254 } 255 } 256 } 257 } 258 259 // incrementPvtdataVersionIfNeeded changes the versions of the private data keys if the version of the corresponding hashed key has 260 // been upgraded. A metadata-update-only type of transaction may have caused the version change of the existing value in the hashed space. 261 // 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 262 // value of the key is available. Otherwise, in this scenario, we end up having the latest value in the private state but the version 263 // gets left as stale and will cause simulation failure because of wrongly assuming that we have stale value 264 func incrementPvtdataVersionIfNeeded( 265 metadataUpdates metadataUpdates, 266 pvtUpdateBatch *privacyenabledstate.PvtUpdateBatch, 267 pubAndHashUpdates *internal.PubAndHashUpdates, 268 db privacyenabledstate.DB) error { 269 270 for collKey := range metadataUpdates { 271 ns, coll, key := collKey.ns, collKey.coll, collKey.key 272 keyHash := util.ComputeStringHash(key) 273 hashedVal := pubAndHashUpdates.HashUpdates.Get(ns, coll, string(keyHash)) 274 if hashedVal == nil { 275 // This key is finally not getting updated in the hashed space by this block - 276 // either the metadata update was on a non-existing key or the key gets deleted by a latter transaction in the block 277 // ignore the metadata update for this key 278 continue 279 } 280 latestVal, err := retrieveLatestVal(ns, coll, key, pvtUpdateBatch, db) 281 if err != nil { 282 return err 283 } 284 if latestVal == nil || // latest value not found either in db or in the pvt updates (caused by commit with missing data) 285 version.AreSame(latestVal.Version, hashedVal.Version) { // version is already same as in hashed space - No version increment because of metadata-only transaction took place 286 continue 287 } 288 // TODO - computing hash could be avoided. In the hashed updates, we can augment additional info that 289 // which original version has been renewed 290 latestValHash := util.ComputeHash(latestVal.Value) 291 if bytes.Equal(latestValHash, hashedVal.Value) { // since we allow block commits with missing pvt data, the private value available may be stale. 292 // upgrade the version only if the pvt value matches with corresponding hash in the hashed space 293 pvtUpdateBatch.Put(ns, coll, key, latestVal.Value, hashedVal.Version) 294 } 295 } 296 return nil 297 } 298 299 type collKey struct { 300 ns, coll, key string 301 } 302 303 type metadataUpdates map[collKey]bool 304 305 func addEntriesToMetadataUpdates(metadataUpdates metadataUpdates, pvtRWSet *rwsetutil.TxPvtRwSet) { 306 for _, ns := range pvtRWSet.NsPvtRwSet { 307 for _, coll := range ns.CollPvtRwSets { 308 for _, metadataWrite := range coll.KvRwSet.MetadataWrites { 309 ns, coll, key := ns.NameSpace, coll.CollectionName, metadataWrite.Key 310 metadataUpdates[collKey{ns, coll, key}] = true 311 } 312 } 313 } 314 } 315 316 func retrieveLatestVal(ns, coll, key string, pvtUpdateBatch *privacyenabledstate.PvtUpdateBatch, 317 db privacyenabledstate.DB) (val *statedb.VersionedValue, err error) { 318 val = pvtUpdateBatch.Get(ns, coll, key) 319 if val == nil { 320 val, err = db.GetPrivateData(ns, coll, key) 321 } 322 return 323 }