Skip to content

Commit d323148

Browse files
feat(ledger): optimize pvtdata retrieval and extend regression tests
Optimizes GetPvtDataByBlockNum by caching purge markers (N+1 fix) and adds regression test for FAB-15704. Signed-off-by: Sukanya Patnaik <[email protected]>
1 parent 356f2b4 commit d323148

File tree

7 files changed

+98
-23
lines changed

7 files changed

+98
-23
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package tests
8+
9+
import (
10+
"testing"
11+
12+
"github.com/hyperledger/fabric/core/ledger/kvledger"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestRollbackKVLedgerPvtDataRevival(t *testing.T) {
17+
env := newEnv(t)
18+
defer env.cleanup()
19+
env.initLedgerMgmt()
20+
l := env.createTestLedgerFromGenesisBlk("ledgerRevival")
21+
collConf := []*collConf{{name: "coll1", btl: 1}}
22+
23+
// Block 1: Deploy cc1 with coll1 (BTL=1)
24+
l.simulateDeployTx("cc1", collConf)
25+
l.cutBlockAndCommitLegacy()
26+
27+
// Block 2: Commit pvt data "key1"="value1" in "coll1"
28+
l.simulateDataTx("", func(s *simulator) {
29+
s.setPvtdata("cc1", "coll1", "key1", "value1")
30+
})
31+
l.cutBlockAndCommitLegacy()
32+
33+
// Verify key1 exists
34+
l.verifyPvtState("cc1", "coll1", "key1", "value1")
35+
36+
// Block 3: Commit empty block. key1 should still be live (BTL=1 means live in block 2 and 3? or just 2?)
37+
// In TestRollbackKVLedgerWithBTL:
38+
// block 2 (data) -> block 3 (live) -> block 4 (purged).
39+
l.cutBlockAndCommitLegacy()
40+
// Verify key1 still exists
41+
l.verifyPvtState("cc1", "coll1", "key1", "value1")
42+
43+
// Block 4: Commit empty block. key1 should be purged.
44+
l.cutBlockAndCommitLegacy()
45+
46+
// Verify key1 is purged
47+
l.verifyPvtState("cc1", "coll1", "key1", "")
48+
49+
env.closeLedgerMgmt()
50+
51+
// Rollback to Block 2.
52+
// This makes the ledger height 2.
53+
// The state should reflect Block 2 (key1 present).
54+
// However, the pvt data for Block 2 *might* have been purged from pvtstore at Block 4 commit.
55+
// Let's see if Rollback handles this.
56+
err := kvledger.RollbackKVLedger(env.initializer.Config.RootFSPath, "ledgerRevival", 2)
57+
require.NoError(t, err)
58+
59+
env.initLedgerMgmt()
60+
l = env.openTestLedger("ledgerRevival")
61+
62+
// Verify key1 is present
63+
// If this fails, then Rollback does not restore purged private data.
64+
l.verifyPvtState("cc1", "coll1", "key1", "value1")
65+
}

core/ledger/kvledger/tests/rollback_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ func TestRollbackKVLedger(t *testing.T) {
6262
require.NoError(t, err)
6363
require.Equal(t, bcInfo, actualBcInfo)
6464
dataHelper.verifyLedgerContent(l)
65-
// TODO: extend integration test with BTL support for pvtData. FAB-15704
6665
}
6766

6867
func TestRollbackKVLedgerWithBTL(t *testing.T) {

core/ledger/pvtdatastorage/store.go

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ func (s *Store) GetPvtDataByBlockNum(blockNum uint64, filter ledger.PvtNsCollFil
491491
var currentTxNum uint64
492492
var currentTxWsetAssember *txPvtdataAssembler
493493
firstItr := true
494+
purgeMarkerCache := make(map[string]*version.Height)
494495

495496
for itr.Next() {
496497
dataKeyBytes := itr.Key()
@@ -524,7 +525,21 @@ func (s *Store) GetPvtDataByBlockNum(blockNum uint64, filter ledger.PvtNsCollFil
524525
return nil, err
525526
}
526527

527-
if err := s.removePurgedDataFromCollPvtRWset(dataKey, dataValue); err != nil {
528+
529+
// Check the cache for purge marker height
530+
cacheKey := dataKey.ns + "~" + dataKey.coll
531+
var purgeMarkerHt *version.Height
532+
var exists bool
533+
if purgeMarkerHt, exists = purgeMarkerCache[cacheKey]; !exists {
534+
var err error
535+
purgeMarkerHt, err = s.retrieveLatestPurgeKeyCollMarkerHt(dataKey.ns, dataKey.coll)
536+
if err != nil {
537+
return nil, err
538+
}
539+
purgeMarkerCache[cacheKey] = purgeMarkerHt
540+
}
541+
542+
if err := s.removePurgedDataFromCollPvtRWset(dataKey, dataValue, purgeMarkerHt); err != nil {
528543
return nil, err
529544
}
530545

@@ -558,19 +573,7 @@ func (s *Store) retrieveLatestPurgeKeyCollMarkerHt(ns, coll string) (*version.He
558573
// or the height of `purgeMarkerCollKey` is lower than the <ns, coll> in the data key (which means that the last purge of any key from the collection
559574
// was prior to the given key commit). The main purpose of this function is to optimize while filtering the purge data by avoiding computing hashes
560575
// of individual keys, all the time
561-
func (s *Store) keyPotentiallyPurged(k *dataKey) (bool, error) {
562-
purgeKeyCollMarkerHt, err := s.retrieveLatestPurgeKeyCollMarkerHt(k.ns, k.coll)
563-
if purgeKeyCollMarkerHt == nil || err != nil {
564-
return false, err
565-
}
566-
567-
keyHt := &version.Height{
568-
BlockNum: k.blkNum,
569-
TxNum: k.txNum,
570-
}
571576

572-
return keyHt.Compare(purgeKeyCollMarkerHt) <= 0, nil
573-
}
574577

575578
func (s *Store) RemoveAppInitiatedPurgesUsingReconMarker(
576579
kvHashes map[string][]byte, ns, coll string, blkNum, txNum uint64,
@@ -611,10 +614,18 @@ func (s *Store) RemoveAppInitiatedPurgesUsingReconMarker(
611614
return trimmedKVHashes, nil
612615
}
613616

614-
func (s *Store) removePurgedDataFromCollPvtRWset(k *dataKey, v *rwset.CollectionPvtReadWriteSet) error {
615-
purgePossible, err := s.keyPotentiallyPurged(k)
616-
if !purgePossible || err != nil {
617-
return err
617+
func (s *Store) removePurgedDataFromCollPvtRWset(k *dataKey, v *rwset.CollectionPvtReadWriteSet, purgeMarkerHt *version.Height) error {
618+
var purgePossible bool
619+
if purgeMarkerHt != nil {
620+
keyHt := &version.Height{
621+
BlockNum: k.blkNum,
622+
TxNum: k.txNum,
623+
}
624+
purgePossible = keyHt.Compare(purgeMarkerHt) <= 0
625+
}
626+
627+
if !purgePossible {
628+
return nil
618629
}
619630

620631
collRWSet, err := rwsetutil.CollPvtRwSetFromProtoMsg(v)

docs/source/prereqs.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ Optional: Install the latest Fabric supported version of [Go](https://golang.org
7373
installed (only required if you will be writing Go chaincode or SDK applications).
7474

7575
```shell
76-
brew install [email protected].4
77-
go version # => go1.25.4 darwin/amd64
76+
brew install [email protected].5
77+
go version # => go1.25.5 darwin/amd64
7878
```
7979

8080
### JQ

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/hyperledger/fabric
22

3-
go 1.25.4
3+
go 1.25.5
44

55
require (
66
code.cloudfoundry.org/clock v1.15.0

tools/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module tools
22

3-
go 1.25.4
3+
go 1.25.5
44

55
tool (
66
github.com/AlekSi/gocov-xml

vagrant/golang.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# SPDX-License-Identifier: Apache-2.0
66

77
GOROOT='/opt/go'
8-
GO_VERSION=1.25.4
8+
GO_VERSION=1.25.5
99

1010
# ----------------------------------------------------------------
1111
# Install Golang

0 commit comments

Comments
 (0)