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

Update config and args #669

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
319 changes: 193 additions & 126 deletions bin/strata-client/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,171 +1,238 @@
use std::path::PathBuf;

use argh::FromArgs;
use bitcoin::Network;
use strata_config::{
bridge::RelayerConfig, btcio::BtcioConfig, BitcoindConfig, ClientConfig, ClientMode, Config,
ExecConfig, FullNodeConfig, RethELConfig, SequencerConfig, SyncConfig,
};
use toml::value::Table;

use crate::errors::{ConfigError, InitError};

/// Configs overridable by environment. Mostly for sensitive data.
#[derive(Debug, Clone)]
pub struct EnvArgs {
// TODO: relevant items that will be populated from env vars
}

impl EnvArgs {
pub fn from_env() -> Self {
// Here we load particular env vars that should probably override the config.
Self {}
}

Check warning on line 18 in bin/strata-client/src/args.rs

View check run for this annotation

Codecov / codecov/patch

bin/strata-client/src/args.rs#L15-L18

Added lines #L15 - L18 were not covered by tests

/// Get strings of overrides gathered from env.
pub fn get_overrides(&self) -> Vec<String> {
// TODO: add stuffs as necessary
Vec::new()
}

Check warning on line 24 in bin/strata-client/src/args.rs

View check run for this annotation

Codecov / codecov/patch

bin/strata-client/src/args.rs#L21-L24

Added lines #L21 - L24 were not covered by tests
}

#[derive(Debug, Clone, FromArgs)]
#[argh(description = "Alpen Strata sequencer")]
#[argh(description = "Strata client")]
pub struct Args {
// TODO: default config location
// Config non-overriding args
#[argh(option, short = 'c', description = "path to configuration")]
pub config: Option<PathBuf>,
pub config: PathBuf,

// Config overriding args
/// Data directory path that will override the path in the config toml.
#[argh(
option,
short = 'd',
description = "datadir path that will contain databases"
)]
pub datadir: Option<PathBuf>,

#[argh(option, short = 'h', description = "JSON-RPC host")]
/// Switch that indicates if the client is running as a sequencer.
#[argh(switch, description = "is sequencer")]
pub sequencer: bool,

/// Rollup params path that will override the params in the config toml.
#[argh(option, description = "rollup params")]
pub rollup_params: Option<PathBuf>,

/// Rpc host that the client will listen to.
#[argh(option, description = "rpc host")]
pub rpc_host: Option<String>,

#[argh(option, short = 'r', description = "JSON-RPC port")]
/// Rpc port that the client will listen to.
#[argh(option, description = "rpc port")]
pub rpc_port: Option<u16>,

#[argh(option, description = "bitcoind RPC host")]
pub bitcoind_host: Option<String>,

#[argh(option, description = "bitcoind RPC user")]
pub bitcoind_user: Option<String>,
/// Other generic overrides to the config toml.
/// Will be used, for example, as `-o btcio.reader.client_poll_dur_ms=1000 -o exec.reth.rpc_url=http://reth`
#[argh(option, short = 'o', description = "generic config overrides")]
pub overrides: Vec<String>,
}

#[argh(option, description = "bitcoind RPC password")]
pub bitcoind_password: Option<String>,
impl Args {
/// Get strings of overrides gathered from args.
pub fn get_overrides(&self) -> Result<Vec<String>, InitError> {
let mut overrides = self.overrides.clone();
overrides.extend_from_slice(&self.get_direct_overrides()?);
Ok(overrides)
}

/// Max retries for Bitcoin RPC calls.
#[argh(option, description = "max retries for bitcoin RPC (default: 3)")]
pub bitcoind_retry_count: Option<u8>,
/// Overrides passed directly as args and not as overrides.
fn get_direct_overrides(&self) -> Result<Vec<String>, InitError> {
let mut overrides = Vec::new();
if self.sequencer {
overrides.push("client.is_sequencer=true".to_string());
}
if let Some(datadir) = &self.datadir {
let dd = datadir.to_str().ok_or(anyhow::anyhow!(
"Invalid datadir override path {:?}",
datadir
))?;
overrides.push(format!("client.datadir={dd}"));

Check warning on line 84 in bin/strata-client/src/args.rs

View check run for this annotation

Codecov / codecov/patch

bin/strata-client/src/args.rs#L80-L84

Added lines #L80 - L84 were not covered by tests
}
if let Some(rpc_host) = &self.rpc_host {
overrides.push(format!("client.rpc_host={rpc_host}"));

Check warning on line 87 in bin/strata-client/src/args.rs

View check run for this annotation

Codecov / codecov/patch

bin/strata-client/src/args.rs#L87

Added line #L87 was not covered by tests
}
if let Some(rpc_port) = &self.rpc_port {
overrides.push(format!("client.rpc_port={rpc_port}"));

Check warning on line 90 in bin/strata-client/src/args.rs

View check run for this annotation

Codecov / codecov/patch

bin/strata-client/src/args.rs#L90

Added line #L90 was not covered by tests
}

/// Timeout duration for btc request retries in ms. Defaults to `1000`.
#[argh(
option,
description = "max interval between bitcoin RPC retries in ms (default: 1000)"
)]
pub bitcoind_retry_interval: Option<u64>,
Ok(overrides)
}
}

