Skip to content

Commit 6921258

Browse files
test: example of configuring config.toml values in systest (#667)
* allow testnet config to be configured via flags * configured * use tag * updates * remove comment * readme stuff * maybe update sdk and comet versions * replace the store * fix * just change ordering i guess * deleting test case * just skip it * Update tests/systemtests/go.mod Co-authored-by: Alex | Cosmos Labs <[email protected]> * tidy * add systemtest for cosmos tx compat --------- Co-authored-by: Alex | Cosmos Labs <[email protected]>
1 parent 9ab5cfe commit 6921258

File tree

13 files changed

+476
-99
lines changed

13 files changed

+476
-99
lines changed

evmd/cmd/evmd/cmd/testnet.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ import (
99
"path/filepath"
1010
"time"
1111

12+
"github.com/cosmos/evm/config"
13+
14+
cosmosevmhd "github.com/cosmos/evm/crypto/hd"
15+
cosmosevmkeyring "github.com/cosmos/evm/crypto/keyring"
16+
"github.com/cosmos/evm/evmd"
17+
cosmosevmserverconfig "github.com/cosmos/evm/server/config"
1218
"github.com/spf13/cobra"
1319
"github.com/spf13/pflag"
1420

@@ -42,12 +48,7 @@ import (
4248
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
4349
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
4450

45-
"github.com/cosmos/evm/config"
46-
cosmosevmhd "github.com/cosmos/evm/crypto/hd"
47-
cosmosevmkeyring "github.com/cosmos/evm/crypto/keyring"
48-
"github.com/cosmos/evm/evmd"
4951
customnetwork "github.com/cosmos/evm/evmd/tests/network"
50-
cosmosevmserverconfig "github.com/cosmos/evm/server/config"
5152
evmnetwork "github.com/cosmos/evm/testutil/integration/evm/network"
5253
evmtypes "github.com/cosmos/evm/x/vm/types"
5354
)
@@ -66,6 +67,7 @@ var (
6667
flagPrintMnemonic = "print-mnemonic"
6768
flagSingleHost = "single-host"
6869
flagCommitTimeout = "commit-timeout"
70+
configChanges = "config-changes"
6971
flagValidatorPowers = "validator-powers"
7072
unsafeStartValidatorFn UnsafeStartValidatorCmdCreator
7173
)
@@ -93,6 +95,7 @@ type initArgs struct {
9395
startingIPAddress string
9496
singleMachine bool
9597
useDocker bool
98+
configChanges []string
9699
validatorPowers []int64
97100
}
98101

@@ -196,6 +199,7 @@ Example:
196199
return err
197200
}
198201
args.algo, _ = cmd.Flags().GetString(flags.FlagKeyType)
202+
args.configChanges, _ = cmd.Flags().GetStringSlice(configChanges)
199203

200204
validatorPowers, _ := cmd.Flags().GetIntSlice(flagValidatorPowers)
201205
args.validatorPowers, err = parseValidatorPowers(validatorPowers, args.numValidators)
@@ -215,6 +219,7 @@ Example:
215219
cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list [email protected]:46656, [email protected]:46656, ...)")
216220
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
217221
cmd.Flags().Bool(flagsUseDocker, false, "test network via docker")
222+
cmd.Flags().StringSlice(configChanges, []string{}, "Config changes to apply to the node: i.e. consensus.timeout_commit=50s")
218223

219224
return cmd
220225
}
@@ -401,6 +406,11 @@ func initTestnetFiles(
401406
}
402407
}
403408

