Skip to content
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

Khalil/1888 network config improvements #4340

Merged
merged 43 commits into from
Jun 20, 2023

Conversation

kc1116
Copy link
Contributor

@kc1116 kc1116 commented May 11, 2023

This PR addresses the current issues related to config management in Flow. Here is the current structure when adding a new config value;

  • Add default values
  • Add a new CLI flag
  • Update node builder BaseConfig
  • Update node builder Default BaseConfig
  • Update all usages
    This process requires multiple changes to multiple files in multiple packages of the code base and can be error-prone when updating or adding multiple config values. This structure leads to default values existing in multiple different places throughout the code base making it hard for developers to get a full picture of the entire Flow configuration with default values. Furthermore, each config value can easily be erroneously overridden by the application code during node startup because the entire NodeConfig is accessible from the node builder struct.

This PR aims to address these issues by providing the following changes;

  • Consolidate configuration values into a single, centralized location, eliminating the need for updating multiple files and reducing the likelihood of errors. All configuration values should be initialized in one space from a single source of truth config.yml . All configuration values should be accessible from a single package and should not be overridable by the application code.
  • Implement a configuration management library Viper to handle the parsing, validation, and merging of configuration files. This allows us to use various config file types. Viper also pairs directly with pflag package allowing CLI flags to easily override default values defined in config.yml.
  • Adopt a standardized config file for establishing a default configuration
  • Reduce the number of steps needed to add a new config value
  • Consolidate configuration value validation for all network configuration values.

This PR establishes a new config package and a standardized default configuration file config.yml. config.yml represents the entire Flow config with default values for each of the relevant subcomponents for Flow. You will also find a config.go file that defines the FlowConfig struct. This struct is used to unmarshal config.yml after it has been read. The FlowConfig struct fields are configuration structs for the various subcomponents of Flow. The FlowConfig struct contains a Validate() func that invokes all the validation funcs defined on each sub-configuration structs, allowing devs to define Validate methods of any of the sub-configuration structs. Finally, defining CLI flags for each sub-component should be done in the corresponding sub-package for that component in the config package. Now configuration structs, validation funcs and pflag instantiation are contained in the config sub-package for the component they are related to rather then all flags being instantiated inside scaffold.go separate from config struct definitions and separate from any default value definitions. All configuration for the flow network has been moved to the sub-packge config/network. In the package a configuration struct for each subcomponent of the network is defined, validate funcs are defined as required on any of the configuration structs and all network CLI flags are defined in this package. This setup now contains and updates the entire network configuration from a single package.

Example steps to add new configuration value for the network config.

  • Add new config value to the network-configs key of config.yml
  networking-connection-pruning: true
  • Add new field for the config value on the parent network config struct in the config/network/config.go file
NetworkConnectionPruning bool `mapstructure:"networking-connection-pruning"`
  • Add validation for new config value if needed
  • Add CLI flag to override defaults to the InitializeNetworkFlags func in config/network/flags.go
func InitializeNetworkFlags(flags *pflag.FlagSet, defaultNetConfig *Config) {
	flags.Bool(NetworkingConnectionPruning, defaultNetConfig.NetworkConnectionPruning, "enabling connection trimming")
.  .  .  .  .
}

Now adding a new config value and accessing the new config value is completely contained in the config package.

I suggest starting PR review from the new config package, most of the other changes are usage updates.

- add network config to config.yml
- update network config cli flags to use defaults from config package
- add config parsing and flag binding
@kc1116 kc1116 requested a review from peterargue May 11, 2023 16:40
@kc1116 kc1116 changed the base branch from master to khalil/1885-clusterid-provider May 11, 2023 16:41
@github-actions
Copy link
Contributor

github-actions bot commented May 11, 2023

FVM Benchstat comparison

This branch with compared with the base branch onflow:master commit 5ab3243

The command (for i in {1..7}; do go test ./fvm ./engine/execution/computation --bench . --tags relic -shuffle=on --benchmem --run ^$; done) was used.

Collapsed results for better readability

