Skip to content

Commit d476133

Browse files
author
Solar Mithril
committed
Use toml file vibecoded
1 parent adcbb7c commit d476133

File tree

7 files changed

+184
-39
lines changed

7 files changed

+184
-39
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ reqwest = { version = "0.12.8" }
154154
serde = { version = "1.0.210" }
155155
serde_json = { version = "1.0.128" }
156156
serde_with = { version = "3.8.1" }
157+
toml = { version = "0.8" }
157158
secp256k1 = { version = "0.30" }
158159
derive_more = { version = "2" }
159160
tokio-stream = "0.1.16"

crates/op-rbuilder/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ tracing.workspace = true
8080
eyre.workspace = true
8181
serde_with.workspace = true
8282
serde.workspace = true
83+
toml.workspace = true
8384
secp256k1.workspace = true
8485
tokio.workspace = true
8586
jsonrpsee = { workspace = true }
@@ -124,6 +125,7 @@ vergen-git2.workspace = true
124125
alloy-provider = { workspace = true, default-features = true, features = [
125126
"txpool-api",
126127
] }
128+
tempfile = "3.8"
127129

128130
[features]
129131
default = ["jemalloc"]

crates/op-rbuilder/src/args/op.rs

Lines changed: 153 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,30 @@ use crate::{primitives::reth::engine_api_builder::EnginePeer, tx_signer::Signer}
77
use alloy_rpc_types_engine::JwtSecret;
88
use anyhow::{anyhow, Result};
99
use reth_optimism_node::args::RollupArgs;
10+
use serde::Deserialize;
1011
use std::path::PathBuf;
1112
use url::Url;
1213

