Skip to content

Commit 2969aaa

Browse files
committed
[factory] implement versioned stateDB to support archive mode
1 parent a57eee3 commit 2969aaa

9 files changed

+238
-41
lines changed

blockchain/config.go

+5
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

+4-1
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)

db/builder.go

+9
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

+25
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

+27
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+
}
+88
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+
}

state/factory/statedb.go

+23-28
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import (
2020

2121
"github.com/iotexproject/iotex-core/v2/action"
2222
"github.com/iotexproject/iotex-core/v2/action/protocol"
23-
"github.com/iotexproject/iotex-core/v2/action/protocol/execution/evm"
24-
"github.com/iotexproject/iotex-core/v2/action/protocol/staking"
2523
"github.com/iotexproject/iotex-core/v2/actpool"
2624
"github.com/iotexproject/iotex-core/v2/blockchain/block"
2725
"github.com/iotexproject/iotex-core/v2/blockchain/genesis"
@@ -40,6 +38,8 @@ type (
4038
atHeight(uint64) db.KVStore
4139
getHeight() (uint64, error)
4240
putHeight(uint64) error
41+
metadataNS() string
42+
flusherOptions(bool) []db.KVStoreFlusherOption
4343
}
4444
// stateDB implements StateFactory interface, tracks changes to account/contract and batch-commits to DB
4545
stateDB struct {
@@ -53,6 +53,7 @@ type (
5353
protocolView protocol.View
5454
skipBlockValidationOnPut bool
5555
ps *patchStore
56+
metaNS string // metadata namespace, only meaningful when archive-mode enabled
5657
}
5758
)
5859

@@ -91,6 +92,14 @@ func DisableWorkingSetCacheOption() StateDBOption {
9192
}
9293
}
9394

95+
// MetadataNamespaceOption specifies the metadat namespace for versioned DB
96+
func MetadataNamespaceOption(ns string) StateDBOption {
97+
return func(sdb *stateDB, cfg *Config) error {
98+
sdb.metaNS = ns
99+
return nil
100+
}
101+
}
102+
94103
// NewStateDB creates a new state db
95104
func NewStateDB(cfg Config, dao db.KVStore, opts ...StateDBOption) (Factory, error) {
96105
sdb := stateDB{
@@ -106,7 +115,15 @@ func NewStateDB(cfg Config, dao db.KVStore, opts ...StateDBOption) (Factory, err
106115
return nil, err
107116
}
108117
}
109-
sdb.dao = newDaoRetrofitter(dao)
118+
if cfg.Chain.EnableArchiveMode {
119+
daoVersioned, ok := dao.(db.KvVersioned)
120+
if !ok {
121+
return nil, errors.Wrap(ErrNotSupported, "cannot enable archive mode StateDB with non-versioned DB")
122+
}
123+
sdb.dao = newDaoRetrofitterArchive(daoVersioned, sdb.metaNS)
124+
} else {
125+
sdb.dao = newDaoRetrofitter(dao)
126+
}
110127
timerFactory, err := prometheustimer.New(
111128
"iotex_statefactory_perf",
112129
"Performance of state factory module",
@@ -181,7 +198,7 @@ func (sdb *stateDB) newWorkingSet(ctx context.Context, height uint64) (*workingS
181198
flusher, err := db.NewKVStoreFlusher(
182199
sdb.dao.atHeight(height),
183200
batch.NewCachedBatch(),
184-
sdb.flusherOptions(!g.IsEaster(height))...,
201+
sdb.dao.flusherOptions(!g.IsEaster(height))...,
185202
)
186203
if err != nil {
187204
return nil, err
@@ -193,11 +210,10 @@ func (sdb *stateDB) newWorkingSet(ctx context.Context, height uint64) (*workingS
193210
flusher.KVStoreWithBuffer().MustPut(p.Namespace, p.Key, p.Value)
194211
}
195212
}
196-
store := newStateDBWorkingSetStore(sdb.protocolView, flusher, g.IsNewfoundland(height))
213+
store := newStateDBWorkingSetStore(sdb.protocolView, flusher, g.IsNewfoundland(height), sdb.dao.metadataNS())
197214
if err := store.Start(ctx); err != nil {
198215
return nil, err
199216
}
200-
201217
return newWorkingSet(height, store), nil
202218
}
203219

@@ -271,7 +287,6 @@ func (sdb *stateDB) WorkingSet(ctx context.Context) (protocol.StateManager, erro
271287
}
272288

273289
func (sdb *stateDB) WorkingSetAtHeight(ctx context.Context, height uint64) (protocol.StateManager, error) {
274-
// TODO: implement archive mode
275290
return sdb.newWorkingSet(ctx, height)
276291
}
277292

@@ -368,29 +383,9 @@ func (sdb *stateDB) ReadView(name string) (interface{}, error) {
368383
}
369384

370385
//======================================
371-
// private trie constructor functions
386+
// private statedb functions
372387
//======================================
373388

374-
func (sdb *stateDB) flusherOptions(preEaster bool) []db.KVStoreFlusherOption {
375-
opts := []db.KVStoreFlusherOption{
376-
db.SerializeOption(func(wi *batch.WriteInfo) []byte {
377-
if preEaster {
378-
return wi.SerializeWithoutWriteType()
379-
}
380-
return wi.Serialize()
381-
}),
382-
}
383-
if !preEaster {
384-
return opts
385-
}
386-
return append(
387-
opts,
388-
db.SerializeFilterOption(func(wi *batch.WriteInfo) bool {
389-
return wi.Namespace() == evm.CodeKVNameSpace || wi.Namespace() == staking.CandsMapNS
390-
}),
391-
)
392-
}
393-
394389
func (sdb *stateDB) state(h uint64, ns string, addr []byte, s interface{}) error {
395390
data, err := sdb.dao.atHeight(h).Get(ns, addr)
396391
if err != nil {

state/factory/workingsetstore_statedb.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ import (
1919
type stateDBWorkingSetStore struct {
2020
*workingSetStoreCommon
2121
readBuffer bool
22+
metaNS string // metadata namespace, only meaningful when archive-mode enabled
2223
}
2324

24-
func newStateDBWorkingSetStore(view protocol.View, flusher db.KVStoreFlusher, readBuffer bool) workingSetStore {
25+
func newStateDBWorkingSetStore(view protocol.View, flusher db.KVStoreFlusher, readBuffer bool, ns string) workingSetStore {
2526
return &stateDBWorkingSetStore{
2627
workingSetStoreCommon: &workingSetStoreCommon{
2728
flusher: flusher,
2829
view: view,
2930
},
3031
readBuffer: readBuffer,
32+
metaNS: ns,
3133
}
3234
}
3335

@@ -61,7 +63,7 @@ func (store *stateDBWorkingSetStore) States(ns string, keys [][]byte) ([][]byte,
6163
func (store *stateDBWorkingSetStore) Finalize(height uint64) error {
6264
// Persist current chain Height
6365
store.flusher.KVStoreWithBuffer().MustPut(
64-
AccountKVNamespace,
66+
store.metaNS,
6567
[]byte(CurrentHeightKey),
6668
byteutil.Uint64ToBytes(height),
6769
)

0 commit comments

Comments
 (0)