-
Notifications
You must be signed in to change notification settings - Fork 886
[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 all 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,110 @@ | ||
| package registry_test | ||
|
|
||
| import ( | ||
| "reflect" | ||
| "sort" | ||
| "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) | ||
| } | ||
| } | ||
|
|
||
| // TestSortingAResolvedNestedListLeavesTheSectionsOwnDefaultAlone is the same property one level down. | ||
| // | ||
| // A copy of the outer list or map still points at the inner storage, so a caller sorting an inner list | ||
| // reaches the section's own default exactly as directly as sorting the outer one would. The damage is the | ||
| // same and it is worse to find: the process-wide default is rewritten, and the resolution that carries it | ||
| // is the next one, in whatever code happens to run after. | ||
| // | ||
| // Driven through a second resolution rather than by inspecting the variable, because what matters is not | ||
| // that a copy was taken but that the next caller is unaffected. | ||
| func TestSortingAResolvedNestedListLeavesTheSectionsOwnDefaultAlone(t *testing.T) { | ||
| type nested struct { | ||
| Deny map[string][]string `mapstructure:"deny"` | ||
| } | ||
| // A package-level default is the shape every section uses, so the value handed out is the one the | ||
| // process keeps. | ||
| shared := nested{Deny: map[string][]string{"rpc": {"zebra", "aardvark"}}} | ||
| registry.RegisterSection("detach_nested", &nested{}, func(registry.Mode) any { return shared }) | ||
|
|
||
| first, err := registry.Resolve(registry.ModeValidator, registry.Sources{}) | ||
| if err != nil { | ||
| t.Fatalf("Resolve: %v", err) | ||
| } | ||
| handed, ok := first.Values["detach_nested.deny"].(map[string][]string) | ||
| if !ok { | ||
| t.Fatalf("detach_nested.deny resolved to %T, want a map of lists", first.Values["detach_nested.deny"]) | ||
| } | ||
| // What a caller does with a list it was given. | ||
| sort.Strings(handed["rpc"]) | ||
|
|
||
| second, err := registry.Resolve(registry.ModeValidator, registry.Sources{}) | ||
| if err != nil { | ||
| t.Fatalf("Resolve: %v", err) | ||
| } | ||
| again, ok := second.Values["detach_nested.deny"].(map[string][]string) | ||
| if !ok { | ||
| t.Fatalf("the second resolution carried %T", second.Values["detach_nested.deny"]) | ||
| } | ||
| if want := []string{"zebra", "aardvark"}; !reflect.DeepEqual(again["rpc"], want) { | ||
| t.Errorf("a later resolution carries %v, want %v. Sorting a list inside a resolved value rewrote "+ | ||
| "the section's own default, so every resolution after it carries the sorted one", | ||
| again["rpc"], 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
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,54 @@ | ||
| 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. | ||
| // | ||
| // The two interface toggles differ from what this package's own reader falls back to, and the difference is | ||
| // worth naming because it looks like a behaviour change and is not one. The reader's default has both open | ||
| // for every kind of node, and it keeps that when the key is absent from a file. The values here follow the | ||
| // rule the provisioning command applies instead, so they close both for a node that serves no queries. | ||
| // | ||
| // Nothing flips as a result. A resolution records which keys a source supplied, and only those are ever | ||
| // delivered, so a node whose file omits these keys has nothing written for them and the reader's own | ||
| // fallback still answers. A value from here reaches a node when somebody wrote it and at no other time. | ||
| // | ||
| // What would change that is a writer that renders every declared key into a file. Then these two are | ||
| // supplied, the node closes them, and an operator can at least read why in the file they were handed. That | ||
| // is the case to remember when such a writer is built, which is why it is written here rather than left to | ||
| // be discovered there. | ||
| 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 | ||
| } | ||
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.