14+
/// Configuration structure for engine peers loaded from TOML
15+
#[derive(Debug, Clone, Deserialize)]
16+
pub struct EnginePeersConfig {
17+
/// Default JWT file path used by all peers unless overridden
18+
pub default_jwt_path: PathBuf,
19+
/// List of engine peers
20+
pub peers: Vec<EnginePeerConfig>,
21+
}
22+
23+
/// Configuration for a single engine peer
24+
#[derive(Debug, Clone, Deserialize)]
25+
pub struct EnginePeerConfig {
26+
/// URL of the engine peer
27+
pub url: Url,
28+
/// Optional JWT path override for this peer
29+
pub jwt_path: Option<PathBuf>,
30+
}
31+
1332
/// Parameters for rollup configuration
14-
#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)]
33+
#[derive(Debug, Clone, Default, clap::Args)]
1534
#[command(next_help_heading = "Rollup")]
1635
pub struct OpRbuilderArgs {
1736
/// Rollup configuration
@@ -51,8 +70,8 @@ pub struct OpRbuilderArgs {
5170
pub playground: Option<PathBuf>,
5271
#[command(flatten)]
5372
pub flashblocks: FlashblocksArgs,
54-
/// List or builders in the network that FCU would be propagated to
55-
#[arg(long = "builder.engine-api-peer", value_parser = parse_engine_peer_arg, action = clap::ArgAction::Append)]
73+
/// Path to TOML configuration file for engine peers
74+
#[arg(long = "builder.engine-peers-config", env = "ENGINE_PEERS_CONFIG", value_parser = parse_engine_peers_config)]
5675
pub engine_peers: Vec<EnginePeer>,
5776
}
5877

@@ -64,39 +83,6 @@ fn expand_path(s: &str) -> Result<PathBuf> {
6483
.map_err(|e| anyhow!("invalid path after expansion: {e}"))
6584
}
6685

67-
/// Parse engine peer configuration string for clap argument parsing.
68-
///
69-
/// Format: "url@jwt_path" (JWT path is required)
70-
/// - url: HTTP/HTTPS endpoint of the peer builder
71-
/// - jwt_path: File path to JWT token for authentication (required after @)
72-
fn parse_engine_peer_arg(s: &str) -> Result<EnginePeer> {
73-
let s = s.trim();
74-
75-
if s.is_empty() {
76-
return Err(anyhow!("Engine peer cannot be empty"));
77-
}
78-
79-
// Find the @ delimiter - it's required
80-
// Caution: this will misshandle cases when pathname contains `@` symbols, we do not expect such filenames tho
81-
let (url_part, jwt_path_part) = s.rsplit_once('@').ok_or_else(|| anyhow!("Engine peer must include JWT path after '@' (format: url@jwt_path). Urls with @ in the path are not accepted."))?;
82-
83-
if url_part.is_empty() {
84-
return Err(anyhow!("URL part cannot be empty"));
85-
}
86-
87-
if jwt_path_part.is_empty() {
88-
return Err(anyhow!("JWT path cannot be empty (format: url@jwt_path)"));
89-
}
90-
91-
let url = Url::parse(url_part)?;
92-
93-
let jwt_path = PathBuf::from(jwt_path_part);
94-
95-
let jwt_secret = JwtSecret::from_file(&jwt_path)?;
96-
97-
Ok(EnginePeer::new(url, jwt_secret))
98-
}
99-
10086
/// Parameters for Flashblocks configuration
10187
/// The names in the struct are prefixed with `flashblocks` to avoid conflicts
10288
/// with the standard block building configuration since these args are flattened
@@ -139,3 +125,134 @@ pub struct FlashblocksArgs {
139125
)]
140126
pub flashblocks_block_time: u64,
141127
}
128+
129+
impl EnginePeersConfig {
130+
/// Load configuration from a TOML file
131+
pub fn from_file(path: &PathBuf) -> Result<Self> {
132+
let content = std::fs::read_to_string(path).map_err(|e| {
133+
anyhow!(
134+
"Failed to read engine peers config file {}: {}",
135+
path.display(),
136+
e
137+
)
138+
})?;
139+
140+
let config: Self = toml::from_str(&content).map_err(|e| {
141+
anyhow!(
142+
"Failed to parse engine peers config file {}: {}",
143+
path.display(),
144+
e
145+
)
146+
})?;
147+
148+
Ok(config)
149+
}
150+
151+
/// Convert to vector of EnginePeer instances
152+
pub fn to_engine_peers(&self) -> Result<Vec<EnginePeer>> {
153+
let mut engine_peers = Vec::new();
154+
155+
for peer in &self.peers {
156+
let jwt_path = peer.jwt_path.as_ref().unwrap_or(&self.default_jwt_path);
157+
let jwt_secret = JwtSecret::from_file(jwt_path)
158+
.map_err(|e| anyhow!("Failed to load JWT from {}: {}", jwt_path.display(), e))?;
159+
160+
engine_peers.push(EnginePeer::new(peer.url.clone(), jwt_secret));
161+
}
162+
163+
Ok(engine_peers)
164+
}
165+
}
166+
167+
/// Parse engine peers configuration from TOML file for clap
168+
fn parse_engine_peers_config(s: &str) -> Result<Vec<EnginePeer>> {
169+
let path = PathBuf::from(s);
170+
let config = EnginePeersConfig::from_file(&path)?;
171+
config.to_engine_peers()
172+
}
173+
174+
#[cfg(test)]
175+
mod tests {
176+
use super::*;
177+
use std::io::Write;
178+
use tempfile::NamedTempFile;
179+
180+
#[test]
181+
fn test_engine_peers_config_parsing() -> Result<()> {
182+
// Create temporary JWT files for testing
183+
let mut temp_default_jwt = NamedTempFile::new()?;
184+
let mut temp_custom_jwt = NamedTempFile::new()?;
185+
186+
// Write dummy JWT content (for testing purposes)
187+
temp_default_jwt.write_all(b"dummy.jwt.token")?;
188+
temp_custom_jwt.write_all(b"custom.jwt.token")?;
189+
temp_default_jwt.flush()?;
190+
temp_custom_jwt.flush()?;
191+
192+
let toml_content = format!(
193+
r#"
194+
default_jwt_path = "{}"
195+
196+
[[peers]]
197+
url = "http://builder1.example.com:8551"
198+
199+
[[peers]]
200+
url = "http://builder2.example.com:8551"
201+
jwt_path = "{}"
202+
"#,
203+
temp_default_jwt.path().display(),
204+
temp_custom_jwt.path().display()
205+
);
206+
207+
let config: EnginePeersConfig = toml::from_str(&toml_content)?;
208+
209+
assert_eq!(config.peers.len(), 2);
210+
211+
// First peer should use default JWT
212+
assert_eq!(
213+
config.peers[0].url.as_str(),
214+
"http://builder1.example.com:8551"
215+
);
216+
217+
// Second peer should have custom JWT
218+
assert_eq!(
219+
config.peers[1].url.as_str(),
220+
"http://builder2.example.com:8551"
221+
);
222+
223+
// Test that we can convert to engine peers successfully
224+
let engine_peers = config.to_engine_peers()?;
225+
assert_eq!(engine_peers.len(), 2);
226+
227+
Ok(())
228+
}
229+
230+
#[test]
231+
fn test_engine_peers_config_from_file() -> Result<()> {
232+
// Create temporary JWT file
233+
let mut temp_jwt = NamedTempFile::new()?;
234+
temp_jwt.write_all(b"test.jwt.token")?;
235+
temp_jwt.flush()?;
236+
237+
let toml_content = format!(
238+
r#"
239+
default_jwt_path = "{}"
240+
241+
[[peers]]
242+
url = "http://test.example.com:8551"
243+
"#,
244+
temp_jwt.path().display()
245+
);
246+
247+
let mut temp_file = NamedTempFile::new()?;
248+
temp_file.write_all(toml_content.as_bytes())?;
249+
temp_file.flush()?;
250+
251+
let config = EnginePeersConfig::from_file(&temp_file.path().to_path_buf())?;
252+
253+
assert_eq!(config.peers.len(), 1);
254+
assert_eq!(config.peers[0].url.as_str(), "http://test.example.com:8551");
255+
256+
Ok(())
257+
}
258+
}