old.txtnew.txt
time/opdelta
pkg:github.com/onflow/flow-go/fvm goos:linux goarch:amd64
RuntimeTransaction/convert_int_to_string_and_concatenate_it-236.5ms ± 3%39.2ms ± 8%+7.33%(p=0.001 n=7+7)
RuntimeTransaction/get_signer_address-234.3ms ± 4%35.8ms ± 1%+4.29%(p=0.009 n=6+5)
RuntimeTransaction/reference_tx-233.8ms ± 4%34.1ms ± 2%~(p=0.628 n=7+6)
RuntimeTransaction/convert_int_to_string-235.4ms ± 3%35.7ms ± 4%~(p=0.731 n=6+7)
RuntimeTransaction/get_public_account-237.4ms ± 4%37.4ms ± 5%~(p=0.836 n=6+7)
RuntimeTransaction/get_account_and_get_balance-2288ms ± 4%288ms ± 1%~(p=0.731 n=7+6)
RuntimeTransaction/get_account_and_get_available_balance-2281ms ± 2%281ms ± 2%~(p=1.000 n=7+7)
RuntimeTransaction/get_account_and_get_storage_used-240.0ms ± 3%39.7ms ± 3%~(p=0.620 n=7+7)
RuntimeTransaction/get_account_and_get_storage_capacity-2250ms ± 2%249ms ± 5%~(p=1.000 n=7+7)
RuntimeTransaction/get_signer_vault-242.1ms ± 5%41.1ms ± 6%~(p=0.318 n=7+7)
RuntimeTransaction/get_signer_receiver-252.1ms ± 7%51.5ms ± 4%~(p=0.805 n=7+7)
RuntimeTransaction/transfer_tokens-2211ms ± 2%211ms ± 4%~(p=0.620 n=7+7)
RuntimeTransaction/load_and_save_empty_string_on_signers_address-240.6ms ± 7%40.7ms ± 4%~(p=1.000 n=7+7)
RuntimeTransaction/load_and_save_long_string_on_signers_address-282.7ms ± 6%82.9ms ± 5%~(p=0.805 n=7+7)
RuntimeTransaction/create_new_account-2854ms ± 1%859ms ± 2%~(p=0.259 n=7+7)
RuntimeTransaction/call_empty_contract_function-237.2ms ± 4%36.9ms ± 7%~(p=0.902 n=7+7)
RuntimeTransaction/emit_event-251.3ms ± 5%51.5ms ± 6%~(p=0.805 n=7+7)
RuntimeTransaction/borrow_array_from_storage-2140ms ± 1%141ms ± 4%~(p=0.805 n=7+7)
RuntimeTransaction/copy_array_from_storage-2144ms ± 3%144ms ± 1%~(p=0.534 n=7+6)
RuntimeNFTBatchTransfer-2125ms ± 3%124ms ± 4%~(p=0.445 n=7+6)
pkg:github.com/onflow/flow-go/engine/execution/computation goos:linux goarch:amd64
ComputeBlock/16/cols/128/txes-24.80s ± 2%4.84s ± 1%~(p=0.456 n=7+7)
 
computationdelta
pkg:github.com/onflow/flow-go/fvm goos:linux goarch:amd64
RuntimeTransaction/reference_tx-2202 ± 0%202 ± 0%~(all equal)
RuntimeTransaction/convert_int_to_string-2402 ± 0%402 ± 0%~(all equal)
RuntimeTransaction/convert_int_to_string_and_concatenate_it-2502 ± 0%502 ± 0%~(all equal)
RuntimeTransaction/get_signer_address-2302 ± 0%302 ± 0%~(all equal)
RuntimeTransaction/get_public_account-2402 ± 0%402 ± 0%~(all equal)
RuntimeTransaction/get_account_and_get_balance-21.00k ± 0%1.00k ± 0%~(all equal)
RuntimeTransaction/get_account_and_get_available_balance-23.10k ± 0%3.10k ± 0%~(all equal)
RuntimeTransaction/get_account_and_get_storage_used-2402 ± 0%402 ± 0%~(all equal)
RuntimeTransaction/get_account_and_get_storage_capacity-21.70k ± 0%1.70k ± 0%~(all equal)
RuntimeTransaction/get_signer_vault-2402 ± 0%402 ± 0%~(all equal)
RuntimeTransaction/get_signer_receiver-2602 ± 0%602 ± 0%~(all equal)
RuntimeTransaction/transfer_tokens-23.50k ± 0%3.50k ± 0%~(all equal)
RuntimeTransaction/load_and_save_empty_string_on_signers_address-2602 ± 0%602 ± 0%~(all equal)
RuntimeTransaction/load_and_save_long_string_on_signers_address-2602 ± 0%602 ± 0%~(all equal)
RuntimeTransaction/create_new_account-2202 ± 0%202 ± 0%~(all equal)
RuntimeTransaction/call_empty_contract_function-2402 ± 0%402 ± 0%~(all equal)
RuntimeTransaction/emit_event-2602 ± 0%602 ± 0%~(all equal)
RuntimeTransaction/borrow_array_from_storage-22.60k ± 0%2.60k ± 0%~(all equal)
RuntimeTransaction/copy_array_from_storage-22.60k ± 0%2.60k ± 0%~(all equal)
 
