1+ //! OCaml Node Configuration Module
2+ //!
3+ //! This module provides configuration structures and executable management
4+ //! for OCaml Mina nodes in the testing framework. It supports multiple
5+ //! execution modes including local binaries and Docker containers.
6+ //!
7+ //! # Key Components
8+ //!
9+ //! - [`OcamlNodeExecutable`] - Execution method selection (local/Docker)
10+ //! - [`OcamlNodeTestingConfig`] - High-level node configuration
11+ //! - [`OcamlNodeConfig`] - Low-level process configuration
12+ //! - [`DaemonJson`] - Genesis configuration management
13+ //!
14+ //! # Executable Auto-Detection
15+ //!
16+ //! The module automatically detects the best available execution method:
17+ //! 1. Local `mina` binary (preferred)
18+ //! 2. Docker with default image (fallback)
19+ //! 3. Custom Docker images (configurable)
20+
121use std:: {
222 ffi:: { OsStr , OsString } ,
323 fs,
@@ -6,13 +26,25 @@ use std::{
626 str:: FromStr ,
727} ;
828
9- use node:: { account:: AccountSecretKey , p2p:: connection:: outgoing:: P2pConnectionOutgoingInitOpts } ;
29+ use node:: {
30+ account:: AccountSecretKey ,
31+ core:: log:: { info, system_time, warn} ,
32+ p2p:: connection:: outgoing:: P2pConnectionOutgoingInitOpts ,
33+ } ;
1034use serde:: { Deserialize , Serialize } ;
1135
36+ /// High-level configuration for OCaml node testing scenarios.
37+ ///
38+ /// This struct provides the main configuration interface for creating
39+ /// OCaml nodes in test scenarios, abstracting away low-level details
40+ /// like port allocation and process management.
1241#[ derive( Serialize , Deserialize , Debug , Clone ) ]
1342pub struct OcamlNodeTestingConfig {
43+ /// List of initial peer connection targets
1444 pub initial_peers : Vec < P2pConnectionOutgoingInitOpts > ,
45+ /// Genesis ledger configuration (file path or in-memory)
1546 pub daemon_json : DaemonJson ,
47+ /// Optional block producer secret key
1648 pub block_producer : Option < AccountSecretKey > ,
1749}
1850
@@ -53,10 +85,40 @@ pub struct OcamlNodeConfig {
5385 pub block_producer : Option < AccountSecretKey > ,
5486}
5587
88+ /// OCaml node execution methods.
89+ ///
90+ /// Supports multiple ways of running the OCaml Mina daemon,
91+ /// from local binaries to Docker containers with automatic
92+ /// detection and fallback behavior.
5693#[ derive( Serialize , Deserialize , Debug , Clone ) ]
5794pub enum OcamlNodeExecutable {
95+ /// Use locally installed Mina binary
96+ ///
97+ /// # Arguments
98+ /// * `String` - Path to the mina executable
99+ ///
100+ /// # Example
101+ /// ```
102+ /// OcamlNodeExecutable::Installed("/usr/local/bin/mina".to_string())
103+ /// ```
58104 Installed ( String ) ,
105+
106+ /// Use specific Docker image
107+ ///
108+ /// # Arguments
109+ /// * `String` - Docker image tag
110+ ///
111+ /// # Example
112+ /// ```
113+ /// OcamlNodeExecutable::Docker("minaprotocol/mina-daemon:3.0.0".to_string())
114+ /// ```
59115 Docker ( String ) ,
116+
117+ /// Use default Docker image
118+ ///
119+ /// Falls back to the predefined default image when no local
120+ /// binary is available. See [`DEFAULT_DOCKER_IMAGE`] for the
121+ /// current default.
60122 DockerDefault ,
61123}
62124
@@ -81,17 +143,40 @@ impl OcamlNodeConfig {
81143 {
82144 match & self . executable {
83145 OcamlNodeExecutable :: Installed ( program) => {
146+ info ! ( system_time( ) ; "Using local Mina binary: {}" , program) ;
84147 let mut cmd = Command :: new ( program) ;
85148 cmd. envs ( envs) ;
86149 cmd
87150 }
88- OcamlNodeExecutable :: Docker ( tag) => self . docker_run_cmd ( tag, envs) ,
151+ OcamlNodeExecutable :: Docker ( tag) => {
152+ info ! ( system_time( ) ; "Using custom Docker image: {}" , tag) ;
153+ self . docker_run_cmd ( tag, envs)
154+ }
89155 OcamlNodeExecutable :: DockerDefault => {
156+ info ! (
157+ system_time( ) ;
158+ "Using default Docker image: {}" ,
159+ OcamlNodeExecutable :: DEFAULT_DOCKER_IMAGE
160+ ) ;
90161 self . docker_run_cmd ( OcamlNodeExecutable :: DEFAULT_DOCKER_IMAGE , envs)
91162 }
92163 }
93164 }
94165
166+ /// Create a Docker run command with proper configuration.
167+ ///
168+ /// Sets up a Docker container with appropriate networking, user mapping,
169+ /// volume mounts, and environment variables for running OCaml Mina daemon.
170+ ///
171+ /// # Arguments
172+ /// * `tag` - Docker image tag to use
173+ /// * `envs` - Environment variables to pass to the container
174+ ///
175+ /// # Docker Configuration
176+ /// - Uses host networking for P2P connectivity
177+ /// - Maps host user ID to avoid permission issues
178+ /// - Mounts node directory for persistent data
179+ /// - Sets working directory to `/tmp` for key generation
95180 fn docker_run_cmd < I , K , V > ( & self , tag : & str , envs : I ) -> Command
96181 where
97182 I : IntoIterator < Item = ( K , V ) > ,
@@ -104,9 +189,18 @@ impl OcamlNodeConfig {
104189 let uid = std:: env:: var ( "$UID" ) . unwrap_or_else ( |_| "1000" . to_owned ( ) ) ;
105190 let container_name = OcamlNodeExecutable :: docker_container_name ( & self . dir ) ;
106191
192+ info ! (
193+ system_time( ) ;
194+ "Configuring Docker container: name={}, image={}, uid={}, mount={}" ,
195+ container_name,
196+ tag,
197+ uid,
198+ dir_path
199+ ) ;
200+
107201 // set docker opts
108202 cmd. arg ( "run" )
109- . args ( [ "--name" . to_owned ( ) , container_name] )
203+ . args ( [ "--name" . to_owned ( ) , container_name. clone ( ) ] )
110204 . args ( [ "--network" , "host" ] )
111205 . args ( [ "--user" . to_owned ( ) , format ! ( "{uid}:{uid}" ) ] )
112206 . args ( [ "-v" . to_owned ( ) , format ! ( "{dir_path}:{dir_path}" ) ] )
@@ -116,14 +210,19 @@ impl OcamlNodeConfig {
116210 . args ( [ "-w" , "/tmp" ] ) ;
117211
118212 // set docker container envs
213+ let mut env_count = 0 ;
119214 for ( key, value) in envs {
120215 let arg: OsString = [ key. as_ref ( ) , value. as_ref ( ) ] . join ( OsStr :: new ( "=" ) ) ;
121216 cmd. args ( [ "-e" . as_ref ( ) , arg. as_os_str ( ) ] ) ;
217+ env_count += 1 ;
122218 }
123219
220+ info ! ( system_time( ) ; "Added {} environment variables to Docker container" , env_count) ;
221+
124222 // set docker image
125223 cmd. arg ( tag) ;
126224
225+ info ! ( system_time( ) ; "Docker command configured for container: {}" , container_name) ;
127226 cmd
128227 }
129228}
@@ -138,40 +237,119 @@ impl OcamlNodeExecutable {
138237 format ! ( "mina_testing_ocaml_{}" , & path[ 1 ..] )
139238 }
140239
141- /// Additional logic for killing the node.
240+ /// Clean up resources when terminating an OCaml node.
241+ ///
242+ /// Handles cleanup logic specific to the execution method:
243+ /// - Local binaries: No additional cleanup needed
244+ /// - Docker containers: Stop and remove the container
245+ ///
246+ /// # Arguments
247+ /// * `tmp_dir` - Temporary directory used by the node
142248 pub fn kill ( & self , tmp_dir : & temp_dir:: TempDir ) {
143249 match self {
144- OcamlNodeExecutable :: Installed ( _) => { }
250+ OcamlNodeExecutable :: Installed ( program) => {
251+ info ! ( system_time( ) ; "No additional cleanup needed for local binary: {}" , program) ;
252+ }
145253 OcamlNodeExecutable :: Docker ( _) | OcamlNodeExecutable :: DockerDefault => {
254+ let name = Self :: docker_container_name ( tmp_dir) ;
255+ let image_info = match self {
256+ OcamlNodeExecutable :: Docker ( img) => img. clone ( ) ,
257+ OcamlNodeExecutable :: DockerDefault => Self :: DEFAULT_DOCKER_IMAGE . to_string ( ) ,
258+ _ => unreachable ! ( ) ,
259+ } ;
260+
261+ info ! (
262+ system_time( ) ;
263+ "Cleaning up Docker container: {} (image: {})" ,
264+ name,
265+ image_info
266+ ) ;
267+
146268 // stop container.
269+ info ! ( system_time( ) ; "Stopping Docker container: {}" , name) ;
147270 let mut cmd = Command :: new ( "docker" ) ;
148- let name = Self :: docker_container_name ( tmp_dir) ;
149- cmd. args ( [ "stop" . to_owned ( ) , name] ) ;
150- let _ = cmd. status ( ) ;
271+ cmd. args ( [ "stop" . to_owned ( ) , name. clone ( ) ] ) ;
272+ match cmd. status ( ) {
273+ Ok ( status) if status. success ( ) => {
274+ info ! ( system_time( ) ; "Successfully stopped Docker container: {}" , name) ;
275+ }
276+ Ok ( status) => {
277+ warn ! (
278+ system_time( ) ;
279+ "Docker stop command failed for container {}: exit code {:?}" ,
280+ name,
281+ status. code( )
282+ ) ;
283+ }
284+ Err ( e) => {
285+ warn ! ( system_time( ) ; "Failed to stop Docker container {}: {}" , name, e) ;
286+ }
287+ }
151288
152289 // remove container.
290+ info ! ( system_time( ) ; "Removing Docker container: {}" , name) ;
153291 let mut cmd = Command :: new ( "docker" ) ;
154- let name = Self :: docker_container_name ( tmp_dir) ;
155- cmd. args ( [ "rm" . to_owned ( ) , name] ) ;
156- let _ = cmd. status ( ) ;
292+ cmd. args ( [ "rm" . to_owned ( ) , name. clone ( ) ] ) ;
293+ match cmd. status ( ) {
294+ Ok ( status) if status. success ( ) => {
295+ info ! ( system_time( ) ; "Successfully removed Docker container: {}" , name) ;
296+ }
297+ Ok ( status) => {
298+ warn ! (
299+ system_time( ) ;
300+ "Docker rm command failed for container {}: exit code {:?}" ,
301+ name,
302+ status. code( )
303+ ) ;
304+ }
305+ Err ( e) => {
306+ warn ! ( system_time( ) ; "Failed to remove Docker container {}: {}" , name, e) ;
307+ }
308+ }
157309 }
158310 }
159311 }
160312
313+ /// Automatically detect and return the best available OCaml executable.
314+ ///
315+ /// This method implements the auto-detection strategy:
316+ /// 1. First, attempt to use locally installed `mina` binary
317+ /// 2. If not found, fall back to Docker with default image
318+ /// 3. Automatically pull the Docker image if needed
319+ ///
320+ /// # Returns
321+ /// * `Ok(OcamlNodeExecutable)` - Best available execution method
322+ /// * `Err(anyhow::Error)` - No usable execution method found
323+ ///
324+ /// # Docker Fallback
325+ /// When falling back to Docker, this method will automatically
326+ /// pull the default image if not already present locally.
161327 pub fn find_working ( ) -> anyhow:: Result < Self > {
162328 let program_name = Self :: DEFAULT_MINA_EXECUTABLE ;
329+ info ! ( system_time( ) ; "Attempting to find local Mina binary: {}" , program_name) ;
330+
163331 match Command :: new ( program_name)
164332 . stdout ( Stdio :: null ( ) )
165333 . stderr ( Stdio :: null ( ) )
166334 . spawn ( )
167335 {
168- Ok ( _) => return Ok ( Self :: Installed ( program_name. to_owned ( ) ) ) ,
336+ Ok ( _) => {
337+ info ! ( system_time( ) ; "Found working local Mina binary: {}" , program_name) ;
338+ return Ok ( Self :: Installed ( program_name. to_owned ( ) ) ) ;
339+ }
169340 Err ( err) => match err. kind ( ) {
170- std:: io:: ErrorKind :: NotFound => { }
341+ std:: io:: ErrorKind :: NotFound => {
342+ info ! ( system_time( ) ; "Local Mina binary not found, falling back to Docker" ) ;
343+ }
171344 _ => anyhow:: bail!( "'{program_name}' returned an error: {err}" ) ,
172345 } ,
173346 } ;
174347
348+ info ! (
349+ system_time( ) ;
350+ "Pulling default Docker image: {}" ,
351+ Self :: DEFAULT_DOCKER_IMAGE
352+ ) ;
175353 let mut cmd = Command :: new ( "docker" ) ;
176354
177355 let status = cmd
@@ -184,6 +362,7 @@ impl OcamlNodeExecutable {
184362 anyhow:: bail!( "error status pulling ocaml node: {status:?}" ) ;
185363 }
186364
365+ info ! ( system_time( ) ; "Successfully pulled Docker image, using DockerDefault" ) ;
187366 Ok ( Self :: DockerDefault )
188367 }
189368}
0 commit comments