2
2
crate :: { fetch_spl, new_spinner_progress_bar, NodeType , SOLANA_RELEASE , SUN , WRITING } ,
3
3
log:: * ,
4
4
rand:: Rng ,
5
+ serde:: { Deserialize , Serialize } ,
5
6
solana_core:: gen_keys:: GenKeys ,
6
7
solana_sdk:: {
7
8
native_token:: sol_to_lamports,
8
- signature:: { write_keypair_file, Keypair } ,
9
+ signature:: { write_keypair_file, Keypair , Signer } ,
9
10
} ,
10
11
std:: {
12
+ collections:: HashMap ,
11
13
error:: Error ,
12
14
fs:: { File , OpenOptions } ,
13
15
io:: { self , BufRead , BufWriter , Read , Write } ,
@@ -24,6 +26,35 @@ pub const DEFAULT_INTERNAL_NODE_SOL: f64 = 100.0;
24
26
pub const DEFAULT_BOOTSTRAP_NODE_STAKE_SOL : f64 = 10.0 ;
25
27
pub const DEFAULT_BOOTSTRAP_NODE_SOL : f64 = 100.0 ;
26
28
pub const DEFAULT_CLIENT_LAMPORTS_PER_SIGNATURE : u64 = 42 ;
29
+ const VALIDATOR_ACCOUNTS_KEYPAIR_COUNT : usize = 3 ;
30
+ const RPC_ACCOUNTS_KEYPAIR_COUNT : usize = 1 ;
31
+
32
+ #[ derive( Debug , Deserialize ) ]
33
+ struct ValidatorStakes {
34
+ balance_lamports : u64 ,
35
+ stake_lamports : u64 ,
36
+ }
37
+
38
+ fn generate_filename ( node_type : & NodeType , account_type : & str , index : usize ) -> String {
39
+ match node_type {
40
+ NodeType :: Bootstrap => format ! ( "{node_type}/{account_type}.json" ) ,
41
+ NodeType :: Standard | NodeType :: RPC => {
42
+ format ! ( "{node_type}-{account_type}-{index}.json" )
43
+ }
44
+ NodeType :: Client ( _, _) => panic ! ( "Client type not supported" ) ,
45
+ }
46
+ }
47
+
48
+ /// A validator account where the data is encoded as a Base64 string.
49
+ /// Includes the vote account and stake account.
50
+ #[ derive( Serialize , Deserialize , Debug , Clone ) ]
51
+ pub struct ValidatorAccounts {
52
+ pub balance_lamports : u64 ,
53
+ pub stake_lamports : u64 ,
54
+ pub identity_account : String ,
55
+ pub vote_account : String ,
56
+ pub stake_account : String ,
57
+ }
27
58
28
59
fn parse_spl_genesis_file (
29
60
spl_file : & PathBuf ,
@@ -68,6 +99,7 @@ pub struct GenesisFlags {
68
99
pub internal_node_sol : f64 ,
69
100
pub internal_node_stake_sol : f64 ,
70
101
pub skip_primordial_stakes : bool ,
102
+ pub validator_accounts_file : Option < PathBuf > ,
71
103
}
72
104
73
105
fn append_client_accounts_to_file (
@@ -97,11 +129,18 @@ fn append_client_accounts_to_file(
97
129
pub struct Genesis {
98
130
config_dir : PathBuf ,
99
131
key_generator : GenKeys ,
132
+ pub validator_stakes_file : Option < PathBuf > ,
133
+ validator_accounts : HashMap < String , ValidatorAccounts > ,
100
134
pub flags : GenesisFlags ,
101
135
}
102
136
103
137
impl Genesis {
104
- pub fn new ( config_dir : PathBuf , flags : GenesisFlags , retain_previous_genesis : bool ) -> Self {
138
+ pub fn new (
139
+ config_dir : PathBuf ,
140
+ validator_stakes_file : Option < PathBuf > ,
141
+ flags : GenesisFlags ,
142
+ retain_previous_genesis : bool ,
143
+ ) -> Self {
105
144
// if we are deploying a heterogeneous cluster
106
145
// all deployments after the first must retain the original genesis directory
107
146
if !retain_previous_genesis {
@@ -116,6 +155,8 @@ impl Genesis {
116
155
Self {
117
156
config_dir,
118
157
key_generator : GenKeys :: new ( seed) ,
158
+ validator_stakes_file,
159
+ validator_accounts : HashMap :: default ( ) ,
119
160
flags,
120
161
}
121
162
}
@@ -149,52 +190,100 @@ impl Genesis {
149
190
}
150
191
} ;
151
192
152
- let account_types: Vec < String > = if let Some ( tag) = deployment_tag {
153
- account_types
154
- . into_iter ( )
155
- . map ( |acct| format ! ( "{}-{}" , acct, tag) )
156
- . collect ( )
157
- } else {
158
- account_types
193
+ let account_types: Vec < String > = match deployment_tag {
194
+ Some ( tag) => account_types
159
195
. into_iter ( )
160
- . map ( |acct| acct. to_string ( ) )
161
- . collect ( )
196
+ . map ( |acct| format ! ( "{acct}-{tag}" ) )
197
+ . collect ( ) ,
198
+ None => account_types. into_iter ( ) . map ( String :: from) . collect ( ) ,
162
199
} ;
163
200
164
201
let total_accounts_to_generate = number_of_accounts * account_types. len ( ) ;
165
202
let keypairs = self
166
203
. key_generator
167
204
. gen_n_keypairs ( total_accounts_to_generate as u64 ) ;
168
205
206
+ if node_type == NodeType :: Standard {
207
+ self . initialize_validator_accounts ( & node_type, & keypairs) ;
208
+ }
209
+
169
210
self . write_accounts_to_file ( & node_type, & account_types, & keypairs) ?;
211
+ // self.initialize_validator_accounts(&keypairs);
170
212
171
213
Ok ( ( ) )
172
214
}
173
215
174
216
fn write_accounts_to_file (
175
- & self ,
217
+ & mut self ,
176
218
node_type : & NodeType ,
177
219
account_types : & [ String ] ,
178
220
keypairs : & [ Keypair ] ,
179
221
) -> Result < ( ) , Box < dyn Error > > {
180
- for ( i, keypair) in keypairs. iter ( ) . enumerate ( ) {
181
- let account_index = i / account_types. len ( ) ;
182
- let account = & account_types[ i % account_types. len ( ) ] ;
183
- info ! ( "Account: {account}, node_type: {node_type}" ) ;
184
- let filename = match node_type {
185
- NodeType :: Bootstrap => {
186
- format ! ( "{node_type}/{account}.json" )
222
+ let chunk_size = match node_type {
223
+ NodeType :: Bootstrap | NodeType :: Standard => VALIDATOR_ACCOUNTS_KEYPAIR_COUNT ,
224
+ NodeType :: RPC => RPC_ACCOUNTS_KEYPAIR_COUNT ,
225
+ NodeType :: Client ( _, _) => return Err ( "Client type not supported" . into ( ) ) ,
226
+ } ;
227
+ for ( i, account_type_keypair) in keypairs. chunks_exact ( chunk_size) . enumerate ( ) {
228
+ match node_type {
229
+ NodeType :: Bootstrap | NodeType :: Standard => {
230
+ // Create a filename for each type of account based on node type and index
231
+ let identity_filename =
232
+ generate_filename ( node_type, account_types[ 0 ] . as_str ( ) , i) ;
233
+ let stake_filename = generate_filename ( node_type, account_types[ 1 ] . as_str ( ) , i) ;
234
+ let vote_filename = generate_filename ( node_type, account_types[ 2 ] . as_str ( ) , i) ;
235
+
236
+ write_keypair_file (
237
+ & account_type_keypair[ 0 ] ,
238
+ self . config_dir . join ( identity_filename) ,
239
+ ) ?;
240
+ write_keypair_file (
241
+ & account_type_keypair[ 1 ] ,
242
+ self . config_dir . join ( vote_filename) ,
243
+ ) ?;
244
+ write_keypair_file (
245
+ & account_type_keypair[ 2 ] ,
246
+ self . config_dir . join ( stake_filename) ,
247
+ ) ?;
187
248
}
188
- NodeType :: Standard | NodeType :: RPC => {
189
- format ! ( "{node_type}-{account}-{account_index}.json" )
249
+ NodeType :: RPC => {
250
+ let identity_filename =
251
+ generate_filename ( node_type, account_types[ 0 ] . as_str ( ) , i) ;
252
+ write_keypair_file (
253
+ & account_type_keypair[ 0 ] ,
254
+ self . config_dir . join ( identity_filename) ,
255
+ ) ?;
190
256
}
191
- NodeType :: Client ( _, _) => panic ! ( "Client type not supported" ) ,
257
+ NodeType :: Client ( _, _) => return Err ( "Client type not supported" . into ( ) ) ,
258
+ }
259
+ }
260
+
261
+ Ok ( ( ) )
262
+ }
263
+
264
+ fn initialize_validator_accounts ( & mut self , node_type : & NodeType , keypairs : & [ Keypair ] ) {
265
+ if node_type != & NodeType :: Standard {
266
+ return ;
267
+ }
268
+ for ( i, account_type_keypair) in keypairs
269
+ . chunks_exact ( VALIDATOR_ACCOUNTS_KEYPAIR_COUNT )
270
+ . enumerate ( )
271
+ {
272
+ let identity_account = account_type_keypair[ 0 ] . pubkey ( ) . to_string ( ) ;
273
+ let vote_account = account_type_keypair[ 1 ] . pubkey ( ) . to_string ( ) ;
274
+ let stake_account = account_type_keypair[ 2 ] . pubkey ( ) . to_string ( ) ;
275
+
276
+ let validator_account = ValidatorAccounts {
277
+ balance_lamports : 0 ,
278
+ stake_lamports : 0 ,
279
+ identity_account,
280
+ vote_account,
281
+ stake_account,
192
282
} ;
193
283
194
- let outfile = self . config_dir . join ( & filename ) ;
195
- write_keypair_file ( keypair , outfile ) ? ;
284
+ let key = format ! ( "v{i}" ) ;
285
+ self . validator_accounts . insert ( key , validator_account ) ;
196
286
}
197
- Ok ( ( ) )
198
287
}
199
288
200
289
pub fn create_client_accounts (
@@ -284,11 +373,7 @@ impl Genesis {
284
373
Ok ( child)
285
374
}
286
375
287
- fn setup_genesis_flags (
288
- & self ,
289
- num_validators : usize ,
290
- image_tag : & str ,
291
- ) -> Result < Vec < String > , Box < dyn Error > > {
376
+ fn setup_genesis_flags ( & self ) -> Result < Vec < String > , Box < dyn Error > > {
292
377
let mut args = vec ! [
293
378
"--bootstrap-validator-lamports" . to_string( ) ,
294
379
sol_to_lamports( self . flags. bootstrap_validator_sol) . to_string( ) ,
@@ -349,28 +434,20 @@ impl Genesis {
349
434
args. push ( path) ;
350
435
}
351
436
352
- if !self . flags . skip_primordial_stakes {
353
- for i in 0 ..num_validators {
354
- args. push ( "--internal-validator" . to_string ( ) ) ;
355
- for account_type in [ "identity" , "vote-account" , "stake-account" ] . iter ( ) {
356
- let path = self
357
- . config_dir
358
- . join ( format ! ( "validator-{account_type}-{image_tag}-{i}.json" ) )
359
- . into_os_string ( )
360
- . into_string ( )
361
- . map_err ( |_| "Failed to convert path to string" ) ?;
362
- args. push ( path) ;
363
- }
364
- }
365
-
366
- // stake delegated from internal_node_sol
367
- let internal_node_lamports =
368
- self . flags . internal_node_sol - self . flags . internal_node_stake_sol ;
369
- args. push ( "--internal-validator-lamports" . to_string ( ) ) ;
370
- args. push ( sol_to_lamports ( internal_node_lamports) . to_string ( ) ) ;
371
-
372
- args. push ( "--internal-validator-stake-lamports" . to_string ( ) ) ;
373
- args. push ( sol_to_lamports ( self . flags . internal_node_stake_sol ) . to_string ( ) ) ;
437
+ if let Some ( validator_accounts_file) = & self . flags . validator_accounts_file {
438
+ args. push ( "--validator-accounts-file" . to_string ( ) ) ;
439
+ args. push (
440
+ validator_accounts_file
441
+ . clone ( )
442
+ . into_os_string ( )
443
+ . into_string ( )
444
+ . map_err ( |err| {
445
+ std:: io:: Error :: new (
446
+ std:: io:: ErrorKind :: InvalidData ,
447
+ format ! ( "Invalid Unicode data in path: {:?}" , err) ,
448
+ )
449
+ } ) ?,
450
+ ) ;
374
451
}
375
452
376
453
if let Some ( slots_per_epoch) = self . flags . slots_per_epoch {
@@ -400,10 +477,8 @@ impl Genesis {
400
477
& mut self ,
401
478
solana_root_path : & Path ,
402
479
exec_path : & Path ,
403
- num_validators : usize ,
404
- image_tag : & str ,
405
480
) -> Result < ( ) , Box < dyn Error > > {
406
- let mut args = self . setup_genesis_flags ( num_validators , image_tag ) ?;
481
+ let mut args = self . setup_genesis_flags ( ) ?;
407
482
let mut spl_args = self . setup_spl_args ( solana_root_path) . await ?;
408
483
args. append ( & mut spl_args) ;
409
484
@@ -505,4 +580,59 @@ impl Genesis {
505
580
506
581
Ok ( bank_hash)
507
582
}
583
+
584
+ pub fn load_validator_genesis_stakes_from_file ( & mut self ) -> io:: Result < ( ) > {
585
+ let validator_stakes_file = match & self . validator_stakes_file {
586
+ Some ( file) => file,
587
+ None => {
588
+ warn ! ( "validator_stakes_file is None" ) ;
589
+ return Ok ( ( ) ) ;
590
+ }
591
+ } ;
592
+ let file = File :: open ( validator_stakes_file) ?;
593
+ let validator_stakes: HashMap < String , ValidatorStakes > = serde_yaml:: from_reader ( file)
594
+ . map_err ( |err| io:: Error :: new ( io:: ErrorKind :: Other , format ! ( "{err:?}" ) ) ) ?;
595
+
596
+ if validator_stakes. len ( ) != self . validator_accounts . len ( ) {
597
+ return Err ( io:: Error :: new (
598
+ io:: ErrorKind :: Other ,
599
+ format ! (
600
+ "Number of validator stakes ({}) does not match number of validator accounts ({})" ,
601
+ validator_stakes. len( ) , self . validator_accounts. len( )
602
+ ) ,
603
+ ) ) ;
604
+ }
605
+
606
+ // match `validator_stakes` with corresponding `ValidatorAccounts` and update balance and stake
607
+ for ( key, stake) in validator_stakes {
608
+ if let Some ( validator_account) = self . validator_accounts . get_mut ( & key) {
609
+ validator_account. balance_lamports = stake. balance_lamports ;
610
+ validator_account. stake_lamports = stake. stake_lamports ;
611
+ } else {
612
+ return Err ( io:: Error :: new (
613
+ io:: ErrorKind :: Other ,
614
+ format ! ( "Validator account for key '{key}' not found" ) ,
615
+ ) ) ;
616
+ }
617
+ }
618
+
619
+ self . write_validator_genesis_accouts_to_file ( ) ?;
620
+ Ok ( ( ) )
621
+ }
622
+
623
+ fn write_validator_genesis_accouts_to_file ( & mut self ) -> std:: io:: Result < ( ) > {
624
+ // get ValidatorAccounts vec to write to file for solana-genesis
625
+ let validator_accounts_vec: Vec < ValidatorAccounts > =
626
+ self . validator_accounts . values ( ) . cloned ( ) . collect ( ) ;
627
+ let output_file = self . config_dir . join ( "validator-genesis-accounts.yml" ) ;
628
+ self . flags . validator_accounts_file = Some ( output_file. clone ( ) ) ;
629
+
630
+ // write ValidatorAccouns to yaml file for solana-genesis
631
+ let file = File :: create ( & output_file) ?;
632
+ serde_yaml:: to_writer ( file, & validator_accounts_vec)
633
+ . map_err ( |err| io:: Error :: new ( io:: ErrorKind :: Other , format ! ( "{err:?}" ) ) ) ?;
634
+
635
+ info ! ( "Validator genesis accounts successfully written to {output_file:?}" ) ;
636
+ Ok ( ( ) )
637
+ }
508
638
}
0 commit comments