Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ address = "127.0.0.1:33950"
key_path = "keys.example.json"

[[modules]]
id = "DA_COMMIT"
id = "DA_COMMIT_RAW"
path = "target/debug/da_commit"
sleep_secs = 5

[[modules]]
id = "DA_COMMIT"
docker_image="da_commit"
Copy link
Contributor Author

@David-Petrov David-Petrov Jun 26, 2024

Choose a reason for hiding this comment

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

That's the example image that must be available in the local registry before starting commit-boost.
You can find the example source code in this repo.

NOTE: Whilst we're still in development, you will have to modify the Cargo.toml of the example module to point to a correct source for the cb-... dependencies.

sleep_secs = 5
2 changes: 2 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ eyre.workspace = true

tree_hash.workspace = true
tree_hash_derive.workspace = true

bollard = "0.16.1"
49 changes: 39 additions & 10 deletions crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::process::Stdio;

use cb_common::{
config::{CommitBoostConfig, CONFIG_PATH_ENV, MODULE_ID_ENV},
config::{CommitBoostConfig, ModuleSource, CONFIG_PATH_ENV, MODULE_ID_ENV},
utils::print_logo,
};
use cb_crypto::service::SigningService;
Expand Down Expand Up @@ -69,24 +69,53 @@ impl Args {
Command::Start { config: config_path } => {
let config = CommitBoostConfig::from_file(&config_path);

// Initialize Docker client
let docker = bollard::Docker::connect_with_local_defaults().expect("Failed to connect to Docker");
Copy link
Contributor Author

@David-Petrov David-Petrov Jun 26, 2024

Choose a reason for hiding this comment

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

Here, we currently expect that CB and the docker engine run on the same machine.

However, the way we connect to the docker daemon is modifiable if more complex needs arise: ref.


if let Some(modules) = config.modules {
let signer_config = config.signer.expect("missing signer config with modules");
// start signing server
tokio::spawn(SigningService::run(config.chain, signer_config));

// this mocks the commit boost client starting containers, processes etc
let mut child_handles = Vec::with_capacity(modules.len());

for module in modules {
let child = std::process::Command::new(module.path)
.env(MODULE_ID_ENV, module.id)
.env(CONFIG_PATH_ENV, &config_path)
.spawn()
.expect("failed to start process");
match module.source {
ModuleSource::DockerImageId(docker_image) => {
let config = bollard::container::Config {
image: Some(docker_image.clone()),
host_config: Some(bollard::secret::HostConfig {
binds: {
let full_config_path = std::fs::canonicalize(&config_path).unwrap().to_string_lossy().to_string();
Some(vec![format!("{}:{}", full_config_path, "/config.toml")])
},
network_mode: Some(String::from("host")), // Use the host network
Copy link
Contributor Author

@David-Petrov David-Petrov Jun 26, 2024

Choose a reason for hiding this comment

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

It's worth noting here that I'm making substantial use of the host network mode in order to share the networking namespace of the host with the module itself.
This is sacrificing sandboxing for easier configuration.

NOTE: This is a beta feature for Docker desktop (if you're using that), and must be enabled explicitly:

The host networking driver only works on Linux hosts, but is available as a Beta feature, on Docker Desktop version 4.29 and later.

Might not be the best long-term decision, but for simplicity's sake it's closer to native; just like the alternative (previous) approach of launching modules as subprocesses.

..Default::default()
}),
env: Some(vec![
format!("{}={}", MODULE_ID_ENV, module.id),
format!("{}={}", CONFIG_PATH_ENV, "/config.toml"),
]),
..Default::default()
};

child_handles.push(child);
}
let container = docker.create_container::<&str, String>(None, config).await?;
let container_id = container.id;
docker.start_container::<String>(&container_id, None).await?;
println!("Started container: {} from image {}", container_id, &docker_image);
},
ModuleSource::Path(path) => {
let child = std::process::Command::new(path)
.env(MODULE_ID_ENV, module.id)
.env(CONFIG_PATH_ENV, &config_path)
.spawn()
.expect("failed to start process");

// start signing server
tokio::spawn(SigningService::run(config.chain, signer_config));
child_handles.push(child);
},
}
}
}

// start pbs server
Expand Down
46 changes: 45 additions & 1 deletion crates/common/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,57 @@ const fn default_u256() -> U256 {
}

#[derive(Debug, Deserialize, Serialize)]
pub enum ModuleSource {
#[serde(rename = "path")]
Path(String),
#[serde(rename = "docker_image")]
DockerImageId(String),
}

#[derive(Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ModuleConfig<T = ()> {
pub id: String,
pub path: String,
#[serde(flatten)]
pub source: ModuleSource,
#[serde(flatten)]
pub extra: T,
}

impl<'de, T> Deserialize<'de> for ModuleConfig<T>
Copy link
Contributor Author

@David-Petrov David-Petrov Jun 26, 2024

Choose a reason for hiding this comment

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

This custom deserializer is the quick approach I took to scratch my itch for modelling a strict config structure: exactly one of path or docker_image must be specified in the config.example.toml for a given module - marking the module as either launchable from a raw binary (like it used to be) OR from a docker image (the new addition).

We could choose another approach for modelling the config (i.e. an additional tag specifying the variant, and then the necessary source data (path or docker image id); or similar...)

where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct InnerModuleConfig<T> {
id: String,
path: Option<String>,
docker_image: Option<String>,
#[serde(flatten)]
extra: T,
}

let inner = InnerModuleConfig::deserialize(deserializer)?;

let source = match (inner.path, inner.docker_image) {
(Some(path), None) => ModuleSource::Path(path),
(None, Some(docker_image)) => ModuleSource::DockerImageId(docker_image),
(Some(_), Some(_)) => return Err(de::Error::custom("Cannot have both `path` and `docker_image`")),
(None, None) => return Err(de::Error::custom("Must have either `path` or `docker_image`")),
};

Ok(ModuleConfig {
id: inner.id,
source,
extra: inner.extra,
})
}
}

#[derive(Debug, Deserialize, Serialize)]
pub struct StartModuleConfig<T = ()> {
pub chain: Chain,
Expand Down
2 changes: 1 addition & 1 deletion crates/pbs/src/boost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
#[async_trait]
pub trait BuilderApi<S: BuilderApiState>: 'static {
/// Use to extend the BuilderApi
fn routes() -> Option<Router<BuilderState<S>>> {
fn extra_routes() -> Option<Router<BuilderState<S>>> {
None
}

Expand Down
2 changes: 1 addition & 1 deletion crates/pbs/src/routes/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub fn create_app_router<S: BuilderApiState, T: BuilderApi<S>>(state: BuilderSta

let builder_api = Router::new().nest(BULDER_API_PATH, builder_routes);

let app = if let Some(extra_routes) = T::routes() {
let app = if let Some(extra_routes) = T::extra_routes() {
builder_api.merge(extra_routes)
} else {
builder_api
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_boost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl BuilderApi<StatusCounter> for MyBuilderApi {
Ok(())
}

fn routes() -> Option<Router<BuilderState<StatusCounter>>> {
fn extra_routes() -> Option<Router<BuilderState<StatusCounter>>> {
let router = Router::new().route("/custom/stats", get(handle_stats));
Some(router)
}
Expand Down