Skip to content

Commit 062a21e

Browse files
committed
switch to --validator-balances-file instead of flags for each individual validator
1 parent af2e71e commit 062a21e

File tree

7 files changed

+238
-82
lines changed

7 files changed

+238
-82
lines changed

Diff for: Cargo.lock

+6-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ openssl = "0.10.66"
2020
rand = "0.8.5"
2121
reqwest = { version = "0.11.23", features = ["blocking", "brotli", "deflate", "gzip", "rustls-tls", "json"] }
2222
rustls = { version = "0.21.11", default-features = false, features = ["quic"] }
23+
serde = "1.0.208"
24+
serde_yaml = "0.9.34"
2325
solana-accounts-db = "1.18.20"
2426
solana-clap-v3-utils = "1.18.20"
2527
solana-core = "1.18.20"

Diff for: README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ cargo run --bin cluster --
9090
--base-image <base-image> # e.g. ubuntu:20.04
9191
--image-name <docker-image-name> # e.g. cluster-image
9292
# validator config
93+
--skip-primordial-accounts
9394
--full-rpc
94-
--internal-node-sol <Sol>
95-
--internal-node-stake-sol <Sol>
95+
--internal-node-sol <sol>
96+
--internal-node-stake-sol <sol>
9697
# kubernetes config
9798
--cpu-requests <cores>
9899
--memory-requests <memory>

Diff for: src/genesis.rs

+185-55
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ use {
22
crate::{fetch_spl, new_spinner_progress_bar, NodeType, SOLANA_RELEASE, SUN, WRITING},
33
log::*,
44
rand::Rng,
5+
serde::{Deserialize, Serialize},
56
solana_core::gen_keys::GenKeys,
67
solana_sdk::{
78
native_token::sol_to_lamports,
8-
signature::{write_keypair_file, Keypair},
9+
signature::{write_keypair_file, Keypair, Signer},
910
},
1011
std::{
12+
collections::HashMap,
1113
error::Error,
1214
fs::{File, OpenOptions},
1315
io::{self, BufRead, BufWriter, Read, Write},
@@ -24,6 +26,35 @@ pub const DEFAULT_INTERNAL_NODE_SOL: f64 = 100.0;
2426
pub const DEFAULT_BOOTSTRAP_NODE_STAKE_SOL: f64 = 10.0;
2527
pub const DEFAULT_BOOTSTRAP_NODE_SOL: f64 = 100.0;
2628
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+
}
2758

2859
fn parse_spl_genesis_file(
2960
spl_file: &PathBuf,
@@ -68,6 +99,7 @@ pub struct GenesisFlags {
6899
pub internal_node_sol: f64,
69100
pub internal_node_stake_sol: f64,
70101
pub skip_primordial_stakes: bool,
102+
pub validator_accounts_file: Option<PathBuf>,
71103
}
72104

73105
fn append_client_accounts_to_file(
@@ -97,11 +129,18 @@ fn append_client_accounts_to_file(
97129
pub struct Genesis {
98130
config_dir: PathBuf,
99131
key_generator: GenKeys,
132+
pub validator_stakes_file: Option<PathBuf>,
133+
validator_accounts: HashMap<String, ValidatorAccounts>,
100134
pub flags: GenesisFlags,
101135
}
102136

103137
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 {
105144
// if we are deploying a heterogeneous cluster
106145
// all deployments after the first must retain the original genesis directory
107146
if !retain_previous_genesis {
@@ -116,6 +155,8 @@ impl Genesis {
116155
Self {
117156
config_dir,
118157
key_generator: GenKeys::new(seed),
158+
validator_stakes_file,
159+
validator_accounts: HashMap::default(),
119160
flags,
120161
}
121162
}
@@ -149,52 +190,100 @@ impl Genesis {
149190
}
150191
};
151192

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
159195
.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(),
162199
};
163200

164201
let total_accounts_to_generate = number_of_accounts * account_types.len();
165202
let keypairs = self
166203
.key_generator
167204
.gen_n_keypairs(total_accounts_to_generate as u64);
168205

206+
if node_type == NodeType::Standard {
207+
self.initialize_validator_accounts(&node_type, &keypairs);
208+
}
209+
169210
self.write_accounts_to_file(&node_type, &account_types, &keypairs)?;
211+
// self.initialize_validator_accounts(&keypairs);
170212

171213
Ok(())
172214
}
173215

174216
fn write_accounts_to_file(
175-
&self,
217+
&mut self,
176218
node_type: &NodeType,
177219
account_types: &[String],
178220
keypairs: &[Keypair],
179221
) -> 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+
)?;
187248
}
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+
)?;
190256
}
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,
192282
};
193283

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);
196286
}
197-
Ok(())
198287
}
199288

200289
pub fn create_client_accounts(
@@ -284,11 +373,7 @@ impl Genesis {
284373
Ok(child)
285374
}
286375

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>> {
292377
let mut args = vec![
293378
"--bootstrap-validator-lamports".to_string(),
294379
sol_to_lamports(self.flags.bootstrap_validator_sol).to_string(),
@@ -349,28 +434,20 @@ impl Genesis {
349434
args.push(path);
350435
}
351436

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+
);
374451
}
375452

376453
if let Some(slots_per_epoch) = self.flags.slots_per_epoch {
@@ -400,10 +477,8 @@ impl Genesis {
400477
&mut self,
401478
solana_root_path: &Path,
402479
exec_path: &Path,
403-
num_validators: usize,
404-
image_tag: &str,
405480
) -> Result<(), Box<dyn Error>> {
406-
let mut args = self.setup_genesis_flags(num_validators, image_tag)?;
481+
let mut args = self.setup_genesis_flags()?;
407482
let mut spl_args = self.setup_spl_args(solana_root_path).await?;
408483
args.append(&mut spl_args);
409484

@@ -505,4 +580,59 @@ impl Genesis {
505580

506581
Ok(bank_hash)
507582
}
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+
}
508638
}

0 commit comments

Comments
 (0)