interactionsdelta
pkg:github.com/onflow/flow-go/fvm goos:linux goarch:amd64
RuntimeTransaction/reference_tx-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/convert_int_to_string-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/convert_int_to_string_and_concatenate_it-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/get_signer_address-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/get_public_account-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/get_account_and_get_balance-246.3k ± 0%46.3k ± 0%~(all equal)
RuntimeTransaction/get_account_and_get_available_balance-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/get_account_and_get_storage_used-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/get_account_and_get_storage_capacity-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/get_signer_vault-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/get_signer_receiver-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/transfer_tokens-238.1k ± 0%38.1k ± 0%~(all equal)
RuntimeTransaction/load_and_save_empty_string_on_signers_address-238.3k ± 0%38.3k ± 0%~(all equal)
RuntimeTransaction/load_and_save_long_string_on_signers_address-242.1k ± 0%42.1k ± 0%~(all equal)
RuntimeTransaction/create_new_account-283.8k ± 0%83.8k ± 0%~(all equal)
RuntimeTransaction/call_empty_contract_function-238.3k ± 0%38.3k ± 0%~(all equal)
RuntimeTransaction/emit_event-238.3k ± 0%38.3k ± 0%~(all equal)
RuntimeTransaction/borrow_array_from_storage-243.4k ± 0%43.4k ± 0%~(all equal)
RuntimeTransaction/copy_array_from_storage-243.4k ± 0%43.4k ± 0%~(p=0.462 n=7+7)
 
alloc/opdelta
pkg:github.com/onflow/flow-go/fvm goos:linux goarch:amd64
RuntimeTransaction/convert_int_to_string_and_concatenate_it-235.1MB ± 3%35.9MB ± 3%+2.29%(p=0.038 n=7+7)
RuntimeTransaction/get_account_and_get_available_balance-2107MB ± 1%108MB ± 0%+0.91%(p=0.018 n=7+5)
RuntimeTransaction/reference_tx-235.1MB ± 3%35.5MB ± 3%~(p=0.456 n=7+7)
RuntimeTransaction/convert_int_to_string-234.9MB ± 4%35.5MB ± 4%~(p=0.456 n=7+7)
RuntimeTransaction/get_signer_address-235.0MB ± 4%35.4MB ± 3%~(p=0.318 n=7+7)
RuntimeTransaction/get_public_account-236.9MB ± 4%37.0MB ± 3%~(p=1.000 n=7+7)
RuntimeTransaction/get_account_and_get_balance-2120MB ± 3%119MB ± 4%~(p=0.805 n=7+7)
RuntimeTransaction/get_account_and_get_storage_used-237.3MB ± 2%37.0MB ± 3%~(p=0.620 n=7+7)
RuntimeTransaction/get_account_and_get_storage_capacity-2103MB ± 3%102MB ± 4%~(p=0.620 n=7+7)
RuntimeTransaction/get_signer_vault-237.6MB ± 5%36.7MB ± 5%~(p=0.259 n=7+7)
RuntimeTransaction/get_signer_receiver-240.7MB ± 7%40.4MB ± 3%~(p=0.535 n=7+7)
RuntimeTransaction/transfer_tokens-284.4MB ± 4%84.2MB ± 6%~(p=0.902 n=7+7)
RuntimeTransaction/load_and_save_empty_string_on_signers_address-236.8MB ± 3%36.7MB ± 3%~(p=0.945 n=6+7)
RuntimeTransaction/load_and_save_long_string_on_signers_address-252.9MB ± 4%54.1MB ± 3%~(p=0.051 n=7+6)
RuntimeTransaction/create_new_account-2183MB ± 3%183MB ± 3%~(p=0.710 n=7+7)
RuntimeTransaction/call_empty_contract_function-236.0MB ± 4%35.8MB ± 6%~(p=0.902 n=7+7)
RuntimeTransaction/emit_event-240.8MB ± 4%41.0MB ± 6%~(p=1.000 n=7+7)
RuntimeTransaction/borrow_array_from_storage-270.2MB ± 3%69.6MB ± 4%~(p=0.535 n=7+7)
RuntimeTransaction/copy_array_from_storage-281.4MB ± 2%82.5MB ± 2%~(p=0.259 n=7+7)
RuntimeNFTBatchTransfer-255.5MB ± 2%55.6MB ± 3%~(p=0.805 n=7+7)
pkg:github.com/onflow/flow-go/engine/execution/computation goos:linux goarch:amd64
ComputeBlock/16/cols/128/txes-21.24GB ± 2%1.23GB ± 0%~(p=0.535 n=7+7)
 
