Skip to content

Commit a9cfc36

Browse files
committed
Merge branch 'main' into lt/unify-bls
2 parents 19329d6 + 9feb4d2 commit a9cfc36

File tree

12 files changed

+396
-19
lines changed

12 files changed

+396
-19
lines changed

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Docs
33
on:
44
push:
55
branches:
6-
- main
6+
- stable
77
# Review gh actions docs if you want to further define triggers, paths, etc
88
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
99

config.example.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Some fields are optional and can be omitted, in which case the default value, if present, will be used.
33

44
# Chain spec ID. Supported values:
5-
# A network ID. Supported values: Mainnet, Holesky, Sepolia, Helder, Hoodi.
5+
# A network ID. Supported values: Mainnet, Holesky, Sepolia, Helder, Hoodi. Lower case values e.g. "mainnet" are also accepted
66
# A custom object, e.g., chain = { genesis_time_secs = 1695902400, path = "/path/to/spec.json" }, with a path to a chain spec file, either in .json format (e.g., as returned by the beacon endpoint /eth/v1/config/spec), or in .yml format (see examples in tests/data).
77
# A custom object, e.g., chain = { genesis_time_secs = 1695902400, slot_time_secs = 12, genesis_fork_version = "0x01017000" }.
88
chain = "Holesky"
@@ -212,19 +212,21 @@ jwt_auth_fail_timeout_seconds = 300
212212
[signer.local.loader]
213213
# File: path to the keys file
214214
key_path = "./tests/data/keys.example.json"
215-
# ValidatorsDir: format of the keystore (lighthouse, prysm, teku or lodestar)
215+
# ValidatorsDir: format of the keystore (lighthouse, prysm, teku, lodestar, or nimbus)
216216
# format = "lighthouse"
217217
# ValidatorsDir: full path to the keys directory
218-
# For lighthouse, it's de path to the directory where the `<pubkey>/voting-keystore.json` directories are located.
218+
# For lighthouse, it's the path to the directory where the `<pubkey>` directories are located, under each of which is a `voting-keystore.json` file.
219219
# For prysm, it's the path to the `all-accounts.keystore.json` file.
220220
# For teku, it's the path to the directory where all `<pubkey>.json` files are located.
221221
# For lodestar, it's the path to the directory where all `<pubkey>.json` files are located.
222+
# For nimbus, it's the path to the directory where the `<pubkey>` directories are located, under each of which is a `keystore.json` file.
222223
# keys_path = ""
223224
# ValidatorsDir: full path to the secrets file/directory
224-
# For lighthouse, it's de path to the directory where the `<pubkey>.json` files are located.
225+
# For lighthouse, it's the path to the directory where the `<pubkey>` files are located.
225226
# For prysm, it's the path to the file containing the wallet decryption password.
226227
# For teku, it's the path to the directory where all `<pubkey>.txt` files are located.
227228
# For lodestar, it's the path to the file containing the decryption password.
229+
# For nimbus, it's the path to the directory where the `<pubkey>` files are located.
228230
# secrets_path = ""
229231
# Configuration for how the Signer module should store proxy delegations. Supported types of store are:
230232
# - File: store keys and delegations from a plain text file (unsafe, use only for testing purposes)

crates/cli/src/docker_init.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
256256

