-
Notifications
You must be signed in to change notification settings - Fork 885
[ConfigManager] Register Sections 3/4 #3976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
25fa5f9
config: the EVM sections enter the registry
bdchatham dc4a458
config: hand out values nothing else holds, and answer the EVM interf…
bdchatham 7fc9154
config: name what these declared values are
bdchatham c59c216
Merge branch 'main' into plt-775-sections-3
bdchatham 484a614
fix(config): copy a handed-out value all the way down
bdchatham d7a8614
docs(config): name the divergence between a declared value and the re…
bdchatham File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package registry_test | ||
|
|
||
| import ( | ||
| "reflect" | ||
| "testing" | ||
|
|
||
| "github.com/sei-protocol/sei-chain/config/registry" | ||
| ) | ||
|
|
||
| // listBearing is a probe whose default is a package-level variable, which is the usual shape. | ||
| type listBearing struct { | ||
| Allowed []string `mapstructure:"allowed"` | ||
| Labels map[string]string `mapstructure:"labels"` | ||
| Absent []string `mapstructure:"absent"` | ||
| } | ||
|
|
||
| var listBearingDefault = listBearing{ | ||
| Allowed: []string{"callTracer", "prestateTracer"}, | ||
| Labels: map[string]string{"chain": "pacific-1"}, | ||
| } | ||
|
|
||
| // TestAResolvedListIsTheCallersToWriteInto covers what a caller may do with a resolved value. | ||
| // | ||
| // A section's default is usually a package-level variable, so handing out its slice hands out the array | ||
| // that variable holds. A caller sorting or de-duplicating a resolved list in place, which is what a caller | ||
| // producing deterministic output does, would rewrite that variable for the whole process: every later | ||
| // resolution and every reader that copies the same struct. Two of the lists this reaches in practice are | ||
| // deny lists, so the rewrite is silent and it is a security control. | ||
| func TestAResolvedListIsTheCallersToWriteInto(t *testing.T) { | ||
| registry.Reset() | ||
| registry.RegisterSection("probe", &listBearing{}, func(registry.Mode) any { return listBearingDefault }) | ||
| for _, d := range registry.Defects() { | ||
| t.Fatalf("the probe was refused: %v", d.Err) | ||
| } | ||
|
|
||
| resolved, err := registry.Resolve(registry.ModeFull, registry.Sources{}) | ||
| if err != nil { | ||
| t.Fatalf("Resolve: %v", err) | ||
| } | ||
|
|
||
| resolved.Values["probe.allowed"].([]string)[0] = "written-by-the-caller" | ||
| resolved.Values["probe.labels"].(map[string]string)["chain"] = "written-by-the-caller" | ||
|
|
||
| if got := listBearingDefault.Allowed[0]; got != "callTracer" { | ||
| t.Errorf("writing into the resolved list changed the section's own default to %q, so every later "+ | ||
| "resolution and every reader copying that struct carries the caller's value", got) | ||
| } | ||
| if got := listBearingDefault.Labels["chain"]; got != "pacific-1" { | ||
| t.Errorf("writing into the resolved map changed the section's own default to %q", got) | ||
| } | ||
|
|
||
| again, err := registry.Resolve(registry.ModeFull, registry.Sources{}) | ||
| if err != nil { | ||
| t.Fatalf("Resolve: %v", err) | ||
| } | ||
| if got := again.Values["probe.allowed"]; !reflect.DeepEqual(got, []string{"callTracer", "prestateTracer"}) { | ||
| t.Errorf("a later resolution carries %v, so one caller's edit reached another's answer", got) | ||
| } | ||
|
|
||
| // A nil list stays nil rather than becoming an empty one, because absent and empty are different | ||
| // answers to a reader that checks length. | ||
| if got := again.Values["probe.absent"]; got == nil || !reflect.ValueOf(got).IsNil() { | ||
| t.Errorf("an unset list resolved to %#v, want a nil slice of its own type", got) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package config | ||
|
|
||
| import "github.com/sei-protocol/sei-chain/config/registry" | ||
|
|
||
| // SectionName is this section's name in the configuration key space. | ||
| const SectionName = "evm" | ||
|
|
||
| // Registration puts this package's configuration section in the registry. | ||
| // | ||
| // The owning package registers its own section, so the struct, the values and the keys come from one | ||
| // place. This section's mapstructure tags already spell the keys its reader resolves, so the registry | ||
| // derives what a node reads rather than restating them. | ||
| func init() { | ||
| registry.RegisterSection(SectionName, &Config{}, defaults) | ||
| } | ||
|
|
||
| // defaults is what the seid init command writes for a node of this kind. | ||
| // | ||
| // That command applies the same mode rule to this section's own defaults and renders the result, and what | ||
| // it renders is passed through rather than refilled from a mode-blind copy, so a declared value here is the | ||
| // value that reaches the file. | ||
| // | ||
| // The two interface toggles are what the rule changes. A full node and an archive node serve queries, which | ||
| // is what these interfaces are for; a validator and a seed serve none, and leaving them open would put a | ||
| // public request surface on the node that holds a signing key. The rule is read from the registry rather | ||
| // than restated, because the package that owns the node mode imports this one and cannot be imported back. | ||
| // | ||
| // Two values come from the machine rather than from a decision, and they are not one case. The worker pool | ||
| // has a portable answer: the pool re-measures whenever the value it is given is not positive, so a file | ||
| // carrying zero lets every node size itself, and a caller rendering into a file should write that rather | ||
| // than this. The simulation call limit has no portable answer, because zero there is not a request to | ||
| // measure but the absence of a limit, and the limit is the only bound on how many simulations a node runs | ||
| // at once. Both describe the host that resolved them, so neither travels. | ||
| func defaults(mode registry.Mode) any { | ||
| cfg := DefaultConfig | ||
| serves := registry.IsFullnodeMode(mode) | ||
| cfg.HTTPEnabled = serves | ||
|
bdchatham marked this conversation as resolved.
|
||
| cfg.WSEnabled = serves | ||
| return cfg | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| package config | ||
|
|
||
| import ( | ||
| "reflect" | ||
| "sort" | ||
| "testing" | ||
|
|
||
| "github.com/sei-protocol/sei-chain/config/registry" | ||
| ) | ||
|
|
||
| // TestDeclaredKeysAreTheOnesItsReaderResolves holds the derived keys against the reader's own constants. | ||
| // | ||
| // The section registers the struct its reader fills, so a mapstructure tag is the only spelling of these | ||
| // keys and there is no second list to fall behind. What remains is the constants ReadConfig looks up, | ||
| // which state the same keys again in the same file, and a rename that moves one and not the other | ||
| // compiles. | ||
| // | ||
| // Written out rather than derived from the struct, because a list derived from the same tags would agree | ||
| // with itself whatever those tags said. | ||
| func TestDeclaredKeysAreTheOnesItsReaderResolves(t *testing.T) { | ||
| for _, defect := range registry.Defects() { | ||
| if defect.Section == SectionName { | ||
| t.Fatalf("%s was refused, so none of its keys is declared: %v", SectionName, defect.Err) | ||
| } | ||
| } | ||
| want := []string{ | ||
| flagHTTPEnabled, flagHTTPPort, flagWSEnabled, flagWSPort, | ||
| flagReadTimeout, flagReadHeaderTimeout, flagWriteTimeout, flagIdleTimeout, | ||
| flagSimulationGasLimit, flagSimulationEVMTimeout, flagCORSOrigins, flagWSOrigins, | ||
| flagFilterTimeout, flagMaxTxPoolTxs, flagCheckTxTimeout, flagSlow, | ||
| flagEnableSimulation, flagDenyList, flagMaxLogNoBlock, flagMaxLogBytes, | ||
| flagMaxBlocksForLog, flagMaxEstimateGasCalls, flagMaxStateOverrideAccounts, | ||
| flagMaxStateOverrideSlots, flagMaxSubscriptionsNewHead, flagMaxSubscriptionsLogs, | ||
| flagEnableTestAPI, flagMaxConcurrentTraceCalls, flagMaxConcurrentSimulationCalls, | ||
| flagMaxTraceLookbackBlocks, flagTraceTimeout, flagMaxTraceStructLogBytes, | ||
| flagTraceAllowedTracers, flagTraceAllowJSTracers, flagEnableParallelizedBlockTrace, | ||
| flagRPCStatsInterval, flagWorkerPoolSize, flagWorkerQueueSize, flagEVMLegacySeiApis, | ||
| flagTraceBakeEnabled, flagTraceBakeWorkers, flagTraceBakeQueueSize, flagTraceBakeTracers, | ||
| flagTraceBakeWindowBlocks, flagTraceBakeUseSnapshot, flagTraceBakeSnapshotWindow, | ||
| flagIPRateLimitRPS, flagIPRateLimitBurst, flagRateLimitingEnabled, flagTrustedProxyCIDRs, | ||
| flagBatchRequestLimit, flagBatchResponseMaxSize, flagMaxRequestBodyBytes, | ||
| flagMaxConcurrentRequestBytes, flagWSAdmissionTimeout, flagMaxOpenConnections, | ||
| flagBodyReadIdleTimeout, | ||
| } | ||
| sort.Strings(want) | ||
|
|
||
| section, ok := registry.Lookup(SectionName) | ||
| if !ok { | ||
| t.Fatalf("%s is not registered, so nothing resolves its keys", SectionName) | ||
| } | ||
| declared := map[string]bool{} | ||
| for _, key := range section.Keys { | ||
| declared[key] = true | ||
| } | ||
| for _, key := range want { | ||
| if !declared[key] { | ||
| t.Errorf("the reader resolves %s and no tag declares it", key) | ||
| } | ||
| delete(declared, key) | ||
| } | ||
| for key := range declared { | ||
| t.Errorf("%s is declared and no constant in this file resolves it", key) | ||
| } | ||
| } | ||
|
|
||
| // TestEachKindOfNodeResolvesTheInterfacesItIsFor is the mode-varying part of this section. | ||
| // | ||
| // A full node and an archive node serve queries, which is what these two interfaces are for. A validator | ||
| // and a seed serve none, and an open interface on the node that holds a signing key is a public request | ||
| // surface on the one node meant to expose the least. The values are written out here rather than taken | ||
| // from the same rule the section reads, so a change to that rule fails this and gets looked at. | ||
| func TestEachKindOfNodeResolvesTheInterfacesItIsFor(t *testing.T) { | ||
| serving := map[registry.Mode]bool{ | ||
| registry.ModeValidator: false, | ||
| registry.ModeSeed: false, | ||
| registry.ModeFull: true, | ||
| registry.ModeArchive: true, | ||
| } | ||
| for _, mode := range registry.Modes() { | ||
| want, named := serving[mode] | ||
| if !named { | ||
| t.Fatalf("mode %q has no expectation here, so a mode was added and this was not revisited", mode) | ||
| } | ||
| resolved, err := registry.Resolve(mode, registry.Sources{}) | ||
| if err != nil { | ||
| t.Fatalf("mode %q: %v", mode, err) | ||
| } | ||
| for _, key := range []string{flagHTTPEnabled, flagWSEnabled} { | ||
| if got := resolved.Values[key]; got != want { | ||
| t.Errorf("mode %q: %s resolves to %v, want %v", mode, key, got, want) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // TestEachKeyResolvesToTheValueItsFieldHolds covers the binding a key set cannot show. | ||
| // | ||
| // Resolving carries the key a tag produced together with the value that tag's field held. Comparing the | ||
| // defaults struct against itself does not: two tags on each other's fields leave the key set identical and | ||
| // every field still holding the value it always did, so a list and a URL change places unnoticed. | ||
| func TestEachKeyResolvesToTheValueItsFieldHolds(t *testing.T) { | ||
| resolved, err := registry.Resolve(registry.ModeFull, registry.Sources{}) | ||
| if err != nil { | ||
| t.Fatalf("%v", err) | ||
| } | ||
| for key, want := range map[string]any{ | ||
| flagCORSOrigins: DefaultConfig.CORSOrigins, | ||
| flagDenyList: DefaultConfig.DenyList, | ||
| flagTraceAllowedTracers: DefaultConfig.TraceAllowedTracers, | ||
| flagEVMLegacySeiApis: DefaultConfig.EnabledLegacySeiApis, | ||
| flagTrustedProxyCIDRs: DefaultConfig.TrustedProxyCIDRs, | ||
| flagReadTimeout: DefaultConfig.ReadTimeout, | ||
| flagHTTPPort: DefaultConfig.HTTPPort, | ||
| flagIPRateLimitRPS: DefaultConfig.IPRateLimitRPS, | ||
| flagMaxLogBytes: DefaultConfig.MaxLogBytes, | ||
| } { | ||
| if got := resolved.Values[key]; !reflect.DeepEqual(got, want) { | ||
| t.Errorf("%s resolves to %#v (%T), want %#v (%T)", key, got, got, want, want) | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package blocktest | ||
|
|
||
| import "github.com/sei-protocol/sei-chain/config/registry" | ||
|
|
||
| // SectionName is this section's name in the configuration key space. | ||
| const SectionName = "eth_blocktest" | ||
|
|
||
| // Registration puts this package's configuration section in the registry. | ||
| // | ||
| // The owning package registers its own section, so the struct, the values and the keys come from one | ||
| // place. This section's mapstructure tags already spell the keys its reader resolves, so the registry | ||
| // derives what a node reads rather than restating them. | ||
| func init() { | ||
| registry.RegisterSection(SectionName, &Config{}, defaults) | ||
| } | ||
|
|
||
| // defaults is what the seid init command writes for a node of this kind. | ||
| // | ||
| // The same values for every mode. This section drives a harness against recorded block data, which is | ||
| // not something any kind of node does while serving a chain. | ||
| // | ||
| // The data path is a tilde path, and it resolves as written. Whoever opens it expands the tilde, so a | ||
| // caller that renders this value into a file writes the same text an operator would. | ||
| func defaults(registry.Mode) any { return DefaultConfig } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.