allocs/opdelta
pkg:github.com/onflow/flow-go/fvm goos:linux goarch:amd64
RuntimeTransaction/reference_tx-283.7k ± 0%83.7k ± 0%~(p=0.521 n=7+7)
RuntimeTransaction/convert_int_to_string-295.9k ± 0%95.9k ± 0%~(p=0.397 n=7+7)
RuntimeTransaction/convert_int_to_string_and_concatenate_it-2107k ± 0%107k ± 0%~(p=0.078 n=7+7)
RuntimeTransaction/get_signer_address-287.9k ± 0%87.9k ± 0%~(p=0.687 n=7+7)
RuntimeTransaction/get_public_account-2111k ± 0%111k ± 0%~(p=0.516 n=7+7)
RuntimeTransaction/get_account_and_get_balance-21.30M ± 0%1.30M ± 0%~(p=0.128 n=7+7)
RuntimeTransaction/get_account_and_get_available_balance-21.24M ± 0%1.24M ± 0%~(p=0.457 n=5+7)
RuntimeTransaction/get_account_and_get_storage_used-2122k ± 0%122k ± 0%~(p=0.138 n=7+7)
RuntimeTransaction/get_account_and_get_storage_capacity-21.12M ± 0%1.12M ± 0%~(p=0.736 n=7+7)
RuntimeTransaction/get_signer_vault-2124k ± 0%124k ± 0%~(p=0.176 n=7+7)
RuntimeTransaction/get_signer_receiver-2200k ± 0%200k ± 0%~(p=1.000 n=7+6)
RuntimeTransaction/transfer_tokens-2850k ± 0%850k ± 0%~(p=0.334 n=7+7)
RuntimeTransaction/load_and_save_empty_string_on_signers_address-2123k ± 0%123k ± 0%~(p=0.051 n=7+7)
RuntimeTransaction/load_and_save_long_string_on_signers_address-2205k ± 0%205k ± 0%~(p=0.686 n=7+7)
RuntimeTransaction/create_new_account-22.32M ± 0%2.32M ± 0%~(p=0.175 n=7+7)
RuntimeTransaction/call_empty_contract_function-299.2k ± 0%99.2k ± 0%~(p=0.473 n=7+7)
RuntimeTransaction/emit_event-2137k ± 0%137k ± 0%~(p=0.330 n=7+7)
RuntimeTransaction/borrow_array_from_storage-2337k ± 0%337k ± 0%~(p=0.641 n=7+7)
RuntimeTransaction/copy_array_from_storage-2294k ± 0%294k ± 0%~(p=0.534 n=6+7)
RuntimeNFTBatchTransfer-2275k ± 1%275k ± 1%~(p=0.934 n=7+7)
pkg:github.com/onflow/flow-go/engine/execution/computation goos:linux goarch:amd64
ComputeBlock/16/cols/128/txes-217.5M ± 0%17.5M ± 0%~(p=0.053 n=7+7)
 
us/txdelta
pkg:github.com/onflow/flow-go/engine/execution/computation goos:linux goarch:amd64
ComputeBlock/16/cols/128/txes-22.34k ± 2%2.36k ± 1%~(p=0.435 n=7+7)
 

@codecov-commenter
Copy link

codecov-commenter commented May 11, 2023

Codecov Report

Merging #4340 (1ac9af9) into master (12f898b) will increase coverage by 0.06%.
The diff coverage is 63.76%.

@@            Coverage Diff             @@
##           master    #4340      +/-   ##
==========================================
+ Coverage   54.09%   54.16%   +0.06%     
==========================================
  Files         563      896     +333     
  Lines       56220    84264   +28044     
==========================================
+ Hits        30414    45644   +15230     
- Misses      23459    35096   +11637     
- Partials     2347     3524    +1177     
Flag Coverage Δ
unittests 54.16% <63.76%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
admin/commands/storage/read_range_blocks.go 0.00% <0.00%> (ø)
...dmin/commands/storage/read_range_cluster_blocks.go 0.00% <0.00%> (ø)
cmd/execution_builder.go 0.00% <0.00%> (ø)
cmd/node_builder.go 93.87% <ø> (-2.07%) ⬇️
cmd/scaffold.go 14.66% <0.00%> (+0.16%) ⬆️
cmd/util/ledger/reporters/export_reporter.go 0.00% <ø> (ø)
cmd/utils.go 20.58% <ø> (+3.51%) ⬆️
cmd/verification_builder.go 0.00% <0.00%> (ø)
config/network/config.go 0.00% <0.00%> (ø)
config/network/flags.go 0.00% <0.00%> (ø)
... and 59 more

