main.rs 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369
  1. //! A command-line executable for generating the chain's genesis config.
  2. #![allow(clippy::arithmetic_side_effects)]
  3. use {
  4. agave_feature_set::FEATURE_NAMES,
  5. agave_snapshots::hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
  6. base64::{prelude::BASE64_STANDARD, Engine},
  7. clap::{crate_description, crate_name, value_t, value_t_or_exit, App, Arg, ArgMatches},
  8. itertools::Itertools,
  9. solana_account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
  10. solana_clap_utils::{
  11. input_parsers::{
  12. cluster_type_of, pubkey_of, pubkeys_of, unix_timestamp_from_rfc3339_datetime,
  13. },
  14. input_validators::{
  15. is_pubkey, is_pubkey_or_keypair, is_rfc3339_datetime, is_slot, is_url_or_moniker,
  16. is_valid_percentage, normalize_to_url_if_moniker,
  17. },
  18. },
  19. solana_clock as clock,
  20. solana_cluster_type::ClusterType,
  21. solana_commitment_config::CommitmentConfig,
  22. solana_entry::poh::compute_hashes_per_tick,
  23. solana_epoch_schedule::EpochSchedule,
  24. solana_feature_gate_interface as feature,
  25. solana_fee_calculator::FeeRateGovernor,
  26. solana_genesis::{
  27. genesis_accounts::add_genesis_accounts, Base64Account, StakedValidatorAccountInfo,
  28. ValidatorAccountsFile,
  29. },
  30. solana_genesis_config::GenesisConfig,
  31. solana_inflation::Inflation,
  32. solana_keypair::{read_keypair_file, Keypair},
  33. solana_ledger::{blockstore::create_new_ledger, blockstore_options::LedgerColumnOptions},
  34. solana_loader_v3_interface::state::UpgradeableLoaderState,
  35. solana_native_token::LAMPORTS_PER_SOL,
  36. solana_poh_config::PohConfig,
  37. solana_pubkey::Pubkey,
  38. solana_rent::Rent,
  39. solana_rpc_client::rpc_client::RpcClient,
  40. solana_rpc_client_api::request::MAX_MULTIPLE_ACCOUNTS,
  41. solana_sdk_ids::system_program,
  42. solana_signer::Signer,
  43. solana_stake_interface::state::StakeStateV2,
  44. solana_stake_program::stake_state,
  45. solana_vote_program::vote_state::{self, VoteStateV4},
  46. std::{
  47. collections::HashMap,
  48. error,
  49. fs::File,
  50. io::{self, Read},
  51. path::PathBuf,
  52. process,
  53. slice::Iter,
  54. str::FromStr,
  55. time::Duration,
  56. },
  57. };
  58. pub enum AccountFileFormat {
  59. Pubkey,
  60. Keypair,
  61. }
  62. fn pubkey_from_str(key_str: &str) -> Result<Pubkey, Box<dyn error::Error>> {
  63. Pubkey::from_str(key_str).or_else(|_| {
  64. let bytes: Vec<u8> = serde_json::from_str(key_str)?;
  65. let keypair =
  66. Keypair::try_from(bytes.as_ref()).map_err(|e| std::io::Error::other(e.to_string()))?;
  67. Ok(keypair.pubkey())
  68. })
  69. }
  70. pub fn load_genesis_accounts(file: &str, genesis_config: &mut GenesisConfig) -> io::Result<u64> {
  71. let mut lamports = 0;
  72. let accounts_file = File::open(file)?;
  73. let genesis_accounts: HashMap<String, Base64Account> =
  74. serde_yaml::from_reader(accounts_file)
  75. .map_err(|err| io::Error::other(format!("{err:?}")))?;
  76. for (key, account_details) in genesis_accounts {
  77. let pubkey = pubkey_from_str(key.as_str())
  78. .map_err(|err| io::Error::other(format!("Invalid pubkey/keypair {key}: {err:?}")))?;
  79. let owner_program_id = Pubkey::from_str(account_details.owner.as_str()).map_err(|err| {
  80. io::Error::other(format!(
  81. "Invalid owner: {}: {:?}",
  82. account_details.owner, err
  83. ))
  84. })?;
  85. let mut account = AccountSharedData::new(account_details.balance, 0, &owner_program_id);
  86. if account_details.data != "~" {
  87. account.set_data_from_slice(
  88. &BASE64_STANDARD
  89. .decode(account_details.data.as_str())
  90. .map_err(|err| {
  91. io::Error::other(format!(
  92. "Invalid account data: {}: {:?}",
  93. account_details.data, err
  94. ))
  95. })?,
  96. );
  97. }
  98. account.set_executable(account_details.executable);
  99. lamports += account.lamports();
  100. genesis_config.add_account(pubkey, account);
  101. }
  102. Ok(lamports)
  103. }
  104. pub fn load_validator_accounts(
  105. file: &str,
  106. commission: u8,
  107. rent: &Rent,
  108. genesis_config: &mut GenesisConfig,
  109. ) -> io::Result<()> {
  110. let accounts_file = File::open(file)?;
  111. let validator_genesis_accounts: Vec<StakedValidatorAccountInfo> =
  112. serde_yaml::from_reader::<_, ValidatorAccountsFile>(accounts_file)
  113. .map_err(|err| io::Error::other(format!("{err:?}")))?
  114. .validator_accounts;
  115. for account_details in validator_genesis_accounts {
  116. let pubkeys = [
  117. pubkey_from_str(account_details.identity_account.as_str()).map_err(|err| {
  118. io::Error::other(format!(
  119. "Invalid pubkey/keypair {}: {:?}",
  120. account_details.identity_account, err
  121. ))
  122. })?,
  123. pubkey_from_str(account_details.vote_account.as_str()).map_err(|err| {
  124. io::Error::other(format!(
  125. "Invalid pubkey/keypair {}: {:?}",
  126. account_details.vote_account, err
  127. ))
  128. })?,
  129. pubkey_from_str(account_details.stake_account.as_str()).map_err(|err| {
  130. io::Error::other(format!(
  131. "Invalid pubkey/keypair {}: {:?}",
  132. account_details.stake_account, err
  133. ))
  134. })?,
  135. ];
  136. add_validator_accounts(
  137. genesis_config,
  138. &mut pubkeys.iter(),
  139. account_details.balance_lamports,
  140. account_details.stake_lamports,
  141. commission,
  142. rent,
  143. None,
  144. )?;
  145. }
  146. Ok(())
  147. }
  148. fn check_rpc_genesis_hash(
  149. cluster_type: &ClusterType,
  150. rpc_client: &RpcClient,
  151. ) -> Result<(), Box<dyn std::error::Error>> {
  152. if let Some(genesis_hash) = cluster_type.get_genesis_hash() {
  153. let rpc_genesis_hash = rpc_client.get_genesis_hash()?;
  154. if rpc_genesis_hash != genesis_hash {
  155. return Err(format!(
  156. "The genesis hash for the specified cluster {cluster_type:?} does not match the \
  157. genesis hash reported by the specified RPC. Cluster genesis hash: \
  158. {genesis_hash}, RPC reported genesis hash: {rpc_genesis_hash}"
  159. )
  160. .into());
  161. }
  162. }
  163. Ok(())
  164. }
  165. fn features_to_deactivate_for_cluster(
  166. cluster_type: &ClusterType,
  167. matches: &ArgMatches<'_>,
  168. ) -> Result<Vec<Pubkey>, Box<dyn error::Error>> {
  169. let mut features_to_deactivate = pubkeys_of(matches, "deactivate_feature").unwrap_or_default();
  170. if cluster_type == &ClusterType::Development {
  171. return Ok(features_to_deactivate);
  172. }
  173. // if we're here, the cluster type must be one of "mainnet-beta", "testnet", or "devnet"
  174. assert!(matches!(
  175. cluster_type,
  176. ClusterType::MainnetBeta | ClusterType::Testnet | ClusterType::Devnet
  177. ));
  178. let json_rpc_url = normalize_to_url_if_moniker(
  179. matches
  180. .value_of("json_rpc_url")
  181. .unwrap_or(matches.value_of("cluster_type").unwrap()),
  182. );
  183. let rpc_client = RpcClient::new_with_commitment(json_rpc_url, CommitmentConfig::confirmed());
  184. check_rpc_genesis_hash(cluster_type, &rpc_client)?;
  185. for feature_ids in FEATURE_NAMES
  186. .keys()
  187. .cloned()
  188. .collect::<Vec<Pubkey>>()
  189. .chunks(MAX_MULTIPLE_ACCOUNTS)
  190. {
  191. rpc_client
  192. .get_multiple_accounts(feature_ids)
  193. .map_err(|err| format!("Failed to fetch: {err}"))?
  194. .into_iter()
  195. .zip(feature_ids)
  196. .for_each(|(maybe_account, feature_id)| {
  197. if maybe_account
  198. .as_ref()
  199. .and_then(feature::from_account)
  200. .and_then(|feature| feature.activated_at)
  201. .is_none()
  202. {
  203. features_to_deactivate.push(*feature_id);
  204. }
  205. });
  206. }
  207. Ok(features_to_deactivate)
  208. }
  209. fn add_validator_accounts(
  210. genesis_config: &mut GenesisConfig,
  211. pubkeys_iter: &mut Iter<Pubkey>,
  212. lamports: u64,
  213. stake_lamports: u64,
  214. commission: u8,
  215. rent: &Rent,
  216. authorized_pubkey: Option<&Pubkey>,
  217. ) -> io::Result<()> {
  218. rent_exempt_check(
  219. stake_lamports,
  220. rent.minimum_balance(StakeStateV2::size_of()),
  221. )?;
  222. loop {
  223. let Some(identity_pubkey) = pubkeys_iter.next() else {
  224. break;
  225. };
  226. let vote_pubkey = pubkeys_iter.next().unwrap();
  227. let stake_pubkey = pubkeys_iter.next().unwrap();
  228. genesis_config.add_account(
  229. *identity_pubkey,
  230. AccountSharedData::new(lamports, 0, &system_program::id()),
  231. );
  232. let vote_account = vote_state::create_v4_account_with_authorized(
  233. identity_pubkey,
  234. identity_pubkey,
  235. identity_pubkey,
  236. None,
  237. u16::from(commission) * 100,
  238. rent.minimum_balance(VoteStateV4::size_of()).max(1),
  239. );
  240. genesis_config.add_account(
  241. *stake_pubkey,
  242. stake_state::create_account(
  243. authorized_pubkey.unwrap_or(identity_pubkey),
  244. vote_pubkey,
  245. &vote_account,
  246. rent,
  247. stake_lamports,
  248. ),
  249. );
  250. genesis_config.add_account(*vote_pubkey, vote_account);
  251. }
  252. Ok(())
  253. }
  254. fn rent_exempt_check(stake_lamports: u64, exempt: u64) -> io::Result<()> {
  255. if stake_lamports < exempt {
  256. Err(io::Error::other(format!(
  257. "error: insufficient validator stake lamports: {stake_lamports} for rent exemption, \
  258. requires {exempt}"
  259. )))
  260. } else {
  261. Ok(())
  262. }
  263. }
  264. #[allow(clippy::cognitive_complexity)]
  265. fn main() -> Result<(), Box<dyn error::Error>> {
  266. let default_faucet_pubkey = solana_cli_config::Config::default().keypair_path;
  267. let fee_rate_governor = FeeRateGovernor::default();
  268. let (
  269. default_target_lamports_per_signature,
  270. default_target_signatures_per_slot,
  271. default_fee_burn_percentage,
  272. ) = {
  273. (
  274. &fee_rate_governor.target_lamports_per_signature.to_string(),
  275. &fee_rate_governor.target_signatures_per_slot.to_string(),
  276. &fee_rate_governor.burn_percent.to_string(),
  277. )
  278. };
  279. let rent = Rent::default();
  280. let (
  281. default_lamports_per_byte_year,
  282. default_rent_exemption_threshold,
  283. default_rent_burn_percentage,
  284. ) = {
  285. (
  286. &rent.lamports_per_byte_year.to_string(),
  287. &rent.exemption_threshold.to_string(),
  288. &rent.burn_percent.to_string(),
  289. )
  290. };
  291. // vote account
  292. let default_bootstrap_validator_lamports = &(500 * LAMPORTS_PER_SOL)
  293. .max(rent.minimum_balance(VoteStateV4::size_of()))
  294. .to_string();
  295. // stake account
  296. let default_bootstrap_validator_stake_lamports = &(LAMPORTS_PER_SOL / 2)
  297. .max(rent.minimum_balance(StakeStateV2::size_of()))
  298. .to_string();
  299. let default_target_tick_duration = PohConfig::default().target_tick_duration;
  300. let default_ticks_per_slot = &clock::DEFAULT_TICKS_PER_SLOT.to_string();
  301. let default_cluster_type = "mainnet-beta";
  302. let default_genesis_archive_unpacked_size = MAX_GENESIS_ARCHIVE_UNPACKED_SIZE.to_string();
  303. let matches = App::new(crate_name!())
  304. .about(crate_description!())
  305. .version(solana_version::version!())
  306. .arg(
  307. Arg::with_name("creation_time")
  308. .long("creation-time")
  309. .value_name("RFC3339 DATE TIME")
  310. .validator(is_rfc3339_datetime)
  311. .takes_value(true)
  312. .help(
  313. "Time when the bootstrap validator will start the cluster [default: current \
  314. system time]",
  315. ),
  316. )
  317. .arg(
  318. Arg::with_name("bootstrap_validator")
  319. .short("b")
  320. .long("bootstrap-validator")
  321. .value_name("IDENTITY_PUBKEY VOTE_PUBKEY STAKE_PUBKEY")
  322. .takes_value(true)
  323. .validator(is_pubkey_or_keypair)
  324. .number_of_values(3)
  325. .multiple(true)
  326. .required(true)
  327. .help("The bootstrap validator's identity, vote and stake pubkeys"),
  328. )
  329. .arg(
  330. Arg::with_name("ledger_path")
  331. .short("l")
  332. .long("ledger")
  333. .value_name("DIR")
  334. .takes_value(true)
  335. .required(true)
  336. .help("Use directory as persistent ledger location"),
  337. )
  338. .arg(
  339. Arg::with_name("faucet_lamports")
  340. .short("t")
  341. .long("faucet-lamports")
  342. .value_name("LAMPORTS")
  343. .takes_value(true)
  344. .help("Number of lamports to assign to the faucet"),
  345. )
  346. .arg(
  347. Arg::with_name("faucet_pubkey")
  348. .short("m")
  349. .long("faucet-pubkey")
  350. .value_name("PUBKEY")
  351. .takes_value(true)
  352. .validator(is_pubkey_or_keypair)
  353. .requires("faucet_lamports")
  354. .default_value(&default_faucet_pubkey)
  355. .help("Path to file containing the faucet's pubkey"),
  356. )
  357. .arg(
  358. Arg::with_name("bootstrap_stake_authorized_pubkey")
  359. .long("bootstrap-stake-authorized-pubkey")
  360. .value_name("BOOTSTRAP STAKE AUTHORIZED PUBKEY")
  361. .takes_value(true)
  362. .validator(is_pubkey_or_keypair)
  363. .help(
  364. "Path to file containing the pubkey authorized to manage the bootstrap \
  365. validator's stake [default: --bootstrap-validator IDENTITY_PUBKEY]",
  366. ),
  367. )
  368. .arg(
  369. Arg::with_name("bootstrap_validator_lamports")
  370. .long("bootstrap-validator-lamports")
  371. .value_name("LAMPORTS")
  372. .takes_value(true)
  373. .default_value(default_bootstrap_validator_lamports)
  374. .help("Number of lamports to assign to the bootstrap validator"),
  375. )
  376. .arg(
  377. Arg::with_name("bootstrap_validator_stake_lamports")
  378. .long("bootstrap-validator-stake-lamports")
  379. .value_name("LAMPORTS")
  380. .takes_value(true)
  381. .default_value(default_bootstrap_validator_stake_lamports)
  382. .help("Number of lamports to assign to the bootstrap validator's stake account"),
  383. )
  384. .arg(
  385. Arg::with_name("target_lamports_per_signature")
  386. .long("target-lamports-per-signature")
  387. .value_name("LAMPORTS")
  388. .takes_value(true)
  389. .default_value(default_target_lamports_per_signature)
  390. .help(
  391. "The cost in lamports that the cluster will charge for signature verification \
  392. when the cluster is operating at target-signatures-per-slot",
  393. ),
  394. )
  395. .arg(
  396. Arg::with_name("lamports_per_byte_year")
  397. .long("lamports-per-byte-year")
  398. .value_name("LAMPORTS")
  399. .takes_value(true)
  400. .default_value(default_lamports_per_byte_year)
  401. .help(
  402. "The cost in lamports that the cluster will charge per byte per year for \
  403. accounts with data",
  404. ),
  405. )
  406. .arg(
  407. Arg::with_name("rent_exemption_threshold")
  408. .long("rent-exemption-threshold")
  409. .value_name("NUMBER")
  410. .takes_value(true)
  411. .default_value(default_rent_exemption_threshold)
  412. .help(
  413. "amount of time (in years) the balance has to include rent for to qualify as \
  414. rent exempted account",
  415. ),
  416. )
  417. .arg(
  418. Arg::with_name("rent_burn_percentage")
  419. .long("rent-burn-percentage")
  420. .value_name("NUMBER")
  421. .takes_value(true)
  422. .default_value(default_rent_burn_percentage)
  423. .help("percentage of collected rent to burn")
  424. .validator(is_valid_percentage),
  425. )
  426. .arg(
  427. Arg::with_name("fee_burn_percentage")
  428. .long("fee-burn-percentage")
  429. .value_name("NUMBER")
  430. .takes_value(true)
  431. .default_value(default_fee_burn_percentage)
  432. .help("percentage of collected fee to burn")
  433. .validator(is_valid_percentage),
  434. )
  435. .arg(
  436. Arg::with_name("vote_commission_percentage")
  437. .long("vote-commission-percentage")
  438. .value_name("NUMBER")
  439. .takes_value(true)
  440. .default_value("100")
  441. .help("percentage of vote commission")
  442. .validator(is_valid_percentage),
  443. )
  444. .arg(
  445. Arg::with_name("target_signatures_per_slot")
  446. .long("target-signatures-per-slot")
  447. .value_name("NUMBER")
  448. .takes_value(true)
  449. .default_value(default_target_signatures_per_slot)
  450. .help(
  451. "Used to estimate the desired processing capacity of the cluster. When the \
  452. latest slot processes fewer/greater signatures than this value, the \
  453. lamports-per-signature fee will decrease/increase for the next slot. A value \
  454. of 0 disables signature-based fee adjustments",
  455. ),
  456. )
  457. .arg(
  458. Arg::with_name("target_tick_duration")
  459. .long("target-tick-duration")
  460. .value_name("MILLIS")
  461. .takes_value(true)
  462. .help("The target tick rate of the cluster in milliseconds"),
  463. )
  464. .arg(
  465. Arg::with_name("hashes_per_tick")
  466. .long("hashes-per-tick")
  467. .value_name("NUM_HASHES|\"auto\"|\"sleep\"")
  468. .takes_value(true)
  469. .default_value("auto")
  470. .help(
  471. "How many PoH hashes to roll before emitting the next tick. If \"auto\", \
  472. determine based on --target-tick-duration and the hash rate of this \
  473. computer. If \"sleep\", for development sleep for --target-tick-duration \
  474. instead of hashing",
  475. ),
  476. )
  477. .arg(
  478. Arg::with_name("ticks_per_slot")
  479. .long("ticks-per-slot")
  480. .value_name("TICKS")
  481. .takes_value(true)
  482. .default_value(default_ticks_per_slot)
  483. .help("The number of ticks in a slot"),
  484. )
  485. .arg(
  486. Arg::with_name("slots_per_epoch")
  487. .long("slots-per-epoch")
  488. .value_name("SLOTS")
  489. .validator(is_slot)
  490. .takes_value(true)
  491. .help("The number of slots in an epoch"),
  492. )
  493. .arg(
  494. Arg::with_name("enable_warmup_epochs")
  495. .long("enable-warmup-epochs")
  496. .help(
  497. "When enabled epochs start short and will grow. Useful for warming up stake \
  498. quickly during development",
  499. ),
  500. )
  501. .arg(
  502. Arg::with_name("primordial_accounts_file")
  503. .long("primordial-accounts-file")
  504. .value_name("FILENAME")
  505. .takes_value(true)
  506. .multiple(true)
  507. .help("The location of pubkey for primordial accounts and balance"),
  508. )
  509. .arg(
  510. Arg::with_name("validator_accounts_file")
  511. .long("validator-accounts-file")
  512. .value_name("FILENAME")
  513. .takes_value(true)
  514. .multiple(true)
  515. .help(
  516. "The location of a file containing a list of identity, vote, and stake \
  517. pubkeys and balances for validator accounts to bake into genesis",
  518. ),
  519. )
  520. .arg(
  521. Arg::with_name("cluster_type")
  522. .long("cluster-type")
  523. .possible_values(&ClusterType::STRINGS)
  524. .takes_value(true)
  525. .default_value(default_cluster_type)
  526. .help("Selects the features that will be enabled for the cluster"),
  527. )
  528. .arg(
  529. Arg::with_name("deactivate_feature")
  530. .long("deactivate-feature")
  531. .takes_value(true)
  532. .value_name("FEATURE_PUBKEY")
  533. .validator(is_pubkey)
  534. .multiple(true)
  535. .help(
  536. "Deactivate this feature in genesis. Compatible with --cluster-type \
  537. development",
  538. ),
  539. )
  540. .arg(
  541. Arg::with_name("max_genesis_archive_unpacked_size")
  542. .long("max-genesis-archive-unpacked-size")
  543. .value_name("NUMBER")
  544. .takes_value(true)
  545. .default_value(&default_genesis_archive_unpacked_size)
  546. .help("maximum total uncompressed file size of created genesis archive"),
  547. )
  548. .arg(
  549. Arg::with_name("bpf_program")
  550. .long("bpf-program")
  551. .value_name("ADDRESS LOADER SBF_PROGRAM.SO")
  552. .takes_value(true)
  553. .number_of_values(3)
  554. .multiple(true)
  555. .help("Install a SBF program at the given address"),
  556. )
  557. .arg(
  558. Arg::with_name("upgradeable_program")
  559. .long("upgradeable-program")
  560. .value_name("ADDRESS UPGRADEABLE_LOADER SBF_PROGRAM.SO UPGRADE_AUTHORITY")
  561. .takes_value(true)
  562. .number_of_values(4)
  563. .multiple(true)
  564. .help(
  565. "Install an upgradeable SBF program at the given address with the given \
  566. upgrade authority (or \"none\")",
  567. ),
  568. )
  569. .arg(
  570. Arg::with_name("inflation")
  571. .required(false)
  572. .long("inflation")
  573. .takes_value(true)
  574. .possible_values(&["pico", "full", "none"])
  575. .help("Selects inflation"),
  576. )
  577. .arg(
  578. Arg::with_name("json_rpc_url")
  579. .short("u")
  580. .long("url")
  581. .value_name("URL_OR_MONIKER")
  582. .takes_value(true)
  583. .global(true)
  584. .validator(is_url_or_moniker)
  585. .help(
  586. "URL for Solana's JSON RPC or moniker (or their first letter): [mainnet-beta, \
  587. testnet, devnet, localhost]. Used for cloning feature sets",
  588. ),
  589. )
  590. .get_matches();
  591. let ledger_path = PathBuf::from(matches.value_of("ledger_path").unwrap());
  592. let rent = Rent {
  593. lamports_per_byte_year: value_t_or_exit!(matches, "lamports_per_byte_year", u64),
  594. exemption_threshold: value_t_or_exit!(matches, "rent_exemption_threshold", f64),
  595. burn_percent: value_t_or_exit!(matches, "rent_burn_percentage", u8),
  596. };
  597. let bootstrap_validator_pubkeys = pubkeys_of(&matches, "bootstrap_validator").unwrap();
  598. assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);
  599. // Ensure there are no duplicated pubkeys in the --bootstrap-validator list
  600. {
  601. let mut v = bootstrap_validator_pubkeys.clone();
  602. v.sort();
  603. v.dedup();
  604. if v.len() != bootstrap_validator_pubkeys.len() {
  605. eprintln!("Error: --bootstrap-validator pubkeys cannot be duplicated");
  606. process::exit(1);
  607. }
  608. }
  609. let bootstrap_validator_lamports =
  610. value_t_or_exit!(matches, "bootstrap_validator_lamports", u64);
  611. let bootstrap_validator_stake_lamports =
  612. value_t_or_exit!(matches, "bootstrap_validator_stake_lamports", u64);
  613. let bootstrap_stake_authorized_pubkey =
  614. pubkey_of(&matches, "bootstrap_stake_authorized_pubkey");
  615. let faucet_lamports = value_t!(matches, "faucet_lamports", u64).unwrap_or(0);
  616. let faucet_pubkey = pubkey_of(&matches, "faucet_pubkey");
  617. let ticks_per_slot = value_t_or_exit!(matches, "ticks_per_slot", u64);
  618. let mut fee_rate_governor = FeeRateGovernor::new(
  619. value_t_or_exit!(matches, "target_lamports_per_signature", u64),
  620. value_t_or_exit!(matches, "target_signatures_per_slot", u64),
  621. );
  622. fee_rate_governor.burn_percent = value_t_or_exit!(matches, "fee_burn_percentage", u8);
  623. let mut poh_config = PohConfig {
  624. target_tick_duration: if matches.is_present("target_tick_duration") {
  625. Duration::from_micros(value_t_or_exit!(matches, "target_tick_duration", u64))
  626. } else {
  627. default_target_tick_duration
  628. },
  629. ..PohConfig::default()
  630. };
  631. let cluster_type = cluster_type_of(&matches, "cluster_type").unwrap();
  632. // Get the features to deactivate if provided
  633. let features_to_deactivate = features_to_deactivate_for_cluster(&cluster_type, &matches)
  634. .unwrap_or_else(|e| {
  635. eprintln!("{e}");
  636. std::process::exit(1);
  637. });
  638. match matches.value_of("hashes_per_tick").unwrap() {
  639. "auto" => match cluster_type {
  640. ClusterType::Development => {
  641. let hashes_per_tick =
  642. compute_hashes_per_tick(poh_config.target_tick_duration, 1_000_000);
  643. poh_config.hashes_per_tick = Some(hashes_per_tick / 2); // use 50% of peak ability
  644. }
  645. ClusterType::Devnet | ClusterType::Testnet | ClusterType::MainnetBeta => {
  646. poh_config.hashes_per_tick = Some(clock::DEFAULT_HASHES_PER_TICK);
  647. }
  648. },
  649. "sleep" => {
  650. poh_config.hashes_per_tick = None;
  651. }
  652. _ => {
  653. poh_config.hashes_per_tick = Some(value_t_or_exit!(matches, "hashes_per_tick", u64));
  654. }
  655. }
  656. let slots_per_epoch = if matches.value_of("slots_per_epoch").is_some() {
  657. value_t_or_exit!(matches, "slots_per_epoch", u64)
  658. } else {
  659. match cluster_type {
  660. ClusterType::Development => clock::DEFAULT_DEV_SLOTS_PER_EPOCH,
  661. ClusterType::Devnet | ClusterType::Testnet | ClusterType::MainnetBeta => {
  662. clock::DEFAULT_SLOTS_PER_EPOCH
  663. }
  664. }
  665. };
  666. let epoch_schedule = EpochSchedule::custom(
  667. slots_per_epoch,
  668. slots_per_epoch,
  669. matches.is_present("enable_warmup_epochs"),
  670. );
  671. let mut genesis_config = GenesisConfig {
  672. ticks_per_slot,
  673. poh_config,
  674. fee_rate_governor,
  675. rent,
  676. epoch_schedule,
  677. cluster_type,
  678. ..GenesisConfig::default()
  679. };
  680. if let Ok(raw_inflation) = value_t!(matches, "inflation", String) {
  681. let inflation = match raw_inflation.as_str() {
  682. "pico" => Inflation::pico(),
  683. "full" => Inflation::full(),
  684. "none" => Inflation::new_disabled(),
  685. _ => unreachable!(),
  686. };
  687. genesis_config.inflation = inflation;
  688. }
  689. let commission = value_t_or_exit!(matches, "vote_commission_percentage", u8);
  690. let rent = genesis_config.rent.clone();
  691. add_validator_accounts(
  692. &mut genesis_config,
  693. &mut bootstrap_validator_pubkeys.iter(),
  694. bootstrap_validator_lamports,
  695. bootstrap_validator_stake_lamports,
  696. commission,
  697. &rent,
  698. bootstrap_stake_authorized_pubkey.as_ref(),
  699. )?;
  700. if let Some(creation_time) = unix_timestamp_from_rfc3339_datetime(&matches, "creation_time") {
  701. genesis_config.creation_time = creation_time;
  702. }
  703. if let Some(faucet_pubkey) = faucet_pubkey {
  704. genesis_config.add_account(
  705. faucet_pubkey,
  706. AccountSharedData::new(faucet_lamports, 0, &system_program::id()),
  707. );
  708. }
  709. solana_stake_program::add_genesis_accounts(&mut genesis_config);
  710. solana_runtime::genesis_utils::activate_all_features(&mut genesis_config);
  711. if !features_to_deactivate.is_empty() {
  712. solana_runtime::genesis_utils::deactivate_features(
  713. &mut genesis_config,
  714. &features_to_deactivate,
  715. );
  716. }
  717. if let Some(files) = matches.values_of("primordial_accounts_file") {
  718. for file in files {
  719. load_genesis_accounts(file, &mut genesis_config)?;
  720. }
  721. }
  722. if let Some(files) = matches.values_of("validator_accounts_file") {
  723. for file in files {
  724. load_validator_accounts(file, commission, &rent, &mut genesis_config)?;
  725. }
  726. }
  727. let max_genesis_archive_unpacked_size =
  728. value_t_or_exit!(matches, "max_genesis_archive_unpacked_size", u64);
  729. let issued_lamports = genesis_config
  730. .accounts
  731. .values()
  732. .map(|account| account.lamports)
  733. .sum::<u64>();
  734. add_genesis_accounts(&mut genesis_config, issued_lamports - faucet_lamports);
  735. let parse_address = |address: &str, input_type: &str| {
  736. address.parse::<Pubkey>().unwrap_or_else(|err| {
  737. eprintln!("Error: invalid {input_type} {address}: {err}");
  738. process::exit(1);
  739. })
  740. };
  741. let parse_program_data = |program: &str| {
  742. let mut program_data = vec![];
  743. File::open(program)
  744. .and_then(|mut file| file.read_to_end(&mut program_data))
  745. .unwrap_or_else(|err| {
  746. eprintln!("Error: failed to read {program}: {err}");
  747. process::exit(1);
  748. });
  749. program_data
  750. };
  751. if let Some(values) = matches.values_of("bpf_program") {
  752. for (address, loader, program) in values.tuples() {
  753. let address = parse_address(address, "address");
  754. let loader = parse_address(loader, "loader");
  755. let program_data = parse_program_data(program);
  756. genesis_config.add_account(
  757. address,
  758. AccountSharedData::from(Account {
  759. lamports: genesis_config.rent.minimum_balance(program_data.len()),
  760. data: program_data,
  761. executable: true,
  762. owner: loader,
  763. rent_epoch: 0,
  764. }),
  765. );
  766. }
  767. }
  768. if let Some(values) = matches.values_of("upgradeable_program") {
  769. for (address, loader, program, upgrade_authority) in values.tuples() {
  770. let address = parse_address(address, "address");
  771. let loader = parse_address(loader, "loader");
  772. let program_data_elf = parse_program_data(program);
  773. let upgrade_authority_address = if upgrade_authority == "none" {
  774. Pubkey::default()
  775. } else {
  776. upgrade_authority.parse::<Pubkey>().unwrap_or_else(|_| {
  777. read_keypair_file(upgrade_authority)
  778. .map(|keypair| keypair.pubkey())
  779. .unwrap_or_else(|err| {
  780. eprintln!(
  781. "Error: invalid upgrade_authority {upgrade_authority}: {err}"
  782. );
  783. process::exit(1);
  784. })
  785. })
  786. };
  787. let (programdata_address, _) =
  788. Pubkey::find_program_address(&[address.as_ref()], &loader);
  789. let mut program_data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
  790. slot: 0,
  791. upgrade_authority_address: Some(upgrade_authority_address),
  792. })
  793. .unwrap();
  794. program_data.extend_from_slice(&program_data_elf);
  795. genesis_config.add_account(
  796. programdata_address,
  797. AccountSharedData::from(Account {
  798. lamports: genesis_config.rent.minimum_balance(program_data.len()),
  799. data: program_data,
  800. owner: loader,
  801. executable: false,
  802. rent_epoch: 0,
  803. }),
  804. );
  805. let program_data = bincode::serialize(&UpgradeableLoaderState::Program {
  806. programdata_address,
  807. })
  808. .unwrap();
  809. genesis_config.add_account(
  810. address,
  811. AccountSharedData::from(Account {
  812. lamports: genesis_config.rent.minimum_balance(program_data.len()),
  813. data: program_data,
  814. owner: loader,
  815. executable: true,
  816. rent_epoch: 0,
  817. }),
  818. );
  819. }
  820. }
  821. agave_logger::setup();
  822. create_new_ledger(
  823. &ledger_path,
  824. &genesis_config,
  825. max_genesis_archive_unpacked_size,
  826. LedgerColumnOptions::default(),
  827. )?;
  828. println!("{genesis_config}");
  829. Ok(())
  830. }
  831. #[cfg(test)]
  832. mod tests {
  833. use {
  834. super::*,
  835. solana_borsh::v1 as borsh1,
  836. solana_genesis_config::GenesisConfig,
  837. solana_stake_interface as stake,
  838. std::{collections::HashMap, fs::remove_file, io::Write, path::Path},
  839. };
  840. #[test]
  841. fn test_append_primordial_accounts_to_genesis() {
  842. // Test invalid file returns error
  843. assert!(load_genesis_accounts("unknownfile", &mut GenesisConfig::default()).is_err());
  844. let mut genesis_config = GenesisConfig::default();
  845. let mut genesis_accounts = HashMap::new();
  846. genesis_accounts.insert(
  847. solana_pubkey::new_rand().to_string(),
  848. Base64Account {
  849. owner: solana_pubkey::new_rand().to_string(),
  850. balance: 2,
  851. executable: false,
  852. data: String::from("aGVsbG8="),
  853. },
  854. );
  855. genesis_accounts.insert(
  856. solana_pubkey::new_rand().to_string(),
  857. Base64Account {
  858. owner: solana_pubkey::new_rand().to_string(),
  859. balance: 1,
  860. executable: true,
  861. data: String::from("aGVsbG8gd29ybGQ="),
  862. },
  863. );
  864. genesis_accounts.insert(
  865. solana_pubkey::new_rand().to_string(),
  866. Base64Account {
  867. owner: solana_pubkey::new_rand().to_string(),
  868. balance: 3,
  869. executable: true,
  870. data: String::from("bWUgaGVsbG8gdG8gd29ybGQ="),
  871. },
  872. );
  873. let serialized = serde_yaml::to_string(&genesis_accounts).unwrap();
  874. let path = Path::new("test_append_primordial_accounts_to_genesis.yml");
  875. let mut file = File::create(path).unwrap();
  876. file.write_all(b"---\n").unwrap();
  877. file.write_all(&serialized.into_bytes()).unwrap();
  878. load_genesis_accounts(
  879. "test_append_primordial_accounts_to_genesis.yml",
  880. &mut genesis_config,
  881. )
  882. .expect("test_append_primordial_accounts_to_genesis.yml");
  883. // Test valid file returns ok
  884. remove_file(path).unwrap();
  885. {
  886. // Test all accounts were added
  887. assert_eq!(genesis_config.accounts.len(), genesis_accounts.len());
  888. // Test account data matches
  889. for (pubkey_str, b64_account) in genesis_accounts.iter() {
  890. let pubkey = pubkey_str.parse().unwrap();
  891. assert_eq!(
  892. b64_account.owner,
  893. genesis_config.accounts[&pubkey].owner.to_string()
  894. );
  895. assert_eq!(
  896. b64_account.balance,
  897. genesis_config.accounts[&pubkey].lamports
  898. );
  899. assert_eq!(
  900. b64_account.executable,
  901. genesis_config.accounts[&pubkey].executable
  902. );
  903. assert_eq!(
  904. b64_account.data,
  905. BASE64_STANDARD.encode(&genesis_config.accounts[&pubkey].data)
  906. );
  907. }
  908. }
  909. // Test more accounts can be appended
  910. let mut genesis_accounts1 = HashMap::new();
  911. genesis_accounts1.insert(
  912. solana_pubkey::new_rand().to_string(),
  913. Base64Account {
  914. owner: solana_pubkey::new_rand().to_string(),
  915. balance: 6,
  916. executable: true,
  917. data: String::from("eW91IGFyZQ=="),
  918. },
  919. );
  920. genesis_accounts1.insert(
  921. solana_pubkey::new_rand().to_string(),
  922. Base64Account {
  923. owner: solana_pubkey::new_rand().to_string(),
  924. balance: 5,
  925. executable: false,
  926. data: String::from("bWV0YSBzdHJpbmc="),
  927. },
  928. );
  929. genesis_accounts1.insert(
  930. solana_pubkey::new_rand().to_string(),
  931. Base64Account {
  932. owner: solana_pubkey::new_rand().to_string(),
  933. balance: 10,
  934. executable: false,
  935. data: String::from("YmFzZTY0IHN0cmluZw=="),
  936. },
  937. );
  938. let serialized = serde_yaml::to_string(&genesis_accounts1).unwrap();
  939. let path = Path::new("test_append_primordial_accounts_to_genesis.yml");
  940. let mut file = File::create(path).unwrap();
  941. file.write_all(b"---\n").unwrap();
  942. file.write_all(&serialized.into_bytes()).unwrap();
  943. load_genesis_accounts(
  944. "test_append_primordial_accounts_to_genesis.yml",
  945. &mut genesis_config,
  946. )
  947. .expect("test_append_primordial_accounts_to_genesis.yml");
  948. remove_file(path).unwrap();
  949. // Test total number of accounts is correct
  950. assert_eq!(
  951. genesis_config.accounts.len(),
  952. genesis_accounts.len() + genesis_accounts1.len()
  953. );
  954. // Test old accounts are still there
  955. for (pubkey_str, b64_account) in genesis_accounts.iter() {
  956. let pubkey = &pubkey_str.parse().unwrap();
  957. assert_eq!(
  958. b64_account.balance,
  959. genesis_config.accounts[pubkey].lamports,
  960. );
  961. }
  962. // Test new account data matches
  963. for (pubkey_str, b64_account) in genesis_accounts1.iter() {
  964. let pubkey = pubkey_str.parse().unwrap();
  965. assert_eq!(
  966. b64_account.owner,
  967. genesis_config.accounts[&pubkey].owner.to_string()
  968. );
  969. assert_eq!(
  970. b64_account.balance,
  971. genesis_config.accounts[&pubkey].lamports,
  972. );
  973. assert_eq!(
  974. b64_account.executable,
  975. genesis_config.accounts[&pubkey].executable,
  976. );
  977. assert_eq!(
  978. b64_account.data,
  979. BASE64_STANDARD.encode(&genesis_config.accounts[&pubkey].data),
  980. );
  981. }
  982. // Test accounts from keypairs can be appended
  983. let account_keypairs: Vec<_> = (0..3).map(|_| Keypair::new()).collect();
  984. let mut genesis_accounts2 = HashMap::new();
  985. genesis_accounts2.insert(
  986. serde_json::to_string(&account_keypairs[0].to_bytes().to_vec()).unwrap(),
  987. Base64Account {
  988. owner: solana_pubkey::new_rand().to_string(),
  989. balance: 20,
  990. executable: true,
  991. data: String::from("Y2F0IGRvZw=="),
  992. },
  993. );
  994. genesis_accounts2.insert(
  995. serde_json::to_string(&account_keypairs[1].to_bytes().to_vec()).unwrap(),
  996. Base64Account {
  997. owner: solana_pubkey::new_rand().to_string(),
  998. balance: 15,
  999. executable: false,
  1000. data: String::from("bW9ua2V5IGVsZXBoYW50"),
  1001. },
  1002. );
  1003. genesis_accounts2.insert(
  1004. serde_json::to_string(&account_keypairs[2].to_bytes().to_vec()).unwrap(),
  1005. Base64Account {
  1006. owner: solana_pubkey::new_rand().to_string(),
  1007. balance: 30,
  1008. executable: true,
  1009. data: String::from("Y29tYSBtb2Nh"),
  1010. },
  1011. );
  1012. let serialized = serde_yaml::to_string(&genesis_accounts2).unwrap();
  1013. let path = Path::new("test_append_primordial_accounts_to_genesis.yml");
  1014. let mut file = File::create(path).unwrap();
  1015. file.write_all(b"---\n").unwrap();
  1016. file.write_all(&serialized.into_bytes()).unwrap();
  1017. load_genesis_accounts(
  1018. "test_append_primordial_accounts_to_genesis.yml",
  1019. &mut genesis_config,
  1020. )
  1021. .expect("genesis");
  1022. remove_file(path).unwrap();
  1023. // Test total number of accounts is correct
  1024. assert_eq!(
  1025. genesis_config.accounts.len(),
  1026. genesis_accounts.len() + genesis_accounts1.len() + genesis_accounts2.len()
  1027. );
  1028. // Test old accounts are still there
  1029. for (pubkey_str, b64_account) in genesis_accounts {
  1030. let pubkey = pubkey_str.parse().unwrap();
  1031. assert_eq!(
  1032. b64_account.balance,
  1033. genesis_config.accounts[&pubkey].lamports,
  1034. );
  1035. }
  1036. // Test new account data matches
  1037. for (pubkey_str, b64_account) in genesis_accounts1 {
  1038. let pubkey = pubkey_str.parse().unwrap();
  1039. assert_eq!(
  1040. b64_account.owner,
  1041. genesis_config.accounts[&pubkey].owner.to_string(),
  1042. );
  1043. assert_eq!(
  1044. b64_account.balance,
  1045. genesis_config.accounts[&pubkey].lamports,
  1046. );
  1047. assert_eq!(
  1048. b64_account.executable,
  1049. genesis_config.accounts[&pubkey].executable,
  1050. );
  1051. assert_eq!(
  1052. b64_account.data,
  1053. BASE64_STANDARD.encode(&genesis_config.accounts[&pubkey].data),
  1054. );
  1055. }
  1056. // Test account data for keypairs matches
  1057. account_keypairs.iter().for_each(|keypair| {
  1058. let keypair_str = serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap();
  1059. let pubkey = keypair.pubkey();
  1060. assert_eq!(
  1061. genesis_accounts2[&keypair_str].owner,
  1062. genesis_config.accounts[&pubkey].owner.to_string(),
  1063. );
  1064. assert_eq!(
  1065. genesis_accounts2[&keypair_str].balance,
  1066. genesis_config.accounts[&pubkey].lamports,
  1067. );
  1068. assert_eq!(
  1069. genesis_accounts2[&keypair_str].executable,
  1070. genesis_config.accounts[&pubkey].executable,
  1071. );
  1072. assert_eq!(
  1073. genesis_accounts2[&keypair_str].data,
  1074. BASE64_STANDARD.encode(&genesis_config.accounts[&pubkey].data),
  1075. );
  1076. });
  1077. }
  1078. #[test]
  1079. fn test_genesis_account_struct_compatibility() {
  1080. #[rustfmt::skip]
  1081. let yaml_string_pubkey =
  1082. "---\
  1083. \n98frSc8R8toHoS3tQ1xWSvHCvGEADRM9hAm5qmUKjSDX:\
  1084. \n balance: 4\
  1085. \n owner: Gw6S9CPzR8jHku1QQMdiqcmUKjC2dhJ3gzagWduA6PGw\
  1086. \n data:\
  1087. \n executable: true\
  1088. \n88frSc8R8toHoS3tQ1xWSvHCvGEADRM9hAm5qmUKjSDX:\
  1089. \n balance: 3\
  1090. \n owner: Gw7S9CPzR8jHku1QQMdiqcmUKjC2dhJ3gzagWduA6PGw\
  1091. \n data: ~\
  1092. \n executable: true\
  1093. \n6s36rsNPDfRSvzwek7Ly3mQu9jUMwgqBhjePZMV6Acp4:\
  1094. \n balance: 2\
  1095. \n owner: DBC5d45LUHTCrq42ZmCdzc8A8ufwTaiYsL9pZY7KU6TR\
  1096. \n data: aGVsbG8=\
  1097. \n executable: false\
  1098. \n8Y98svZv5sPHhQiPqZvqA5Z5djQ8hieodscvb61RskMJ:\
  1099. \n balance: 1\
  1100. \n owner: DSknYr8cPucRbx2VyssZ7Yx3iiRqNGD38VqVahkUvgV1\
  1101. \n data: aGVsbG8gd29ybGQ=\
  1102. \n executable: true";
  1103. let tmpfile = tempfile::NamedTempFile::new().unwrap();
  1104. let path = tmpfile.path();
  1105. let mut file = File::create(path).unwrap();
  1106. file.write_all(yaml_string_pubkey.as_bytes()).unwrap();
  1107. let mut genesis_config = GenesisConfig::default();
  1108. load_genesis_accounts(path.to_str().unwrap(), &mut genesis_config).expect("genesis");
  1109. remove_file(path).unwrap();
  1110. assert_eq!(genesis_config.accounts.len(), 4);
  1111. #[rustfmt::skip]
  1112. let yaml_string_keypair =
  1113. "---\
  1114. \n\"[17,12,234,59,35,246,168,6,64,36,169,164,219,96,253,79,238,202,164,160,195,89,9,\
  1115. 96,179,117,255,239,32,64,124,66,233,130,19,107,172,54,86,32,119,148,4,39,199,40,122,\
  1116. 230,249,47,150,168,163,159,83,233,97,18,25,238,103,25,253,108]\":\
  1117. \n balance: 20\
  1118. \n owner: 9ZfsP6Um1KU8d5gNzTsEbSJxanKYp5EPF36qUu4FJqgp\
  1119. \n data: Y2F0IGRvZw==\
  1120. \n executable: true\
  1121. \n\"[36,246,244,43,37,214,110,50,134,148,148,8,205,82,233,67,223,245,122,5,149,232,\
  1122. 213,125,244,182,26,29,56,224,70,45,42,163,71,62,222,33,229,54,73,136,53,174,128,103,\
  1123. 247,235,222,27,219,129,180,77,225,174,220,74,201,123,97,155,159,234]\":\
  1124. \n balance: 15\
  1125. \n owner: F9dmtjJPi8vfLu1EJN4KkyoGdXGmVfSAhxz35Qo9RDCJ\
  1126. \n data: bW9ua2V5IGVsZXBoYW50\
  1127. \n executable: false\
  1128. \n\"[103,27,132,107,42,149,72,113,24,138,225,109,209,31,158,6,26,11,8,76,24,128,131,\
  1129. 215,156,80,251,114,103,220,111,235,56,22,87,5,209,56,53,12,224,170,10,66,82,42,11,138,\
  1130. 51,76,120,27,166,200,237,16,200,31,23,5,57,22,131,221]\":\
  1131. \n balance: 30
  1132. \n owner: AwAR5mAbNPbvQ4CvMeBxwWE8caigQoMC2chkWAbh2b9V
  1133. \n data: Y29tYSBtb2Nh
  1134. \n executable: true";
  1135. let tmpfile = tempfile::NamedTempFile::new().unwrap();
  1136. let path = tmpfile.path();
  1137. let mut file = File::create(path).unwrap();
  1138. file.write_all(yaml_string_keypair.as_bytes()).unwrap();
  1139. let mut genesis_config = GenesisConfig::default();
  1140. load_genesis_accounts(path.to_str().unwrap(), &mut genesis_config).expect("genesis");
  1141. remove_file(path).unwrap();
  1142. assert_eq!(genesis_config.accounts.len(), 3);
  1143. }
  1144. #[test]
  1145. fn test_append_validator_accounts_to_genesis() {
  1146. // Test invalid file returns error
  1147. assert!(load_validator_accounts(
  1148. "unknownfile",
  1149. 100,
  1150. &Rent::default(),
  1151. &mut GenesisConfig::default()
  1152. )
  1153. .is_err());
  1154. let mut genesis_config = GenesisConfig::default();
  1155. let validator_accounts = vec![
  1156. StakedValidatorAccountInfo {
  1157. identity_account: solana_pubkey::new_rand().to_string(),
  1158. vote_account: solana_pubkey::new_rand().to_string(),
  1159. stake_account: solana_pubkey::new_rand().to_string(),
  1160. balance_lamports: 100000000000,
  1161. stake_lamports: 10000000000,
  1162. },
  1163. StakedValidatorAccountInfo {
  1164. identity_account: solana_pubkey::new_rand().to_string(),
  1165. vote_account: solana_pubkey::new_rand().to_string(),
  1166. stake_account: solana_pubkey::new_rand().to_string(),
  1167. balance_lamports: 200000000000,
  1168. stake_lamports: 20000000000,
  1169. },
  1170. StakedValidatorAccountInfo {
  1171. identity_account: solana_pubkey::new_rand().to_string(),
  1172. vote_account: solana_pubkey::new_rand().to_string(),
  1173. stake_account: solana_pubkey::new_rand().to_string(),
  1174. balance_lamports: 300000000000,
  1175. stake_lamports: 30000000000,
  1176. },
  1177. ];
  1178. let serialized = serde_yaml::to_string(&validator_accounts).unwrap();
  1179. // write accounts to file
  1180. let path = Path::new("test_append_validator_accounts_to_genesis.yml");
  1181. let mut file = File::create(path).unwrap();
  1182. file.write_all(b"validator_accounts:\n").unwrap();
  1183. file.write_all(serialized.as_bytes()).unwrap();
  1184. load_validator_accounts(
  1185. "test_append_validator_accounts_to_genesis.yml",
  1186. 100,
  1187. &Rent::default(),
  1188. &mut genesis_config,
  1189. )
  1190. .expect("Failed to load validator accounts");
  1191. remove_file(path).unwrap();
  1192. let accounts_per_validator = 3;
  1193. let expected_accounts_len = validator_accounts.len() * accounts_per_validator;
  1194. {
  1195. assert_eq!(genesis_config.accounts.len(), expected_accounts_len);
  1196. // test account data matches
  1197. for b64_account in validator_accounts.iter() {
  1198. // check identity
  1199. let identity_pk = b64_account.identity_account.parse().unwrap();
  1200. assert_eq!(
  1201. system_program::id(),
  1202. genesis_config.accounts[&identity_pk].owner
  1203. );
  1204. assert_eq!(
  1205. b64_account.balance_lamports,
  1206. genesis_config.accounts[&identity_pk].lamports
  1207. );
  1208. // check vote account
  1209. let vote_pk = b64_account.vote_account.parse().unwrap();
  1210. let vote_data = genesis_config.accounts[&vote_pk].data.clone();
  1211. let vote_state = VoteStateV4::deserialize(&vote_data, &vote_pk).unwrap();
  1212. assert_eq!(vote_state.node_pubkey, identity_pk);
  1213. assert_eq!(vote_state.authorized_withdrawer, identity_pk);
  1214. let authorized_voters = &vote_state.authorized_voters;
  1215. assert_eq!(authorized_voters.first().unwrap().1, &identity_pk);
  1216. // check stake account
  1217. let stake_pk = b64_account.stake_account.parse().unwrap();
  1218. assert_eq!(
  1219. b64_account.stake_lamports,
  1220. genesis_config.accounts[&stake_pk].lamports
  1221. );
  1222. let stake_data = genesis_config.accounts[&stake_pk].data.clone();
  1223. let stake_state =
  1224. borsh1::try_from_slice_unchecked::<StakeStateV2>(&stake_data).unwrap();
  1225. assert!(
  1226. matches!(stake_state, StakeStateV2::Stake(_, _, _)),
  1227. "Expected StakeStateV2::Stake variant"
  1228. );
  1229. if let StakeStateV2::Stake(meta, stake, stake_flags) = stake_state {
  1230. assert_eq!(meta.authorized.staker, identity_pk);
  1231. assert_eq!(meta.authorized.withdrawer, identity_pk);
  1232. assert_eq!(stake.delegation.voter_pubkey, vote_pk);
  1233. let stake_account = AccountSharedData::new(
  1234. b64_account.stake_lamports,
  1235. StakeStateV2::size_of(),
  1236. &solana_stake_program::id(),
  1237. );
  1238. let rent_exempt_reserve =
  1239. &Rent::default().minimum_balance(stake_account.data().len());
  1240. assert_eq!(
  1241. stake.delegation.stake,
  1242. b64_account.stake_lamports - rent_exempt_reserve
  1243. );
  1244. assert_eq!(stake_flags, stake::stake_flags::StakeFlags::empty());
  1245. }
  1246. }
  1247. }
  1248. }
  1249. }