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 }