github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/core/ledger/kvledger/txmgmt/pvtstatepurgemgmt/purge_mgr.go (about)

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