Skip to content

Commit 2ee5230

Browse files
feat(confix): add migration to v2 (backport #21052) (#21103)
Co-authored-by: Akhil Kumar P <36399231+akhilkumarpilli@users.noreply.github.com> Co-authored-by: akhilkumarpilli <akhilkumar7947@gmail.com>
1 parent 7d801cc commit 2ee5230

9 files changed

Lines changed: 306 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i
4242

4343
### Features
4444

45+
* (tools/confix) [#21052](https://github.com/cosmos/cosmos-sdk/pull/21052) Add a migration to v2 config.
4546
* (tests) [#20013](https://github.com/cosmos/cosmos-sdk/pull/20013) Introduce system tests to run multi node local testnet in CI
4647
* (runtime) [#19953](https://github.com/cosmos/cosmos-sdk/pull/19953) Implement `core/transaction.Service` in runtime.
4748
* (client) [#19905](https://github.com/cosmos/cosmos-sdk/pull/19905) Add grpc client config to `client.toml`.

store/v2/commitment/iavl/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package iavl
22

33
// Config is the configuration for the IAVL tree.
44
type Config struct {
5-
CacheSize int `mapstructure:"cache_size"`
6-
SkipFastStorageUpgrade bool `mapstructure:"skip_fast_storage_upgrade"`
5+
CacheSize int `mapstructure:"cache-size" toml:"cache-size" comment:"CacheSize set the size of the iavl tree cache."`
6+
SkipFastStorageUpgrade bool `mapstructure:"skip-fast-storage-upgrade" toml:"skip-fast-storage-upgrade" comment:"If true, the tree will work like no fast storage and always not upgrade fast storage."`
77
}
88

99
// DefaultConfig returns the default configuration for the IAVL tree.

tools/confix/cmd/migrate.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,31 @@ In case of any error in updating the file, no output is written.`,
3232
RunE: func(cmd *cobra.Command, args []string) error {
3333
var configPath string
3434
clientCtx := client.GetClientContextFromCmd(cmd)
35+
36+
configType := confix.AppConfigType
37+
isClient, _ := cmd.Flags().GetBool(confix.ClientConfigType)
38+
39+
if isClient {
40+
configType = confix.ClientConfigType
41+
}
42+
3543
switch {
3644
case len(args) > 1:
3745
configPath = args[1]
3846
case clientCtx.HomeDir != "":
39-
configPath = filepath.Join(clientCtx.HomeDir, "config", "app.toml")
47+
suffix := "app.toml"
48+
if isClient {
49+
suffix = "client.toml"
50+
}
51+
configPath = filepath.Join(clientCtx.HomeDir, "config", suffix)
4052
default:
4153
return errors.New("must provide a path to the app.toml or client.toml")
4254
}
4355

56+
if strings.HasSuffix(configPath, "client.toml") && !isClient {
57+
return errors.New("app.toml file expected, got client.toml, use --client flag to migrate client.toml")
58+
}
59+
4460
targetVersion := args[0]
4561
plan, ok := confix.Migrations[targetVersion]
4662
if !ok {
@@ -62,15 +78,10 @@ In case of any error in updating the file, no output is written.`,
6278
outputPath = ""
6379
}
6480

65-
configType := confix.AppConfigType
66-
if ok, _ := cmd.Flags().GetBool(confix.ClientConfigType); ok {
67-
configPath = strings.ReplaceAll(configPath, "app.toml", "client.toml") // for the case we are using the home dir of client ctx
68-
configType = confix.ClientConfigType
69-
} else if strings.HasSuffix(configPath, "client.toml") {
70-
return errors.New("app.toml file expected, got client.toml, use --client flag to migrate client.toml")
71-
}
81+
// get transformation steps and formatDoc in which plan need to be applied
82+
steps, formatDoc := plan(rawFile, targetVersion, configType)
7283

73-
if err := confix.Upgrade(ctx, plan(rawFile, targetVersion, configType), configPath, outputPath, FlagSkipValidate); err != nil {
84+
if err := confix.Upgrade(ctx, steps, formatDoc, configPath, outputPath, FlagSkipValidate); err != nil {
7485
return fmt.Errorf("failed to migrate config: %w", err)
7586
}
7687

tools/confix/cmd/mutate.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,12 @@ func SetCommand() *cobra.Command {
7373
ctx = confix.WithLogWriter(ctx, cmd.ErrOrStderr())
7474
}
7575

76-
return confix.Upgrade(ctx, plan, filename, outputPath, FlagSkipValidate)
76+
doc, err := confix.LoadConfig(filename)
77+
if err != nil {
78+
return err
79+
}
80+
81+
return confix.Upgrade(ctx, plan, doc, filename, outputPath, FlagSkipValidate)
7782
},
7883
}
7984

tools/confix/data/v2-app.toml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[comet]
2+
# min-retain-blocks defines the minimum block height offset from the current block being committed, such that all blocks past this offset are pruned from CometBFT. A value of 0 indicates that no blocks should be pruned.
3+
min-retain-blocks = 0
4+
# index-events defines the set of events in the form {eventType}.{attributeKey}, which informs CometBFT what to index. If empty, all events will be indexed.
5+
index-events = []
6+
# halt-height contains a non-zero block height at which a node will gracefully halt and shutdown that can be used to assist upgrades and testing.
7+
halt-height = 0
8+
# halt-time contains a non-zero minimum block time (in Unix seconds) at which a node will gracefully halt and shutdown that can be used to assist upgrades and testing.
9+
halt-time = 0
10+
# address defines the CometBFT RPC server address to bind to.
11+
address = 'tcp://127.0.0.1:26658'
12+
# transport defines the CometBFT RPC server transport protocol: socket, grpc
13+
transport = 'socket'
14+
# trace enables the CometBFT RPC server to output trace information about its internal operations.
15+
trace = false
16+
# standalone starts the application without the CometBFT node. The node should be started separately.
17+
standalone = false
18+
19+
[grpc]
20+
# Enable defines if the gRPC server should be enabled.
21+
enable = true
22+
# Address defines the gRPC server address to bind to.
23+
address = 'localhost:9090'
24+
# MaxRecvMsgSize defines the max message size in bytes the server can receive.
25+
# The default value is 10MB.
26+
max-recv-msg-size = 10485760
27+
# MaxSendMsgSize defines the max message size in bytes the server can send.
28+
# The default value is math.MaxInt32.
29+
max-send-msg-size = 2147483647
30+
31+
[store]
32+
# The type of database for application and snapshots databases.
33+
app-db-backend = 'goleveldb'
34+
35+
[store.options]
36+
# State storage database type. Currently we support: 0 for SQLite, 1 for Pebble
37+
ss-type = 0
38+
# State commitment database type. Currently we support:0 for iavl, 1 for iavl v2
39+
sc-type = 0
40+
41+
# Pruning options for state storage
42+
[store.options.ss-pruning-option]
43+
# Number of recent heights to keep on disk.
44+
keep-recent = 2
45+
# Height interval at which pruned heights are removed from disk.
46+
interval = 1
47+
48+
# Pruning options for state commitment
49+
[store.options.sc-pruning-option]
50+
# Number of recent heights to keep on disk.
51+
keep-recent = 2
52+
# Height interval at which pruned heights are removed from disk.
53+
interval = 1
54+
55+
[store.options.iavl-config]
56+
# CacheSize set the size of the iavl tree cache.
57+
cache-size = 100000
58+
# If true, the tree will work like no fast storage and always not upgrade fast storage.
59+
skip-fast-storage-upgrade = true

tools/confix/data/v2-client.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# This is a TOML config file.
2+
# For more information, see https://github.com/toml-lang/toml
3+
4+
###############################################################################
5+
### Client Configuration ###
6+
###############################################################################
7+
8+
# The network chain ID
9+
chain-id = ""
10+
# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory)
11+
keyring-backend = "test"
12+
# Default key name, if set, defines the default key to use for signing transaction when the --from flag is not specified
13+
keyring-default-keyname = ""
14+
# CLI output format (text|json)
15+
output = "text"
16+
# <host>:<port> to CometBFT RPC interface for this chain
17+
node = "tcp://localhost:26657"
18+
# Transaction broadcasting mode (sync|async)
19+
broadcast-mode = "sync"
20+
21+
# gRPC server endpoint to which the client will connect.
22+
# It can be overwritten by the --grpc-addr flag in each command.
23+
grpc-address = ""
24+
25+
# Allow the gRPC client to connect over insecure channels.
26+
# It can be overwritten by the --grpc-insecure flag in each command.
27+
grpc-insecure = false

tools/confix/match.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package confix
2+
3+
import (
4+
"sort"
5+
6+
"github.com/creachadair/tomledit"
7+
"github.com/creachadair/tomledit/transform"
8+
)
9+
10+
// MatchKeys diffs the keyspaces of the TOML documents in files lhs and rhs.
11+
// Comments, order, and values are ignored for comparison purposes.
12+
// It will return in the format of map[oldKey]newKey
13+
func MatchKeys(lhs, rhs *tomledit.Document) map[string]string {
14+
matches := matchDocs(map[string]string{}, allKVs(lhs.Global), allKVs(rhs.Global))
15+
16+
lsec, rsec := lhs.Sections, rhs.Sections
17+
transform.SortSectionsByName(lsec)
18+
transform.SortSectionsByName(rsec)
19+
20+
i, j := 0, 0
21+
for i < len(lsec) && j < len(rsec) {
22+
switch {
23+
case lsec[i].Name.Before(rsec[j].Name):
24+
i++
25+
case rsec[j].Name.Before(lsec[i].Name):
26+
j++
27+
default:
28+
matches = matchDocs(matches, allKVs(lsec[i]), allKVs(rsec[j]))
29+
i++
30+
j++
31+
}
32+
}
33+
34+
return matches
35+
}
36+
37+
// matchDocs get all the keys matching in lhs and rhs.
38+
// value of keys are ignored
39+
func matchDocs(matchesMap map[string]string, lhs, rhs []KV) map[string]string {
40+
sort.Slice(lhs, func(i, j int) bool {
41+
return lhs[i].Key < lhs[j].Key
42+
})
43+
sort.Slice(rhs, func(i, j int) bool {
44+
return rhs[i].Key < rhs[j].Key
45+
})
46+
47+
i, j := 0, 0
48+
for i < len(lhs) && j < len(rhs) {
49+
switch {
50+
case lhs[i].Key < rhs[j].Key:
51+
i++
52+
case lhs[i].Key > rhs[j].Key:
53+
j++
54+
default:
55+
// key exists in both lhs and rhs
56+
matchesMap[lhs[i].Key] = rhs[j].Key
57+
i++
58+
j++
59+
}
60+
}
61+
62+
return matchesMap
63+
}

tools/confix/migrations.go

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,42 @@ const (
1919
)
2020

2121
// MigrationMap defines a mapping from a version to a transformation plan.
22-
type MigrationMap map[string]func(from *tomledit.Document, to, planType string) transform.Plan
22+
type MigrationMap map[string]func(from *tomledit.Document, to, planType string) (transform.Plan, *tomledit.Document)
2323

2424
var Migrations = MigrationMap{
2525
"v0.45": NoPlan, // Confix supports only the current supported SDK version. So we do not support v0.44 -> v0.45.
2626
"v0.46": PlanBuilder,
2727
"v0.47": PlanBuilder,
2828
"v0.50": PlanBuilder,
2929
"v0.52": PlanBuilder,
30+
"v2": V2PlanBuilder,
3031
// "v0.xx.x": PlanBuilder, // add specific migration in case of configuration changes in minor versions
3132
}
3233

34+
type v2KeyChangesMap map[string][]string
35+
36+
// list all the keys which are need to be modified in v2
37+
var v2KeyChanges = v2KeyChangesMap{
38+
"min-retain-blocks": []string{"comet.min-retain-blocks"},
39+
"index-events": []string{"comet.index-events"},
40+
"halt-height": []string{"comet.halt-height"},
41+
"halt-time": []string{"comet.halt-time"},
42+
"app-db-backend": []string{"store.app-db-backend"},
43+
"pruning-keep-recent": []string{
44+
"store.options.ss-pruning-option.keep-recent",
45+
"store.options.sc-pruning-option.keep-recent",
46+
},
47+
"pruning-interval": []string{
48+
"store.options.ss-pruning-option.interval",
49+
"store.options.sc-pruning-option.interval",
50+
},
51+
"iavl-cache-size": []string{"store.options.iavl-config.cache-size"},
52+
"iavl-disable-fastnode": []string{"store.options.iavl-config.skip-fast-storage-upgrade"},
53+
// Add other key mappings as needed
54+
}
55+
3356
// PlanBuilder is a function that returns a transformation plan for a given diff between two files.
34-
func PlanBuilder(from *tomledit.Document, to, planType string) transform.Plan {
57+
func PlanBuilder(from *tomledit.Document, to, planType string) (transform.Plan, *tomledit.Document) {
3558
plan := transform.Plan{}
3659
deletedSections := map[string]bool{}
3760

@@ -114,11 +137,105 @@ func PlanBuilder(from *tomledit.Document, to, planType string) transform.Plan {
114137
plan = append(plan, step)
115138
}
116139

117-
return plan
140+
return plan, from
118141
}
119142

120143
// NoPlan returns a no-op plan.
121-
func NoPlan(_ *tomledit.Document, to, planType string) transform.Plan {
144+
func NoPlan(from *tomledit.Document, to, planType string) (transform.Plan, *tomledit.Document) {
122145
fmt.Printf("no migration needed to %s\n", to)
123-
return transform.Plan{}
146+
return transform.Plan{}, from
147+
}
148+
149+
// V2PlanBuilder is a function that returns a transformation plan to convert to v2 config
150+
func V2PlanBuilder(from *tomledit.Document, to, planType string) (transform.Plan, *tomledit.Document) {
151+
target, err := LoadLocalConfig(to, planType)
152+
if err != nil {
153+
panic(fmt.Errorf("failed to parse file: %w. This file should have been valid", err))
154+
}
155+
156+
plan := transform.Plan{}
157+
plan = updateMatchedKeysPlan(from, target, plan)
158+
plan = applyKeyChangesPlan(from, plan)
159+
160+
return plan, target
161+
}
162+
163+
// updateMatchedKeysPlan updates all matched keys with old key values
164+
func updateMatchedKeysPlan(from, target *tomledit.Document, plan transform.Plan) transform.Plan {
165+
matches := MatchKeys(from, target)
166+
for oldKey, newKey := range matches {
167+
oldEntry := getEntry(from, oldKey)
168+
if oldEntry == nil {
169+
continue
170+
}
171+
172+
// check if the key "app-db-backend" exists and if its value is empty in the existing config
173+
// If the value is empty, update the key value with the default value
174+
// of v2 i.e., goleveldb to prevent network failures.
175+
if isAppDBBackend(newKey, oldEntry) {
176+
continue // lets keep app-db-backend with v2 default value
177+
}
178+
179+
// update newKey value with old entry value
180+
step := createUpdateStep(oldKey, newKey, oldEntry)
181+
plan = append(plan, step)
182+
}
183+
return plan
184+
}
185+
186+
// applyKeyChangesPlan checks if key changes are needed with the "to" version and applies them
187+
func applyKeyChangesPlan(from *tomledit.Document, plan transform.Plan) transform.Plan {
188+
changes := v2KeyChanges
189+
for oldKey, newKeys := range changes {
190+
oldEntry := getEntry(from, oldKey)
191+
if oldEntry == nil {
192+
continue
193+
}
194+
195+
for _, newKey := range newKeys {
196+
// check if the key "app-db-backend" exists and if its value is empty in the existing config
197+
// If the value is empty, update the key value with the default value
198+
// of v2 i.e., goleveldb to prevent network failures.
199+
if isAppDBBackend(newKey, oldEntry) {
200+
continue // lets keep app-db-backend with v2 default value
201+
}
202+
203+
// update newKey value with old entry value
204+
step := createUpdateStep(oldKey, newKey, oldEntry)
205+
plan = append(plan, step)
206+
}
207+
}
208+
return plan
209+
}
210+
211+
// getEntry retrieves the first entry for the given key from the document
212+
func getEntry(doc *tomledit.Document, key string) *parser.KeyValue {
213+
splitKeys := strings.Split(key, ".")
214+
entry := doc.First(splitKeys...)
215+
if entry == nil || entry.KeyValue == nil {
216+
return nil
217+
}
218+
return entry.KeyValue
219+
}
220+
221+
// isAppDBBackend checks if the key is "store.app-db-backend" and the value is empty
222+
func isAppDBBackend(key string, entry *parser.KeyValue) bool {
223+
return key == "store.app-db-backend" && entry.Value.String() == `""`
224+
}
225+
226+
// createUpdateStep creates a transformation step to update a key with a new key value
227+
func createUpdateStep(oldKey, newKey string, oldEntry *parser.KeyValue) transform.Step {
228+
return transform.Step{
229+
Desc: fmt.Sprintf("updating %s key with %s key", oldKey, newKey),
230+
T: transform.Func(func(_ context.Context, doc *tomledit.Document) error {
231+
splitNewKeys := strings.Split(newKey, ".")
232+
newEntry := doc.First(splitNewKeys...)
233+
if newEntry == nil || newEntry.KeyValue == nil {
234+
return nil
235+
}
236+
237+
newEntry.KeyValue.Value = oldEntry.Value
238+
return nil
239+
}),
240+
}
124241
}

0 commit comments

Comments
 (0)