diff --git a/config/tendermintbase/tendermintbase.go b/config/tendermintbase/tendermintbase.go index 882d74bade..2e9350c9a2 100644 --- a/config/tendermintbase/tendermintbase.go +++ b/config/tendermintbase/tendermintbase.go @@ -12,6 +12,12 @@ const ( RPCSectionName = "rpc" ConsensusSectionName = "consensus" MempoolSectionName = "mempool" + + StateSyncSectionName = "statesync" + TxIndexSectionName = "tx-index" + InstrumentationSectionName = "instrumentation" + PrivValidatorSectionName = "priv-validator" + SelfRemediationSectionName = "self-remediation" ) // removedSettings are the consensus paths this section does not declare. @@ -61,6 +67,13 @@ var neverReachTheMempool = []string{ "pending-ttl-num-blocks", } +// fixedForEveryNode is the metric prefix this section does not declare. +// +// The node marks the field deprecated and states that its metrics always use one fixed prefix, so the +// value is not the node's to vary and not an operator's to set. Its declared value was that fixed prefix, +// which reads as a setting whose default happens to be this, and the generated file does not write it. +const fixedForEveryNode = "namespace" + // Registration puts these sections in the configuration registry. // // Neither the package that defines these settings nor the package that decides them can register them. The @@ -79,6 +92,14 @@ func init() { append([]string{filledFromTheCommandLine}, removedSettings...)...) registry.RegisterSectionExcluding(MempoolSectionName, &tmcfg.MempoolConfig{}, mempoolDefaults, append([]string{filledFromTheCommandLine}, neverReachTheMempool...)...) + registry.RegisterSection(StateSyncSectionName, &tmcfg.StateSyncConfig{}, stateSyncDefaults) + registry.RegisterSection(TxIndexSectionName, &tmcfg.TxIndexConfig{}, txIndexDefaults) + registry.RegisterSectionExcluding(InstrumentationSectionName, &tmcfg.InstrumentationConfig{}, + instrumentationDefaults, fixedForEveryNode) + registry.RegisterSectionExcluding(PrivValidatorSectionName, &tmcfg.PrivValidatorConfig{}, + privValidatorDefaults, filledFromTheCommandLine) + registry.RegisterSection(SelfRemediationSectionName, &tmcfg.SelfRemediationConfig{}, + selfRemediationDefaults) } // forMode is the configuration the seid init command writes for a kind of node. @@ -159,3 +180,40 @@ func consensusDefaults(mode registry.Mode) any { return *forMode(mode).Consensus // The same values for every mode. What a node holds before a transaction is decided is a limit on its own // memory and bandwidth, and nothing in the binary makes one follow from what kind of node is asking. func mempoolDefaults(mode registry.Mode) any { return *forMode(mode).Mempool } + +// The one path the state sync section does not declare. +// +// The list of servers to fetch a snapshot from has no default and cannot have one: the addresses are the +// operator's own peers. An empty list is not a value they can inherit, and any address written here would +// name a host this binary does not know exists. + +// stateSyncDefaults is what a generated file carries for the state sync section. +// +// The same values for every mode. Whether a node starts from a snapshot is a decision about how it is being +// brought up rather than about what it will be, and every kind of node can be brought up either way. +func stateSyncDefaults(mode registry.Mode) any { return *forMode(mode).StateSync } + +// txIndexDefaults is what a generated file carries for the transaction index section. +// +// Answered per mode, for the indexer alone. A node that serves queries indexes transactions so it can +// answer them, and a validator and a seed serve none, so they index nothing and keep the write. +func txIndexDefaults(mode registry.Mode) any { return *forMode(mode).TxIndex } + +// instrumentationDefaults is what a generated file carries for the instrumentation section. +// +// The same values for every mode. What a node measures about itself is a decision about how it is operated, +// and an operator who collects metrics collects them from every kind of node they run. +func instrumentationDefaults(mode registry.Mode) any { return *forMode(mode).Instrumentation } + +// privValidatorDefaults is what a generated file carries for the signing key section. +// +// The same values for every mode. These are paths and an address for reaching a signer, and a node that +// does not sign simply does not use them, so varying them by kind would state a difference the binary does +// not make. +func privValidatorDefaults(mode registry.Mode) any { return *forMode(mode).PrivValidator } + +// 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 +// describes a node that has stopped making progress, which is the same condition whatever the node is for. +func selfRemediationDefaults(mode registry.Mode) any { return *forMode(mode).SelfRemediation } diff --git a/config/tendermintbase/tendermintbase_test.go b/config/tendermintbase/tendermintbase_test.go index 4bec01f23d..c975118ee6 100644 --- a/config/tendermintbase/tendermintbase_test.go +++ b/config/tendermintbase/tendermintbase_test.go @@ -49,6 +49,32 @@ var whatVariesByNodeKind = map[string]map[registry.Mode]string{ registry.ModeFull: "false", registry.ModeArchive: "false", }, + "tx-index.indexer": { + registry.ModeValidator: "[null]", + registry.ModeSeed: "[null]", + registry.ModeFull: "[kv]", + registry.ModeArchive: "[kv]", + }, +} + +// declaredSections are the sections this package registers, so a test walks the set rather than a list that +// has to be extended alongside it. +func declaredSections() []string { + return []string{ + P2PSectionName, RPCSectionName, ConsensusSectionName, MempoolSectionName, + StateSyncSectionName, TxIndexSectionName, InstrumentationSectionName, + PrivValidatorSectionName, SelfRemediationSectionName, + } +} + +// ours reports whether a key belongs to a section this package registers. +func ours(key string) bool { + for _, name := range declaredSections() { + if strings.HasPrefix(key, name+".") { + return true + } + } + return false } // TestWhatVariesByNodeKindIsTheRecordedSet measures the mode rules through the declared values. @@ -68,7 +94,7 @@ func TestWhatVariesByNodeKindIsTheRecordedSet(t *testing.T) { var measured []string for key := range byMode[registry.ModeValidator] { - if !strings.HasPrefix(key, P2PSectionName+".") && !strings.HasPrefix(key, RPCSectionName+".") { + if !ours(key) { continue } seen := map[string]bool{} @@ -118,6 +144,11 @@ var declaredAgainst = []struct { {RPCSectionName, &tmcfg.RPCConfig{}, 1}, {ConsensusSectionName, &tmcfg.ConsensusConfig{}, len(removedSettings) + 1}, {MempoolSectionName, &tmcfg.MempoolConfig{}, len(neverReachTheMempool) + 1}, + {StateSyncSectionName, &tmcfg.StateSyncConfig{}, 0}, + {TxIndexSectionName, &tmcfg.TxIndexConfig{}, 0}, + {InstrumentationSectionName, &tmcfg.InstrumentationConfig{}, 1}, + {PrivValidatorSectionName, &tmcfg.PrivValidatorConfig{}, 1}, + {SelfRemediationSectionName, &tmcfg.SelfRemediationConfig{}, 0}, } // TestNoDeclaredKeyNamesADeprecatedField holds every section against its struct's own marking. @@ -491,6 +522,62 @@ func fieldTagged(typ reflect.Type, rel string) (reflect.StructField, bool) { return reflect.StructField{}, false } +// TestTheStateSyncKeysAreDeclaredAsASet holds the section to the set an operator fills together. +// +// Turning state sync on means writing every one of these, so a key this space refuses is one an operator +// writes and is told nothing reads. The snapshot servers were left out on the reasoning that a key with +// no default cannot be declared, and three siblings in the same section disprove it: the trust height, +// the trust hash and the scratch directory each state a zero value and are declared. The generated file +// writes all five. +// +// Their declared values are asserted to be the empty ones, because that is what makes the set coherent: +// none of these is a value the binary can know, and the section states so rather than inventing one. +func TestTheStateSyncKeysAreDeclaredAsASet(t *testing.T) { + registered, ok := registry.Lookup(StateSyncSectionName) + if !ok { + t.Fatalf("%s is not registered; Defects: %v", StateSyncSectionName, registry.Defects()) + } + if len(registered.Excluded) != 0 { + t.Errorf("the section excludes %v, and every path it carries is one an operator writes", + registered.Excluded) + } + declared := map[string]bool{} + for _, key := range registered.Keys { + declared[key] = true + } + for _, rel := range []string{"rpc-servers", "trust-height", "trust-hash", "temp-dir", "use-p2p"} { + if !declared[StateSyncSectionName+"."+rel] { + t.Errorf("%s.%s is one of the keys an operator fills to turn state sync on and the section "+ + "does not declare it", StateSyncSectionName, rel) + } + } + if got := tmcfg.DefaultStateSyncConfig().RPCServers; len(got) != 0 { + t.Errorf("the node now ships snapshot servers %v, so the declared empty list is no longer what "+ + "a generated file carries", got) + } +} + +// TestEverySectionThisPackageRegistersIsUsable is the check no single section here can make. +// +// A registration the registry cannot use is recorded rather than panicked, so a section that failed to +// register is absent rather than loud, and two of the refusals depend on what else has registered. Nothing +// is enumerated beyond the section names this package owns, so adding one is covered by adding it there. +func TestEverySectionThisPackageRegistersIsUsable(t *testing.T) { + for _, name := range declaredSections() { + registered, ok := registry.Lookup(name) + if !ok { + t.Errorf("%s is not registered; Defects: %v", name, registry.Defects()) + continue + } + if len(registered.Keys) == 0 { + t.Errorf("%s registered and declares no key", name) + } + } + for _, d := range registry.Defects() { + t.Errorf("the registry refused %s: %v", d.Section, d.Err) + } +} + // 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