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  }