crates/op-rbuilder/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ where
7272
let builder_config = BuilderConfig::<B::Config>::try_from(builder_args.clone())
7373
.expect("Failed to convert rollup args to builder config");
7474

75-
// Engine peers are already parsed in the config
75+
// Engine peers from configuration
7676
let engine_peers = builder_args.engine_peers;
7777

7878
let engine_builder: OpEngineApiBuilder<OpEngineValidatorBuilder> =

crates/op-rbuilder/src/primitives/reth/engine_api_builder.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use reth_optimism_rpc::engine::OP_ENGINE_CAPABILITIES;
99
pub use reth_optimism_rpc::OpEngineApi;
1010
use reth_payload_builder::PayloadStore;
1111
use reth_rpc_engine_api::EngineCapabilities;
12+
use std::sync::{atomic::AtomicBool, Arc};
1213

1314
use crate::traits::NodeComponents;
1415
use alloy_eips::eip7685::Requests;
@@ -38,15 +39,22 @@ use tracing::{self, log::warn};
3839
use url::Url;
3940

4041
/// Configuration for an engine peer with JWT authentication
41-
#[derive(Debug, Clone, PartialEq, Eq)]
42+
#[derive(Debug, Clone)]
4243
pub struct EnginePeer {
4344
pub url: Url,
4445
pub jwt: JwtSecret,
46+
// Flag the indicates if FCU was successfully received
47+
// Caution: could be subjected to race conditions in case 2 FCU issued in short time
48+
pub healthy: Arc<AtomicBool>,
4549
}
4650

4751
impl EnginePeer {
4852
pub fn new(url: Url, jwt_path: JwtSecret) -> Self {
49-
Self { url, jwt: jwt_path }
53+
Self {
54+
url,
55+
jwt: jwt_path,
56+
healthy: Arc::new(AtomicBool::new(false)),
57+
}
5058
}
5159

5260
pub fn http_client(&self) -> impl SubscriptionClientT + Clone + Send + Sync + Unpin + 'static {

engine-peers.example.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Default JWT file path used by all peers unless overridden
2+
default_jwt_path = "/path/to/default.jwt"
3+
4+
# List of engine peers
5+
[[peers]]
6+
url = "http://builder1.example.com:8551"
7+
# This peer will use the default JWT
8+
9+
[[peers]]
10+
url = "http://builder2.example.com:8551"
11+
jwt_path = "/path/to/custom.jwt" # Override default JWT for this peer
12+
13+
[[peers]]
14+
url = "https://builder3.example.com:8551"
15+
jwt_path = "/path/to/another.jwt" # Another custom JWT

0 commit comments

Comments
 (0)