Skip to content

Commit 1c93d4f

Browse files
committed
[factory] implement versioned stateDB to support archive mode
1 parent a3309f0 commit 1c93d4f

File tree

11 files changed

+286
-58
lines changed

11 files changed

+286
-58
lines changed

blockchain/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ type (
5252
EnableStateDBCaching bool `yaml:"enableStateDBCaching"`
5353
// EnableArchiveMode is only meaningful when EnableTrielessStateDB is false
5454
EnableArchiveMode bool `yaml:"enableArchiveMode"`
55+
// VersionedNamespaces specifies the versioned namespaces for versioned state DB
56+
VersionedNamespaces []string `yaml:"versionedNamespaces"`
57+
// VersionedMetadata specifies the metadata namespace for versioned state DB
58+
VersionedMetadata string `yaml:"versionedMetadata"`
5559
// EnableAsyncIndexWrite enables writing the block actions' and receipts' index asynchronously
5660
EnableAsyncIndexWrite bool `yaml:"enableAsyncIndexWrite"`
5761
// deprecated
@@ -107,6 +111,7 @@ var (
107111
GravityChainAPIs: []string{},
108112
},
109113
EnableTrielessStateDB: true,
114+
VersionedNamespaces: []string{},
110115
EnableStateDBCaching: false,
111116
EnableArchiveMode: false,
112117
EnableAsyncIndexWrite: true,

chainservice/builder.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ func (builder *Builder) createFactory(forTest bool) (factory.Factory, error) {
173173
factory.RegistryStateDBOption(builder.cs.registry),
174174
factory.DefaultPatchOption(),
175175
}
176-
if builder.cfg.Chain.EnableStateDBCaching {
176+
if builder.cfg.Chain.EnableArchiveMode {
177+
dao, err = db.CreateKVStoreVersioned(factoryDBCfg, builder.cfg.Chain.TrieDBPath, builder.cfg.Chain.VersionedNamespaces)
178+
opts = append(opts, factory.MetadataNamespaceOption(builder.cfg.Chain.VersionedMetadata))
179+
} else if builder.cfg.Chain.EnableStateDBCaching {
177180
dao, err = db.CreateKVStoreWithCache(factoryDBCfg, builder.cfg.Chain.TrieDBPath, builder.cfg.Chain.StateDBCacheSize)
178181
} else {
179182
dao, err = db.CreateKVStore(factoryDBCfg, builder.cfg.Chain.TrieDBPath)

config/config.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,20 @@ func ValidateRollDPoS(cfg Config) error {
256256

257257
// ValidateArchiveMode validates the state factory setting
258258
func ValidateArchiveMode(cfg Config) error {
259-
if !cfg.Chain.EnableArchiveMode || !cfg.Chain.EnableTrielessStateDB {
260-
return nil
259+
if cfg.Chain.EnableArchiveMode && cfg.Chain.EnableTrielessStateDB {
260+
if len(cfg.Chain.VersionedMetadata) == 0 {
261+
return errors.Wrap(ErrInvalidCfg, "State DB archive mode is enabled with empty metadata namespace")
262+
}
263+
if len(cfg.Chain.VersionedNamespaces) == 0 {
264+
return errors.Wrap(ErrInvalidCfg, "State DB archive mode is enabled with empty versioned namespace")
265+
}
266+
for _, v := range cfg.Chain.VersionedNamespaces {
267+
if len(v) == 0 {
268+
return errors.Wrap(ErrInvalidCfg, "State DB archive mode is enabled with empty versioned namespace")
269+
}
270+
}
261271
}
262-
263-
return errors.Wrap(ErrInvalidCfg, "Archive mode is incompatible with trieless state DB")
272+
return nil
264273
}
265274

266275
// ValidateAPI validates the api configs

config/config_test.go

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -254,20 +254,42 @@ func TestValidateRollDPoS(t *testing.T) {
254254
}
255255

256256
func TestValidateArchiveMode(t *testing.T) {
257+
r := require.New(t)
257258
cfg := Default
258-
cfg.Chain.EnableArchiveMode = true
259-
cfg.Chain.EnableTrielessStateDB = true
260-
require.Error(t, ErrInvalidCfg, errors.Cause(ValidateArchiveMode(cfg)))
261-
require.EqualError(t, ValidateArchiveMode(cfg), "Archive mode is incompatible with trieless state DB: invalid config value")
262-
cfg.Chain.EnableArchiveMode = false
263-
cfg.Chain.EnableTrielessStateDB = true
264-
require.NoError(t, errors.Cause(ValidateArchiveMode(cfg)))
265-
cfg.Chain.EnableArchiveMode = true
266-
cfg.Chain.EnableTrielessStateDB = false
267-
require.NoError(t, errors.Cause(ValidateArchiveMode(cfg)))
268-
cfg.Chain.EnableArchiveMode = false
269-
cfg.Chain.EnableTrielessStateDB = false
270-
require.NoError(t, errors.Cause(ValidateArchiveMode(cfg)))
259+
cfg.Chain.VersionedMetadata = "meta"
260+
for _, v := range [][2]bool{
261+
{false, false},
262+
{false, true},
263+
{true, false},
264+
{true, true},
265+
} {
266+
cfg.Chain.EnableArchiveMode = v[0]
267+
cfg.Chain.EnableTrielessStateDB = v[1]
268+
if !(cfg.Chain.EnableArchiveMode && cfg.Chain.EnableTrielessStateDB) {
269+
r.NoError(ValidateArchiveMode(cfg))
270+
continue
271+
}
272+
for _, v := range []struct {
273+
ns []string
274+
err string
275+
}{
276+
{[]string{"", ""}, "State DB archive mode is enabled with empty versioned namespace"},
277+
{[]string{"Account", ""}, "State DB archive mode is enabled with empty versioned namespace"},
278+
{[]string{"", "Account"}, "State DB archive mode is enabled with empty versioned namespace"},
279+
{[]string{"Account", "Contract"}, ""},
280+
} {
281+
cfg.Chain.VersionedNamespaces = v.ns
282+
if len(v.err) > 0 {
283+
r.ErrorContains(ValidateArchiveMode(cfg), v.err)
284+
} else {
285+
r.NoError(ValidateArchiveMode(cfg))
286+
}
287+
}
288+
cfg.Chain.VersionedMetadata = ""
289+
r.ErrorContains(ValidateArchiveMode(cfg), "State DB archive mode is enabled with empty metadata namespace")
290+
cfg.Chain.VersionedMetadata = "meta"
291+
r.NoError(ValidateArchiveMode(cfg))
292+
}
271293
}
272294

273295
func TestValidateActPool(t *testing.T) {

db/builder.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,12 @@ func CreateKVStoreWithCache(cfg Config, dbPath string, cacheSize int) (KVStore,
3232

3333
return NewKvStoreWithCache(dao, cacheSize), nil
3434
}
35+
36+
// CreateKVStoreVersioned creates versioned db from config and db path
37+
func CreateKVStoreVersioned(cfg Config, dbPath string, vns []string) (KVStore, error) {
38+
if len(dbPath) == 0 {
39+
return nil, ErrEmptyDBPath
40+
}
41+
cfg.DbPath = dbPath
42+
return NewKVStoreWithVersion(cfg, VersionedNamespaceOption(vns...)), nil
43+
}

db/kvstore_versioned.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,35 @@ type (
4141
KvVersioned interface {
4242
lifecycle.StartStopper
4343

44+
KVStore
45+
46+
// Base returns the underlying KVStore
47+
Base() KVStore
48+
4449
// Version returns the key's most recent version
4550
Version(string, []byte) (uint64, error)
4651

4752
// SetVersion sets the version, and returns a KVStore to call Put()/Get()
4853
SetVersion(uint64) KVStore
4954
}
55+
56+
// KvWithVersion wraps the versioned DB implementation with a certain version
57+
KvWithVersion struct {
58+
// code in PR4518
59+
}
5060
)
61+
62+
type Option func(*KvWithVersion)
63+
64+
func VersionedNamespaceOption(ns ...string) Option {
65+
return func(k *KvWithVersion) {
66+
// code in PR4518
67+
}
68+
}
69+
70+
// NewKVStoreWithVersion implements a KVStore that can handle both versioned
71+
// and non-versioned namespace
72+
func NewKVStoreWithVersion(cfg Config, opts ...Option) KvVersioned {
73+
// code in PR4518
74+
return nil
75+
}

state/factory/daoretrofitter.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import (
1010

1111
"github.com/pkg/errors"
1212

13+
"github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm"
14+
"github.com/iotexproject/iotex-core/v2/action/protocol/staking"
1315
"github.com/iotexproject/iotex-core/v2/db"
16+
"github.com/iotexproject/iotex-core/v2/db/batch"
1417
"github.com/iotexproject/iotex-core/v2/pkg/util/byteutil"
1518
)
1619

@@ -47,3 +50,27 @@ func (rtf *daoRTF) getHeight() (uint64, error) {
4750
func (rtf *daoRTF) putHeight(h uint64) error {
4851
return rtf.dao.Put(AccountKVNamespace, []byte(CurrentHeightKey), byteutil.Uint64ToBytes(h))
4952
}
53+
54+
func (rtf *daoRTF) metadataNS() string {
55+
return AccountKVNamespace
56+
}
57+
58+
func (rtf *daoRTF) flusherOptions(preEaster bool) []db.KVStoreFlusherOption {
59+
opts := []db.KVStoreFlusherOption{
60+
db.SerializeOption(func(wi *batch.WriteInfo) []byte {
61+
if preEaster {
62+
return wi.SerializeWithoutWriteType()
63+
}
64+
return wi.Serialize()
65+
}),
66+
}
67+
if !preEaster {
68+
return opts
69+
}
70+
return append(
71+
opts,
72+
db.SerializeFilterOption(func(wi *batch.WriteInfo) bool {
73+
return wi.Namespace() == evm.CodeKVNameSpace || wi.Namespace() == staking.CandsMapNS
74+
}),
75+
)
76+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) 2024 IoTeX Foundation
2+
// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
3+
// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
4+
// This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
5+
6+
package factory
7+
8+
import (
9+
"context"
10+
11+
"github.com/pkg/errors"
12+
13+
"github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm"
14+
"github.com/iotexproject/iotex-core/v2/action/protocol/staking"
15+
"github.com/iotexproject/iotex-core/v2/db"
16+
"github.com/iotexproject/iotex-core/v2/db/batch"
17+
"github.com/iotexproject/iotex-core/v2/pkg/util/byteutil"
18+
)
19+
20+
type daoRTFArchive struct {
21+
daoVersioned db.KvVersioned
22+
metaNS string // namespace for metadata
23+
}
24+
25+
func newDaoRetrofitterArchive(dao db.KvVersioned, mns string) *daoRTFArchive {
26+
return &daoRTFArchive{
27+
daoVersioned: dao,
28+
metaNS: mns,
29+
}
30+
}
31+
32+
func (rtf *daoRTFArchive) Start(ctx context.Context) error {
33+
return rtf.daoVersioned.Start(ctx)
34+
}
35+
36+
func (rtf *daoRTFArchive) Stop(ctx context.Context) error {
37+
return rtf.daoVersioned.Stop(ctx)
38+
}
39+
40+
func (rtf *daoRTFArchive) atHeight(h uint64) db.KVStore {
41+
return rtf.daoVersioned.SetVersion(h)
42+
}
43+
44+
func (rtf *daoRTFArchive) getHeight() (uint64, error) {
45+
height, err := rtf.daoVersioned.Base().Get(rtf.metaNS, []byte(CurrentHeightKey))
46+
if err != nil {
47+
return 0, errors.Wrap(err, "failed to get factory's height from underlying DB")
48+
}
49+
return byteutil.BytesToUint64(height), nil
50+
}
51+
52+
func (rtf *daoRTFArchive) putHeight(h uint64) error {
53+
return rtf.daoVersioned.Base().Put(rtf.metaNS, []byte(CurrentHeightKey), byteutil.Uint64ToBytes(h))
54+
}
55+
56+
func (rtf *daoRTFArchive) metadataNS() string {
57+
return rtf.metaNS
58+
}
59+
60+
func (rtf *daoRTFArchive) flusherOptions(preEaster bool) []db.KVStoreFlusherOption {
61+
opts := []db.KVStoreFlusherOption{
62+
db.SerializeOption(func(wi *batch.WriteInfo) []byte {
63+
// current height is moved to the metadata namespace
64+
// transform it back for the purpose of calculating digest
65+
wi = rtf.transCurrentHeight(wi)
66+
if preEaster {
67+
return wi.SerializeWithoutWriteType()
68+
}
69+
return wi.Serialize()
70+
}),
71+
}
72+
if !preEaster {
73+
return opts
74+
}
75+
return append(
76+
opts,
77+
db.SerializeFilterOption(func(wi *batch.WriteInfo) bool {
78+
return wi.Namespace() == evm.CodeKVNameSpace || wi.Namespace() == staking.CandsMapNS
79+
}),
80+
)
81+
}
82+
83+
func (rtf *daoRTFArchive) transCurrentHeight(wi *batch.WriteInfo) *batch.WriteInfo {
84+
if wi.Namespace() == rtf.metaNS && string(wi.Key()) == CurrentHeightKey {
85+
return batch.NewWriteInfo(wi.WriteType(), AccountKVNamespace, wi.Key(), wi.Value(), wi.Error())
86+
}
87+
return wi
88+
}

0 commit comments

Comments
 (0)