-
Notifications
You must be signed in to change notification settings - Fork 5
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
Update config and args #669
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
@@ Coverage Diff @@
## main #669 +/- ##
==========================================
+ Coverage 54.05% 54.72% +0.67%
==========================================
Files 320 320
Lines 34158 34490 +332
==========================================
+ Hits 18464 18876 +412
+ Misses 15694 15614 -80
|
Commit: 2947eeb SP1 Performance Test Results
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor nits.
These are awesome, thanks! Nice work.
8cd663b
to
bd24021
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in DMs, the round trip through json and the weird special casing of strings is a problem. This forces that the config be well-formed before applying the overrides, which would breaks use cases like where the user has an incomplete config wants to "finish" it by setting overrides at run time. Maybe that specific scenario isn't particularly common, but it makes it generally more flexible if we apply the overrides on the toml parsing first before trying to interpret it concretely.
bin/strata-client/src/args.rs
Outdated
/// Parse valid overrides. This first splits the entries by '=' to get key and value and then splits | ||
/// the key by '.' which is the update path. | ||
fn parse_overrides(overrides: &[String]) -> anyhow::Result<Vec<Override>> { | ||
let mut result = Vec::new(); | ||
for item in overrides { | ||
let (key, value) = item | ||
.split_once("=") | ||
.ok_or(anyhow!("Invalid override: must be in 'key=value' format"))?; | ||
let path: Vec<_> = key.split(".").map(|x| x.to_string()).collect(); | ||
result.push((path, value.to_string())); | ||
} | ||
Ok(result) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Functional programming heuristic: The way this is written, it's not possible to parse just a single override string. You always have to parse a list of override strings. The body of the loop is where the hard part is happening, and then we don't need a function just to transform elements of a list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Truee.
bin/strata-client/src/args.rs
Outdated
[key] => { | ||
config[key] = parse_value(str_value)?; | ||
} | ||
[key, other @ ..] => { | ||
apply_override(other, str_value, &mut config[key])?; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The config[key]
bit panics if config
isn't a json dict. The snippet I sent gives a safer way to handle this that gives smarter error handling.
bin/strata-client/src/args.rs
Outdated
match serde_json::from_str(str_value) { | ||
Ok(v) => Ok(v), | ||
Err(e) => match serde_json::from_str::<T>(&format!("\"{str_value}\"")) { | ||
Ok(v) => Ok(v), | ||
Err(_) => Err(e), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is messy because it's trying to parse all possible json values. Don't try to, constrain behavior. It's only reasonable right now to support bools, ints, and strings. When the user passes something they expect to be a string then it implicitly needs to be a valid "the chars between the quotes" of a json string token, so it'd break unexpectedly if they pass something that has a \
in it, like maybe someone running Windows would.
bin/strata-client/src/args.rs
Outdated
pub fn override_config(&self, config: &mut Config) -> anyhow::Result<bool> { | ||
// Override using -o params. | ||
let mut overridden = self.override_generic(config)?; | ||
|
||
// Override by explicitly parsed args like datadir, rpc_host and rpc_port. | ||
if let Some(datadir) = &self.datadir { | ||
config.client.datadir = datadir.into(); | ||
overridden = true | ||
} | ||
if let Some(rpc_host) = &self.rpc_host { | ||
config.client.rpc_host = rpc_host.to_string(); | ||
overridden = true | ||
} | ||
if let Some(rpc_port) = &self.rpc_port { | ||
config.client.rpc_port = *rpc_port; | ||
overridden = true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These can be lowered to just being overrides instead of having to maintain additional special casing for these.
fn extract_cmdline_override_strs(args: &Args) -> anyhow::Result<Vec<Override>> {
let mut overrides = Vec::new();
if let Some(dd) = &self.datadir {
overrides.push(Override::new("client.datadir", Value::String(dd.to_owned()));
}
// the rest
Ok(overrides)
}
So really you'd have some .get_overrides()
fn that just returns a list of overrides, the ones explicitly added and then the ones generated from the other options like -d
and -p
. Also I think this is missing a few options.
This pattern also makes it easy when we want to parse values from the env, it's all just the override system.
bin/strata-client/src/args.rs
Outdated
} | ||
// Convert back to json. | ||
*config = from_value(json_config) | ||
.context("Should be able to create Config from serde json Value")?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These contexts are for adding more information about what the program was trying to do when the fn was called, not just literally what went wrong if there were to be an error. That would be the job of the inner error type.
functional-tests/factory/factory.py
Outdated
# bitcoind | ||
"-o", f"bitcoind.rpc_url={bitcoind_config["bitcoind_sock"]}", | ||
"-o", f"bitcoind.rpc_user={bitcoind_config["bitcoind_user"]}", | ||
"-o", f"bitcoind.rpc_password={bitcoind_config["bitcoind_pass"]}", | ||
"-o", "bitcoind.network=regtest", | ||
|
||
# reth | ||
"-o", f"exec.reth.rpc_url={reth_config["reth_socket"]}", | ||
"-o", f"exec.reth.secret={reth_config["reth_secret_path"]}", | ||
"--sequencer" | ||
|
||
# client | ||
"-o", f"client.sync_endpoint={sequencer_rpc}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good to see here, but really we should be writing all of these to the config file instead of just building the whole configuration as overrides (and same in above case). It's also less likely to silently break if we use the nice types you also added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah good point. Changed.
functional-tests/factory/config.py
Outdated
client: ClientConfig | ||
bitcoind: BitcoindConfig | ||
btcio: BtcioConfig | ||
sync: SyncConfig | ||
exec: ExecConfig | ||
relayer: RelayerConfig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we also use field(default_factory=...)
for these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can totally! changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 7e15fca
e0a8f83
to
72f0863
Compare
@delbonis , made the changes. Seems like I was wrong about using json would let us dynamically parse stuffs. Removed all the json manipulation now. |
bin/strata-client/src/args.rs
Outdated
#[argh( | ||
option, | ||
description = "max interval between bitcoin RPC retries in ms (default: 1000)" | ||
)] | ||
pub bitcoind_retry_interval: Option<u64>, | ||
type Override = (Vec<String>, String); | ||
|
||
#[argh(option, short = 'n', description = "L1 network to run on")] | ||
pub network: Option<Network>, | ||
/// Parses an overrides This first splits the string by '=' to get key and value and then splits | ||
/// the key by '.' which is the update path. | ||
pub fn parse_override(override_str: &str) -> Result<Override, ConfigError> { | ||
let (key, value) = override_str | ||
.split_once("=") | ||
.ok_or(ConfigError::InvalidOverride(override_str.to_string()))?; | ||
let path: Vec<_> = key.split(".").map(|x| x.to_string()).collect(); | ||
Ok((path, value.to_string())) | ||
} | ||
|
||
#[argh(switch, description = "start sequencer bookkeeping tasks")] | ||
pub sequencer: bool, | ||
pub fn apply_overrides(overrides: Vec<String>, table: &mut Table) -> Result<Config, ConfigError> { | ||
for res in overrides.iter().map(String::as_str).map(parse_override) { | ||
let (path, val) = res?; | ||
apply_override(&path, &val, table)?; | ||
} | ||
|
||
#[argh(option, description = "sequencer rpc host:port")] | ||
pub sequencer_rpc: Option<String>, | ||
toml::Value::Table(table.clone()) | ||
.try_into() | ||
.map_err(|_| ConfigError::ConfigNotParseable) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not go the rest of the way with this and have a
struct Override {
key: String, // (using `.split_once` to traverse the path)
value: toml::Value
}
So that we can complain about parse failures before we try to manipulate the configuration? This more cleanly separates the stages since it's not having to do more parsing in the override phase, making it more functional and easier to reason about.
Also the way the apply_overrides
function works is weird. It mutates its input and then parses and it and returns that? Same thing where it's merging phases into a conglomeration that's hard to reason about. Also the error reporting here is weak, it throws away all the information the user might need to figure out what they did wrong.
Pseudocode for making it 3 distinct phases:
let abstract_config = ...;
let overrides = override_strs.map(parse_override);
overrides.for_each(apply_override(abstract_config));
let final_config = parse(abstract_config);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this makes sense. Agree that the apply_overrides
is a bit weird. changed.
bin/strata-client/src/args.rs
Outdated
.parse::<i64>() | ||
.map(toml::Value::Integer) | ||
.or_else(|_| str_value.parse::<bool>().map(toml::Value::Boolean)) | ||
.unwrap_or(toml::Value::String(str_value.to_string())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use unwrap_or_else
. The way it's written now always constructs the value even if it parses successfully as a string or bool beforehand.
bin/strata-client/src/helpers.rs
Outdated
let conf = | ||
toml::from_str::<Config>(&config_str).map_err(|err| SerdeError::new(config_str, err))?; | ||
Ok(conf) | ||
Ok(toml::from_str(&config_str).map_err(|_| ConfigError::ConfigNotParseable)?) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just return the toml error! It has moderately detailed error reporting!
bin/strata-client/src/main.rs
Outdated
@@ -137,9 +140,9 @@ fn main_inner(args: Args) -> anyhow::Result<()> { | |||
|
|||
let mut methods = jsonrpsee::Methods::new(); | |||
|
|||
match &config.client.client_mode { | |||
match &args.sequencer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be pulling from the parsed config instead of args.
Also, if
?
bin/strata-client/src/args.rs
Outdated
let mut overrides = Vec::new(); | ||
if let Some(datadir) = &self.datadir { | ||
overrides.push(format!("client.datadir={}", datadir.to_string_lossy())); | ||
} | ||
if let Some(rpc_host) = &self.rpc_host { | ||
overrides.push(format!("client.rpc_host={}", rpc_host)); | ||
} | ||
if let Some(rpc_port) = &self.rpc_port { | ||
overrides.push(format!("client.rpc_port={}", rpc_port)); | ||
} | ||
|
||
/// Max retries for Bitcoin RPC calls. | ||
#[argh(option, description = "max retries for bitcoin RPC (default: 3)")] | ||
pub bitcoind_retry_count: Option<u8>, | ||
overrides |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can do format!("foo.bar={bar}")
instead of having to put it at the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks mostly good but there's some minor tweaks here.
a14ac35
to
ffde02e
Compare
ffde02e
to
40f8dbc
Compare
284faf1
to
7bf93c2
Compare
Description
This PR does the following:
Args
struct. It now only accepts--datadir
,--config
,--rollup-params
,--rpc-host
,--rpc-port
and generic override param-o
.-o
is something like./strata-client --config configfile.toml -o bitcoind.rpc_user=user -o btcio.reader.client_poll_dur_ms=1000 -o exec.reth.rpc_url=http://reth
-o
will override the contents from config file and the default values.factory/config.py
module corresponding tostrata_config::Config
with default values set and conversion to toml.Note that the previous args passing no longer works and this has impacts on deployment too. Possibly need to update docker stuffs as well.
Type of Change
Notes to Reviewers
Checklist
Related Issues