Skip to content

Commit e00dfcb

Browse files
cendhumanish-sethi
authored andcommitted
recon: update BTL bookkeeping managed by purge mgr
Before we commit the pvtData of old blocks to the stateDB, we must update the BTL bookkeeping accordingly by creating appropriate expiryEntries. FAB-12888 #done Change-Id: Id16b69e5a2e0d59c2497ddad7fda3d82ed23e0a5 Signed-off-by: senthil <cendhu@gmail.com> Signed-off-by: manish <manish.sethi@gmail.com>
1 parent 6bf6ead commit e00dfcb

File tree

12 files changed

+183
-12
lines changed

12 files changed

+183
-12
lines changed

core/ledger/kvledger/txmgmt/privacyenabledstate/db.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,12 @@ func (h HashedUpdateBatch) ToCompositeKeyMap() map[HashedCompositeKey]*statedb.V
199199
return m
200200
}
201201

202+
// PvtdataCompositeKeyMap is a map of PvtdataCompositeKey to VersionedValue
203+
type PvtdataCompositeKeyMap map[PvtdataCompositeKey]*statedb.VersionedValue
204+
202205
// ToCompositeKeyMap rearranges the update batch data in the form of a single map
203-
func (p PvtUpdateBatch) ToCompositeKeyMap() map[PvtdataCompositeKey]*statedb.VersionedValue {
204-
m := make(map[PvtdataCompositeKey]*statedb.VersionedValue)
206+
func (p PvtUpdateBatch) ToCompositeKeyMap() PvtdataCompositeKeyMap {
207+
m := make(PvtdataCompositeKeyMap)
205208
for ns, nsBatch := range p.UpdateMap {
206209
for _, coll := range nsBatch.GetCollectionNames() {
207210
for key, vv := range nsBatch.GetUpdates(coll) {

core/ledger/kvledger/txmgmt/pvtstatepurgemgmt/expiry_keeper.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type expiryKeeper interface {
4141
updateBookkeeping(toTrack []*expiryInfo, toClear []*expiryInfoKey) error
4242
// retrieve returns the keys info that are supposed to be expired by the given block number
4343
retrieve(expiringAtBlkNum uint64) ([]*expiryInfo, error)
44+
// retrieveByExpiryKey retrieves the expiryInfo for given expiryKey
45+
retrieveByExpiryKey(expiryKey *expiryInfoKey) (*expiryInfo, error)
4446
}
4547

4648
func newExpiryKeeper(ledgerid string, provider bookkeeping.Provider) expiryKeeper {
@@ -95,6 +97,15 @@ func (ek *expKeeper) retrieve(expiringAtBlkNum uint64) ([]*expiryInfo, error) {
9597
return listExpinfo, nil
9698
}
9799

100+
func (ek *expKeeper) retrieveByExpiryKey(expiryKey *expiryInfoKey) (*expiryInfo, error) {
101+
key := encodeExpiryInfoKey(expiryKey)
102+
value, err := ek.db.Get(key)
103+
if err != nil {
104+
return nil, err
105+
}
106+
return decodeExpiryInfo(key, value)
107+
}
108+
98109
func encodeKV(expinfo *expiryInfo) (key []byte, value []byte, err error) {
99110
key = encodeExpiryInfoKey(expinfo.expiryInfoKey)
100111
value, err = encodeExpiryInfoValue(expinfo.pvtdataKeys)

core/ledger/kvledger/txmgmt/pvtstatepurgemgmt/expiry_schedule_builder_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func TestBuildExpiryScheduleWithMissingPvtdata(t *testing.T) {
107107
}
108108

109109
func putPvtAndHashUpdates(t *testing.T, updates *privacyenabledstate.UpdateBatch, ns, coll, key string, value []byte, ver *version.Height) {
110-
updates.PvtUpdates.Put(ns, coll, key, value, ver)
110+
putPvtUpdates(updates, ns, coll, key, value, ver)
111111
putHashUpdates(updates, ns, coll, key, value, ver)
112112
}
113113

@@ -120,6 +120,10 @@ func putHashUpdates(updates *privacyenabledstate.UpdateBatch, ns, coll, key stri
120120
updates.HashUpdates.Put(ns, coll, util.ComputeStringHash(key), util.ComputeHash(value), ver)
121121
}
122122

123+
func putPvtUpdates(updates *privacyenabledstate.UpdateBatch, ns, coll, key string, value []byte, ver *version.Height) {
124+
updates.PvtUpdates.Put(ns, coll, key, value, ver)
125+
}
126+
123127
func deleteHashUpdates(updates *privacyenabledstate.UpdateBatch, ns, coll, key string, ver *version.Height) {
124128
updates.HashUpdates.Delete(ns, coll, util.ComputeStringHash(key), ver)
125129
}

core/ledger/kvledger/txmgmt/pvtstatepurgemgmt/purge_mgr.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb"
1616
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
1717
"github.com/hyperledger/fabric/core/ledger/pvtdatapolicy"
18+
"github.com/hyperledger/fabric/core/ledger/util"
1819
)
1920

2021
// PurgeMgr manages purging of the expired pvtdata
@@ -27,6 +28,8 @@ type PurgeMgr interface {
2728
DeleteExpiredAndUpdateBookkeeping(
2829
pvtUpdates *privacyenabledstate.PvtUpdateBatch,
2930
hashedUpdates *privacyenabledstate.HashedUpdateBatch) error
31+
// UpdateBookkeepingForPvtDataOfOldBlocks updates the existing expiry entries in the bookkeeper with the given pvtUpdates
32+
UpdateBookkeepingForPvtDataOfOldBlocks(pvtUpdates *privacyenabledstate.PvtUpdateBatch) error
3033
// BlockCommitDone is a callback to the PurgeMgr when the block is committed to the ledger
3134
BlockCommitDone() error
3235
}
@@ -37,7 +40,7 @@ type keyAndVersion struct {
3740
purgeKeyOnly bool
3841
}
3942

40-
type expiryInfoMap map[*privacyenabledstate.HashedCompositeKey]*keyAndVersion
43+
type expiryInfoMap map[privacyenabledstate.HashedCompositeKey]*keyAndVersion
4144

4245
type workingset struct {
4346
toPurge expiryInfoMap
@@ -86,6 +89,28 @@ func (p *purgeMgr) WaitForPrepareToFinish() {
8689
p.lock.Unlock()
8790
}
8891

92+
func (p *purgeMgr) UpdateBookkeepingForPvtDataOfOldBlocks(pvtUpdates *privacyenabledstate.PvtUpdateBatch) error {
93+
builder := newExpiryScheduleBuilder(p.btlPolicy)
94+
pvtUpdateCompositeKeyMap := pvtUpdates.ToCompositeKeyMap()
95+
for k, vv := range pvtUpdateCompositeKeyMap {
96+
builder.add(k.Namespace, k.CollectionName, k.Key, util.ComputeStringHash(k.Key), vv)
97+
}
98+
99+
var updatedList []*expiryInfo
100+
for _, toAdd := range builder.getExpiryInfo() {
101+
toUpdate, err := p.expKeeper.retrieveByExpiryKey(toAdd.expiryInfoKey)
102+
if err != nil {
103+
return err
104+
}
105+
// Though we could update the existing entry (as there should be one due
106+
// to only the keyHash of this pvtUpdateKey), for simplicity and to be less
107+
// expensive, we append a new entry
108+
toUpdate.pvtdataKeys.addAll(toAdd.pvtdataKeys)
109+
updatedList = append(updatedList, toUpdate)
110+
}
111+
return p.expKeeper.updateBookkeeping(updatedList, nil)
112+
}
113+
89114
// DeleteExpiredAndUpdateBookkeeping implements function in the interface 'PurgeMgr'
90115
func (p *purgeMgr) DeleteExpiredAndUpdateBookkeeping(
91116
pvtUpdates *privacyenabledstate.PvtUpdateBatch,
@@ -216,18 +241,18 @@ func (p *purgeMgr) preloadCommittedVersionsInCache(expInfoMap expiryInfoMap) {
216241
}
217242
var hashedKeys []*privacyenabledstate.HashedCompositeKey
218243
for k := range expInfoMap {
219-
hashedKeys = append(hashedKeys, k)
244+
hashedKeys = append(hashedKeys, &k)
220245
}
221246
p.db.LoadCommittedVersionsOfPubAndHashedKeys(nil, hashedKeys)
222247
}
223248

224249
func transformToExpiryInfoMap(expiryInfo []*expiryInfo) expiryInfoMap {
225-
var expinfoMap expiryInfoMap = make(map[*privacyenabledstate.HashedCompositeKey]*keyAndVersion)
250+
expinfoMap := make(expiryInfoMap)
226251
for _, expinfo := range expiryInfo {
227252
for ns, colls := range expinfo.pvtdataKeys.Map {
228253
for coll, keysAndHashes := range colls.Map {
229254
for _, keyAndHash := range keysAndHashes.List {
230-
compositeKey := &privacyenabledstate.HashedCompositeKey{Namespace: ns, CollectionName: coll, KeyHash: string(keyAndHash.Hash)}
255+
compositeKey := privacyenabledstate.HashedCompositeKey{Namespace: ns, CollectionName: coll, KeyHash: string(keyAndHash.Hash)}
231256
expinfoMap[compositeKey] = &keyAndVersion{key: keyAndHash.Key, committingBlock: expinfo.expiryInfoKey.committingBlk}
232257
}
233258
}

core/ledger/kvledger/txmgmt/pvtstatepurgemgmt/purge_mgr_test.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,63 @@ func testPurgeMgr(t *testing.T, dbEnv privacyenabledstate.TestEnv) {
100100
testHelper.checkPvtdataDoesNotExist("ns1", "coll4", "pvtkey4")
101101
}
102102

103+
func TestPurgeMgrForCommittingPvtDataOfOldBlocks(t *testing.T) {
104+
dbEnvs := []privacyenabledstate.TestEnv{
105+
&privacyenabledstate.LevelDBCommonStorageTestEnv{},
106+
&privacyenabledstate.CouchDBCommonStorageTestEnv{},
107+
}
108+
for _, dbEnv := range dbEnvs {
109+
t.Run(dbEnv.GetName(), func(t *testing.T) { testPurgeMgrForCommittingPvtDataOfOldBlocks(t, dbEnv) })
110+
}
111+
}
112+
113+
func testPurgeMgrForCommittingPvtDataOfOldBlocks(t *testing.T, dbEnv privacyenabledstate.TestEnv) {
114+
ledgerid := "testledger-purge-mgr-pvtdata-oldblocks"
115+
btlPolicy := btltestutil.SampleBTLPolicy(
116+
map[[2]string]uint64{
117+
{"ns1", "coll1"}: 1,
118+
},
119+
)
120+
121+
testHelper := &testHelper{}
122+
testHelper.init(t, ledgerid, btlPolicy, dbEnv)
123+
defer testHelper.cleanup()
124+
125+
// committing block 1
126+
block1Updates := privacyenabledstate.NewUpdateBatch()
127+
// pvt data pvtkey1 is missing but the pvtkey2 is present.
128+
// pvtkey1 and pvtkey2 both would get expired and purged while committing block 3
129+
putHashUpdates(block1Updates, "ns1", "coll1", "pvtkey1", []byte("pvtvalue1-1"), version.NewHeight(1, 1))
130+
putPvtAndHashUpdates(t, block1Updates, "ns1", "coll1", "pvtkey2", []byte("pvtvalue1-2"), version.NewHeight(1, 1))
131+
testHelper.commitUpdatesForTesting(1, block1Updates)
132+
133+
// pvtkey1 should not exist but pvtkey2 should exist
134+
testHelper.checkOnlyPvtKeyDoesNotExist("ns1", "coll1", "pvtkey1")
135+
testHelper.checkPvtdataExists("ns1", "coll1", "pvtkey2", []byte("pvtvalue1-2"))
136+
137+
// committing block 2
138+
block2Updates := privacyenabledstate.NewUpdateBatch()
139+
testHelper.commitUpdatesForTesting(2, block2Updates)
140+
141+
// Commit pvtkey1 via commit of missing data and this should be added to toPurge list as it
142+
// should be removed while committing block 3
143+
block1PvtData := privacyenabledstate.NewUpdateBatch()
144+
putPvtUpdates(block1PvtData, "ns1", "coll1", "pvtkey1", []byte("pvtvalue1-1"), version.NewHeight(1, 1))
145+
testHelper.commitPvtDataOfOldBlocksForTesting(block1PvtData)
146+
147+
// both pvtkey1 and pvtkey1 should exist
148+
testHelper.checkPvtdataExists("ns1", "coll1", "pvtkey1", []byte("pvtvalue1-1"))
149+
testHelper.checkPvtdataExists("ns1", "coll1", "pvtkey2", []byte("pvtvalue1-2"))
150+
151+
// committing block 3
152+
block3Updates := privacyenabledstate.NewUpdateBatch()
153+
testHelper.commitUpdatesForTesting(3, block3Updates)
154+
155+
// both pvtkey1 and pvtkey1 should not exist
156+
testHelper.checkPvtdataDoesNotExist("ns1", "coll1", "pvtkey1")
157+
testHelper.checkPvtdataDoesNotExist("ns1", "coll1", "pvtkey2")
158+
}
159+
103160
func TestKeyUpdateBeforeExpiryBlock(t *testing.T) {
104161
dbEnv := &privacyenabledstate.LevelDBCommonStorageTestEnv{}
105162
ledgerid := "testledger-perge-mgr"
@@ -222,8 +279,9 @@ type testHelper struct {
222279
bookkeepingEnv *bookkeeping.TestEnv
223280
dbEnv privacyenabledstate.TestEnv
224281

225-
db privacyenabledstate.DB
226-
purgeMgr PurgeMgr
282+
db privacyenabledstate.DB
283+
purgeMgr PurgeMgr
284+
purgerUsedOnce bool
227285
}
228286

229287
func (h *testHelper) init(t *testing.T, ledgerid string, btlPolicy pvtdatapolicy.BTLPolicy, dbEnv privacyenabledstate.TestEnv) {
@@ -251,6 +309,11 @@ func (h *testHelper) commitUpdatesForTesting(blkNum uint64, updates *privacyenab
251309
h.purgeMgr.BlockCommitDone()
252310
}
253311

312+
func (h *testHelper) commitPvtDataOfOldBlocksForTesting(updates *privacyenabledstate.UpdateBatch) {
313+
assert.NoError(h.t, h.purgeMgr.UpdateBookkeepingForPvtDataOfOldBlocks(updates.PvtUpdates))
314+
assert.NoError(h.t, h.db.ApplyPrivacyAwareUpdates(updates, nil))
315+
}
316+
254317
func (h *testHelper) checkPvtdataExists(ns, coll, key string, value []byte) {
255318
vv, _ := h.fetchPvtdataFronDB(ns, coll, key)
256319
vv, hashVersion := h.fetchPvtdataFronDB(ns, coll, key)
@@ -272,6 +335,12 @@ func (h *testHelper) checkOnlyPvtKeyExists(ns, coll, key string, value []byte) {
272335
assert.Equal(h.t, value, vv.Value)
273336
}
274337

338+
func (h *testHelper) checkOnlyPvtKeyDoesNotExist(ns, coll, key string) {
339+
kv, err := h.db.GetPrivateData(ns, coll, key)
340+
assert.Nil(h.t, err)
341+
assert.Nil(h.t, kv)
342+
}
343+
275344
func (h *testHelper) checkOnlyKeyHashExists(ns, coll, key string) {
276345
vv, hashVersion := h.fetchPvtdataFronDB(ns, coll, key)
277346
assert.Nil(h.t, vv)

core/ledger/kvledger/txmgmt/pvtstatepurgemgmt/pvtdata_key_helper.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ SPDX-License-Identifier: Apache-2.0
66

77
package pvtstatepurgemgmt
88

9+
func (pvtdataKeys *PvtdataKeys) addAll(toAdd *PvtdataKeys) {
10+
for ns, colls := range toAdd.Map {
11+
for coll, keysAndHashes := range colls.Map {
12+
for _, k := range keysAndHashes.List {
13+
pvtdataKeys.add(ns, coll, k.Key, k.Hash)
14+
}
15+
}
16+
}
17+
}
18+
919
func (pvtdataKeys *PvtdataKeys) add(ns string, coll string, key string, keyhash []byte) {
1020
colls := pvtdataKeys.getOrCreateCollections(ns)
1121
keysAndHashes := colls.getOrCreateKeysAndHashes(coll)

core/ledger/kvledger/txmgmt/statedb/commontests/test_common.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,3 +947,22 @@ func TestItrWithoutClose(t *testing.T, itr statedb.ResultsIterator, expectedKeys
947947
assert.NoError(t, err, "An unexpected error was thrown during iterator Next()")
948948
assert.Nil(t, queryResult)
949949
}
950+
951+
func TestApplyUpdatesWithNilHeight(t *testing.T, dbProvider statedb.VersionedDBProvider) {
952+
db, err := dbProvider.GetDBHandle("test-apply-updates-with-nil-height")
953+
assert.NoError(t, err)
954+
955+
batch1 := statedb.NewUpdateBatch()
956+
batch1.Put("ns", "key1", []byte("value1"), version.NewHeight(1, 4))
957+
savePoint := version.NewHeight(1, 5)
958+
assert.NoError(t, db.ApplyUpdates(batch1, savePoint))
959+
960+
batch2 := statedb.NewUpdateBatch()
961+
batch2.Put("ns", "key1", []byte("value2"), version.NewHeight(1, 1))
962+
assert.NoError(t, db.ApplyUpdates(batch2, nil))
963+
964+
ht, err := db.GetLatestSavePoint()
965+
assert.NoError(t, err)
966+
assert.Equal(t, savePoint, ht) // savepoint should still be what was set with batch1
967+
// (because batch2 calls ApplyUpdates with savepoint as nil)
968+
}

core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,14 @@ func (vdb *VersionedDB) ensureFullCommitAndRecordSavepoint(height *version.Heigh
489489
if err := vdb.ensureFullCommit(dbs); err != nil {
490490
return err
491491
}
492+
493+
// If a given height is nil, it denotes that we are committing pvt data of old blocks.
494+
// In this case, we should not store a savepoint for recovery. The lastUpdatedOldBlockList
495+
// in the pvtstore acts as a savepoint for pvt data.
496+
if height == nil {
497+
return nil
498+
}
499+
492500
// construct savepoint document and save
493501
savepointCouchDoc, err := encodeSavepoint(height)
494502
if err != nil {

core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,3 +686,9 @@ func TestPaginatedQueryValidation(t *testing.T) {
686686
assert.Error(t, err, "An should have been thrown for an invalid options")
687687

688688
}
689+
690+
func TestApplyUpdatesWithNilHeight(t *testing.T) {
691+
env := NewTestVDBEnv(t)
692+
defer env.Cleanup()
693+
commontests.TestApplyUpdatesWithNilHeight(t, env.DBProvider)
694+
}

core/ledger/kvledger/txmgmt/statedb/stateleveldb/stateleveldb.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,13 @@ func (vdb *versionedDB) ApplyUpdates(batch *statedb.UpdateBatch, height *version
185185
}
186186
}
187187
}
188-
dbBatch.Put(savePointKey, height.ToBytes())
188+
// Record a savepoint at a given height
189+
// If a given height is nil, it denotes that we are committing pvt data of old blocks.
190+
// In this case, we should not store a savepoint for recovery. The lastUpdatedOldBlockList
191+
// in the pvtstore acts as a savepoint for pvt data.
192+
if height != nil {
193+
dbBatch.Put(savePointKey, height.ToBytes())
194+
}
189195
// Setting snyc to true as a precaution, false may be an ok optimization after further testing.
190196
if err := vdb.db.WriteBatch(dbBatch, true); err != nil {
191197
return err

core/ledger/kvledger/txmgmt/statedb/stateleveldb/stateleveldb_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,9 @@ func TestPaginatedRangeQuery(t *testing.T) {
130130
defer env.Cleanup()
131131
commontests.TestPaginatedRangeQuery(t, env.DBProvider)
132132
}
133+
134+
func TestApplyUpdatesWithNilHeight(t *testing.T) {
135+
env := NewTestVDBEnv(t)
136+
defer env.Cleanup()
137+
commontests.TestApplyUpdatesWithNilHeight(t, env.DBProvider)
138+
}

core/ledger/kvledger/txmgmt/txmgr/lockbasedtxmgr/lockbased_txmgr.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ func (txmgr *LockBasedTxMgr) Commit() error {
399399
}
400400
defer func() {
401401
txmgr.pvtdataPurgeMgr.PrepareForExpiringKeys(txmgr.current.blockNum() + 1)
402-
logger.Debugf("Cleared version cache and launched the background routine for preparing keys to purge with the next block")
402+
logger.Debugf("launched the background routine for preparing keys to purge with the next block")
403403
txmgr.reset()
404404
}()
405405

@@ -423,7 +423,11 @@ func (txmgr *LockBasedTxMgr) Commit() error {
423423
}
424424
// only during the exclusive lock duration, we should clear the cache as the cache is being
425425
// used by the old pvtData committer as well
426-
txmgr.clearCache()
426+
txmgr.clearCache() // note that we should clear the cache before calling
427+
// PrepareForExpiringKeys as it uses the cache as well. To be precise,
428+
// we should not clear the cache until PrepareForExpiringKeys completes
429+
// the task.
430+
logger.Debugf("cleared cached")
427431
txmgr.commitRWLock.Unlock()
428432
// EXCLUSIVE LOCK ENDS
429433
logger.Debugf("Updates committed to state database")

0 commit comments

Comments
 (0)