409+
if err := parseAndApplyConfigChanges(nodeConfig, args.configChanges); err != nil {
410+
_ = os.RemoveAll(args.outputDir)
411+
return fmt.Errorf("failed to apply config changes for node %d: %w", i, err)
412+
}
413+
404414
nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(nodeConfig)
405415
if err != nil {
406416
_ = os.RemoveAll(args.outputDir)

evmd/cmd/evmd/cmd/testnet_utils.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strconv"
7+
"strings"
8+
"time"
9+
10+
cmtconfig "github.com/cometbft/cometbft/config"
11+
)
12+
13+
// parseAndApplyConfigChanges parses the config changes string and applies them to the nodeConfig
14+
func parseAndApplyConfigChanges(nodeConfig *cmtconfig.Config, configChanges []string) error {
15+
if len(configChanges) == 0 {
16+
return nil
17+
}
18+
19+
if err := applyConfigOverrides(nodeConfig, configChanges); err != nil {
20+
return err
21+
}
22+
23+
return nil
24+
}
25+
26+
// updateConfigField updates a field in the config based on dot notation
27+
// Example: "consensus.timeout_propose=5s" or "log_level=debug" (for BaseConfig fields)
28+
func updateConfigField(config *cmtconfig.Config, fieldPath, value string) error {
29+
parts := strings.Split(fieldPath, ".")
30+
31+
configValue := reflect.ValueOf(config).Elem()
32+
33+
// Handle BaseConfig fields (squashed/embedded fields)
34+
if len(parts) == 1 {
35+
// This might be a BaseConfig field, try to find it in the embedded struct
36+
baseConfigField := configValue.FieldByName("BaseConfig")
37+
if baseConfigField.IsValid() {
38+
targetFieldName := getFieldName(baseConfigField.Type(), parts[0])
39+
if targetFieldName != "" {
40+
targetField := baseConfigField.FieldByName(targetFieldName)
41+
if targetField.IsValid() && targetField.CanSet() {
42+
return setFieldValue(targetField, value)
43+
}
44+
}
45+
}
46+
47+
// If not found in BaseConfig, try in the main Config struct
48+
targetFieldName := getFieldName(configValue.Type(), parts[0])
49+
if targetFieldName != "" {
50+
targetField := configValue.FieldByName(targetFieldName)
51+
if targetField.IsValid() && targetField.CanSet() {
52+
return setFieldValue(targetField, value)
53+
}
54+
}
55+
56+
return fmt.Errorf("field not found: %s", parts[0])
57+
}
58+
59+
// Handle nested fields (e.g., consensus.timeout_propose)
60+
current := configValue
61+
for i, part := range parts[:len(parts)-1] {
62+
field := current.FieldByName(getFieldName(current.Type(), part))
63+
if !field.IsValid() {
64+
return fmt.Errorf("field not found: %s", strings.Join(parts[:i+1], "."))
65+
}
66+
67+
// If it's a pointer to a struct, get the element
68+
if field.Kind() == reflect.Ptr {
69+
if field.IsNil() {
70+
// Initialize the pointer if it's nil
71+
field.Set(reflect.New(field.Type().Elem()))
72+
}
73+
field = field.Elem()
74+
}
75+
current = field
76+
}
77+
78+
// Set the final field
79+
finalFieldName := parts[len(parts)-1]
80+
targetField := current.FieldByName(getFieldName(current.Type(), finalFieldName))
81+
if !targetField.IsValid() {
82+
return fmt.Errorf("field not found: %s", finalFieldName)
83+
}
84+
85+
if !targetField.CanSet() {
86+
return fmt.Errorf("field cannot be set: %s", finalFieldName)
87+
}
88+
89+
return setFieldValue(targetField, value)
90+
}
91+
92+
// getFieldName finds the struct field name from mapstructure tag or field name
93+
func getFieldName(structType reflect.Type, tagName string) string {
94+
for i := 0; i < structType.NumField(); i++ {
95+
field := structType.Field(i)
96+
97+
// Check mapstructure tag
98+
if tag := field.Tag.Get("mapstructure"); tag != "" {
99+
if tag == tagName {
100+
return field.Name
101+
}
102+
}
103+
104+
// Check field name (case insensitive)
105+
if strings.EqualFold(field.Name, tagName) {
106+
return field.Name
107+
}
108+
}
109+
return ""
110+
}
111+
112+
// setFieldValue sets the field value based on its type
113+
func setFieldValue(field reflect.Value, value string) error {
114+
switch field.Type() {
115+
case reflect.TypeOf(time.Duration(0)):
116+
duration, err := time.ParseDuration(value)
117+
if err != nil {
118+
return fmt.Errorf("invalid duration format: %s", value)
119+
}
120+
field.Set(reflect.ValueOf(duration))
121+
122+
case reflect.TypeOf(""):
123+
field.SetString(value)
124+
125+
case reflect.TypeOf(true):
126+
boolVal, err := strconv.ParseBool(value)
127+
if err != nil {
128+
return fmt.Errorf("invalid boolean format: %s", value)
129+
}
130+
field.SetBool(boolVal)
131+
132+
case reflect.TypeOf(int64(0)):
133+
intVal, err := strconv.ParseInt(value, 10, 64)
134+
if err != nil {
135+
return fmt.Errorf("invalid int64 format: %s", value)
136+
}
137+
field.SetInt(intVal)
138+
139+
case reflect.TypeOf(int(0)):
140+
intVal, err := strconv.Atoi(value)
141+
if err != nil {
142+
return fmt.Errorf("invalid int format: %s", value)
143+
}
144+
field.SetInt(int64(intVal))
145+
146+
case reflect.TypeOf([]string{}):
147+
// Handle string slices - split by comma
148+
var slice []string
149+
if strings.TrimSpace(value) != "" {
150+
// Split by comma and trim whitespace
151+
parts := strings.Split(value, ",")
152+
for _, part := range parts {
153+
slice = append(slice, strings.TrimSpace(part))
154+
}
155+
}
156+
field.Set(reflect.ValueOf(slice))
157+
158+
default:
159+
return fmt.Errorf("unsupported field type: %v", field.Type())
160+
}
161+
162+
return nil
163+
}
164+
165+
// parseConfigOverride parses a string like "consensus.timeout_propose=5s"
166+
func parseConfigOverride(override string) (string, string, error) {
167+
parts := strings.SplitN(override, "=", 2)
168+
if len(parts) != 2 {
169+
return "", "", fmt.Errorf("invalid override format: %s (expected field=value)", override)
170+
}
171+
return parts[0], parts[1], nil
172+
}
173+
174+
// applyConfigOverrides applies multiple overrides to the config
175+
func applyConfigOverrides(config *cmtconfig.Config, overrides []string) error {
176+
for _, override := range overrides {
177+
fieldPath, value, err := parseConfigOverride(override)
178+
if err != nil {
179+
return err
180+
}
181+
182+
if err := updateConfigField(config, fieldPath, value); err != nil {
183+
return fmt.Errorf("failed to set %s: %w", fieldPath, err)
184+
}
185+
}
186+
return nil
187+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
cmtconfig "github.com/cometbft/cometbft/config"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestParseAndApplyConfigChanges(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
override string
15+
expectedValue interface{}
16+
checkFunc func(*cmtconfig.Config) interface{}
17+
}{
18+
{
19+
name: "consensus timeout_propose",
20+
override: "consensus.timeout_propose=10s",
21+
expectedValue: 10 * time.Second,
22+
checkFunc: func(cfg *cmtconfig.Config) interface{} { return cfg.Consensus.TimeoutPropose },
23+
},
24+
{
25+
name: "consensus create_empty_blocks",
26+
override: "consensus.create_empty_blocks=false",
27+
expectedValue: false,
28+
checkFunc: func(cfg *cmtconfig.Config) interface{} { return cfg.Consensus.CreateEmptyBlocks },
29+
},
30+
{
31+
name: "consensus double_sign_check_height",
32+
override: "consensus.double_sign_check_height=500",
33+
expectedValue: int64(500),
34+
checkFunc: func(cfg *cmtconfig.Config) interface{} { return cfg.Consensus.DoubleSignCheckHeight },
35+
},
36+
{
37+
name: "baseconfig home",
38+
override: "home=/custom/path",
39+
expectedValue: "/custom/path",
40+
checkFunc: func(cfg *cmtconfig.Config) interface{} { return cfg.RootDir },
41+
},
42+
{
43+
name: "baseconfig log_level",
44+
override: "log_level=error",
45+
expectedValue: "error",
46+
checkFunc: func(cfg *cmtconfig.Config) interface{} { return cfg.LogLevel },
47+
},
48+
{
49+
name: "baseconfig log_format",
50+
override: "log_format=json",
51+
expectedValue: "json",
52+
checkFunc: func(cfg *cmtconfig.Config) interface{} { return cfg.LogFormat },
53+
},
54+
{
55+
name: "baseconfig db_backend",
56+
override: "db_backend=badgerdb",
57+
expectedValue: "badgerdb",
58+
checkFunc: func(cfg *cmtconfig.Config) interface{} { return cfg.DBBackend },
59+
},
60+
{
61+
name: "string slice single value",
62+
override: "statesync.rpc_servers=production",
63+
expectedValue: []string{"production"},
64+
checkFunc: func(cfg *cmtconfig.Config) interface{} {
65+
return cfg.StateSync.RPCServers
66+
},
67+
},
68+
{
69+
name: "string slice multiple values",
70+
override: "statesync.rpc_servers=production,monitoring,critical",
71+
expectedValue: []string{"production", "monitoring", "critical"},
72+
checkFunc: func(cfg *cmtconfig.Config) interface{} {
73+
return cfg.StateSync.RPCServers
74+
},
75+
},
76+
{
77+
name: "string slice empty",
78+
override: "statesync.rpc_servers=",
79+
expectedValue: []string(nil),
80+
checkFunc: func(cfg *cmtconfig.Config) interface{} {
81+
return cfg.StateSync.RPCServers
82+
},
83+
},
84+
}
85+
86+
for _, tt := range tests {
87+
t.Run(tt.name, func(t *testing.T) {
88+
cfg := cmtconfig.DefaultConfig()
89+
90+
err := parseAndApplyConfigChanges(cfg, []string{tt.override})
91+
require.NoError(t, err)
92+
93+
actualValue := tt.checkFunc(cfg)
94+
require.Equal(t, tt.expectedValue, actualValue)
95+
})
96+
}
97+
}

