Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions config/tendermintbase/tendermintbase.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,34 @@ const (
InstrumentationSectionName = "instrumentation"
PrivValidatorSectionName = "priv-validator"
SelfRemediationSectionName = "self-remediation"

// RootSectionName identifies the keys that sit at the top of the file with no table of their own. The
// name is for lookups and reports and is not part of any key.
RootSectionName = "node_base"
)

// notWritableInThisFile are root paths this section does not declare.
//
// Neither is a setting an operator can usefully write here. The home directory is where this file is found,
// so a value inside it would be the file naming its own location, and the command line already carries it.
// The node mode is the same fact the file states at the top under its own name, and declaring a second
// spelling would let the two disagree, with the resolution answering for one and the node reading the
// other.
var notWritableInThisFile = []string{"home", "mode"}

// nodeRootSchema declares the keys that sit at the root of the node's configuration file.
//
// The node's own top-level type carries these and the nine tables both, so declaring against it directly
// would declare every table's keys a second time. This squashes the same base group that type squashes, so
// fourteen spellings still come from the node's own tags, and restates only the two fields it holds beside
// that group. A test holds those two against it.
type nodeRootSchema struct {
tmcfg.BaseConfig `mapstructure:",squash"`

AutobahnConfigFile string `mapstructure:"autobahn-config-file"`
HashVaultDisabledUnsafe bool `mapstructure:"hash-vault-disabled-unsafe"`
}

// removedSettings are the consensus paths this section does not declare.
//
// Every one is a setting the node removed, and the struct marks each field deprecated. The fields are kept
Expand Down Expand Up @@ -75,6 +101,8 @@ func init() {
privValidatorDefaults, filledFromTheCommandLine)
registry.RegisterSection(SelfRemediationSectionName, &tmcfg.SelfRemediationConfig{},
selfRemediationDefaults)
registry.RegisterRootKeysExcluding(RootSectionName, &nodeRootSchema{}, rootDefaults,
notWritableInThisFile...)
}

// forMode is the configuration the seid init command writes for a kind of node.
Expand Down Expand Up @@ -164,6 +192,19 @@ func instrumentationDefaults(mode registry.Mode) any { return *forMode(mode).Ins
// not make.
func privValidatorDefaults(mode registry.Mode) any { return *forMode(mode).PrivValidator }

// rootDefaults is what a generated file carries at the top of the node's configuration file.
//
// The same values for every mode. These name where a node keeps its data and how it logs, and nothing in
// the binary makes either follow from what kind of node is asking.
func rootDefaults(mode registry.Mode) any {
live := forMode(mode)
return nodeRootSchema{
BaseConfig: live.BaseConfig,
AutobahnConfigFile: live.AutobahnConfigFile,
HashVaultDisabledUnsafe: live.HashVaultDisabledUnsafe,
}
}

// selfRemediationDefaults is what a generated file carries for the self remediation section.
//
// The same values for every mode. These are the thresholds at which a node restarts itself, and each one
Expand Down
110 changes: 110 additions & 0 deletions config/tendermintbase/tendermintbase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,116 @@ func TestEverySectionThisPackageRegistersIsUsable(t *testing.T) {
}
}

// TestTheRootSchemaCarriesWhatTheNodesOwnTypeCarries closes the one place a spelling is restated.
//
// The root section declares against a schema rather than the node's top-level type, because that type
// carries the nine tables as well and declaring against it would declare their keys twice. The schema
// squashes the same base group, so fourteen keys still derive from the node's own tags, and it restates two
// fields by hand. This holds those two to the type they came from: name, tag and type each, and the count of
// non-table fields, so a third one appearing there fails here rather than going undeclared.
func TestTheRootSchemaCarriesWhatTheNodesOwnTypeCarries(t *testing.T) {
live := reflect.TypeOf(tmcfg.Config{})
schema := reflect.TypeOf(nodeRootSchema{})

var restated int
for i := 0; i < live.NumField(); i++ {
f := live.Field(i)
tag, ok := f.Tag.Lookup("mapstructure")
if !ok {
continue
}
// The squashed base group and the nine tables are not restated; everything else is.
if strings.Contains(tag, "squash") {
continue
}
if f.Type.Kind() == reflect.Pointer && f.Type.Elem().Kind() == reflect.Struct {
continue
}
restated++

got, found := schema.FieldByName(f.Name)
if !found {
t.Errorf("the node's type carries %s (%s) at its root and the schema does not, so the key is "+
"not declared at all", f.Name, tag)
continue
}
if want := got.Tag.Get("mapstructure"); want != tag {
t.Errorf("%s is tagged %q on the node's type and %q here, so the declared key is not the one "+
"the reader decodes", f.Name, tag, want)
}
if got.Type != f.Type {
t.Errorf("%s is a %s on the node's type and a %s here, so the declared value has a shape the "+
"reader does not read", f.Name, f.Type, got.Type)
}
}

// The schema holds the squashed group plus exactly the restated fields, so a field added here that the
// node's type does not carry fails too.
if want := restated + 1; schema.NumField() != want {
t.Errorf("the schema carries %d fields and the node's type has %d root fields beside the squashed "+
"group, so %d were expected", schema.NumField(), restated, want)
}
}

// TestTheRootPathsLeftOutAreTheOnesTheFileAlreadyStates names why two root paths are not declared.
//
// The home directory is where this file is found, so a value inside it would be the file naming its own
// location, and the command line already carries it. The node mode is the fact the file states at the top
// under its own name, and a second spelling would let the two disagree: the resolution answers for one and
// the node reads the other.
//
// Written out here rather than read from the list the registration uses. Comparing that list against itself
// agrees however it changes, so a path dropped from it would leave this passing while the key became
// declared.
func TestTheRootPathsLeftOutAreTheOnesTheFileAlreadyStates(t *testing.T) {
registered, ok := registry.Lookup(RootSectionName)
if !ok {
t.Fatalf("%s is not registered; Defects: %v", RootSectionName, registry.Defects())
}
declared := map[string]bool{}
for _, key := range registered.Keys {
declared[key] = true
}
for key, why := range map[string]string{
"home": "the file's own location, which the command line carries",
"mode": "the fact the file states at the top under its own name",
} {
if declared[key] {
t.Errorf("%q is declared at the root and it is %s, so an operator can write a second value "+
"for something already settled", key, why)
}
}
if len(registered.Excluded) != 2 {
t.Errorf("the root section excludes %v and two paths were expected", registered.Excluded)
}
}

// TestNoRootKeyCollidesWithAnotherSectionsName covers the collision the registry does not refuse.
//
// A key at the top of the file that is also a section's name cannot be written: no file holds both a value
// for that name and a table under it, so one of the two settings is unreachable and nothing says which. The
// registry does not catch it, and this package is the first to declare root keys beside another package's,
// so the check belongs here until it moves.
func TestNoRootKeyCollidesWithAnotherSectionsName(t *testing.T) {
sections := map[string]bool{}
for _, s := range registry.Sections() {
if s.Prefix != "" {
sections[s.Prefix] = true
}
}
for _, s := range registry.Sections() {
if s.Prefix != "" {
continue
}
for _, key := range s.Keys {
if sections[key] {
t.Errorf("%s declares %q at the top of the file and a section is named %q, so one of the "+
"two cannot be written and nothing reports which", s.Name, key, key)
}
}
}
}

// TestNoSectionDeclaresTheRootDirectory covers a field five of these sections carry.
//
// Each holds a root directory tagged the same as the key at the top of the file, and the node fills every
Expand Down
Loading