use super::wallet::HotWallet;
use crate::{
wallet::Result as WalletResult, CashNote, DerivationIndex, MainPubkey, MainSecretKey,
NanoTokens, SignedSpend, Spend, SpendReason, TransferError, UniquePubkey,
};
use bls::SecretKey;
use lazy_static::lazy_static;
use std::{
collections::{BTreeMap, BTreeSet},
fmt::Debug,
path::PathBuf,
};
use thiserror::Error;
pub(super) const GENESIS_CASHNOTE_AMOUNT: u64 = (0.3 * TOTAL_SUPPLY as f64) as u64;
pub const GENESIS_INPUT_DERIVATION_INDEX: DerivationIndex = DerivationIndex([0u8; 32]);
pub const GENESIS_OUTPUT_DERIVATION_INDEX: DerivationIndex = DerivationIndex([1u8; 32]);
const DEFAULT_LIVE_GENESIS_SK: &str =
"23746be7fa5df26c3065eb7aa26860981e435c1853cafafe472417bc94f340e9";
const DEFAULT_LIVE_GENESIS_PK: &str = "9934c21469a68415e6b06a435709e16bff6e92bf302aeb0ea9199d2d06a55f1b1a21e155853d3f94ae31f8f313f886ee";
const MIN_ROYALTY_FEE: u64 = 1;
pub fn calculate_royalties_fee(store_cost: NanoTokens) -> NanoTokens {
let fees_amount = std::cmp::max(
MIN_ROYALTY_FEE,
((store_cost.as_nano() as f64 * 0.15) / 0.85) as u64,
);
NanoTokens::from(fees_amount)
}
pub(super) type GenesisResult<T> = Result<T, Error>;
pub const TOTAL_SUPPLY: u64 = u32::MAX as u64 * u64::pow(10, 9);
#[derive(Error, Debug, Clone)]
pub enum Error {
#[error("Genesis CashNote error:: {0}")]
GenesisCashNoteError(String),
#[error("Failed to parse reason: {0}")]
FailedToParseReason(#[from] Box<TransferError>),
#[error("Failed to perform wallet action: {0}")]
WalletError(String),
}
lazy_static! {
pub static ref GENESIS_PK: MainPubkey = {
let compile_time_key = option_env!("GENESIS_PK").unwrap_or(DEFAULT_LIVE_GENESIS_PK);
let runtime_key =
std::env::var("GENESIS_PK").unwrap_or_else(|_| compile_time_key.to_string());
if runtime_key == DEFAULT_LIVE_GENESIS_PK {
warn!("USING DEFAULT GENESIS SK (9934c2) FOR TESTING PURPOSES! EXPECTING PAIRED SK (23746b) TO BE USED!");
} else if runtime_key == compile_time_key {
warn!("Using compile-time GENESIS_PK: {}", compile_time_key);
} else {
warn!("Overridden by runtime GENESIS_PK: {}", runtime_key);
}
match MainPubkey::from_hex(&runtime_key) {
Ok(pk) => {
info!("Genesis PK: {pk:?}");
pk
}
Err(err) => panic!("Failed to parse genesis PK: {err:?}"),
}
};
}
lazy_static! {
pub static ref GENESIS_SPEND_UNIQUE_KEY: UniquePubkey = GENESIS_PK.new_unique_pubkey(&GENESIS_OUTPUT_DERIVATION_INDEX);
}
lazy_static! {
pub static ref GENESIS_SK_STR: String = {
let compile_time_key = option_env!("GENESIS_SK").unwrap_or(DEFAULT_LIVE_GENESIS_SK);
let runtime_key =
std::env::var("GENESIS_SK").unwrap_or_else(|_| compile_time_key.to_string());
if runtime_key == DEFAULT_LIVE_GENESIS_SK {
warn!("USING DEFAULT GENESIS SK (23746b) FOR TESTING PURPOSES! EXPECTING PAIRED PK (9934c2) TO BE USED!");
} else if runtime_key == compile_time_key {
warn!("Using compile-time GENESIS_SK");
} else {
warn!("Overridden by runtime GENESIS_SK");
}
runtime_key
};
}
lazy_static! {
pub static ref GENESIS_CASHNOTE: CashNote = {
match create_first_cash_note_from_key(&get_genesis_sk()) {
Ok(cash_note) => cash_note,
Err(err) => panic!("Failed to create genesis CashNote: {err:?}"),
}
};
}
pub fn get_genesis_sk() -> MainSecretKey {
match SecretKey::from_hex(&GENESIS_SK_STR) {
Ok(sk) => MainSecretKey::new(sk),
Err(err) => panic!("Failed to parse genesis SK: {err:?}"),
}
}
pub fn is_genesis_spend(spend: &SignedSpend) -> bool {
let bytes = spend.spend.to_bytes_for_signing();
spend.spend.unique_pubkey == *GENESIS_SPEND_UNIQUE_KEY
&& GENESIS_SPEND_UNIQUE_KEY.verify(&spend.derived_key_sig, bytes)
&& spend.spend.amount() == NanoTokens::from(GENESIS_CASHNOTE_AMOUNT)
}
pub fn load_genesis_wallet() -> Result<HotWallet, Error> {
info!("Loading genesis...");
if let Ok(wallet) = get_existing_genesis_wallet() {
return Ok(wallet);
}
let mut genesis_wallet = create_genesis_wallet();
info!(
"Depositing genesis CashNote: {:?}",
GENESIS_CASHNOTE.unique_pubkey()
);
genesis_wallet
.deposit_and_store_to_disk(&vec![GENESIS_CASHNOTE.clone()])
.map_err(|err| Error::WalletError(err.to_string()))
.expect("Genesis wallet shall be stored successfully.");
let genesis_balance = genesis_wallet.balance();
info!("Genesis wallet balance: {genesis_balance}");
Ok(genesis_wallet)
}
fn create_genesis_wallet() -> HotWallet {
let root_dir = get_genesis_dir();
let wallet_dir = root_dir.join("wallet");
std::fs::create_dir_all(&wallet_dir).expect("Genesis wallet path to be successfully created.");
crate::wallet::store_new_keypair(&wallet_dir, &get_genesis_sk(), None)
.expect("Genesis key shall be successfully stored.");
HotWallet::load_from(&root_dir)
.expect("Faucet wallet (after genesis) shall be created successfully.")
}
fn get_existing_genesis_wallet() -> WalletResult<HotWallet> {
let root_dir = get_genesis_dir();
let mut wallet = HotWallet::load_from(&root_dir)?;
wallet.try_load_cash_notes()?;
Ok(wallet)
}
pub fn create_first_cash_note_from_key(
first_cash_note_key: &MainSecretKey,
) -> GenesisResult<CashNote> {
let main_pubkey = first_cash_note_key.main_pubkey();
debug!("genesis cashnote main_pubkey: {:?}", main_pubkey);
let input_sk = first_cash_note_key.derive_key(&GENESIS_INPUT_DERIVATION_INDEX);
let input_pk = input_sk.unique_pubkey();
let output_pk = main_pubkey.new_unique_pubkey(&GENESIS_OUTPUT_DERIVATION_INDEX);
let amount = NanoTokens::from(GENESIS_CASHNOTE_AMOUNT);
let pre_genesis_spend = Spend {
unique_pubkey: input_pk,
reason: SpendReason::default(),
ancestors: BTreeSet::new(),
descendants: BTreeMap::from_iter([(output_pk, amount)]),
royalties: vec![],
};
let parent_spends = BTreeSet::from_iter([SignedSpend::sign(pre_genesis_spend, &input_sk)]);
let genesis_cash_note = CashNote {
parent_spends,
main_pubkey,
derivation_index: GENESIS_OUTPUT_DERIVATION_INDEX,
};
Ok(genesis_cash_note)
}
pub fn get_faucet_data_dir() -> PathBuf {
let mut data_dirs = dirs_next::data_dir().expect("A homedir to exist.");
data_dirs.push("safe");
data_dirs.push("test_faucet");
std::fs::create_dir_all(data_dirs.as_path())
.expect("Faucet test path to be successfully created.");
data_dirs
}
fn get_genesis_dir() -> PathBuf {
let mut data_dirs = dirs_next::data_dir().expect("A homedir to exist.");
data_dirs.push("safe");
data_dirs.push("test_genesis");
std::fs::create_dir_all(data_dirs.as_path())
.expect("Genesis test path to be successfully created.");
data_dirs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_genesis() {
for _ in 0..10 {
let sk = bls::SecretKey::random();
let sk_str = sk.to_hex();
let genesis_sk = MainSecretKey::new(sk);
let main_pubkey = genesis_sk.main_pubkey();
let genesis_cn = match create_first_cash_note_from_key(&genesis_sk) {
Ok(cash_note) => cash_note,
Err(err) => panic!("Failed to create genesis CashNote: {err:?}"),
};
println!("=============================");
println!("secret_key: {sk_str:?}");
println!("main_pub_key: {:?}", main_pubkey.to_hex());
println!(
"genesis_cn.unique_pubkey: {:?}",
genesis_cn.unique_pubkey().to_hex()
);
}
}
}