github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/kvledger/txmgmt/pvtstatepurgemgmt/purge_mgr.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package pvtstatepurgemgmt
     8  
     9  import (
    10  	"math"
    11  	"sync"
    12  
    13  	"github.com/hechain20/hechain/core/ledger/internal/version"
    14  	"github.com/hechain20/hechain/core/ledger/kvledger/bookkeeping"
    15  	"github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/privacyenabledstate"
    16  	"github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/statedb"
    17  	"github.com/hechain20/hechain/core/ledger/pvtdatapolicy"
    18  	"github.com/hechain20/hechain/core/ledger/util"
    19  )
    20  
    21  // PurgeMgr keeps track of the expiry of private data and the private data hashes based on block-to-live
    22  // parameter specified in the corresponding collection config
    23  type PurgeMgr struct {
    24  	btlPolicy pvtdatapolicy.BTLPolicy
    25  	db        *privacyenabledstate.DB
    26  	expKeeper *expiryKeeper
    27  
    28  	lock    *sync.Mutex
    29  	waitGrp *sync.WaitGroup
    30  
    31  	workingset *workingset
    32  }
    33  
    34  type workingset struct {
    35  	toPurge             expiryInfoMap
    36  	toClearFromSchedule []*expiryInfoKey
    37  	expiringBlk         uint64
    38  	err                 error
    39  }
    40  
    41  type expiryInfoMap map[privacyenabledstate.HashedCompositeKey]*keyAndVersion
    42  
    43  type keyAndVersion struct {
    44  	key             string
    45  	committingBlock uint64
    46  	purgeKeyOnly    bool
    47  }
    48  
    49  // InstantiatePurgeMgr instantiates a PurgeMgr.
    50  func InstantiatePurgeMgr(ledgerid string, db *privacyenabledstate.DB, btlPolicy pvtdatapolicy.BTLPolicy, bookkeepingProvider *bookkeeping.Provider) (*PurgeMgr, error) {
    51  	return &PurgeMgr{
    52  		btlPolicy: btlPolicy,
    53  		db:        db,
    54  		expKeeper: newExpiryKeeper(ledgerid, bookkeepingProvider),
    55  		lock:      &sync.Mutex{},
    56  		waitGrp:   &sync.WaitGroup{},
    57  	}, nil
    58  }
    59  
    60  // PrepareForExpiringKeys gives a chance to the PurgeMgr to do background work in advance if any
    61  func (p *PurgeMgr) PrepareForExpiringKeys(expiringAtBlk uint64) {
    62  	p.waitGrp.Add(1)
    63  	go func() {
    64  		p.lock.Lock()
    65  		p.waitGrp.Done()
    66  		defer p.lock.Unlock()
    67  		p.workingset = p.prepareWorkingsetFor(expiringAtBlk)
    68  	}()
    69  	p.waitGrp.Wait()
    70  }
    71  
    72  // WaitForPrepareToFinish holds the caller till the background goroutine launched by 'PrepareForExpiringKeys' is finished
    73  func (p *PurgeMgr) WaitForPrepareToFinish() {
    74  	p.lock.Lock()
    75  	p.lock.Unlock() //lint:ignore SA2001 syncpoint
    76  }
    77  
    78  // UpdateExpiryInfoOfPvtDataOfOldBlocks updates the existing expiry entries in the expiryKeeper with the given pvtUpdates
    79  func (p *PurgeMgr) UpdateExpiryInfoOfPvtDataOfOldBlocks(pvtUpdates *privacyenabledstate.PvtUpdateBatch) error {
    80  	builder := newExpiryScheduleBuilder(p.btlPolicy)
    81  	pvtUpdateCompositeKeyMap := pvtUpdates.ToCompositeKeyMap()
    82  	for k, vv := range pvtUpdateCompositeKeyMap {
    83  		if err := builder.add(k.Namespace, k.CollectionName, k.Key, util.ComputeStringHash(k.Key), vv); err != nil {
    84  			return err
    85  		}
    86  	}
    87  
    88  	var expiryInfoUpdates []*expiryInfo
    89  	for _, toAdd := range builder.getExpiryInfo() {
    90  		toUpdate, err := p.expKeeper.retrieveByExpiryKey(toAdd.expiryInfoKey)
    91  		if err != nil {
    92  			return err
    93  		}
    94  		// Though we could update the existing entry (as there should be one due
    95  		// to only the keyHash of this pvtUpdateKey), for simplicity and to be less
    96  		// expensive, we append a new entry
    97  		toUpdate.pvtdataKeys.addAll(toAdd.pvtdataKeys)
    98  		expiryInfoUpdates = append(expiryInfoUpdates, toUpdate)
    99  	}
   100  
   101  	// As the expiring keys list might have been constructed after the last
   102  	// regular block commit, we need to update the list. This is because,
   103  	// some of the old pvtData which are being committed might get expired
   104  	// during the next regular block commit. As a result, the corresponding
   105  	// hashedKey in the expiring keys list would be missing the pvtData.
   106  	p.addMissingPvtDataToWorkingSet(pvtUpdateCompositeKeyMap)
   107  
   108  	return p.expKeeper.update(expiryInfoUpdates, nil)
   109  }
   110  
   111  func (p *PurgeMgr) addMissingPvtDataToWorkingSet(pvtKeys privacyenabledstate.PvtdataCompositeKeyMap) {
   112  	if p.workingset == nil || len(p.workingset.toPurge) == 0 {
   113  		return
   114  	}
   115  
   116  	for k := range pvtKeys {
   117  		hashedCompositeKey := privacyenabledstate.HashedCompositeKey{
   118  			Namespace:      k.Namespace,
   119  			CollectionName: k.CollectionName,
   120  			KeyHash:        string(util.ComputeStringHash(k.Key)),
   121  		}
   122  
   123  		toPurgeKey, ok := p.workingset.toPurge[hashedCompositeKey]
   124  		if !ok {
   125  			// corresponding hashedKey is not present in the
   126  			// expiring keys list
   127  			continue
   128  		}
   129  
   130  		// if the purgeKeyOnly is set, it means that the version of the pvtKey
   131  		// stored in the stateDB is older than the version of the hashedKey.
   132  		// As a result, only the pvtKey needs to be purged (expiring block height
   133  		// for the recent hashedKey would be higher). If the recent
   134  		// pvtKey of the corresponding hashedKey is being committed, we need to
   135  		// remove the purgeKeyOnly entries from the toPurgeList it is going to be
   136  		// updated by the commit of missing pvtData
   137  		if toPurgeKey.purgeKeyOnly {
   138  			delete(p.workingset.toPurge, hashedCompositeKey)
   139  		} else {
   140  			toPurgeKey.key = k.Key
   141  		}
   142  	}
   143  }
   144  
   145  // UpdateExpiryInfo persists the expiry information for the private data and private data hashes
   146  // This function is expected to be invoked before the updates are applied to the statedb for the block
   147  // commit
   148  func (p *PurgeMgr) UpdateExpiryInfo(
   149  	pvtUpdates *privacyenabledstate.PvtUpdateBatch,
   150  	hashedUpdates *privacyenabledstate.HashedUpdateBatch) error {
   151  	expiryInfoUpdates, err := buildExpirySchedule(p.btlPolicy, pvtUpdates, hashedUpdates)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	return p.expKeeper.update(expiryInfoUpdates, nil)
   156  }
   157  
   158  // AddExpiredEntriesToUpdateBatch add the expired pvtdata to the updateBatch of next block to be committed
   159  func (p *PurgeMgr) AddExpiredEntriesToUpdateBatch(
   160  	pvtUpdates *privacyenabledstate.PvtUpdateBatch,
   161  	hashedUpdates *privacyenabledstate.HashedUpdateBatch) error {
   162  	p.lock.Lock()
   163  	defer p.lock.Unlock()
   164  	if p.workingset.err != nil {
   165  		return p.workingset.err
   166  	}
   167  
   168  	// For each key selected for purging, check if the key is not getting updated in the current block,
   169  	// add its deletion in the update batches for pvt and hashed updates
   170  	for compositeHashedKey, keyAndVersion := range p.workingset.toPurge {
   171  		ns := compositeHashedKey.Namespace
   172  		coll := compositeHashedKey.CollectionName
   173  		keyHash := []byte(compositeHashedKey.KeyHash)
   174  		key := keyAndVersion.key
   175  		purgeKeyOnly := keyAndVersion.purgeKeyOnly
   176  		hashUpdated := hashedUpdates.Contains(ns, coll, keyHash)
   177  		pvtKeyUpdated := pvtUpdates.Contains(ns, coll, key)
   178  
   179  		logger.Debugf("Checking whether the key [ns=%s, coll=%s, keyHash=%x, purgeKeyOnly=%t] "+
   180  			"is updated in the update batch for the committing block - hashUpdated=%t, and pvtKeyUpdated=%t",
   181  			ns, coll, keyHash, purgeKeyOnly, hashUpdated, pvtKeyUpdated)
   182  
   183  		expiringTxVersion := version.NewHeight(p.workingset.expiringBlk, math.MaxUint64)
   184  		if !hashUpdated && !purgeKeyOnly {
   185  			logger.Debugf("Adding the hashed key to be purged to the delete list in the update batch")
   186  			hashedUpdates.Delete(ns, coll, keyHash, expiringTxVersion)
   187  		}
   188  		if key != "" && !pvtKeyUpdated {
   189  			logger.Debugf("Adding the pvt key to be purged to the delete list in the update batch")
   190  			pvtUpdates.Delete(ns, coll, key, expiringTxVersion)
   191  		}
   192  	}
   193  	return nil
   194  }
   195  
   196  // BlockCommitDone is a callback to the PurgeMgr when the block is committed to the ledger
   197  // These orphan entries for purge-schedule can be cleared off in bulk in a separate background routine as well
   198  // If we maintain the following logic (i.e., clear off entries just after block commit), we need a TODO -
   199  // We need to perform a check in the start, because there could be a crash between the block commit and
   200  // invocation to this function resulting in the orphan entry for the deletes scheduled for the last block
   201  // Also, the another way is to club the delete of these entries in the same batch that adds entries for the future expirations -
   202  // however, that requires updating the expiry store by replaying the last block from blockchain in order to sustain a crash between
   203  // entries updates and block commit
   204  func (p *PurgeMgr) BlockCommitDone() error {
   205  	defer func() { p.workingset = nil }()
   206  	return p.expKeeper.update(nil, p.workingset.toClearFromSchedule)
   207  }
   208  
   209  // prepareWorkingsetFor returns a working set for a given expiring block 'expiringAtBlk'.
   210  // This working set contains the pvt data keys that will expire with the commit of block 'expiringAtBlk'.
   211  func (p *PurgeMgr) prepareWorkingsetFor(expiringAtBlk uint64) *workingset {
   212  	logger.Debugf("Preparing potential purge list working-set for expiringAtBlk [%d]", expiringAtBlk)
   213  	workingset := &workingset{expiringBlk: expiringAtBlk}
   214  	// Retrieve the keys from expiryKeeper
   215  	expiryInfo, err := p.expKeeper.retrieve(expiringAtBlk)
   216  	if err != nil {
   217  		workingset.err = err
   218  		return workingset
   219  	}
   220  	// Transform the keys into the form such that for each hashed key that is eligible for purge appears in 'toPurge'
   221  	toPurge := transformToExpiryInfoMap(expiryInfo)
   222  	// Load the latest versions of the hashed keys
   223  	if err = p.preloadCommittedVersionsInCache(toPurge); err != nil {
   224  		workingset.err = err
   225  		return workingset
   226  	}
   227  	var expiryInfoKeysToClear []*expiryInfoKey
   228  
   229  	if len(toPurge) == 0 {
   230  		logger.Debugf("No expiry entry found for expiringAtBlk [%d]", expiringAtBlk)
   231  		return workingset
   232  	}
   233  	logger.Debugf("Total [%d] expiring entries found. Evaluating whether some of these keys have been overwritten in later blocks...", len(toPurge))
   234  
   235  	for purgeEntryK, purgeEntryV := range toPurge {
   236  		logger.Debugf("Evaluating for hashedKey [%s]", purgeEntryK)
   237  		expiryInfoKeysToClear = append(expiryInfoKeysToClear, &expiryInfoKey{committingBlk: purgeEntryV.committingBlock, expiryBlk: expiringAtBlk})
   238  		currentVersion, err := p.db.GetKeyHashVersion(purgeEntryK.Namespace, purgeEntryK.CollectionName, []byte(purgeEntryK.KeyHash))
   239  		if err != nil {
   240  			workingset.err = err
   241  			return workingset
   242  		}
   243  
   244  		if sameVersion(currentVersion, purgeEntryV.committingBlock) {
   245  			logger.Debugf(
   246  				"The version of the hashed key in the committed state and in the expiry entry is same " +
   247  					"hence, keeping the entry in the purge list")
   248  			continue
   249  		}
   250  
   251  		logger.Debugf("The version of the hashed key in the committed state and in the expiry entry is different")
   252  		if purgeEntryV.key != "" {
   253  			logger.Debugf("The expiry entry also contains the raw key along with the key hash")
   254  			committedPvtVerVal, err := p.db.GetPrivateData(purgeEntryK.Namespace, purgeEntryK.CollectionName, purgeEntryV.key)
   255  			if err != nil {
   256  				workingset.err = err
   257  				return workingset
   258  			}
   259  
   260  			if sameVersionFromVal(committedPvtVerVal, purgeEntryV.committingBlock) {
   261  				logger.Debugf(
   262  					"The version of the pvt key in the committed state and in the expiry entry is same" +
   263  						"Including only key in the purge list and not the hashed key")
   264  				purgeEntryV.purgeKeyOnly = true
   265  				continue
   266  			}
   267  		}
   268  
   269  		// If we reached here, the keyhash and private key (if present, in the expiry entry) have been updated in a later block, therefore remove from current purge list
   270  		logger.Debugf("Removing from purge list - the key hash and key (if present, in the expiry entry)")
   271  		delete(toPurge, purgeEntryK)
   272  	}
   273  	// Final keys to purge from state
   274  	workingset.toPurge = toPurge
   275  	// Keys to clear from expiryKeeper
   276  	workingset.toClearFromSchedule = expiryInfoKeysToClear
   277  	return workingset
   278  }
   279  
   280  func (p *PurgeMgr) preloadCommittedVersionsInCache(expInfoMap expiryInfoMap) error {
   281  	if !p.db.IsBulkOptimizable() {
   282  		return nil
   283  	}
   284  	var hashedKeys []*privacyenabledstate.HashedCompositeKey
   285  	for k := range expInfoMap {
   286  		hashedKeys = append(hashedKeys, &k)
   287  	}
   288  	return p.db.LoadCommittedVersionsOfPubAndHashedKeys(nil, hashedKeys)
   289  }
   290  
   291  func transformToExpiryInfoMap(expiryInfo []*expiryInfo) expiryInfoMap {
   292  	expinfoMap := make(expiryInfoMap)
   293  	for _, expinfo := range expiryInfo {
   294  		for ns, colls := range expinfo.pvtdataKeys.Map {
   295  			for coll, keysAndHashes := range colls.Map {
   296  				for _, keyAndHash := range keysAndHashes.List {
   297  					compositeKey := privacyenabledstate.HashedCompositeKey{Namespace: ns, CollectionName: coll, KeyHash: string(keyAndHash.Hash)}
   298  					expinfoMap[compositeKey] = &keyAndVersion{key: keyAndHash.Key, committingBlock: expinfo.expiryInfoKey.committingBlk}
   299  				}
   300  			}
   301  		}
   302  	}
   303  	return expinfoMap
   304  }
   305  
   306  func sameVersion(version *version.Height, blockNum uint64) bool {
   307  	return version != nil && version.BlockNum == blockNum
   308  }
   309  
   310  func sameVersionFromVal(vv *statedb.VersionedValue, blockNum uint64) bool {
   311  	return vv != nil && sameVersion(vv.Version, blockNum)
   312  }