#[argh(option, short = 'n', description = "L1 network to run on")]
pub network: Option<Network>,
type Override = (String, toml::Value);

#[argh(switch, description = "start sequencer bookkeeping tasks")]
pub sequencer: bool,
/// 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_str) = override_str
.split_once("=")
.ok_or(ConfigError::InvalidOverride(override_str.to_string()))?;
Ok((key.to_string(), parse_value(value_str)))
}

#[argh(option, description = "sequencer rpc host:port")]
pub sequencer_rpc: Option<String>,
/// Apply override to config.
pub fn apply_override(
path: &str,
value: toml::Value,
table: &mut Table,
) -> Result<(), ConfigError> {
match path.split_once(".") {
None => {
table.insert(path.to_string(), value);
Ok(())
}
Some((key, rest)) => {
if let Some(t) = table.get_mut(key).and_then(|v| v.as_table_mut()) {
apply_override(rest, value, t)
} else if table.contains_key(key) {
Err(ConfigError::TraverseNonTableAt(key.to_string()))

Check warning on line 123 in bin/strata-client/src/args.rs

View check run for this annotation

Codecov / codecov/patch

bin/strata-client/src/args.rs#L122-L123

Added lines #L122 - L123 were not covered by tests
} else {
Err(ConfigError::MissingKey(key.to_string()))

Check warning on line 125 in bin/strata-client/src/args.rs

View check run for this annotation

Codecov / codecov/patch

bin/strata-client/src/args.rs#L125

Added line #L125 was not covered by tests
}
}
}
}

#[argh(option, description = "reth authrpc host:port")]
pub reth_authrpc: Option<String>,
/// Parses a string into a toml value. First tries as `i64`, then as `bool` and then defaults to
/// `String`.
fn parse_value(str_value: &str) -> toml::Value {
str_value
.parse::<i64>()
.map(toml::Value::Integer)
.or_else(|_| str_value.parse::<bool>().map(toml::Value::Boolean))
.unwrap_or_else(|_| toml::Value::String(str_value.to_string()))
}

#[argh(option, description = "path to reth authrpc jwtsecret")]
pub reth_jwtsecret: PathBuf,
#[cfg(test)]
mod test {

// TODO: allow only for dev/test mode ?
#[argh(option, short = 'p', description = "custom rollup config path")]
pub rollup_params: Option<PathBuf>,
use bitcoin::Network;
use strata_config::{
bridge::RelayerConfig, btcio::BtcioConfig, BitcoindConfig, ClientConfig, Config,
ExecConfig, RethELConfig, SyncConfig,
};

#[argh(option, description = "database retry count")]
pub db_retry_count: Option<u16>,
}
use super::*;

