1
1
use std:: {
2
2
collections:: { HashMap , HashSet } ,
3
+ path:: { Path , PathBuf } ,
3
4
sync:: Arc ,
4
5
} ;
5
6
6
7
use alloy:: rpc:: types:: beacon:: BlsPublicKey ;
7
- use eyre:: { bail, ensure, eyre} ;
8
+ use eyre:: { bail, ensure, eyre, Context } ;
8
9
use serde:: { Deserialize , Serialize } ;
9
10
10
- use super :: { PbsConfig , RelayConfig } ;
11
+ use super :: { load_optional_env_var , PbsConfig , RelayConfig , MUX_PATH_ENV } ;
11
12
use crate :: pbs:: { RelayClient , RelayEntry } ;
12
13
13
- #[ derive( Debug , Clone , Deserialize , Serialize ) ]
14
+ #[ derive( Debug , Deserialize , Serialize ) ]
14
15
pub struct PbsMuxes {
15
16
/// List of PBS multiplexers
16
17
#[ serde( rename = "mux" ) ]
@@ -19,6 +20,7 @@ pub struct PbsMuxes {
19
20
20
21
#[ derive( Debug , Clone ) ]
21
22
pub struct RuntimeMuxConfig {
23
+ pub id : String ,
22
24
pub config : Arc < PbsConfig > ,
23
25
pub relays : Vec < RelayClient > ,
24
26
}
@@ -29,9 +31,18 @@ impl PbsMuxes {
29
31
default_pbs : & PbsConfig ,
30
32
default_relays : & [ RelayConfig ] ,
31
33
) -> eyre:: Result < HashMap < BlsPublicKey , RuntimeMuxConfig > > {
34
+ let mut muxes = self . muxes ;
35
+
36
+ for mux in muxes. iter_mut ( ) {
37
+ if let Some ( loader) = & mux. loader {
38
+ let extra_keys = loader. load ( & mux. id ) ?;
39
+ mux. validator_pubkeys . extend ( extra_keys) ;
40
+ }
41
+ }
42
+
32
43
// check that validator pubkeys are in disjoint sets
33
44
let mut unique_pubkeys = HashSet :: new ( ) ;
34
- for mux in self . muxes . iter ( ) {
45
+ for mux in muxes. iter ( ) {
35
46
for pubkey in mux. validator_pubkeys . iter ( ) {
36
47
if !unique_pubkeys. insert ( pubkey) {
37
48
bail ! ( "duplicate validator pubkey in muxes: {pubkey}" ) ;
@@ -41,11 +52,12 @@ impl PbsMuxes {
41
52
42
53
let mut configs = HashMap :: new ( ) ;
43
54
// fill the configs using the default pbs config and relay entries
44
- for mux in self . muxes {
45
- ensure ! ( !mux. relays. is_empty( ) , "mux config must have at least one relay" ) ;
55
+ for mux in muxes {
56
+ ensure ! ( !mux. relays. is_empty( ) , "mux config {} must have at least one relay" , mux . id ) ;
46
57
ensure ! (
47
58
!mux. validator_pubkeys. is_empty( ) ,
48
- "mux config must have at least one validator pubkey"
59
+ "mux config {} must have at least one validator pubkey" ,
60
+ mux. id
49
61
) ;
50
62
51
63
let mut relay_clients = Vec :: with_capacity ( mux. relays . len ( ) ) ;
@@ -89,7 +101,7 @@ impl PbsMuxes {
89
101
} ;
90
102
let config = Arc :: new ( config) ;
91
103
92
- let runtime_config = RuntimeMuxConfig { config, relays : relay_clients } ;
104
+ let runtime_config = RuntimeMuxConfig { id : mux . id , config, relays : relay_clients } ;
93
105
for pubkey in mux. validator_pubkeys . iter ( ) {
94
106
configs. insert ( * pubkey, runtime_config. clone ( ) ) ;
95
107
}
@@ -100,16 +112,36 @@ impl PbsMuxes {
100
112
}
101
113
102
114
/// Configuration for the PBS Multiplexer
103
- #[ derive( Debug , Clone , Deserialize , Serialize ) ]
115
+ #[ derive( Debug , Deserialize , Serialize ) ]
104
116
pub struct MuxConfig {
117
+ /// Identifier for this mux config
118
+ pub id : String ,
105
119
/// Relays to use for this mux config
106
120
pub relays : Vec < PartialRelayConfig > ,
107
121
/// Which validator pubkeys to match against this mux config
122
+ #[ serde( default ) ]
108
123
pub validator_pubkeys : Vec < BlsPublicKey > ,
124
+ /// Loader for extra validator pubkeys
125
+ pub loader : Option < MuxKeysLoader > ,
109
126
pub timeout_get_header_ms : Option < u64 > ,
110
127
pub late_in_slot_time_ms : Option < u64 > ,
111
128
}
112
129
130
+ impl MuxConfig {
131
+ /// Returns the env, actual path, and internal path to use for the loader
132
+ pub fn loader_env ( & self ) -> Option < ( String , String , String ) > {
133
+ self . loader . as_ref ( ) . map ( |loader| match loader {
134
+ MuxKeysLoader :: File ( path_buf) => {
135
+ let path =
136
+ path_buf. to_str ( ) . unwrap_or_else ( || panic ! ( "invalid path: {:?}" , path_buf) ) ;
137
+ let internal_path = get_mux_path ( & self . id ) ;
138
+
139
+ ( get_mux_env ( & self . id ) , path. to_owned ( ) , internal_path)
140
+ }
141
+ } )
142
+ }
143
+ }
144
+
113
145
#[ derive( Debug , Clone , Deserialize , Serialize ) ]
114
146
/// A relay config with all optional fields. See [`RelayConfig`] for the
115
147
/// description of the fields.
@@ -136,3 +168,39 @@ impl PartialRelayConfig {
136
168
}
137
169
}
138
170
}
171
+
172
+ #[ derive( Debug , Clone , Deserialize , Serialize ) ]
173
+ #[ serde( untagged) ]
174
+ pub enum MuxKeysLoader {
175
+ /// A file containing a list of validator pubkeys
176
+ File ( PathBuf ) ,
177
+ }
178
+
179
+ impl MuxKeysLoader {
180
+ pub fn load ( & self , mux_id : & str ) -> eyre:: Result < Vec < BlsPublicKey > > {
181
+ match self {
182
+ Self :: File ( config_path) => {
183
+ // First try loading from env
184
+ let path: PathBuf = load_optional_env_var ( & get_mux_env ( mux_id) )
185
+ . map ( PathBuf :: from)
186
+ . unwrap_or ( config_path. clone ( ) ) ;
187
+ let file = load_file ( path) ?;
188
+ serde_json:: from_str ( & file) . wrap_err ( "failed to parse mux keys file" )
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ fn load_file < P : AsRef < Path > + std:: fmt:: Debug > ( path : P ) -> eyre:: Result < String > {
195
+ std:: fs:: read_to_string ( & path) . wrap_err ( format ! ( "Unable to find mux keys file: {path:?}" ) )
196
+ }
197
+
198
+ /// A different env var for each mux
199
+ fn get_mux_env ( mux_id : & str ) -> String {
200
+ format ! ( "{MUX_PATH_ENV}_{mux_id}" )
201
+ }
202
+
203
+ /// Path to the mux file
204
+ fn get_mux_path ( mux_id : & str ) -> String {
205
+ format ! ( "/{mux_id}-mux_keys.json" )
206
+ }
0 commit comments