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  }