Idea: Easier, dynamic Client configuration

I want to share an idea for potentially simplifying autonomi::Client configuration based on my recent experience working with it.

Current Configuration

Right now, the Rust autonomi::Client can be configured in several ways:

  • Multiple init functions:
    Client::init()
    Client::init_local()
    Client::init_with_peers(peers: Vec<Multiaddr>)
    Client::init_with_config(config: ClientConfig)
    // and in the next release afaik
    Client::init_alpha()
    
  • The ClientConfig struct, which has many properties:
    pub struct ClientConfig {
        pub init_peers_config: InitialPeersConfig,
        pub evm_network: EvmNetwork,
        pub strategy: ClientOperatingStrategy,
        pub network_id: Option<u8>,
    }
    
    pub InitialPeersConfig {
        pub first: bool,
        pub addrs: Vec<Multiaddr>,
        pub local: bool,
        pub ignore_cache: bool,  
        pub bootstrap_cache_dir: Option<PathBuf>,
    }
    
    pub enum EvmNetwork {
        ArbitrumOne,
        ArbitrumSepolia,
        ArbitrumSepoliaTest,
        Custom(CustomNetwork),
    }
    
    pub struct CustomNetwork {
        pub rpc_url_http: reqwest::Url,
        pub payment_token_address: Address,
        pub data_payments_address: Address,
    }
    
  • Internal environment variables (like ANT_PEERS, RPC_URL, etc.) also affect the final autonomi::Client configuration.

In my project, I found configuring the client and especially allowing different configurations at runtime to be more cumbersome that it needs to be.

  • It requires quite a bit of extra boilerplate code where I need configurability.
  • There are usability considerations: many options need specifying, validating, and user-friendly error reporting.
  • When new options like init_alpha() are introduced, this code needs updates everywhere.

An Approach I Tried: Configuration via URI

To handle this, I created a higher-level ClientConfig type in my project. It serializes to and deserializes from a simple, human-readable URI string: autonomi:config:<details>.

Basic Examples:

autonomi:config:mainnet
autonomi:config:alphanet
autonomi:config:testnet

Custom Local Network Example:

autonomi:config:local?rpc_url=http%3A%2F%2FYOUR_HOST_IP_ADDRESS%3A14143%2F&payment_token_addr=0x...&data_payments_addr=0x...&bootstrap_url=http%3A%2F%2F2FYOUR_HOST_IP_ADDRESS%3A38112%2Fbootstrap.txt

Usage example:

// parse the URI and turn into a valid config
let config = ClientConfig::from_str("autonomi:config:alphanet")?; // ClientConfig implements FromStr

// get a new, fully configured client
let client = config.try_new_client().await?;

// the config can also be turned into a string again
let config_string = config.to_string();

That’s it, nothing else needed. It supports all the options I found in the Client code.

To get a better idea, here is the link to my current implementation:

It works really well in practice. The string format works easily with all kinds of tools. Here is how I use it with the clap crate to parse command line arguments:

use clap::Parser;

#[derive(Debug, Parser)]
#[command(version)]
struct Arguments {
    /// Autonomi Network Configuration
    #[arg(long, short = 'c', env, default_value = "autonomi:config:mainnet")]
    autonomi_config: ClientConfig,
}

/// ...
let arguments = Arguments::parse();
let client = (&arguments.autonomi_config).try_new_client().await?;

The above code defaults to mainnet (using claps default_value) but automatically supports any configuration string the user provides, without extra handling code in my app.

Potential Benefit for autonomi::Client?

Thinking about this, a similar approach could be beneficial for autonomi::Client itself?

Thought: Could we replace the different init functions and environment variables with a single:

Client::init(config_uri: impl AsRef<str>)

Advantages:

  • Simplified and standardised configuration: Set up the client with just one standard string (which is also easy to store).
  • Future-proof: Application code wouldn’t need updates just because new configuration options are added to the core client library later.
  • Easier third-language integration: Maintaining client libraries in other languages would become simpler and more consistent with the Rust core.
  • Clearer, explicit configuration: The URI string provides a single, explicit source of truth for the full client config.

This approach really removed friction for me during development, especially when using a local testnet.

What do you guys think? Open to feedback.

8 Likes

I agree that the client config is cumbersome. I don’t have any immediate reaction to your approach.

For myself I copied a bunch of files from ant-cli which included their cli options for connection, and function calls for connecting, getting secret key etc.

As these evolved I copied and pasted them in and tweaked my code to accept the changes.

I also created a wrapper DwebClient so I can pass around both the Autonomi client and extra settings without additional parameters.

It has worked well for me so I don’t intend to spend time refining this. The Autonomi API has improved but I think it’s probably more complicated than it needs to be.

If I was starting now I’d take a look at what you are doing.

4 Likes

My solution is to simplify using most common defaults. The url approach has one disadvantage for me, that it looses Rust’s type safety.

3 Likes