Skip to content
This repository was archived by the owner on Feb 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
- [x] Setup build config Local
- [x] Build from local commit
- [x] Build from tar (release version)
- [ ] Create Genesis
- [x] Create Genesis
- [x] Generate faucet and bootstrap accounts
- [ ] Build genesis
- [x] Build genesis
- [ ] Docker Build
- [ ] Build Bootstrap Image
- [ ] Push Image to registry
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,20 @@ cargo run --bin cluster --
-n <namespace>
--release-channel <agave-version: e.g. v1.17.28> # note: MUST include the "v"
```

#### Build from Local Repo and Configure Genesis
Example:
```
cargo run --bin cluster --
-n <namespace>
--local-path /home/sol/solana
# genesis config. Optional: Many of these have defaults
--hashes-per-tick <hashes-per-tick>
--enable-warmup-epochs <true|false>
--faucet-lamports <faucet-lamports>
--bootstrap-validator-sol <validator-sol>
--bootstrap-validator-stake-sol <validator-stake>
--max-genesis-archive-unpacked-size <size in bytes>
--target-lamports-per-signature <lamports-per-signature>
--slots-per-epoch <slots-per-epoch>
```
62 changes: 62 additions & 0 deletions fetch-spl.sh
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will very likely forget to update this 😞 and it's currently out of date. Is there a way to move this functionality into solana-genesis or link to it from the solana repo?

Or maybe better, do the logic in some file here rather than relying on a bash script? You're pretty much just fetching files and outputting strings. That might be more portable.

Note: this isn't urgent

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh ok ya i can move this over to its own rust function or something. will add that.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
#
# Fetches the latest SPL programs and produces the solana-genesis command-line
# arguments needed to install them
#

set -e

upgradeableLoader=BPFLoaderUpgradeab1e11111111111111111111111

fetch_program() {
declare name=$1
declare version=$2
declare address=$3
declare loader=$4

declare so=spl_$name-$version.so

if [[ $loader == "$upgradeableLoader" ]]; then
genesis_args+=(--upgradeable-program "$address" "$loader" "$so" none)
else
genesis_args+=(--bpf-program "$address" "$loader" "$so")
fi

if [[ -r $so ]]; then
return
fi

if [[ -r ~/.cache/solana-spl/$so ]]; then
cp ~/.cache/solana-spl/"$so" "$so"
else
echo "Downloading $name $version"
so_name="spl_${name//-/_}.so"
(
set -x
curl -L --retry 5 --retry-delay 2 --retry-connrefused \
-o "$so" \
"https://github.com/solana-labs/solana-program-library/releases/download/$name-v$version/$so_name"
)

mkdir -p ~/.cache/solana-spl
cp "$so" ~/.cache/solana-spl/"$so"
fi

}

fetch_program token 3.5.0 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA BPFLoader2111111111111111111111111111111111
fetch_program token-2022 0.9.0 TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb BPFLoaderUpgradeab1e11111111111111111111111
fetch_program memo 1.0.0 Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo BPFLoader1111111111111111111111111111111111
fetch_program memo 3.0.0 MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr BPFLoader2111111111111111111111111111111111
fetch_program associated-token-account 1.1.2 ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL BPFLoader2111111111111111111111111111111111
fetch_program feature-proposal 1.0.0 Feat1YXHhH6t1juaWF74WLcfv4XoNocjXA6sPWHNgAse BPFLoader2111111111111111111111111111111111

echo "${genesis_args[@]}" > spl-genesis-args.sh

echo
echo "Available SPL programs:"
ls -l spl_*.so

echo
echo "solana-genesis command-line arguments (spl-genesis-args.sh):"
cat spl-genesis-args.sh
222 changes: 219 additions & 3 deletions src/genesis.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,123 @@
use {
crate::ValidatorType,
crate::{new_spinner_progress_bar, ValidatorType, SUN},
log::*,
rand::Rng,
solana_core::gen_keys::GenKeys,
solana_sdk::signature::{write_keypair_file, Keypair},
solana_sdk::{
native_token::sol_to_lamports,
signature::{write_keypair_file, Keypair},
},
std::{
error::Error,
fs::File,
io::Read,
path::{Path, PathBuf},
process::Command,
result::Result,
},
};

pub const DEFAULT_FAUCET_LAMPORTS: u64 = 500000000000000000; // from agave/
pub const DEFAULT_MAX_GENESIS_ARCHIVE_UNPACKED_SIZE: u64 = 1073741824; // from agave/
pub const DEFAULT_INTERNAL_NODE_STAKE_SOL: f64 = 10.0;
pub const DEFAULT_INTERNAL_NODE_SOL: f64 = 100.0;
pub const DEFAULT_BOOTSTRAP_NODE_STAKE_SOL: f64 = 10.0;
pub const DEFAULT_BOOTSTRAP_NODE_SOL: f64 = 100.0;

fn fetch_spl(fetch_spl_file: &PathBuf) -> Result<(), Box<dyn Error>> {
let output = Command::new("bash")
.arg(fetch_spl_file)
.output() // Capture the output of the script
.expect("Failed to run fetch-spl.sh script");

// Check if the script execution was successful
if output.status.success() {
Ok(())
} else {
Err(format!(
"Failed to fun fetch-spl.sh script: {}",
String::from_utf8_lossy(&output.stderr)
)
.into())
}
}

fn parse_spl_genesis_file(spl_file: &PathBuf) -> Result<Vec<String>, Box<dyn Error>> {
// Read entire file into a String
let mut file = File::open(spl_file)?;
let mut content = String::new();
file.read_to_string(&mut content)?;

// Split by whitespace
let mut args = Vec::new();
let mut tokens_iter = content.split_whitespace();

while let Some(token) = tokens_iter.next() {
args.push(token.to_string());
// Find flag delimiters
if token.starts_with("--") {
for next_token in tokens_iter.by_ref() {
if next_token.starts_with("--") {
args.push(next_token.to_string());
} else {
args.push(next_token.to_string());
break;
}
}
}
}
Comment on lines +55 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if I'm being dense, but why is this logic needed? Shouldn't you be able to just take the Vec<String> after the whitespace has been removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right. i had a different (ultimately wrong) understanding of how solana-genesis took in these commands. And when I figured out it was wrong, I didn't change this function. But even now realize this function just doesn't really do anything that isn't done by just taking the Vec.


Ok(args)
}

pub struct GenesisFlags {
pub hashes_per_tick: String,
pub slots_per_epoch: Option<u64>,
pub target_lamports_per_signature: Option<u64>,
pub faucet_lamports: Option<u64>,
pub enable_warmup_epochs: bool,
pub max_genesis_archive_unpacked_size: Option<u64>,
pub cluster_type: String,
pub bootstrap_validator_sol: Option<f64>,
pub bootstrap_validator_stake_sol: Option<f64>,
}

impl std::fmt::Display for GenesisFlags {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"GenesisFlags {{\n\
hashes_per_tick: {:?},\n\
slots_per_epoch: {:?},\n\
target_lamports_per_signature: {:?},\n\
faucet_lamports: {:?},\n\
enable_warmup_epochs: {},\n\
max_genesis_archive_unpacked_size: {:?},\n\
cluster_type: {}\n\
bootstrap_validator_sol: {:?},\n\
bootstrap_validator_stake_sol: {:?},\n\
}}",
self.hashes_per_tick,
self.slots_per_epoch,
self.target_lamports_per_signature,
self.faucet_lamports,
self.enable_warmup_epochs,
self.max_genesis_archive_unpacked_size,
self.cluster_type,
self.bootstrap_validator_sol,
self.bootstrap_validator_stake_sol,
)
}
}

pub struct Genesis {
config_dir: PathBuf,
key_generator: GenKeys,
pub flags: GenesisFlags,
}

impl Genesis {
pub fn new(solana_root: &Path) -> Self {
pub fn new(solana_root: &Path, flags: GenesisFlags) -> Self {
let config_dir = solana_root.join("config-k8s");
if config_dir.exists() {
std::fs::remove_dir_all(&config_dir).unwrap();
Expand All @@ -29,6 +129,7 @@ impl Genesis {
Self {
config_dir,
key_generator: GenKeys::new(seed),
flags,
}
}

Expand Down Expand Up @@ -96,4 +197,119 @@ impl Genesis {
}
Ok(())
}

fn setup_genesis_flags(&self) -> Vec<String> {
let mut args = vec![
"--bootstrap-validator-lamports".to_string(),
sol_to_lamports(
self.flags
.bootstrap_validator_sol
.unwrap_or(DEFAULT_BOOTSTRAP_NODE_SOL),
)
.to_string(),
"--bootstrap-validator-stake-lamports".to_string(),
sol_to_lamports(
self.flags
.bootstrap_validator_stake_sol
.unwrap_or(DEFAULT_BOOTSTRAP_NODE_STAKE_SOL),
)
.to_string(),
"--hashes-per-tick".to_string(),
self.flags.hashes_per_tick.clone(),
"--max-genesis-archive-unpacked-size".to_string(),
self.flags
.max_genesis_archive_unpacked_size
.unwrap_or(DEFAULT_MAX_GENESIS_ARCHIVE_UNPACKED_SIZE)
.to_string(),
"--faucet-lamports".to_string(),
self.flags
.faucet_lamports
.unwrap_or(DEFAULT_FAUCET_LAMPORTS)
.to_string(),
"--faucet-pubkey".to_string(),
self.config_dir
.join("faucet.json")
.to_string_lossy()
Comment thread
joncinque marked this conversation as resolved.
.to_string(),
"--cluster-type".to_string(),
self.flags.cluster_type.to_string(),
"--ledger".to_string(),
self.config_dir
.join("bootstrap-validator")
.to_string_lossy()
.to_string(),
];

if self.flags.enable_warmup_epochs {
args.push("--enable-warmup-epochs".to_string());
}

args.push("--bootstrap-validator".to_string());
["identity", "vote-account", "stake-account"]
.iter()
.for_each(|account_type| {
args.push(
self.config_dir
.join(format!("bootstrap-validator/{}.json", account_type))
.to_string_lossy()
.to_string(),
);
});

if let Some(slots_per_epoch) = self.flags.slots_per_epoch {
args.push("--slots-per-epoch".to_string());
args.push(slots_per_epoch.to_string());
}

if let Some(lamports_per_signature) = self.flags.target_lamports_per_signature {
args.push("--target-lamports-per-signature".to_string());
args.push(lamports_per_signature.to_string());
}

args
}

pub fn setup_spl_args(
&self,
solana_root_path: &PathBuf,
) -> Result<Vec<String>, Box<dyn Error>> {
let fetch_spl_file = solana_root_path.join("fetch-spl.sh");
fetch_spl(&fetch_spl_file)?;

// add in spl stuff
let spl_file = solana_root_path.join("spl-genesis-args.sh");
parse_spl_genesis_file(&spl_file)
}

pub fn generate(
&mut self,
solana_root_path: &PathBuf,
build_path: &PathBuf,
) -> Result<(), Box<dyn Error>> {
let mut args = self.setup_genesis_flags();
let mut spl_args = self.setup_spl_args(solana_root_path)?;
args.append(&mut spl_args);

let progress_bar = new_spinner_progress_bar();
progress_bar.set_message(format!("{SUN}Building Genesis..."));

let executable_path = build_path.join("solana-genesis");
let output = Command::new(executable_path)
.args(&args)
.output()
.expect("Failed to execute solana-genesis");

progress_bar.finish_and_clear();

if !output.status.success() {
return Err(format!(
"Failed to create genesis. err: {}",
String::from_utf8_lossy(&output.stderr)
)
.into());
}
info!("Genesis build complete");

Ok(())
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub mod genesis;
pub mod kubernetes;
pub mod release;

static SUN: Emoji = Emoji("🌞 ", "");
static PACKAGE: Emoji = Emoji("📦 ", "");
static TRUCK: Emoji = Emoji("🚚 ", "");

Expand Down
Loading