impl Args {
pub fn derive_config(&self) -> Result<Config, String> {
let args = self.clone();
Ok(Config {
bitcoind_rpc: BitcoindConfig {
rpc_url: require(args.bitcoind_host, "args: no bitcoin --rpc-url provided")?,
rpc_user: require(args.bitcoind_user, "args: no bitcoin --rpc-user provided")?,
rpc_password: require(
args.bitcoind_password,
"args: no bitcoin --rpc-password provided",
)?,
network: require(args.network, "args: no bitcoin --network provided")?,
retry_count: args.bitcoind_retry_count,
retry_interval: args.bitcoind_retry_interval,
},
fn get_config() -> Config {
Config {
client: ClientConfig {
rpc_host: require(args.rpc_host, "args: no client --rpc-host provided")?,
rpc_port: require(args.rpc_port, "args: no client --rpc-port provided")?,
datadir: require(args.datadir, "args: no client --datadir provided")?,
client_mode: {
if args.sequencer {
ClientMode::Sequencer(SequencerConfig {})
} else if let Some(sequencer_rpc) = args.sequencer_rpc {
ClientMode::FullNode(FullNodeConfig { sequencer_rpc })
} else {
return Err(
"args: no client --sequencer or --sequencer-rpc provided".to_string()
);
}
},
l2_blocks_fetch_limit: 1_000,
db_retry_count: 5,
rpc_host: "".to_string(),
rpc_port: 300,
p2p_port: 300,
sync_endpoint: None,
l2_blocks_fetch_limit: 20,
datadir: "".into(),
db_retry_count: 3,
is_sequencer: false,
},
sync: SyncConfig {
l1_follow_distance: 6,
client_checkpoint_interval: 10,
bitcoind: BitcoindConfig {
rpc_url: "".to_string(),
rpc_user: "".to_string(),
rpc_password: "".to_string(),
network: bitcoin::Network::Regtest,
retry_count: None,
retry_interval: None,
},
btcio: BtcioConfig {
reader: Default::default(),
writer: Default::default(),
broadcaster: Default::default(),
},
exec: ExecConfig {
reth: RethELConfig {
rpc_url: args.reth_authrpc.unwrap_or("".to_string()), // TODO: sensible default
secret: args.reth_jwtsecret,
rpc_url: "".to_string(),
secret: "".into(),
},
},
relayer: RelayerConfig {
// TODO: actually get these from args
refresh_interval: 10,
stale_duration: 120,
relay_misc: true,
refresh_interval: 1,
stale_duration: 2,
relay_misc: false,
},
btcio: BtcioConfig::default(), // TODO: actually get this from args
})
sync: SyncConfig {
l1_follow_distance: 1,
client_checkpoint_interval: 2,
},
}
}

pub fn update_config(&self, config: &mut Config) {
let args = self.clone();
config.exec.reth.secret = args.reth_jwtsecret;

if let Some(rpc_user) = args.bitcoind_user {
config.bitcoind_rpc.rpc_user = rpc_user;
}
if let Some(rpc_url) = args.bitcoind_host {
config.bitcoind_rpc.rpc_url = rpc_url;
}
if let Some(rpc_password) = args.bitcoind_password {
config.bitcoind_rpc.rpc_password = rpc_password;
}
if let Some(retry_count) = args.bitcoind_retry_count {
config.bitcoind_rpc.retry_count = Some(retry_count);
}
if let Some(retry_interval) = args.bitcoind_retry_interval {
config.bitcoind_rpc.retry_interval = Some(retry_interval);
}
if let Some(rpc_host) = args.rpc_host {
config.client.rpc_host = rpc_host;
}
if let Some(rpc_port) = args.rpc_port {
config.client.rpc_port = rpc_port;
}
if let Some(datadir) = args.datadir {
config.client.datadir = datadir;
#[test]
fn test_apply_override() {
let config = get_config();
let mut toml = toml::Value::try_from(&config).unwrap();
let table = toml.as_table_mut().unwrap();
let args = Args {
config: "config_path".into(),
datadir: None,
sequencer: true,
rollup_params: None,
rpc_host: None,
rpc_port: None,
overrides: vec![
"btcio.reader.client_poll_dur_ms=50".to_string(),
"sync.l1_follow_distance=30".to_string(),
"client.rpc_host=rpchost".to_string(),
"bitcoind.network=signet".to_string(),
"client.datadir=some/data/dir/".to_string(),
],
};

let overrides = args
.get_overrides()
.unwrap()
.into_iter()
.map(|x| parse_override(&x).unwrap());

for (path, val) in overrides {
apply_override(&path, val, table).unwrap();
}

if args.sequencer {
config.client.client_mode = ClientMode::Sequencer(SequencerConfig {});
} else if let Some(sequencer_rpc) = args.sequencer_rpc {
config.client.client_mode = ClientMode::FullNode(FullNodeConfig { sequencer_rpc });
}
if let Some(rpc_url) = args.reth_authrpc {
config.exec.reth.rpc_url = rpc_url;
}
if let Some(db_retry_count) = args.db_retry_count {
config.client.db_retry_count = db_retry_count;
}
let new_config: Config = toml.try_into().unwrap();

assert_eq!(new_config.btcio.reader.client_poll_dur_ms, 50);
assert_eq!(new_config.sync.l1_follow_distance, 30);
assert_eq!(&new_config.client.rpc_host, "rpchost");
assert_eq!(new_config.bitcoind.network, Network::Signet);
assert!(new_config.client.is_sequencer);
assert_eq!(
&new_config.client.datadir.to_string_lossy(),
"some/data/dir/"
);
}
}

fn require<T>(field: Option<T>, err_msg: &str) -> Result<T, String> {
field.ok_or_else(|| err_msg.to_string())
}
22 changes: 20 additions & 2 deletions bin/strata-client/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ pub enum InitError {
#[error("io: {0}")]
Io(#[from] io::Error),

#[error("config: {0}")]
MalformedConfig(#[from] SerdeError),
#[error("unparsable params file: {0}")]
UnparsableParamsFile(#[from] SerdeError),

#[error("config: {0:?}")]
MalformedConfig(#[from] ConfigError),

#[error("jwt: {0}")]
MalformedSecret(#[from] JwtError),
Expand All @@ -25,3 +28,18 @@ pub enum InitError {
#[error("{0}")]
Anyhow(#[from] anyhow::Error),
}

#[derive(Debug, Error)]
pub enum ConfigError {
/// Missing key in table.
#[error("missing key: {0}")]
MissingKey(String),

/// Tried to traverse into a primitive.
#[error("can't traverse into non-table key: {0}")]
TraverseNonTableAt(String),

/// Invalid override string.
#[error("Invalid override: '{0}'")]
InvalidOverride(String),
}
Loading
Loading