forked from celestiaorg/celestia-node
-
Notifications
You must be signed in to change notification settings - Fork 0
/
store.go
322 lines (273 loc) · 9 KB
/
store.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
package nodebuilder
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/dgraph-io/badger/v4/options"
"github.com/gofrs/flock"
"github.com/ipfs/go-datastore"
dsbadger "github.com/ipfs/go-ds-badger4"
"github.com/mitchellh/go-homedir"
"github.com/celestiaorg/celestia-node/libs/keystore"
nodemod "github.com/celestiaorg/celestia-node/nodebuilder/node"
"github.com/celestiaorg/celestia-node/nodebuilder/p2p"
"github.com/celestiaorg/celestia-node/share"
)
var (
// ErrOpened is thrown on attempt to open already open/in-use Store.
ErrOpened = errors.New("node: store is in use")
// ErrNotInited is thrown on attempt to open Store without initialization.
ErrNotInited = errors.New("node: store is not initialized")
// ErrNoOpenStore is thrown when no opened Store is found, indicating that no node is running.
ErrNoOpenStore = errors.New("no opened Node Store found (no node is running)")
)
// Store encapsulates storage for the Node. Basically, it is the Store of all Stores.
// It provides access for the Node data stored in root directory e.g. '~/.celestia'.
type Store interface {
// Path reports the FileSystem path of Store.
Path() string
// Keystore provides a Keystore to access keys.
Keystore() (keystore.Keystore, error)
// Datastore provides a Datastore - a KV store for arbitrary data to be stored on disk.
Datastore() (datastore.Batching, error)
// Config loads the stored Node config.
Config() (*Config, error)
// PutConfig alters the stored Node config.
PutConfig(*Config) error
// Close closes the Store freeing up acquired resources and locks.
Close() error
}
// OpenStore creates new FS Store under the given 'path'.
// To be opened the Store must be initialized first, otherwise ErrNotInited is thrown.
// OpenStore takes a file Lock on directory, hence only one Store can be opened at a time under the
// given 'path', otherwise ErrOpened is thrown.
func OpenStore(path string, ring keyring.Keyring) (Store, error) {
path, err := storePath(path)
if err != nil {
return nil, err
}
flk := flock.New(lockPath(path))
ok, err := flk.TryLock()
if err != nil {
return nil, fmt.Errorf("locking file: %w", err)
}
if !ok {
return nil, ErrOpened
}
if !IsInit(path) {
err := errors.Join(ErrNotInited, flk.Unlock())
return nil, err
}
ks, err := keystore.NewFSKeystore(keysPath(path), ring)
if err != nil {
err = errors.Join(err, flk.Unlock())
return nil, err
}
return &fsStore{
path: path,
dirLock: flk,
keys: ks,
}, nil
}
func (f *fsStore) Path() string {
return f.path
}
func (f *fsStore) Config() (*Config, error) {
cfg, err := LoadConfig(configPath(f.path))
if err != nil {
return nil, fmt.Errorf("node: can't load Config: %w", err)
}
return cfg, nil
}
func (f *fsStore) PutConfig(cfg *Config) error {
err := SaveConfig(configPath(f.path), cfg)
if err != nil {
return fmt.Errorf("node: can't save Config: %w", err)
}
return nil
}
func (f *fsStore) Keystore() (_ keystore.Keystore, err error) {
if f.keys == nil {
return nil, fmt.Errorf("node: no Keystore found")
}
return f.keys, nil
}
func (f *fsStore) Datastore() (datastore.Batching, error) {
f.dataMu.Lock()
defer f.dataMu.Unlock()
if f.data != nil {
return f.data, nil
}
cfg := constraintBadgerConfig()
ds, err := dsbadger.NewDatastore(dataPath(f.path), cfg)
if err != nil {
return nil, fmt.Errorf("node: can't open Badger Datastore: %w", err)
}
f.data = ds
return ds, nil
}
func (f *fsStore) Close() (err error) {
err = errors.Join(err, f.dirLock.Close())
f.dataMu.Lock()
if f.data != nil {
err = errors.Join(err, f.data.Close())
}
f.dataMu.Unlock()
return
}
type fsStore struct {
path string
dataMu sync.Mutex
data datastore.Batching
keys keystore.Keystore
dirLock *flock.Flock // protects directory
}
// DiscoverOpened finds a path of an opened Node Store and returns its path.
// If multiple nodes are running, it only returns the path of the first found node.
// Network is favored over node type.
//
// Network preference order: Mainnet, Mocha, Arabica, Private, Custom
// Type preference order: Bridge, Full, Light
func DiscoverOpened() (string, error) {
defaultNetwork := p2p.GetNetworks()
nodeTypes := nodemod.GetTypes()
for _, n := range defaultNetwork {
for _, tp := range nodeTypes {
path, err := DefaultNodeStorePath(tp.String(), n.String())
if err != nil {
return "", err
}
ok, _ := IsOpened(path)
if ok {
return path, nil
}
}
}
return "", ErrNoOpenStore
}
// DefaultNodeStorePath constructs the default node store path using the given
// node type and network.
var DefaultNodeStorePath = func(tp string, network string) (string, error) {
home := os.Getenv("CELESTIA_HOME")
if home == "" {
var err error
home, err = os.UserHomeDir()
if err != nil {
return "", err
}
}
if network == p2p.Mainnet.String() {
return fmt.Sprintf("%s/.celestia-%s", home, strings.ToLower(tp)), nil
}
// only include network name in path for testnets and custom networks
return fmt.Sprintf(
"%s/.celestia-%s-%s",
home,
strings.ToLower(tp),
strings.ToLower(network),
), nil
}
// IsOpened checks if the Store is opened in a directory by checking its file lock.
func IsOpened(path string) (bool, error) {
flk := flock.New(lockPath(path))
ok, err := flk.TryLock()
if err != nil {
return false, fmt.Errorf("locking file: %w", err)
}
err = flk.Unlock()
if err != nil {
return false, fmt.Errorf("unlocking file: %w", err)
}
return !ok, nil
}
func storePath(path string) (string, error) {
return homedir.Expand(filepath.Clean(path))
}
func configPath(base string) string {
return filepath.Join(base, "config.toml")
}
func lockPath(base string) string {
return filepath.Join(base, ".lock")
}
func keysPath(base string) string {
return filepath.Join(base, "keys")
}
func blocksPath(base string) string {
return filepath.Join(base, "blocks")
}
func transientsPath(base string) string {
// we don't actually use the transients directory anymore, but it could be populated from previous
// versions.
return filepath.Join(base, "transients")
}
func indexPath(base string) string {
return filepath.Join(base, "index")
}
func dataPath(base string) string {
return filepath.Join(base, "data")
}
// constraintBadgerConfig returns BadgerDB configuration optimized for low memory usage and more frequent
// compaction which prevents memory spikes.
// This is particularly important for LNs with restricted memory resources.
//
// With the following configuration, a LN uses up to 300iB of RAM during initial sync/sampling
// and up to 200MiB during normal operation. (on 4 core CPU, 8GiB RAM droplet)
//
// With the following configuration and "-tags=jemalloc", a LN uses no more than 180MiB during initial
// sync/sampling and up to 100MiB during normal operation. (same hardware spec)
// NOTE: To enable jemalloc, build celestia-node with "-tags=jemalloc" flag, which configures Badger to
// use jemalloc instead of Go's default allocator.
//
// TODO(@Wondertan): Consider alternative less constraint configuration for FN/BN
// TODO(@Wondertan): Consider dynamic memory allocation based on available RAM
func constraintBadgerConfig() *dsbadger.Options {
opts := dsbadger.DefaultOptions // this must be copied
// ValueLog:
// 2mib default => share.Size - makes sure headers and samples are stored in value log
// This *tremendously* reduces the amount of memory used by the node, up to 10 times less during
// compaction
opts.ValueThreshold = share.Size
// make sure we don't have any limits for stored headers
opts.ValueLogMaxEntries = 100000000
// run value log GC more often to spread the work over time
opts.GcInterval = time.Minute * 1
// default 0.5 => 0.125 - makes sure value log GC is more aggressive on reclaiming disk space
opts.GcDiscardRatio = 0.125
// badger stores checksum for every value, but doesn't verify it by default
// enabling this option may allow us to see detect corrupted data
opts.ChecksumVerificationMode = options.OnBlockRead
opts.VerifyValueChecksum = true
// default 64mib => 0 - disable block cache
// most of our component maintain their own caches, so this is not needed
opts.BlockCacheSize = 0
// not much gain as it compresses the LSM only as well compression requires block cache
opts.Compression = options.None
// MemTables:
// default 64mib => 16mib - decreases memory usage and makes compaction more often
opts.MemTableSize = 16 << 20
// default 5 => 3
opts.NumMemtables = 3
// default 5 => 3
opts.NumLevelZeroTables = 3
// default 15 => 5 - this prevents memory growth on CPU constraint systems by blocking all writers
opts.NumLevelZeroTablesStall = 5
// Compaction:
// Dynamic compactor allocation
compactors := runtime.NumCPU() / 2
if compactors < 2 {
compactors = 2 // can't be less than 2
}
if compactors > opts.MaxLevels { // ensure there is no more compactors than db table levels
compactors = opts.MaxLevels
}
opts.NumCompactors = compactors
// makes sure badger is always compacted on shutdown
opts.CompactL0OnClose = true
return &opts
}