... and 323 files with indirect coverage changes

@kc1116 kc1116 requested a review from jordanschalm May 12, 2023 18:12
Copy link
Member

@jordanschalm jordanschalm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very happy to see movement on standardizing config management - it could certainly use it. Here are some general comments on the approach so far:

Singleton Pattern

I think we should design the config package so that it does not need to be a global singleton. There should a type representing Config, a constructor, and getters should be defined on the type, rather than as public methods on a global variable.

Locality

Most components which can be configured currently define a Config type in their local package (and often a default value for these configs). They then accept an instance of Config in their constructor. Config validation is also generally implemented in the package.

We can think of the global app config as the union of these component configs:

type FlowConfig struct {
  NetworkingConfig
  ConsensusEngineConfig
  ...
}

I think it is beneficial to maintain this component/config locality, for similar reasons to the above point, so I'm wondering how to incorporate that pattern with the changes you're proposing here.

One extreme is, the entire app config is passed into to each component. The global config package handles dynamic updating, validation etc. And the component just read whichever values they need.

The other extreme is, Viper acts as an initialization process, it produces the initial config as output, from which we can extract component-specific configs, then components are individually responsible for validation, dynamic updating, etc.

I feel like the current approach is leaning toward the first extreme. My gut feeling leans more toward the second extreme, and some of my comments highlight that, but the draft is still early so it's difficult to see. I think applying this approach to one of the components which follow the component-defined config pattern, and teasing out how that will interact with this new package, would be a good next step.

