Add ability to fork a local cluster from the latest mainnet-beta snapshot

This commit is contained in:
Michael Vines 2020-08-25 22:27:31 -07:00
parent 450c667aa7
commit df7710a131
2 changed files with 370 additions and 8 deletions

View File

@ -5,7 +5,10 @@ use clap::{
use log::*;
use regex::Regex;
use serde_json::json;
use solana_clap_utils::input_validators::{is_parsable, is_slot};
use solana_clap_utils::{
input_parsers::{pubkey_of, pubkeys_of},
input_validators::{is_parsable, is_pubkey_or_keypair, is_slot, is_valid_percentage},
};
use solana_ledger::entry::Entry;
use solana_ledger::{
ancestor_iterator::AncestorIterator,
@ -23,15 +26,22 @@ use solana_runtime::{
snapshot_utils::SnapshotVersion,
};
use solana_sdk::{
account::Account,
clock::{Epoch, Slot},
genesis_config::GenesisConfig,
genesis_config::{GenesisConfig, OperatingMode},
hash::Hash,
inflation::Inflation,
native_token::{lamports_to_sol, Sol},
native_token::{lamports_to_sol, sol_to_lamports, Sol},
pubkey::Pubkey,
rent::Rent,
shred_version::compute_shred_version,
system_program,
};
use solana_stake_program::stake_state::{self, StakeState};
use solana_vote_program::{
self,
vote_state::{self, VoteState},
};
use solana_vote_program::vote_state::VoteState;
use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
convert::{TryFrom, TryInto},
@ -764,6 +774,15 @@ fn main() {
.takes_value(true)
.default_value(&default_genesis_archive_unpacked_size)
.help("maximum total uncompressed size of unpacked genesis archive");
let hashes_per_tick = Arg::with_name("hashes_per_tick")
.long("hashes-per-tick")
.value_name("NUM_HASHES|\"sleep\"")
.takes_value(true)
.help(
"How many PoH hashes to roll before emitting the next tick. \
If \"sleep\", for development \
sleep for the target tick duration instead of hashing",
);
let snapshot_version_arg = Arg::with_name("snapshot_version")
.long("snapshot-version")
.value_name("SNAPSHOT_VERSION")
@ -771,6 +790,15 @@ fn main() {
.takes_value(true)
.default_value(SnapshotVersion::default().into())
.help("Output snapshot version");
let rent = Rent::default();
let default_bootstrap_validator_lamports = &sol_to_lamports(500.0)
.max(VoteState::get_rent_exempt_reserve(&rent))
.to_string();
let default_bootstrap_validator_stake_lamports = &sol_to_lamports(0.5)
.max(StakeState::get_rent_exempt_reserve(&rent))
.to_string();
let matches = App::new(crate_name!())
.about(crate_description!())
.version(solana_version::version!())
@ -913,6 +941,23 @@ fn main() {
.about("Prints the ledger's genesis hash")
.arg(&max_genesis_archive_unpacked_size_arg)
)
.subcommand(
SubCommand::with_name("modify-genesis")
.about("Modifies genesis parameters")
.arg(&max_genesis_archive_unpacked_size_arg)
.arg(&hashes_per_tick)
.arg(
Arg::with_name("operating_mode")
.long("operating-mode")
.possible_value("development")
.possible_value("preview")
.possible_value("stable")
.takes_value(true)
.help(
"Selects the features that will be enabled for the cluster"
),
)
)
.subcommand(
SubCommand::with_name("shred-version")
.about("Prints the ledger's shred hash")
@ -1008,6 +1053,72 @@ fn main() {
.help("After loading the snapshot slot warp the ledger to WARP_SLOT, \
which could be a slot in a galaxy far far away"),
)
.arg(
Arg::with_name("faucet_lamports")
.short("t")
.long("faucet-lamports")
.value_name("LAMPORTS")
.takes_value(true)
.requires("faucet_pubkey")
.help("Number of lamports to assign to the faucet"),
)
.arg(
Arg::with_name("faucet_pubkey")
.short("m")
.long("faucet-pubkey")
.value_name("PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.requires("faucet_lamports")
.help("Path to file containing the faucet's pubkey"),
)
.arg(
Arg::with_name("bootstrap_validator")
.short("b")
.long("bootstrap-validator")
.value_name("IDENTITY_PUBKEY VOTE_PUBKEY STAKE_PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.number_of_values(3)
.multiple(true)
.help("The bootstrap validator's identity, vote and stake pubkeys"),
)
.arg(
Arg::with_name("bootstrap_stake_authorized_pubkey")
.long("bootstrap-stake-authorized-pubkey")
.value_name("BOOTSTRAP STAKE AUTHORIZED PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help(
"Path to file containing the pubkey authorized to manage the bootstrap \
validator's stake [default: --bootstrap-validator IDENTITY_PUBKEY]",
),
)
.arg(
Arg::with_name("bootstrap_validator_lamports")
.long("bootstrap-validator-lamports")
.value_name("LAMPORTS")
.takes_value(true)
.default_value(default_bootstrap_validator_lamports)
.help("Number of lamports to assign to the bootstrap validator"),
)
.arg(
Arg::with_name("bootstrap_validator_stake_lamports")
.long("bootstrap-validator-stake-lamports")
.value_name("LAMPORTS")
.takes_value(true)
.default_value(default_bootstrap_validator_stake_lamports)
.help("Number of lamports to assign to the bootstrap validator's stake account"),
)
.arg(
Arg::with_name("rent_burn_percentage")
.long("rent-burn-percentage")
.value_name("NUMBER")
.takes_value(true)
.help("Adjust percentage of collected rent to burn")
.validator(is_valid_percentage),
)
.arg(&hashes_per_tick)
.arg(
Arg::with_name("rehash")
.required(false)
@ -1015,6 +1126,13 @@ fn main() {
.takes_value(false)
.help("Re-calculate the bank hash and overwrite the original bank hash."),
)
.arg(
Arg::with_name("remove_stake_accounts")
.required(false)
.long("remove-stake-accounts")
.takes_value(false)
.help("Remove all existing stake accounts from the new snapshot.")
)
).subcommand(
SubCommand::with_name("accounts")
.about("Print account contents after processing in the ledger")
@ -1199,6 +1317,31 @@ fn main() {
open_genesis_config_by(&ledger_path, arg_matches).hash()
);
}
("modify-genesis", Some(arg_matches)) => {
let mut genesis_config = open_genesis_config_by(&ledger_path, arg_matches);
if let Some(operating_mode) = arg_matches.value_of("operating_mode") {
genesis_config.operating_mode = match operating_mode {
"development" => OperatingMode::Development,
"stable" => OperatingMode::Stable,
"preview" => OperatingMode::Preview,
_ => unreachable!(),
};
}
if let Some(hashes_per_tick) = arg_matches.value_of("hashes_per_tick") {
genesis_config.poh_config.hashes_per_tick = match hashes_per_tick {
// Note: Unlike `solana-genesis`, "auto" is not supported here.
"sleep" => None,
_ => Some(value_t_or_exit!(arg_matches, "hashes_per_tick", u64)),
}
}
genesis_config.write(&ledger_path).unwrap_or_else(|err| {
eprintln!("Failed to write genesis config: {:?}", err);
exit(1);
});
println!("{}", open_genesis_config_by(&ledger_path, arg_matches));
}
("shred-version", Some(arg_matches)) => {
let process_options = ProcessOptions {
dev_halt_at_slot: Some(0),
@ -1426,8 +1569,30 @@ fn main() {
("create-snapshot", Some(arg_matches)) => {
let snapshot_slot = value_t_or_exit!(arg_matches, "snapshot_slot", Slot);
let output_directory = value_t_or_exit!(arg_matches, "output_directory", String);
let warp_slot = value_t!(arg_matches, "warp_slot", Slot).ok();
let rehash = arg_matches.is_present("rehash");
let mut warp_slot = value_t!(arg_matches, "warp_slot", Slot).ok();
let mut rehash = arg_matches.is_present("rehash");
let remove_stake_accounts = arg_matches.is_present("remove_stake_accounts");
let new_hard_forks = hardforks_of(arg_matches, "hard_forks");
let faucet_pubkey = pubkey_of(&arg_matches, "faucet_pubkey");
let faucet_lamports = value_t!(arg_matches, "faucet_lamports", u64).unwrap_or(0);
let rent_burn_percentage = value_t!(arg_matches, "rent_burn_percentage", u8);
let hashes_per_tick = arg_matches.value_of("hashes_per_tick");
let bootstrap_stake_authorized_pubkey =
pubkey_of(&arg_matches, "bootstrap_stake_authorized_pubkey");
let bootstrap_validator_lamports =
value_t_or_exit!(arg_matches, "bootstrap_validator_lamports", u64);
let bootstrap_validator_stake_lamports =
value_t_or_exit!(arg_matches, "bootstrap_validator_stake_lamports", u64);
let minimum_stake_lamports = StakeState::get_rent_exempt_reserve(&rent);
if bootstrap_validator_stake_lamports < minimum_stake_lamports {
eprintln!("Error: insufficient --bootstrap-validator-stake-lamports. Minimum amount is {}", minimum_stake_lamports);
exit(1);
}
let bootstrap_validator_pubkeys = pubkeys_of(&arg_matches, "bootstrap_validator");
let snapshot_version =
arg_matches
.value_of("snapshot_version")
@ -1439,7 +1604,7 @@ fn main() {
});
let process_options = ProcessOptions {
dev_halt_at_slot: Some(snapshot_slot),
new_hard_forks: hardforks_of(arg_matches, "hard_forks"),
new_hard_forks,
poh_verify: false,
..ProcessOptions::default()
};
@ -1454,7 +1619,7 @@ fn main() {
snapshot_archive_path,
) {
Ok((bank_forks, _leader_schedule_cache, _snapshot_hash)) => {
let bank = bank_forks
let mut bank = bank_forks
.get(snapshot_slot)
.unwrap_or_else(|| {
eprintln!("Error: Slot {} is not available", snapshot_slot);
@ -1462,6 +1627,136 @@ fn main() {
})
.clone();
if rent_burn_percentage.is_ok() || hashes_per_tick.is_some() {
let mut child_bank =
Bank::new_from_parent(&bank, bank.collector_id(), bank.slot() + 1);
if let Ok(rent_burn_percentage) = rent_burn_percentage {
child_bank.set_rent_burn_percentage(rent_burn_percentage);
}
if let Some(hashes_per_tick) = hashes_per_tick {
child_bank.set_hashes_per_tick(match hashes_per_tick {
// Note: Unlike `solana-genesis`, "auto" is not supported here.
"sleep" => None,
_ => Some(value_t_or_exit!(arg_matches, "hashes_per_tick", u64)),
});
}
bank = Arc::new(child_bank);
}
if let Some(faucet_pubkey) = faucet_pubkey {
rehash = true;
bank.store_account(
&faucet_pubkey,
&Account::new(faucet_lamports, 0, &system_program::id()),
);
}
if remove_stake_accounts {
for (address, mut account) in bank
.get_program_accounts(Some(&solana_stake_program::id()))
.into_iter()
{
account.lamports = 0;
bank.store_account(&address, &account);
}
}
if let Some(bootstrap_validator_pubkeys) = bootstrap_validator_pubkeys {
rehash = true;
assert_eq!(bootstrap_validator_pubkeys.len() % 3, 0);
// Ensure there are no duplicated pubkeys in the --bootstrap-validator list
{
let mut v = bootstrap_validator_pubkeys.clone();
v.sort();
v.dedup();
if v.len() != bootstrap_validator_pubkeys.len() {
eprintln!(
"Error: --bootstrap-validator pubkeys cannot be duplicated"
);
exit(1);
}
}
// Delete existing vote accounts
for (address, mut account) in bank
.get_program_accounts(Some(&solana_vote_program::id()))
.into_iter()
{
account.lamports = 0;
bank.store_account(&address, &account);
}
// Add a new identity/vote/stake account for each of the provided bootstrap
// validators
let mut bootstrap_validator_pubkeys_iter =
bootstrap_validator_pubkeys.iter();
loop {
let identity_pubkey = match bootstrap_validator_pubkeys_iter.next() {
None => break,
Some(identity_pubkey) => identity_pubkey,
};
let vote_pubkey = bootstrap_validator_pubkeys_iter.next().unwrap();
let stake_pubkey = bootstrap_validator_pubkeys_iter.next().unwrap();
bank.store_account(
identity_pubkey,
&Account::new(
bootstrap_validator_lamports,
0,
&system_program::id(),
),
);
let vote_account = vote_state::create_account_with_authorized(
&identity_pubkey,
&identity_pubkey,
&identity_pubkey,
100,
VoteState::get_rent_exempt_reserve(&rent).max(1),
);
bank.store_account(
stake_pubkey,
&stake_state::create_account(
bootstrap_stake_authorized_pubkey
.as_ref()
.unwrap_or(&identity_pubkey),
&vote_pubkey,
&vote_account,
&rent,
bootstrap_validator_stake_lamports,
),
);
bank.store_account(vote_pubkey, &vote_account);
}
// Warp ahead at least two epochs to ensure that the leader schedule will be
// updated to reflect the new bootstrap validator(s)
let minimum_warp_slot =
genesis_config.epoch_schedule.get_first_slot_in_epoch(
genesis_config.epoch_schedule.get_epoch(snapshot_slot) + 2,
);
if let Some(warp_slot) = warp_slot {
if warp_slot < minimum_warp_slot {
eprintln!(
"Error: --warp-slot too close. Must be >= {}",
minimum_warp_slot
);
exit(1);
}
} else {
warn!("Warping to slot {}", minimum_warp_slot);
warp_slot = Some(minimum_warp_slot);
}
}
bank.set_capitalization();
let bank = if let Some(warp_slot) = warp_slot {
Arc::new(Bank::warp_from_parent(
&bank,

View File

@ -0,0 +1,67 @@
#!/usr/bin/env bash
here=$(dirname "$0")
# shellcheck source=multinode-demo/common.sh
source "$here"/common.sh
rm -rf "$SOLANA_CONFIG_DIR"/latest-mainnet-beta-snapshot
mkdir -p "$SOLANA_CONFIG_DIR"/latest-mainnet-beta-snapshot
(
cd "$SOLANA_CONFIG_DIR"/latest-mainnet-beta-snapshot || exit 1
set -x
wget http://api.mainnet-beta.solana.com/genesis.tar.bz2
wget --trust-server-names http://api.mainnet-beta.solana.com/snapshot.tar.bz2
)
snapshot=$(ls "$SOLANA_CONFIG_DIR"/latest-mainnet-beta-snapshot/snapshot-[0-9]*-*.tar.bz2)
if [[ -z $snapshot ]]; then
echo Error: Unable to find latest snapshot
exit 1
fi
if [[ ! $snapshot =~ snapshot-([0-9]*)-.*.tar.bz2 ]]; then
echo Error: Unable to determine snapshot slot for "$snapshot"
exit 1
fi
snapshot_slot="${BASH_REMATCH[1]}"
rm -rf "$SOLANA_CONFIG_DIR"/bootstrap-validator
mkdir -p "$SOLANA_CONFIG_DIR"/bootstrap-validator
# Create genesis ledger
if [[ -r $FAUCET_KEYPAIR ]]; then
cp -f "$FAUCET_KEYPAIR" "$SOLANA_CONFIG_DIR"/faucet.json
else
$solana_keygen new --no-passphrase -fso "$SOLANA_CONFIG_DIR"/faucet.json
fi
if [[ -f $BOOTSTRAP_VALIDATOR_IDENTITY_KEYPAIR ]]; then
cp -f "$BOOTSTRAP_VALIDATOR_IDENTITY_KEYPAIR" "$SOLANA_CONFIG_DIR"/bootstrap-validator/identity.json
else
$solana_keygen new --no-passphrase -so "$SOLANA_CONFIG_DIR"/bootstrap-validator/identity.json
fi
$solana_keygen new --no-passphrase -so "$SOLANA_CONFIG_DIR"/bootstrap-validator/vote-account.json
$solana_keygen new --no-passphrase -so "$SOLANA_CONFIG_DIR"/bootstrap-validator/stake-account.json
cp "$SOLANA_CONFIG_DIR"/latest-mainnet-beta-snapshot/genesis.tar.bz2 \
"$SOLANA_CONFIG_DIR"/bootstrap-validator
$solana_ledger_tool modify-genesis \
--ledger "$SOLANA_CONFIG_DIR"/bootstrap-validator \
--hashes-per-tick sleep \
#--operating-mode preview \
$solana_ledger_tool create-snapshot \
--hashes-per-tick sleep \
--ledger "$SOLANA_CONFIG_DIR"/latest-mainnet-beta-snapshot \
--faucet-pubkey "$SOLANA_CONFIG_DIR"/faucet.json \
--faucet-lamports 500000000000000000 \
--bootstrap-validator "$SOLANA_CONFIG_DIR"/bootstrap-validator/identity.json \
"$SOLANA_CONFIG_DIR"/bootstrap-validator/vote-account.json \
"$SOLANA_CONFIG_DIR"/bootstrap-validator/stake-account.json \
"$snapshot_slot" "$SOLANA_CONFIG_DIR"/bootstrap-validator