tests/systemtests/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
WAIT_TIME ?= 20s
44

55
test:
6-
go test -failfast -timeout=30m -p=1 -mod=readonly -tags='system_test' -v ./... -run TestTxsOrdering --wait-time=$(WAIT_TIME) --block-time=3s --binary evmd --chain-id local-4221
6+
go test -failfast -timeout=30m -p=1 -mod=readonly -tags='system_test' -v ./... -run TestCosmosTxCompat --wait-time=$(WAIT_TIME) --block-time=9.5s --binary evmd --chain-id local-4221
77
go test -failfast -timeout=30m -p=1 -mod=readonly -tags='system_test' -v ./... -run TestTxsReplacement --wait-time=$(WAIT_TIME) --block-time=9.5s --binary evmd --chain-id local-4221
8+
go test -failfast -timeout=30m -p=1 -mod=readonly -tags='system_test' -v ./... -run TestTxsOrdering --wait-time=$(WAIT_TIME) --block-time=3s --binary evmd --chain-id local-4221
89
go test -failfast -timeout=30m -p=1 -mod=readonly -tags='system_test' -v ./... -run TestExceptions --wait-time=$(WAIT_TIME) --block-time=8s --binary evmd --chain-id local-4221
910
go test -failfast -timeout=30m -p=1 -mod=readonly -tags='system_test' -v ./... -run TestEIP7702 --wait-time=$(WAIT_TIME) --block-time=5s --binary evmd --chain-id local-4221
1011
go test -failfast -timeout=30m -p=1 -mod=readonly -tags='system_test' -v ./... -run TestEIP712 --wait-time=$(WAIT_TIME) --block-time=5s --binary evmd --chain-id local-4221

tests/systemtests/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,13 @@ go test -p 1 -mod=readonly -tags='system_test' -v ./... \
5656
```shell
5757
make test
5858
```
59+
60+
## Updating Node's Configuration
61+
62+
New in systemtests v1.4.0, you can now update the `config.toml` of the nodes. To do so, the system under test should be set up like so:
63+
64+
```go
65+
s := systemtest.Sut
66+
s.ResetChain(t)
67+
s.SetupChain("--config-changes=consensus.timeout_commit=10s")
68+
```

0 commit comments

Comments
 (0)