Comment on lines 7 to 10
// NetworkConnectionPruning returns the network connection pruning config value.
func NetworkConnectionPruning() bool {
return conf.GetBool(NetworkingConnectionPruningKey)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By making the config getter functions global, rather than for example attached to a config.Config type, we are committing to a singleton pattern. In my experience this is generally a bad idea.

Even in cases where a singleton is desired, it is generally preferable to create the object in a way which could support multiple instances (even if you only end up using one). That way, for testing purposes we can create individual instances. It also encourages dependency-injection style construction of objects -- it is preferable for a dependency to be passed into an object's constructor, than for the object to be able to access a global singleton representing that dependency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken your feedback into consideration and did away with the singleton style and opted for defining the go structs for each module's config. This updating, validation, and CLI flag instantiation will now be contained the in same package corresponding to the relevant component. i.e: All network configuration is moved to the config/network package.

Copy link
Contributor

@peterargue peterargue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together @kc1116!

I really like the idea of simplifying the config management, and supporting file based configs. I also think moving to a library makes a lot of sense.

config/config.go Outdated Show resolved Hide resolved
config/keys.go Outdated
Comment on lines 55 to 61
func toMapStrInt(vals map[string]interface{}) map[string]int {
m := make(map[string]int)
for key, val := range vals {
m[key] = val.(int)
}
return m
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like a good usecase for generics

config/keys.go Outdated

// toMapStrInt converts map[string]interface{} -> map[string]int
func toMapStrInt(vals map[string]interface{}) map[string]int {
m := make(map[string]int)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
m := make(map[string]int)
m := make(map[string]int, len(vals))

config/keys.go Outdated

const (
// network configuration keys
NetworkingConnectionPruningKey = "networking-connection-pruning"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the rest of our codebase, I seems more natural to define the default and yaml mapping in go. e.g.

type NetworkConfig struct {
	// Connection pruning determines whether connections to nodes
	// that are not part of protocol state should be trimmed
	ConnectionPruningEnabled bool `yaml:"networking-connection-pruning"`
	...
}

var defaultNetworkConfig = NetworkConfig{
	ConnectionPruningEnabled: true,
	...
}

then we could generate the default yaml config using yaml.Marshal()

this comes with a few benefits:

  • Define config and field names in the same file (fewer files to manage)
  • Less likely to have odd whitespace issues due to yaml parsing
  • When we add file based configs for operators, there's a consistent code path for the config file generation
  • We now have a type to add methods to. e.g. BindParams to encapsulate adding pflags

cmd/scaffold.go Outdated
fnb.flags.DurationVar(&fnb.BaseConfig.GossipSubConfig.LocalMeshLogInterval, "gossipsub-local-mesh-logging-interval", defaultConfig.GossipSubConfig.LocalMeshLogInterval, "logging interval for local mesh in gossipsub")
fnb.flags.DurationVar(&fnb.BaseConfig.GossipSubConfig.ScoreTracerInterval, "gossipsub-score-tracer-interval", defaultConfig.GossipSubConfig.ScoreTracerInterval, "logging interval for peer score tracer in gossipsub, set to 0 to disable")
// network config cli flags
fnb.flags.Bool(config.NetworkingConnectionPruningKey, config.NetworkConnectionPruning(), "enabling connection trimming")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're centralizing the config, what if we move the description as well. This could become:

fnb.flags.Bool(config.NetworkConnectionPruningFlag())

where NetworkConnectionPruningFlag is something like

// NetworkConnectionPruningFlag returns pflag config for networking-connection-pruning
func NetworkConnectionPruningFlag() (string, bool, string) {
	return NetworkingConnectionPruningKey,
		 conf.GetBool(NetworkingConnectionPruningKey),
		"enabling connection trimming"
}

This way we don't need to expose the key name const, and can keep it only within the library.

If you implement something like my comment re: NetworkConfig, it could look something like

// NetworkConnectionPruningFlag returns pflag config for networking-connection-pruning
func (c NetworkConfig) NetworkConnectionPruningFlag() (string, bool, string) {
	return NetworkingConnectionPruningKey,
		 c.ConnectionPruningEnabled,
		"enabling connection trimming"
}

@@ -39,7 +39,7 @@ func NewRateLimiter(limit rate.Limit, burst int, lockoutDuration time.Duration,
limiterMap: internal.NewLimiterMap(rateLimiterTTL, cleanUpTickInterval),
limit: limit,
burst: burst,
rateLimitLockoutDuration: lockoutDuration * time.Second,
rateLimitLockoutDuration: lockoutDuration,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you split these fixes out into their own PR. this looks like the rate limits currently have a permanent lockout

Base automatically changed from khalil/1885-clusterid-provider to master May 25, 2023 22:15
kc1116 added 7 commits May 25, 2023 18:23
- define Flow configuration func
- move all network configuration structs to a subpackage of the config package
- update all builder, test and fixtures
@kc1116 kc1116 marked this pull request as ready for review May 30, 2023 05:51
@kc1116 kc1116 requested review from jordanschalm, peterargue and AlexHentschel and removed request for gomisha May 30, 2023 21:13
Copy link
Member

@jordanschalm jordanschalm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All configuration values should be initialized in one space from a single source of truth config.yml

Have you given any thought to role-specific configs? For example, only the consensus node runs the DKG engine. It defines those configs within its main.go file, separate from the FlowNodeBuilder config fields.

Would we want to include those role-specific configs in the central config package as well, or is the idea to focus on role-generic configs only?

Comment on lines 112 to 118
func (c *CtrlMsgValidationConfig) SetRateLimiter(r p2p.BasicRateLimiter) {
c.rateLimiter = r
}

func (c *CtrlMsgValidationConfig) RateLimiter() p2p.BasicRateLimiter {
return c.rateLimiter
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make more sense to put this into ControlMsgValidationInspector, since it is essentially a dependency of that component. Feels a bit out of place here, since it is created in the constructor, rather than read in from flags/config file. Not a big deal though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -478,8 +479,9 @@ func (builder *FollowerServiceBuilder) InitIDProviders() {
return fmt.Errorf("could not initialize ProtocolStateIDCache: %w", err)
}
builder.IDTranslator = translator.NewHierarchicalIDTranslator(idCache, translator.NewPublicNetworkIDTranslator())
fmt.Println(builder.BaseConfig.FlowConfig)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug log

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type ErrInvalidLimitConfig struct {
// controlMsg the control message type.
controlMsg p2p.ControlMessageType
err error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we want to be able to interpret this as part of the error chain, need to implement Unwrap() method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config/network/errors.go Outdated Show resolved Hide resolved
config/network/errors.go Outdated Show resolved Hide resolved
Comment on lines 22 to 23
func NewInvalidLimitConfigErr(controlMsg p2p.ControlMessageType, err error) ErrInvalidLimitConfig {
return ErrInvalidLimitConfig{controlMsg: controlMsg, err: err}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func NewInvalidLimitConfigErr(controlMsg p2p.ControlMessageType, err error) ErrInvalidLimitConfig {
return ErrInvalidLimitConfig{controlMsg: controlMsg, err: err}
func NewInvalidLimitConfigError(controlMsg p2p.ControlMessageType, msg string, args ...any) ErrInvalidLimitConfig {
return InvalidLimitConfigError{controlMsg: controlMsg, err: fmt.Errorf(msg, args...)}

Optional: I find this is a useful pattern to define error types:

  • guarantees MyError.err is non-nil
  • invocation is similar to fmt.Errorf + don't need to embed fmt.Errorf at the callsite + encourages the caller to add additional context (like Errorf)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is less useful for the usage of NewInvalidLimitConfigError since the error is simple the only context needed is the control msg type and the validation err to wrap.

@@ -0,0 +1,124 @@
# Network Configuration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could call this file default-config.yml?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config/config.go Outdated
Comment on lines 17 to 18
//go:embed config.yml
configFile string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the yml file essentially acts as an encoded representation of config defaults. Do we want to allow node operators to actually provide a config.yml file to configure their node as well? I think doing so would be beneficial (and mostly leverage functionality already provided by Viper).

But if we support that, we'll need to adjust this package to accept a file input from the node scaffold, and rename configFile etc. to make it clear it is only defaults.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config/config.go Outdated Show resolved Hide resolved
kc1116 and others added 5 commits June 12, 2023 16:09
- Fix flag binding not working as expected by ensuring there is a alias set from config flag name -> conf file key due to the fact viper prepends property names to keys for nested config values
- reduce the network config to one nested layer under the network-config property to reduce complexity with aliases
- fix printing configs when configuration is loaded
- use mapstructure .squash tag when using nested config structs
Co-authored-by: Jordan Schalm <jordan@dapperlabs.com>
@kc1116
Copy link
Contributor Author

kc1116 commented Jun 16, 2023

All configuration values should be initialized in one space from a single source of truth config.yml

Have you given any thought to role-specific configs? For example, only the consensus node runs the DKG engine. It defines those configs within its main.go file, separate from the FlowNodeBuilder config fields.
Would we want to include those role-specific configs in the central config package as well, or is the idea to focus on role-generic configs only?

I think we should group configs by component or collection of components (used together and required for some specific functionality). There may or may not be components used across multiple node roles. For example, the "Network" is not an explicitly defined component but a set of components that work together to ensure a functioning network. Furthermore "Consensus" is not an explicitly defined component but a collection of components that work together to ensure a functioning consensus layer. So I would want to include all configuration structs for consensus-related components under a single package in the config package. Currently, DKG configs are defined in the main.go for consensus nodes but it probably should be defined in a config/consensus package where we can take a look at a single package and get a full view of all consensus-related configs.

@jordanschalm

@kc1116
Copy link
Contributor Author

kc1116 commented Jun 16, 2023

In my mind, the flow-go repo is a collection of libraries (building blocks) to compose various different micro-services > >(collector, consensus, execution, verification, access, observer, archive node + light clients in the future).

The way I understand your proposal, you are suggesting to have a single default config for everything. I think that introduces multiple fundamental challenges, which I would like to avoid:

I consider it very well possible, that a single default configuration satisfying all possible micro-services does not exist.

For example, consider consensus nodes who have private Random beacon keys that all other nodes do not have. If you configure a default path where those keys are to be found, and that path is verified as part of the Validate() method, only the consensus nodes will start and all other nodes will reject the config saying that the given path does not exist. Please only consider this example for illustrative purposed, potentially this is not even a problem right now, or we can work around it.

Assuming that there is only a single default configuration is a relatively foundational limitation in my opinion. I would prefer if we had the option to have one default configuration per microservice.

@AlexHentschel I agree we should not have a single config for each, and thats not an issue. Currently there is a single default-config.yml because only the network-config is defined in the package. In order to improve readability we can define a .yml file per sub config package in the future. The main goal is to move all configuration to a centralized location to improve clarity, and make updating or editing configurations simpler.

kc1116 and others added 6 commits June 16, 2023 09:35
Co-authored-by: Alexander Hentschel <alex.hentschel@axiomzen.co>
Co-authored-by: Alexander Hentschel <alex.hentschel@axiomzen.co>
…onflow/flow-go into khalil/1888-network-config-improvements
config/config.go Outdated
//
// error: if there is any error encountered while reading new config file, all errors are considered irrecoverable.
// bool: true if the config was overridden by the new config file, false otherwise or if an error is encountered reading the new config file.
func overrideConfigFile(flags *pflag.FlagSet) (error, bool) {
Copy link
Contributor

@yhassanzadeh13 yhassanzadeh13 Jun 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(1) The function overrideConfigFile returns (error, bool). It is more idiomatic in Go to have the error as the last return value. So it is better to have it return (bool, error).

(2) Also, it is good to see that functions have comments that explain what they do. However, the documentation is not self-sufficient regarding the configFilePath.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -92,6 +113,71 @@ func Unmarshall(flowConfig *FlowConfig) error {
return nil
}

// Print prints current configuration keys and values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding "prints current configuration keys and values", the documentation does not explicitly show any print statements. Instead, it stores key-value pairs into a logger (info). This might be slightly confusing for someone reading the comment and expecting direct print output. To improve the structure of the Print function, I'd recommend renaming it to better reflect its purpose and improving its documentation. I'd also recommend passing the necessary configurations as parameters instead of relying on the global state.

Suggestion:

// LogConfig logs configuration keys and values if they were overridden with a config file.
// It also returns a map of keys for which the values were set by a config file.
//
// Parameters:
//     - logger: *zerolog.Event to which the configuration keys and values will be logged.
//     - flags: *pflag.FlagSet containing the set flags.
//     - conf: configuration store (assumed to be of a type that supports AllKeys and Get methods).
//
// Returns:
//     - map[string]struct{}: map of keys for which the values were set by a config file.
func LogConfig(logger *zerolog.Event, flags *pflag.FlagSet, conf interface{ AllKeys() []string; Get(key string) interface{} }) map[string]struct{} {
	keysToAvoid := make(map[string]struct{})

	if flags.Lookup(configFilePath).Changed {
		for _, key := range conf.AllKeys() {
			logger.Str(key, fmt.Sprintf("%v", conf.Get(key)))
			parts := strings.Split(key, ".")
			if len(parts) == 2 {
				keysToAvoid[parts[1]] = struct{}{}
			} else {
				keysToAvoid[key] = struct{}{}
			}
		}
	}

	return keysToAvoid
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m := make(map[string]struct{})
if flags.Lookup(configFilePath).Changed {
for _, key := range conf.AllKeys() {
info.Str(key, fmt.Sprintf("%v", conf.Get(key)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be simplified into info.Str(key, fmt.Sprint(conf.Get(key))).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

// getConfigNameFromPath returns the directory and name of the config file from the provided path string.
func splitConfigPath(path string) (string, string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this function, you are trying to split the path into the directory and the base name of the config file without the extension. This is fine, but it’s good to note that if someone names a config file with multiple dots (e.g., my.config.yaml), this function would consider my as the name which might not be intended. I suggest the following implementation that uses a regular expression pattern and panics if the input path does not match the pattern:

// splitConfigPath returns the directory and base name (without extension) of the config file from the provided path string.
// If the file name does not match the expected pattern, the function panics.
//
// The expected pattern for file names is that they must consist of alphanumeric characters, hyphens, or underscores,
// followed by a single dot and then the extension.
//
// Legitimate Inputs:
//     - /path/to/my_config.yaml
//     - /path/to/my-config123.yaml
//     - my-config.yaml (when in the current directory)
//
// Illegitimate Inputs:
//     - /path/to/my.config.yaml (contains multiple dots)
//     - /path/to/my config.yaml (contains spaces)
//     - /path/to/.config.yaml (does not have a file name before the dot)
//
// Args:
//     - path: The file path string to be split into directory and base name.
//
// Returns:
//     - The directory and base name without extension.
//
// Panics:
//     - If the file name does not match the expected pattern.
func splitConfigPath(path string) (string, string) {
	// Regex to match filenames like 'my_config.yaml' or 'my-config.yaml' but not 'my.config.yaml'
	validFileNamePattern := regexp.MustCompile(`^[a-zA-Z0-9_-]+\.[a-zA-Z0-9]+$`)

	dir, name := filepath.Split(path)

	// Panic if the file name does not match the expected pattern
	if !validFileNamePattern.MatchString(name) {
		panic(fmt.Errorf("Invalid config file name '%s'. Expected pattern: alphanumeric, hyphens, or underscores followed by a single dot and extension", name))
	}

	// Extracting the base name without extension
	baseName := strings.Split(name, ".")[0]
	return dir, baseName
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if !ok {
return fmt.Errorf("invalid network configuration missing configuration key flag name %s check config file and cli flags", flagName)
}
conf.RegisterAlias(fullKey, flagName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be conf.RegisterAlias(flagName, fullKey) according to the documentation of Viper?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No the usage is correct. When keys bound from the CLI are added to the config store they are added without any of the prefixes. Because we want to access config values without using long keys with prefixes we set an alias from the keys bound from the CLI -> keys read in from config files that may have prefixes.

@kc1116 kc1116 merged commit 316a886 into master Jun 20, 2023
@kc1116 kc1116 deleted the khalil/1888-network-config-improvements branch June 20, 2023 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants