Skip to content

Commit 932aaaa

Browse files
authored
Merge pull request #6285 from oasisprotocol/peternose/trivial/persistent-lb-store
go/consensus/cometbft/light: Use persistent store for stateless client
2 parents 8041d0a + a81a9dd commit 932aaaa

File tree

5 files changed

+226
-8
lines changed

5 files changed

+226
-8
lines changed

.changelog/6285.trivial.md

Whitespace-only changes.

go/consensus/cometbft/cometbft.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func New(
4545
}
4646
return node, nil
4747
case config.ModeStatelessClient:
48-
node, err := createStatelessNode(ctx, identity, genesis, doc, genesisDoc, p2p)
48+
node, err := createStatelessNode(ctx, dataDir, identity, genesis, doc, genesisDoc, p2p)
4949
if err != nil {
5050
return nil, fmt.Errorf("failed to create stateless node: %w", err)
5151
}
@@ -97,6 +97,7 @@ func createFullNode(
9797

9898
func createStatelessNode(
9999
ctx context.Context,
100+
dataDir string,
100101
identity *identity.Identity,
101102
genesis genesisAPI.Provider,
102103
doc *genesisAPI.Document,
@@ -108,7 +109,7 @@ func createStatelessNode(
108109
return nil, fmt.Errorf("failed to create provider: %w", err)
109110
}
110111

111-
lightClient, err := createLightClient(ctx, genesisDoc, doc, p2p)
112+
lightClient, err := createLightClient(ctx, dataDir, genesisDoc, doc, p2p)
112113
if err != nil {
113114
return nil, fmt.Errorf("failed to create light client: %w", err)
114115
}
@@ -164,6 +165,7 @@ func createProvider(identity *identity.Identity) (consensusAPI.Backend, error) {
164165

165166
func createLightClient(
166167
ctx context.Context,
168+
dataDir string,
167169
genesisDoc *cmttypes.GenesisDoc,
168170
doc *genesisAPI.Document,
169171
p2p p2pAPI.Service,
@@ -180,6 +182,7 @@ func createLightClient(
180182
Height: int64(config.GlobalConfig.Consensus.LightClient.Trust.Height),
181183
Hash: hash,
182184
},
185+
DataDir: dataDir,
183186
}
184187

185188
return light.NewClient(ctx, doc.ChainContext(), p2p, cfg)

go/consensus/cometbft/light/client.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import (
44
"bytes"
55
"context"
66
"fmt"
7+
"path/filepath"
78
"sync"
89
"time"
910

10-
dbm "github.com/cometbft/cometbft-db"
11+
cmtdb "github.com/cometbft/cometbft-db"
1112
cmtlight "github.com/cometbft/cometbft/light"
1213
cmtlightprovider "github.com/cometbft/cometbft/light/provider"
1314
cmtlightdb "github.com/cometbft/cometbft/light/store/db"
@@ -17,16 +18,36 @@ import (
1718
"github.com/oasisprotocol/oasis-core/go/config"
1819
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
1920
"github.com/oasisprotocol/oasis-core/go/consensus/cometbft/common"
21+
cdb "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/db"
2022
"github.com/oasisprotocol/oasis-core/go/p2p/rpc"
2123
)
2224

25+
const (
26+
// dbName is the name of the database used to store trusted light blocks.
27+
dbName = "consensus/light"
28+
29+
// storeHighWatermark is the maximum number of blocks the pruned store
30+
// can hold before triggering pruning.
31+
storeHighWatermark = 21_000
32+
33+
// storeLowWatermark is the number of blocks to retain in the pruned store
34+
// after pruning is triggered.
35+
storeLowWatermark = 20_000
36+
)
37+
2338
// Config is the configuration for the light client.
2439
type Config struct {
2540
// GenesisDocument is the CometBFT genesis document.
2641
GenesisDocument *cmttypes.GenesisDoc
2742

2843
// TrustOptions are CometBFT light client trust options.
2944
TrustOptions cmtlight.TrustOptions
45+
46+
// DataDir is the node's data directory.
47+
//
48+
// If set, a persistent store is used to store trusted light blocks.
49+
// Otherwise, an in-memory store is used.
50+
DataDir string
3051
}
3152

3253
// Client is a CometBFT consensus light client that talks with remote oasis-nodes that are using
@@ -40,10 +61,7 @@ type Client struct {
4061
lightClient *lazyClient
4162
}
4263

43-
// NewClient creates an internal and non-persistent light client.
44-
//
45-
// This client is instantiated from the provided (obtained out of bound) trusted block
46-
// and is used internally for CometBFT's state sync protocol.
64+
// NewClient creates a new light client.
4765
func NewClient(ctx context.Context, chainContext string, p2p rpc.P2P, cfg Config) (*Client, error) {
4866
pool := NewProviderPool(ctx, chainContext, p2p)
4967
providers := make([]*Provider, 0, numWitnesses+1)
@@ -57,12 +75,27 @@ func NewClient(ctx context.Context, chainContext string, p2p rpc.P2P, cfg Config
5775
witnesses = append(witnesses, provider)
5876
}
5977

78+
var db cmtdb.DB
79+
switch cfg.DataDir {
80+
case "":
81+
db = cmtdb.NewMemDB()
82+
default:
83+
fn := filepath.Join(cfg.DataDir, dbName)
84+
cdb, err := cdb.New(fn, false)
85+
if err != nil {
86+
return nil, err
87+
}
88+
db = cmtdb.NewPrefixDB(cdb, []byte{})
89+
}
90+
store := cmtlightdb.New(db, "")
91+
store = newPrunedStore(store, storeHighWatermark, storeLowWatermark)
92+
6093
lightClient, err := newLazyClient(
6194
cfg.GenesisDocument.ChainID,
6295
cfg.TrustOptions,
6396
primary,
6497
witnesses,
65-
cmtlightdb.New(dbm.NewMemDB(), ""),
98+
store,
6699
cmtlight.MaxRetryAttempts(lcMaxRetryAttempts), // TODO: Make this configurable.
67100
cmtlight.Logger(common.NewLogAdapter(!config.GlobalConfig.Consensus.LogDebug)),
68101
cmtlight.DisableProviderRemoval(),
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package light
2+
3+
import (
4+
"github.com/cometbft/cometbft/light/store"
5+
cmttypes "github.com/cometbft/cometbft/types"
6+
)
7+
8+
// prunedStore is a light block store that automatically prunes old blocks.
9+
//
10+
// When the number of stored blocks exceeds the high watermark, it prunes
11+
// blocks until the number of stored blocks reaches the low watermark.
12+
type prunedStore struct {
13+
store store.Store
14+
high uint16
15+
low uint16
16+
}
17+
18+
// newPrunedStore creates a new store backed by the given store,
19+
// configured with high and low watermarks.
20+
//
21+
// If high is set to zero, automatic pruning is disabled.
22+
func newPrunedStore(store store.Store, high uint16, low uint16) *prunedStore {
23+
return &prunedStore{
24+
store: store,
25+
high: high,
26+
low: low,
27+
}
28+
}
29+
30+
// DeleteLightBlock implements store.Store.
31+
func (p *prunedStore) DeleteLightBlock(height int64) error {
32+
return p.store.DeleteLightBlock(height)
33+
}
34+
35+
// FirstLightBlockHeight implements store.Store.
36+
func (p *prunedStore) FirstLightBlockHeight() (int64, error) {
37+
return p.store.FirstLightBlockHeight()
38+
}
39+
40+
// LastLightBlockHeight implements store.Store.
41+
func (p *prunedStore) LastLightBlockHeight() (int64, error) {
42+
return p.store.LastLightBlockHeight()
43+
}
44+
45+
// LightBlock implements store.Store.
46+
func (p *prunedStore) LightBlock(height int64) (*cmttypes.LightBlock, error) {
47+
return p.store.LightBlock(height)
48+
}
49+
50+
// LightBlockBefore implements store.Store.
51+
func (p *prunedStore) LightBlockBefore(height int64) (*cmttypes.LightBlock, error) {
52+
return p.store.LightBlockBefore(height)
53+
}
54+
55+
// Prune implements store.Store.
56+
func (p *prunedStore) Prune(size uint16) error {
57+
return p.store.Prune(size)
58+
}
59+
60+
// SaveLightBlock implements store.Store.
61+
func (p *prunedStore) SaveLightBlock(lb *cmttypes.LightBlock) error {
62+
if p.high > 0 && p.Size() >= p.high {
63+
if err := p.Prune(p.low); err != nil {
64+
return err
65+
}
66+
}
67+
return p.store.SaveLightBlock(lb)
68+
}
69+
70+
// Size implements store.Store.
71+
func (p *prunedStore) Size() uint16 {
72+
return p.store.Size()
73+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package light
2+
3+
import (
4+
"testing"
5+
6+
cmttypes "github.com/cometbft/cometbft/types"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestPrunedStore(t *testing.T) {
11+
t.Run("Pruning enabled", func(t *testing.T) {
12+
high := uint16(5)
13+
low := uint16(2)
14+
store := newMockStore()
15+
ps := newPrunedStore(store, high, low)
16+
17+
// Fill the store.
18+
for i := range high {
19+
lb := &cmttypes.LightBlock{
20+
SignedHeader: &cmttypes.SignedHeader{
21+
Header: &cmttypes.Header{
22+
Height: int64(i),
23+
},
24+
},
25+
}
26+
err := ps.SaveLightBlock(lb)
27+
require.NoError(t, err)
28+
require.Equal(t, i+1, ps.Size())
29+
}
30+
31+
// Trigger pruning.
32+
lb := &cmttypes.LightBlock{
33+
SignedHeader: &cmttypes.SignedHeader{
34+
Header: &cmttypes.Header{
35+
Height: int64(high),
36+
},
37+
},
38+
}
39+
err := ps.SaveLightBlock(lb)
40+
require.NoError(t, err)
41+
require.Equal(t, low+1, ps.Size())
42+
})
43+
44+
t.Run("Pruning disabled", func(t *testing.T) {
45+
high := uint16(0)
46+
low := uint16(0)
47+
store := newMockStore()
48+
ps := newPrunedStore(store, high, low)
49+
50+
// Fill the store.
51+
for i := range 2 * high {
52+
lb := &cmttypes.LightBlock{
53+
SignedHeader: &cmttypes.SignedHeader{
54+
Header: &cmttypes.Header{
55+
Height: int64(i),
56+
},
57+
},
58+
}
59+
err := ps.SaveLightBlock(lb)
60+
require.NoError(t, err)
61+
require.Equal(t, i+1, ps.Size())
62+
}
63+
})
64+
}
65+
66+
type mockStore struct {
67+
blocks []*cmttypes.LightBlock
68+
}
69+
70+
func newMockStore() *mockStore {
71+
return &mockStore{
72+
blocks: make([]*cmttypes.LightBlock, 0),
73+
}
74+
}
75+
76+
func (s *mockStore) SaveLightBlock(lb *cmttypes.LightBlock) error {
77+
s.blocks = append(s.blocks, lb)
78+
return nil
79+
}
80+
81+
func (s *mockStore) DeleteLightBlock(int64) error {
82+
panic("not implemented")
83+
}
84+
85+
func (s *mockStore) FirstLightBlockHeight() (int64, error) {
86+
panic("not implemented")
87+
}
88+
89+
func (s *mockStore) LastLightBlockHeight() (int64, error) {
90+
panic("not implemented")
91+
}
92+
93+
func (s *mockStore) LightBlock(int64) (*cmttypes.LightBlock, error) {
94+
panic("not implemented")
95+
}
96+
97+
func (s *mockStore) LightBlockBefore(int64) (*cmttypes.LightBlock, error) {
98+
panic("not implemented")
99+
}
100+
101+
func (s *mockStore) Size() uint16 {
102+
return uint16(len(s.blocks))
103+
}
104+
105+
func (s *mockStore) Prune(size uint16) error {
106+
n := max(0, len(s.blocks)-int(size))
107+
s.blocks = s.blocks[n:]
108+
return nil
109+
}

0 commit comments

Comments
 (0)