//! Truthlinked Consensus Src Genesis
//!
//! Owns genesis state construction and boot-time validation.
//! Consensus changes are protocol-critical; preserve deterministic replay, recovery safety, and wire compatibility.
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
use truthlinked_core::cells::StorageKeySpec;
use truthlinked_core::constants::ONE_TRTH;
use truthlinked_core::constants::STORAGE_RENT_GRACE_PERIOD_BLOCKS;
use truthlinked_core::pq_execution::{system_authority_id, treasury_system_cell_id};
use truthlinked_core::pq_identity::{account_id_from_pubkey, DualKeypair};
use truthlinked_runtime::cells::CellAccount;
use truthlinked_runtime::types::AccountRecord;
use truthlinked_state::constants::TOTAL_SUPPLY;
use truthlinked_state::State;
const FOUNDATION_MINT_PUBKEY_HEX: &str = "decbc4bd14caba575328a0ddcfb633009e2ca6058085a9b404753dac5b8d98f11b3650d866f01d14914087498d87970cdd4ebcc5afa76989d15ab816a74cbe7ab44cbca9cd7484a62f4507e23cbc425e1a937b3f00588af9a4d55e278a678075e6e99401612508b1b35856a89a4b1241c50cc32b5863483913d72ac70a6b0764774fad57a0135c2a7c0756ecf8fd7ca6c56bf20ea86c3c5049fb988c6c3240b32d2e08cefccd1f4cd1b8286cbce04ac53b2c2d637a8ed4d2aa7f464fd8ecbad9001b531b7a2e499769059ad701fcdbe905829bace71b9c612ab5d787f526f4c94c7f385eff033433a9747598155e705afecc5136878a260aedda8afddb9564edbc103fca3b302f114048072b549bfc2eccd466243aa8b9edb6ec31cf2fba2e78d69ee77c848322883aec63f33e63363c6f44f8eccdce936e482b89f036e45ac154a1449547f6ba247ab1a953f5ecfb462ec690f5487fea88996bf9bd8de8a6478bc52360355dabc050f1d00a956ea15bd55a50621405ad5e1d5e32d95566994905ffa15dea19920830f9b3d96959182fe04102a67c90dc5cc499af5c59bc011f1b68b8119168ad0bacb22f088ce15c5de2b36fafb328266dccca6f0806c84cc07311bc86d5008bdc95d35c7f54bcb03da196301f7d03cbf7083f9fef89d49c4f26b4b3ab2cc3eeb19938fcb937f30a11b0ae0b2a57748c8a0cfb70be082b1a32d80a9593c51e289e45280b7ae7e0bce690e64dba637648480ddfd4348df1e6a2eface8c6dc6c56f8f65b4dd9eb7db507d1949ada2ad69a73130ae66ada35b78c725bad4b2b9576e0a3b612d75076b7db61481d93e7208db731a03fbe79d2f4fac5960588ffb077e2b45cc0ed8d36e36aac2fa91409b966368f5b00b1ce7ae88b8ed0613e1ae15435b9da407192da122888c196586ad3b9734198ff3f6c27c4c2fc0806886fdb635fc8d7a87a163643d377dd6a104fbed2fae4248d8c5c6cdf49a663193c18bacca954531c42d68e2ae0a1117647bb772f921d6d52a2277d40a3339f8193799858907d80e8a997278b5c0b99b3424badd9bf569cafbd24e1e60dc1878d4609c09d046d000da5d4719571c5b2c3643ffba85c085233d1d1cbc58dfd31bfb23de9ef38ffab3aa56e4818394692e558c7720dc09feac8d66e51102df60d38dbeade04870e0341f482eb51faaf34b3a93118e69d5fa8449aeef127b6bd137e532045bb0042a4b1ff53aa066648c9da516269b0d4486a2b13338ba72274b67dcfee807b8cbc1d822a5110c69a91b9b8c6b450b759d56e17eaccb3bae71993a8a787262a3eef25aa8586f1c59f708493375a5eeb988628472c93cb8fe8d5f45bf19649532a05b28122885ea4b54e2fc7c42a3278c6b0311918a1c2567a6ac77edfcc981294c3d6dde9973fa5b670080830a96a192fd27764662ea0c853895c9255fc244395a4704aec9a957f2d0a2acf8b11b19bca6d107c2cec5d5b547d02915b35757a29a0470a380b2f2728fc917dfedbefa4aab2792a14cf69fe606307d7cdb93b885a958a9705858efdc0831ca591771174f0b03a55f8cce968f6622a95a293a5199175b61167b327a9a8bc17a62b1d4b7aea7ea766c847167a53da2a6e5c6e96d275ed3c0d7e45504a20fb4e2895ec4dba76251b9c200b8f4e163bd97e18b7fd5162ffb26f1fddb277422268722da3505b6d3638551895d17e0f14697a1543246f1b975e52cc2fe355569ceacfaf6dc32a971441100f7750cfc43288cb0ddee4db770e3dbc70997f7c653ec8cf553bb5c2b48dafdf586a1734cb06e63b20b75e3d7c2101235bbbcc7bc49b1f7623dc5b18481fd47a4df78a10056011d57b271075051eaccf4aef4d9f521d9792d4c445800b52894d2d0c84796fa2097feb824283672df0e679c66107ecf32d01861ab204c80565fb819b132b8a7f681fd89561beb32d3505afdbd98b05234f911f0c08500ad0e814d9cd585d3e3a2f3dfb20504ce26ec248db78acfd1af5d490c53f483ab13b67774f763314127f51778aac984d00e69c31d115da5b11a114a256eb7215ccf59e57cd46c78ad33234c38a1e464088ba0298e18079956d4a36c809b1a09e9a6d00395740423056efeab6fab69d211a68da95f70a715b93ef2ce9199af43d6d24960eefec6e2fe4dc0ce0bfd216f1e4fc9852755c0034e1f302cb9229d3a1fe68e3d77561cb5a92b4174761ca8a8dcfedd47133d79bef6d6417a2cab6e888a6959ae52a07b0615f00221b9bad0c505fbdd4e17443eebad8283e71ab90069f3ca6088121f4656dc9866d430d39c71815510cad710310ffdaf46db747b633c26c7b6933355df47def6492374c88c149882db13ba1ff2ce10f29b71b1abf5c4b5dd057ed6c3dde70b9ab94e754efb0a160c5429f16303d55d7f7570c21a790d5a63cc6acc79d584f32ddeaa772da5780a4f73c6d08ae7d9dd22855898fec3b9caae003d83b3fb329a2f3c540d02adcca9b39568cd3855029da05ac20fd92d3deafd04103ade91d5dfc045522c4122747a81381e740035a6c7f3a874c3a8da6af99e1177002460836110a5115376e2f11f6a9a947b68ad944d4e0a04214264fc760974132220af99a72e0d7f100419cc1bcdf2897c7a42c388b47f2b2019920d23a0c629775b5b642d1554d956359805f474aad99ebbc5f74e0dc7af94f87896fca0e433ea62400ee5b8f2b2d678cc4ae7eb3e1b6456dd07ddff6b3bca52a220f594133bf36bc8a4dbf";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenesisValidator {
pub keys_file: String,
pub allocation: u128,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenesisConfig {
pub validators: Vec<GenesisValidator>,
pub foundation_mint_pubkey: Option<String>,
pub genesis_timestamp: u64,
pub network: String, // "mainnet", "testnet", or "devnet"
}
impl GenesisConfig {
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, String> {
let json = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read genesis config: {}", e))?;
serde_json::from_str(&json).map_err(|e| format!("Failed to parse genesis config: {}", e))
}
pub fn mainnet() -> Self {
Self {
validators: vec![],
foundation_mint_pubkey: None,
genesis_timestamp: 1800000000, // Jun 14, 2027 00:00:00 UTC (future mainnet launch)
network: "mainnet".to_string(),
}
}
pub fn testnet() -> Self {
Self {
validators: vec![
GenesisValidator {
keys_file: "validator1_keys.json".to_string(),
allocation: 100_000 * ONE_TRTH,
},
GenesisValidator {
keys_file: "validator2_keys.json".to_string(),
allocation: 100_000 * ONE_TRTH,
},
GenesisValidator {
keys_file: "validator3_keys.json".to_string(),
allocation: 100_000 * ONE_TRTH,
},
],
foundation_mint_pubkey: None,
genesis_timestamp: 1735689600, // Jan 1, 2025 00:00:00 UTC
network: "testnet".to_string(),
}
}
pub fn devnet() -> Self {
Self {
validators: vec![
GenesisValidator {
keys_file: "validator1_keys.json".to_string(),
allocation: 50_000 * ONE_TRTH,
},
GenesisValidator {
keys_file: "validator2_keys.json".to_string(),
allocation: 70_000 * ONE_TRTH,
},
GenesisValidator {
keys_file: "validator3_keys.json".to_string(),
allocation: 80_000 * ONE_TRTH,
},
],
foundation_mint_pubkey: None,
genesis_timestamp: 1704067200, // Jan 1, 2024 00:00:00 UTC
network: "devnet".to_string(),
}
}
pub fn default_devnet() -> Self {
Self::devnet()
}
}
pub fn initialize_genesis(state: &mut State, config: &GenesisConfig) {
use fips204::traits::SerDes;
tracing::info!("═══════════════════════════════════════════════════════");
tracing::info!(" Initializing {} genesis", config.network.to_uppercase());
tracing::info!(
" Genesis timestamp: {} ({})",
config.genesis_timestamp,
chrono::DateTime::from_timestamp(config.genesis_timestamp as i64, 0)
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S UTC").to_string())
.unwrap_or_else(|| "invalid".to_string())
);
tracing::info!("═══════════════════════════════════════════════════════");
let total_supply = TOTAL_SUPPLY;
let validators_allocated: u128 = config.validators.iter().map(|v| v.allocation).sum();
if validators_allocated > total_supply {
panic!("Validator allocations exceed total supply");
}
let mut total_allocation = 0u128;
for (idx, validator) in config.validators.iter().enumerate() {
let keypair = DualKeypair::load(&validator.keys_file)
.unwrap_or_else(|e| panic!("Failed to load {}: {}", validator.keys_file, e));
let dilithium_pubkey = keypair.dilithium_pk.into_bytes().to_vec();
let account_id = account_id_from_pubkey(&dilithium_pubkey);
let stake_amount = (validator.allocation * 80) / 100;
let liquid_amount = validator.allocation.saturating_sub(stake_amount);
state.accounts.insert(
account_id,
AccountRecord {
pubkey_bytes: dilithium_pubkey.clone(),
balance: liquid_amount,
compute_escrow_trth: 0,
nonce: 0,
nfts: vec![],
},
);
state.staking.validators.insert(
dilithium_pubkey.clone(),
truthlinked_staking::ValidatorStake::new(stake_amount as u64),
);
total_allocation = total_allocation.saturating_add(validator.allocation);
tracing::info!(
" Validator {}: {} TLKD ({}% staked, {}% liquid) ({})",
idx + 1,
validator.allocation / ONE_TRTH,
80,
20,
hex::encode(&account_id[..8])
);
}
let pk = hex::decode(FOUNDATION_MINT_PUBKEY_HEX)
.unwrap_or_else(|e| panic!("Failed to decode foundation_mint_pubkey: {}", e));
if pk.len() != 1952 {
panic!("foundation_mint_pubkey must be 1952 bytes (Dilithium pubkey)");
}
let account_id = account_id_from_pubkey(&pk);
state.accounts.insert(
account_id,
AccountRecord {
pubkey_bytes: pk.clone(),
balance: 0,
compute_escrow_trth: 0,
nonce: 0,
nfts: vec![],
},
);
state.foundation_mint_authority = Some(account_id);
tracing::info!(
" Foundation mint authority set: {}",
hex::encode(&account_id[..8])
);
// Deploy System Authority cell (validator-governed upgrade authority)
let system_id = system_authority_id();
if !state.cells.cells.contains_key(&system_id) {
let manifest_hash = CellAccount::compute_manifest_hash(&[], &[], &[], &[], &[]);
state.cells.cells.insert(
system_id,
CellAccount {
cell_id: system_id,
owner: system_id,
bytecode: vec![],
storage: HashMap::new(),
balance: 0,
rent_deposit: 0,
is_token: false,
token_config: None,
created_at: config.genesis_timestamp,
upgraded_at: None,
last_rent_paid_height: 0,
rent_grace_blocks: STORAGE_RENT_GRACE_PERIOD_BLOCKS,
pending_owner: None,
is_immutable: true,
declared_reads: Vec::new(),
declared_writes: Vec::new(),
commutative_keys: Vec::new(),
storage_key_specs: Vec::new(),
oracle_schema_ids: Vec::new(),
governance_proposal: None,
manifest_version: 1,
manifest_hash,
},
);
tracing::info!(
" System authority cell deployed: {}",
hex::encode(&system_id[..8])
);
}
let genesis_timestamp = config.genesis_timestamp;
match truthlinked_mcp::deploy_mcp_genesis_cells(&mut state.cells, system_id, genesis_timestamp)
{
Ok(()) => tracing::info!(" MCP protocol cells deployed at genesis"),
Err(e) => tracing::warn!(" MCP genesis deploy failed (non-fatal): {}", e),
}
{
use truthlinked_core::pq_execution::{
governance_system_cell_id, name_registry_system_cell_id,
oracle_governance_system_cell_id, staking_system_cell_id,
token_governance_system_cell_id, treasury_system_cell_id,
};
use truthlinked_runtime::cells::CellAccount;
let make = |cell_id: [u8; 32]| CellAccount {
cell_id,
owner: system_id,
bytecode: vec![],
storage: std::collections::HashMap::new(),
balance: 0,
rent_deposit: 0,
is_token: false,
token_config: None,
created_at: genesis_timestamp,
upgraded_at: None,
last_rent_paid_height: 0,
rent_grace_blocks: 0,
pending_owner: None,
is_immutable: true,
declared_reads: vec![],
declared_writes: vec![],
commutative_keys: vec![],
storage_key_specs: vec![],
oracle_schema_ids: vec![],
governance_proposal: None,
manifest_version: 1,
manifest_hash: [0u8; 32],
};
use truthlinked_core::pq_execution::wtrth_system_cell_id;
for id in [
staking_system_cell_id(),
governance_system_cell_id(),
treasury_system_cell_id(),
name_registry_system_cell_id(),
token_governance_system_cell_id(),
oracle_governance_system_cell_id(),
wtrth_system_cell_id(),
] {
state.cells.cells.insert(id, make(id));
}
tracing::info!(" System cells registered at genesis (native Rust handlers)");
}
let treasury_reserve = total_supply.saturating_sub(total_allocation);
if let Some(cell) = state.cells.cells.get_mut(&treasury_system_cell_id()) {
cell.balance = cell.balance.saturating_add(treasury_reserve);
} else {
panic!("treasury system cell not deployed");
}
total_allocation = total_allocation.saturating_add(treasury_reserve);
if total_allocation != total_supply {
panic!(
"Genesis allocation mismatch: allocated {} vs supply {}",
total_allocation / ONE_TRTH,
total_supply / ONE_TRTH
);
}
tracing::info!(
" Genesis initialized: {} validators + treasury reserve = {} TLKD total supply",
config.validators.len(),
total_allocation / ONE_TRTH
);
}
#[allow(dead_code)]
fn parse_manifest_slots(manifest: &Value, field: &str) -> Result<Vec<[u8; 32]>, String> {
let arr = manifest[field]
.as_array()
.ok_or_else(|| format!("Missing {} in manifest", field))?;
let mut out = Vec::new();
for v in arr {
let s = v.as_str().ok_or("manifest slot must be string")?;
let bytes = hex::decode(s).map_err(|_| "invalid hex in manifest slot")?;
if bytes.len() != 32 {
return Err("manifest slot must be 32 bytes".to_string());
}
let mut arr32 = [0u8; 32];
arr32.copy_from_slice(&bytes);
out.push(arr32);
}
Ok(out)
}
#[allow(dead_code)]
fn parse_manifest_specs(manifest: &Value) -> Result<Vec<StorageKeySpec>, String> {
let specs = manifest["storage_key_specs"]
.as_array()
.ok_or("Missing storage_key_specs in manifest")?;
let mut out = Vec::new();
for s in specs {
let offset = s["offset"]
.as_u64()
.ok_or("storage_key_specs.offset missing")?;
let len = s["len"].as_u64().ok_or("storage_key_specs.len missing")?;
out.push(StorageKeySpec {
offset: offset as usize,
len: len as usize,
});
}
Ok(out)
}
#[allow(dead_code)]
fn parse_manifest_schema_ids(manifest: &Value) -> Result<Vec<[u8; 32]>, String> {
let empty = Vec::new();
let arr = manifest
.get("oracle_schema_ids")
.and_then(|v| v.as_array())
.unwrap_or(&empty);
let mut out = Vec::new();
for v in arr {
let s = v.as_str().ok_or("oracle_schema_ids entry must be string")?;
let bytes = hex::decode(s).map_err(|_| "invalid hex in oracle_schema_ids")?;
if bytes.len() != 32 {
return Err("oracle_schema_ids entry must be 32 bytes".to_string());
}
let mut arr32 = [0u8; 32];
arr32.copy_from_slice(&bytes);
out.push(arr32);
}
Ok(out)
}