257257
if let Some(mux_config) = cb_config.muxes {
258258
for mux in mux_config.muxes.iter() {
259-
if let Some((env_name, actual_path, internal_path)) = mux.loader_env() {
259+
if let Some((env_name, actual_path, internal_path)) = mux.loader_env()? {
260260
let (key, val) = get_env_val(&env_name, &internal_path);
261261
pbs_envs.insert(key, val);
262262
pbs_volumes.push(Volumes::Simple(format!("{}:{}:ro", actual_path, internal_path)));

crates/common/src/config/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub use utils::*;
2626
#[derive(Debug, Deserialize, Serialize)]
2727
pub struct CommitBoostConfig {
2828
pub chain: Chain,
29+
#[serde(default)]
2930
pub relays: Vec<RelayConfig>,
3031
pub pbs: StaticPbsConfig,
3132
#[serde(flatten)]

crates/common/src/config/mux.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,29 @@ pub struct MuxConfig {
130130

131131
impl MuxConfig {
132132
/// Returns the env, actual path, and internal path to use for the file
133-
/// loader
134-
pub fn loader_env(&self) -> Option<(String, String, String)> {
135-
self.loader.as_ref().and_then(|loader| match loader {
133+
/// loader. In File mode, validates the mux file prior to returning.
134+
pub fn loader_env(&self) -> eyre::Result<Option<(String, String, String)>> {
135+
let Some(loader) = self.loader.as_ref() else {
136+
return Ok(None);
137+
};
138+
139+
match loader {
136140
MuxKeysLoader::File(path_buf) => {
137-
let path =
138-
path_buf.to_str().unwrap_or_else(|| panic!("invalid path: {:?}", path_buf));
139-
let internal_path = get_mux_path(&self.id);
141+
let Some(path) = path_buf.to_str() else {
142+
bail!("invalid path: {:?}", path_buf);
143+
};
144+
145+
let file = load_file(path)?;
146+
// make sure we can load the pubkeys correctly
147+
let _: Vec<BlsPublicKey> =
148+
serde_json::from_str(&file).wrap_err("failed to parse mux keys file")?;
140149

141-
Some((get_mux_env(&self.id), path.to_owned(), internal_path))
150+
let internal_path = get_mux_path(&self.id);
151+
Ok(Some((get_mux_env(&self.id), path.to_owned(), internal_path)))
142152
}
143-
MuxKeysLoader::HTTP { .. } => None,
144-
MuxKeysLoader::Registry { .. } => None,
145-
})
153+
MuxKeysLoader::HTTP { .. } => Ok(None),
154+
MuxKeysLoader::Registry { .. } => Ok(None),
155+
}
146156
}
147157
}
148158

crates/common/src/config/pbs.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,14 @@ pub async fn load_pbs_config() -> Result<PbsModuleConfig> {
228228
let config = CommitBoostConfig::from_env_path()?;
229229
config.validate().await?;
230230

231+
// Make sure relays isn't empty - since the config is still technically valid if
232+
// there are no relays for things like Docker compose generation, this check
233+
// isn't in validate().
234+
ensure!(
235+
!config.relays.is_empty(),
236+
"At least one relay must be configured to run the PBS service"
237+
);
238+
231239
// use endpoint from env if set, otherwise use default host and port
232240
let endpoint = if let Some(endpoint) = load_optional_env_var(PBS_ENDPOINT_ENV) {
233241
endpoint.parse()?

crates/common/src/signer/loader.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ pub enum ValidatorKeysFormat {
4747
Lodestar,
4848
#[serde(alias = "prysm")]
4949
Prysm,
50+
#[serde(alias = "nimbus")]
51+
Nimbus,
5052
}
5153

5254
impl SignerLoader {
@@ -85,6 +87,7 @@ impl SignerLoader {
8587
load_from_lodestar_format(keys_path, secrets_path)
8688
}
8789
ValidatorKeysFormat::Prysm => load_from_prysm_format(keys_path, secrets_path),
90+
ValidatorKeysFormat::Nimbus => load_from_nimbus_format(keys_path, secrets_path),
8891
};
8992
}
9093
})
@@ -275,6 +278,42 @@ fn load_from_prysm_format(
275278
Ok(signers)
276279
}
277280

281+
fn load_from_nimbus_format(
282+
keys_path: PathBuf,
283+
secrets_path: PathBuf,
284+
) -> eyre::Result<Vec<ConsensusSigner>> {
285+
let paths: Vec<_> =
286+
fs::read_dir(&keys_path)?.map(|res| res.map(|e| e.path())).collect::<Result<_, _>>()?;
287+
288+
let signers = paths
289+
.into_par_iter()
290+
.filter_map(|path| {
291+
if !path.is_dir() {
292+
return None
293+
}
294+
295+
let maybe_pubkey = path.file_name().and_then(|d| d.to_str())?;
296+
let Ok(pubkey) = bls_pubkey_from_hex(maybe_pubkey) else {
297+
warn!("Invalid pubkey: {}", maybe_pubkey);
298+
return None
299+
};
300+
301+
let ks_path = keys_path.join(maybe_pubkey).join("keystore.json");
302+
let pw_path = secrets_path.join(pubkey.to_string());
303+
304+
match load_one(ks_path, pw_path) {
305+
Ok(signer) => Some(signer),
306+
Err(e) => {
307+
warn!("Failed to load signer for pubkey: {}, err: {}", pubkey, e);
308+
None
309+
}
310+
}
311+
})
312+
.collect();
313+
314+
Ok(signers)
315+
}
316+
278317
fn load_one(ks_path: PathBuf, pw_path: PathBuf) -> eyre::Result<ConsensusSigner> {
279318
let keystore = Keystore::from_json_file(ks_path).map_err(|_| eyre!("failed reading json"))?;
280319
let password = fs::read(pw_path.clone())
@@ -305,7 +344,7 @@ mod tests {
305344
use super::{load_from_lighthouse_format, load_from_lodestar_format, FileKey};
306345
use crate::{
307346
signer::{
308-
loader::{load_from_prysm_format, load_from_teku_format},
347+
loader::{load_from_nimbus_format, load_from_prysm_format, load_from_teku_format},
309348
BlsSigner,
310349
},
311350
utils::bls_pubkey_from_hex_unchecked,
@@ -401,4 +440,16 @@ mod tests {
401440
"b3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9"
402441
));
403442
}
443+
444+
#[test]
445+
fn test_load_nimbus() {
446+
let result = load_from_nimbus_format(
447+
"../../tests/data/keystores/nimbus-keys".into(),
448+
"../../tests/data/keystores/secrets".into(),
449+
);
450+
451+
assert!(result.is_ok());
452+
453+
test_correct_load(result.unwrap());
454+
}
404455
}

docs/docs/get_started/configuration.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ keys_path = "/path/to/keys"
5252
secrets_path = "/path/to.secrets"
5353
```
5454

55-
We currently support Lighthouse, Prysm, Teku and Lodestar's keystores so it's easier to load the keys. We're working on adding support for additional keystores. These are the expected file structures for each format:
55+
We currently support Lighthouse, Prysm, Teku, Lodestar, and Nimbus's keystores so it's easier to load the keys. We're working on adding support for additional keystores. These are the expected file structures for each format:
5656

5757
<details>
5858
<summary>Lighthouse</summary>
@@ -175,6 +175,37 @@ We currently support Lighthouse, Prysm, Teku and Lodestar's keystores so it's ea
175175
:::
176176
</details>
177177

178+
<details>
179+
<summary>Nimbus</summary>
180+
181+
#### File structure:
182+
```
183+
├── keys
184+
│   ├── <PUBLIC_KEY_1>
185+
│   │   └── keystore.json
186+
│   └── <PUBLIC_KEY_2>
187+
│   └── keystore.json
188+
└── secrets
189+
   ├── <PUBLIC_KEY_1>
190+
   └── <PUBLIC_KEY_2>
191+
```
192+
193+
#### Config:
194+
```toml
195+
[pbs]
196+
...
197+
with_signer = true
198+
199+
[signer]
200+
port = 20000
201+
202+
[signer.local.loader]
203+
format = "nimbus"
204+
keys_path = "keys"
205+
secrets_path = "secrets"
206+
```
207+
</details>
208+
178209
### Proxy keys store
179210

180211
Proxy keys can be used to sign transactions with a different key than the one used to sign the block. Proxy keys are generated by the Signer module and authorized by the validator key. Each module have their own proxy keys, that can be BLS or ECDSA.

0 commit comments

Comments
 (0)