// file: kb-pipeline/src/solana_stateful.rs // version: 2 //! Stateful Localnet and Devnet readiness checks for native Solana execution plans. use std::str::FromStr; // rust-rules: trait-import const COMPLETE_ACCOUNT_DATA_LIMIT: usize = 65_536; const ALT_META_SIZE: usize = 56; const ALT_MAX_ADDRESSES: usize = 256; const FEATURE_ACCOUNT_SIZE: u64 = 9; const SLASHING_REPORT_HEADER_SIZE: usize = 114; const MAX_SHRED_SIZE: usize = 1_232; const ZK_CONTEXT_META_SIZE: usize = 33; /// Stateful readiness outcome for one native Solana operation. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] pub enum SolanaCoreStatefulReadinessStatus { /// The operation has no stateful preflight contract in this layer. NotRequired, /// Every stateful preflight check passed. Ready, /// At least one stateful preflight check failed. Blocked, } /// One machine-readable stateful readiness check. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SolanaCoreStatefulCheck { /// Stable diagnostic code. pub code: std::string::String, /// Whether the check passed. pub passed: bool, /// Operator-readable explanation. pub message: std::string::String, } /// One contextual fact measured during stateful readiness inspection. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SolanaCoreStatefulFact { /// Stable fact key. pub key: std::string::String, /// String representation of the measured value. pub value: std::string::String, } /// Complete request for one native Solana stateful readiness inspection. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SolanaCoreStatefulReadinessRequest { /// HTTP endpoint role used for all state reads. pub query_role: std::string::String, /// Expected Localnet or Devnet cluster. pub cluster: kb_lib::ExApiExecutionCluster, /// Native operation whose external state must be inspected. pub operation: kb_lib::ExSolanaCoreOperation, } /// Complete stateful readiness report produced before simulation. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SolanaCoreStatefulReadinessReport { /// Expected cluster. pub cluster: kb_lib::ExApiExecutionCluster, /// Stable native operation code. pub operation_code: std::string::String, /// Aggregate stateful readiness status. pub status: crate::SolanaCoreStatefulReadinessStatus, /// Highest contextual slot observed while reading accounts. pub context_slot: std::option::Option, /// Ordered stateful checks. pub checks: std::vec::Vec, /// Ordered measured facts. pub facts: std::vec::Vec, } #[derive(Clone, Debug, Eq, PartialEq)] struct AddressLookupTableState { deactivation_slot: u64, authority: std::option::Option, address_count: usize, } /// Inspects Localnet or Devnet state required before simulating one native operation. pub async fn inspect_solana_core_stateful_readiness( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, ) -> kb_core::Result { if request.query_role.trim().is_empty() { return std::result::Result::Err(kb_core::Error::config( "stateful execution readiness query_role must not be empty", )); } match request.cluster { kb_lib::ExApiExecutionCluster::Localnet | kb_lib::ExApiExecutionCluster::Devnet => {}, kb_lib::ExApiExecutionCluster::Testnet | kb_lib::ExApiExecutionCluster::Mainnet => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_cluster_unsupported", "stateful native execution readiness is restricted to Localnet and Devnet", )); }, } let genesis = match pool.get_genesis_hash_for_role(request.query_role.as_str()).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; match validate_cluster(request.cluster, &genesis) { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => return std::result::Result::Err(error), } let mut report = new_report(request); push_fact(&mut report, "genesis_hash", genesis.genesis_hash.as_str()); match &request.operation { kb_lib::ExSolanaCoreOperation::AddressLookupTableCreate { authority, payer, recent_slot, } => { match inspect_alt_create(pool, request, &mut report, authority, payer, *recent_slot) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::AddressLookupTableExtend { lookup_table, authority, payer, new_addresses, } => { match inspect_alt_existing( pool, request, &mut report, lookup_table, authority, AltAction::Extend { payer_present: payer.is_some(), added_addresses: new_addresses.len(), }, ) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::AddressLookupTableFreeze { lookup_table, authority } => { match inspect_alt_existing( pool, request, &mut report, lookup_table, authority, AltAction::Freeze, ) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::AddressLookupTableDeactivate { lookup_table, authority } => { match inspect_alt_existing( pool, request, &mut report, lookup_table, authority, AltAction::Deactivate, ) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::AddressLookupTableClose { lookup_table, authority, .. } => { match inspect_alt_existing( pool, request, &mut report, lookup_table, authority, AltAction::Close, ) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::ConfigCreateAccount { config_account, lamports, max_config_data_space, max_keys, .. } => { match inspect_config_create( pool, request, &mut report, config_account, *lamports, *max_config_data_space, max_keys.len(), ) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::ConfigStore { config_account, keys, data, .. } => { match inspect_config_store( pool, request, &mut report, config_account, keys.len(), data.len(), ) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::FeatureActivate { feature_account, lamports, .. } => { match inspect_feature_activate(pool, request, &mut report, feature_account, *lamports) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::FeatureRevokePendingActivation { feature_account } => { match inspect_feature_revoke(pool, request, &mut report, feature_account).await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::SlashingSubmitDuplicateBlockProof { proof_account, report_account, report_lamports, proof_offset, slot, .. } => { match inspect_slashing_submit( pool, request, &mut report, proof_account, report_account, *report_lamports, *proof_offset, *slot, ) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::SlashingCloseViolationReport { report_account, destination, } => { match inspect_slashing_close(pool, request, &mut report, report_account, destination) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::ZkElGamalVerifyInline { proof_type, context_state, .. } => { match inspect_zk_verify( pool, request, &mut report, *proof_type, std::option::Option::None, context_state.as_ref(), ) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::ZkElGamalVerifyFromAccount { proof_type, proof_account, proof_offset, context_state, } => { match inspect_zk_verify( pool, request, &mut report, *proof_type, std::option::Option::Some((proof_account, *proof_offset)), context_state.as_ref(), ) .await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, } }, kb_lib::ExSolanaCoreOperation::ZkElGamalCloseContextState { context_state, authority, .. } => match inspect_zk_close(pool, request, &mut report, context_state, authority).await { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => { return std::result::Result::Err(error); }, }, _ => { report.status = crate::SolanaCoreStatefulReadinessStatus::NotRequired; push_fact(&mut report, "stateful_policy", "not_required_for_operation"); return std::result::Result::Ok(report); }, } finalize_report(&mut report); return std::result::Result::Ok(report); } fn new_report( request: &crate::SolanaCoreStatefulReadinessRequest, ) -> crate::SolanaCoreStatefulReadinessReport { return crate::SolanaCoreStatefulReadinessReport { cluster: request.cluster, operation_code: request.operation.operation_code().to_string(), status: crate::SolanaCoreStatefulReadinessStatus::Ready, context_slot: std::option::Option::None, checks: std::vec::Vec::new(), facts: std::vec::Vec::new(), }; } fn validate_cluster( expected: kb_lib::ExApiExecutionCluster, genesis: &kb_onchain_transport::GenesisHashResult, ) -> kb_core::Result<()> { return match expected { kb_lib::ExApiExecutionCluster::Devnet => { if genesis.classified_cluster == std::option::Option::Some(kb_lib::ExApiExecutionCluster::Devnet) { std::result::Result::Ok(()) } else { std::result::Result::Err(kb_core::Error::new( "execution_stateful_devnet_genesis_mismatch", "stateful Devnet readiness requires the official Devnet genesis hash", )) } }, kb_lib::ExApiExecutionCluster::Localnet => { if genesis.classified_cluster.is_none() { std::result::Result::Ok(()) } else { std::result::Result::Err(kb_core::Error::new( "execution_stateful_localnet_public_cluster", "Localnet readiness refuses endpoints classified as an official public cluster", )) } }, kb_lib::ExApiExecutionCluster::Testnet | kb_lib::ExApiExecutionCluster::Mainnet => { std::result::Result::Err(kb_core::Error::new( "execution_stateful_cluster_unsupported", "stateful native execution readiness is restricted to Localnet and Devnet", )) }, }; } fn push_check( report: &mut crate::SolanaCoreStatefulReadinessReport, code: &str, passed: bool, message: impl std::convert::Into, ) { report.checks.push(crate::SolanaCoreStatefulCheck { code: code.to_string(), passed, message: message.into(), }); } fn push_fact( report: &mut crate::SolanaCoreStatefulReadinessReport, key: &str, value: impl std::fmt::Display, ) { report.facts.push(crate::SolanaCoreStatefulFact { key: key.to_string(), value: value.to_string(), }); } fn update_context_slot(report: &mut crate::SolanaCoreStatefulReadinessReport, slot: u64) { let current = match report.context_slot { std::option::Option::Some(value) => value.max(slot), std::option::Option::None => slot, }; report.context_slot = std::option::Option::Some(current); } fn finalize_report(report: &mut crate::SolanaCoreStatefulReadinessReport) { report.status = if report.checks.iter().all(|check| return check.passed) { crate::SolanaCoreStatefulReadinessStatus::Ready } else { crate::SolanaCoreStatefulReadinessStatus::Blocked }; } async fn account_with_data( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, pubkey: &kb_lib::MdPubkey, ) -> kb_core::Result { let config = match kb_onchain_transport::GetAccountInfoConfig::confirmed_with_data( COMPLETE_ACCOUNT_DATA_LIMIT, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return pool .get_account_info_for_role(request.query_role.as_str(), pubkey, &config) .await; } async fn rent_minimum( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, data_length: u64, ) -> kb_core::Result { let result = pool .get_minimum_balance_for_rent_exemption_for_role( request.query_role.as_str(), data_length, &kb_onchain_transport::GetMinimumBalanceForRentExemptionConfig::confirmed(), ) .await; return match result { std::result::Result::Ok(value) => std::result::Result::Ok(value.minimum_balance_lamports), std::result::Result::Err(error) => std::result::Result::Err(error), }; } async fn epoch_info( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, ) -> kb_core::Result { return pool .get_epoch_info_for_role( request.query_role.as_str(), &kb_onchain_transport::GetEpochInfoConfig::confirmed(), ) .await; } async fn inspect_alt_create( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, authority: &kb_lib::MdPubkey, payer: &kb_lib::MdPubkey, recent_slot: u64, ) -> kb_core::Result<()> { let authority_address = match solana_pubkey::Pubkey::from_str(authority.0.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(kb_core::Error::config(format!( "invalid ALT authority: {error}" ))); }, }; let payer_address = match solana_pubkey::Pubkey::from_str(payer.0.as_str()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(kb_core::Error::config(format!( "invalid ALT payer: {error}" ))); }, }; let (_, table_address) = solana_address_lookup_table_interface::instruction::create_lookup_table( authority_address, payer_address, recent_slot, ); let table = kb_lib::MdPubkey(table_address.to_string()); let account_result = match account_with_data(pool, request, &table).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, account_result.context.slot); push_fact(report, "lookup_table", table.0.as_str()); push_check( report, "alt_create_target_absent", account_result.account.is_none(), "derived lookup table account must not already exist", ); let epoch = match epoch_info(pool, request).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; push_fact(report, "absolute_slot", epoch.absolute_slot); let last_valid = solana_address_lookup_table_interface::state::estimate_last_valid_slot(recent_slot); push_fact(report, "recent_slot_last_valid", last_valid); push_check( report, "alt_create_recent_slot_not_future", recent_slot <= epoch.absolute_slot, "ALT recent slot must not be ahead of the current confirmed slot", ); push_check( report, "alt_create_recent_slot_still_recent", epoch.absolute_slot <= last_valid, "ALT recent slot must remain in the runtime slot-hashes window", ); let rent = match rent_minimum(pool, request, ALT_META_SIZE as u64).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; push_fact(report, "minimum_rent_lamports", rent); return std::result::Result::Ok(()); } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum AltAction { Extend { payer_present: bool, added_addresses: usize, }, Freeze, Deactivate, Close, } async fn inspect_alt_existing( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, lookup_table: &kb_lib::MdPubkey, authority: &kb_lib::MdPubkey, action: AltAction, ) -> kb_core::Result<()> { let account_result = match account_with_data(pool, request, lookup_table).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, account_result.context.slot); let account = match account_result.account { std::option::Option::Some(value) => value, std::option::Option::None => { push_check(report, "alt_account_exists", false, "lookup table account does not exist"); return std::result::Result::Ok(()); }, }; push_check( report, "alt_owner_matches", account.owner.0 == kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID, "lookup table account must be owned by the Address Lookup Table program", ); push_check( report, "alt_not_executable", !account.executable, "lookup table state account must not be executable", ); let state = match parse_alt_state(account.data.as_slice()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { push_check(report, "alt_state_decodes", false, error.to_string()); return std::result::Result::Ok(()); }, }; push_fact(report, "alt_address_count", state.address_count); push_fact(report, "alt_deactivation_slot", state.deactivation_slot); push_check( report, "alt_authority_matches", state.authority.as_deref() == std::option::Option::Some(authority.0.as_str()), "lookup table authority must match the requested authority", ); let active = state.deactivation_slot == u64::MAX; match action { AltAction::Extend { payer_present, added_addresses } => { push_check( report, "alt_extend_active", active, "lookup table must be active before extension", ); let target_count = state.address_count.saturating_add(added_addresses); push_fact(report, "alt_target_address_count", target_count); push_check( report, "alt_extend_capacity", target_count <= ALT_MAX_ADDRESSES, "lookup table cannot exceed 256 addresses", ); let target_space = ALT_META_SIZE.saturating_add(target_count.saturating_mul(32)); let rent = match rent_minimum(pool, request, target_space as u64).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let top_up = rent.saturating_sub(account.lamports); push_fact(report, "alt_target_space", target_space); push_fact(report, "alt_rent_top_up_lamports", top_up); push_check( report, "alt_extend_payer_available_when_needed", top_up == 0 || payer_present, "ALT extension requires the optional payer/system pair when rent top-up is needed", ); }, AltAction::Freeze => { push_check( report, "alt_freeze_active", active, "lookup table must be active before freezing", ); push_check( report, "alt_freeze_non_empty", state.address_count > 0, "empty lookup tables cannot be frozen", ); }, AltAction::Deactivate => { push_check( report, "alt_deactivate_active", active, "lookup table must still be active before deactivation", ); }, AltAction::Close => { push_check( report, "alt_close_deactivated", !active, "lookup table must be deactivated before closing", ); if !active { let epoch = match epoch_info(pool, request).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let last_valid = solana_address_lookup_table_interface::state::estimate_last_valid_slot( state.deactivation_slot, ); push_fact(report, "alt_close_last_valid_slot", last_valid); push_check( report, "alt_close_cooldown_elapsed", epoch.absolute_slot > last_valid, "lookup table cooldown must be conservatively elapsed before close", ); } }, } return std::result::Result::Ok(()); } fn parse_alt_state(data: &[u8]) -> kb_core::Result { if data.len() < ALT_META_SIZE { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_alt_data_short", "lookup table account data is shorter than the 56-byte metadata region", )); } let tag = match read_u32(data, 0, "ALT program state tag") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if tag != 1 { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_alt_uninitialized", "lookup table account is not in the initialized LookupTable program state", )); } let deactivation_slot = match read_u64(data, 4, "ALT deactivation slot") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let authority = match data[21] { 0 => std::option::Option::None, 1 => { let bytes = match data.get(22..54) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_alt_authority_short", "lookup table authority bytes are truncated", )); }, }; let array = match <[u8; 32]>::try_from(bytes) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_alt_authority_short", "lookup table authority bytes are not exactly 32 bytes", )); }, }; std::option::Option::Some(solana_pubkey::Pubkey::new_from_array(array).to_string()) }, value => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_alt_authority_option_invalid", format!("lookup table authority option tag must be 0 or 1 but received {value}"), )); }, }; let address_bytes = data.len() - ALT_META_SIZE; if address_bytes % 32 != 0 { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_alt_addresses_misaligned", "lookup table address bytes must be aligned to 32-byte public keys", )); } let address_count = address_bytes / 32; if address_count > ALT_MAX_ADDRESSES { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_alt_capacity_invalid", "lookup table account contains more than 256 addresses", )); } return std::result::Result::Ok(AddressLookupTableState { deactivation_slot, authority, address_count, }); } async fn inspect_config_create( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, config_account: &kb_lib::MdPubkey, lamports: u64, max_data_space: u64, max_key_count: usize, ) -> kb_core::Result<()> { let account_result = match account_with_data(pool, request, config_account).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, account_result.context.slot); push_check( report, "config_create_target_absent", account_result.account.is_none(), "new Config account must not already exist", ); let key_bytes = compact_u16_length(max_key_count).saturating_add(max_key_count.saturating_mul(33)); let space = match max_data_space.checked_add(key_bytes as u64) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_config_space_overflow", "Config account allocation length overflow", )); }, }; let rent = match rent_minimum(pool, request, space).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; push_fact(report, "config_account_space", space); push_fact(report, "minimum_rent_lamports", rent); push_check( report, "config_create_rent_sufficient", lamports >= rent, "Config account lamports must meet the current rent-exempt minimum", ); return std::result::Result::Ok(()); } async fn inspect_config_store( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, config_account: &kb_lib::MdPubkey, key_count: usize, data_length: usize, ) -> kb_core::Result<()> { let account_result = match account_with_data(pool, request, config_account).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, account_result.context.slot); let account = match account_result.account { std::option::Option::Some(value) => value, std::option::Option::None => { push_check( report, "config_store_account_exists", false, "Config account does not exist", ); return std::result::Result::Ok(()); }, }; push_check( report, "config_store_owner_matches", account.owner.0 == kb_program_ids::CONFIG_PROGRAM_ID, "Config account must be owned by the Config program", ); push_check( report, "config_store_not_executable", !account.executable, "Config state account must not be executable", ); let required = compact_u16_length(key_count) .saturating_add(key_count.saturating_mul(33)) .saturating_add(data_length); push_fact(report, "config_required_space", required); push_fact(report, "config_existing_space", account.space); push_check( report, "config_store_space_sufficient", account.space >= required as u64, "Config account allocation must fit the complete encoded key list and opaque data", ); return std::result::Result::Ok(()); } async fn inspect_feature_activate( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, feature_account: &kb_lib::MdPubkey, lamports: u64, ) -> kb_core::Result<()> { let account_result = match account_with_data(pool, request, feature_account).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, account_result.context.slot); let existing_lamports = match account_result.account.as_ref() { std::option::Option::Some(account) => account.lamports, std::option::Option::None => 0, }; let reusable = account_result.account.as_ref().map(|account| { return account.owner.0 == kb_program_ids::SYSTEM_PROGRAM_ID && !account.executable && account.space == 0 && account.data.is_empty(); }); push_check( report, "feature_activate_target_available", match reusable { std::option::Option::Some(value) => value, std::option::Option::None => true, }, "feature target must be absent or a prefunded zero-space System account", ); let rent = match rent_minimum(pool, request, FEATURE_ACCOUNT_SIZE).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let required_top_up = rent.saturating_sub(existing_lamports); push_fact(report, "feature_existing_lamports", existing_lamports); push_fact(report, "feature_required_top_up_lamports", required_top_up); push_check( report, "feature_activate_rent_sufficient", lamports >= required_top_up, "feature activation transfer must cover the measured rent shortfall", ); return std::result::Result::Ok(()); } async fn inspect_feature_revoke( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, feature_account: &kb_lib::MdPubkey, ) -> kb_core::Result<()> { let account_result = match account_with_data(pool, request, feature_account).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, account_result.context.slot); let account = match account_result.account { std::option::Option::Some(value) => value, std::option::Option::None => { push_check( report, "feature_revoke_account_exists", false, "feature account does not exist", ); return std::result::Result::Ok(()); }, }; push_check( report, "feature_revoke_owner_matches", account.owner.0 == kb_program_ids::FEATURE_PROGRAM_ID, "feature account must be owned by the Feature program", ); push_check( report, "feature_revoke_not_executable", !account.executable, "feature state account must not be executable", ); let pending = account.data.len() == FEATURE_ACCOUNT_SIZE as usize && account.data.first() == std::option::Option::Some(&0); push_check( report, "feature_revoke_pending", pending, "only a pending feature state encoded as Feature { activated_at: None } may be revoked", ); return std::result::Result::Ok(()); } async fn inspect_slashing_submit( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, proof_account: &kb_lib::MdPubkey, report_account: &kb_lib::MdPubkey, report_lamports: u64, proof_offset: u64, slot: u64, ) -> kb_core::Result<()> { let proof_result = match account_with_data(pool, request, proof_account).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, proof_result.context.slot); let proof = match proof_result.account { std::option::Option::Some(value) => value, std::option::Option::None => { push_check( report, "slashing_proof_account_exists", false, "duplicate-block proof account does not exist", ); return std::result::Result::Ok(()); }, }; push_check( report, "slashing_proof_not_executable", !proof.executable, "duplicate-block proof account must not be executable", ); let packed_length = match duplicate_block_proof_length(proof.data.as_slice(), proof_offset) { std::result::Result::Ok(value) => { push_check( report, "slashing_proof_shape_valid", true, "duplicate-block proof contains two bounded length-prefixed shreds", ); value }, std::result::Result::Err(error) => { push_check(report, "slashing_proof_shape_valid", false, error.to_string()); 0 }, }; push_fact(report, "slashing_packed_proof_bytes", packed_length); let report_result = match account_with_data(pool, request, report_account).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, report_result.context.slot); let report_reusable = report_result.account.as_ref().map(|account| { return account.owner.0 == kb_program_ids::SYSTEM_PROGRAM_ID && !account.executable && account.space == 0 && account.data.is_empty(); }); push_check( report, "slashing_report_target_available", match report_reusable { std::option::Option::Some(value) => value, std::option::Option::None => true, }, "violation report PDA must be absent or an empty prefunded System account", ); let epoch = match epoch_info(pool, request).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; push_fact(report, "absolute_slot", epoch.absolute_slot); push_fact(report, "slots_in_epoch", epoch.slots_in_epoch); let age = epoch.absolute_slot.saturating_sub(slot); push_check( report, "slashing_slot_not_future", slot <= epoch.absolute_slot, "reported duplicate-block slot must not be in the future", ); push_check( report, "slashing_slot_within_one_epoch", slot <= epoch.absolute_slot && age <= epoch.slots_in_epoch, "duplicate-block proof must be submitted within one epoch worth of slots", ); if packed_length > 0 { let report_size = SLASHING_REPORT_HEADER_SIZE.saturating_add(packed_length); let rent = match rent_minimum(pool, request, report_size as u64).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; push_fact(report, "slashing_report_space", report_size); push_fact(report, "minimum_rent_lamports", rent); push_check( report, "slashing_report_rent_sufficient", report_lamports >= rent, "report_lamports must cover the exact report header and retained proof bytes", ); } return std::result::Result::Ok(()); } async fn inspect_slashing_close( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, report_account: &kb_lib::MdPubkey, destination: &kb_lib::MdPubkey, ) -> kb_core::Result<()> { let account_result = match account_with_data(pool, request, report_account).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, account_result.context.slot); let account = match account_result.account { std::option::Option::Some(value) => value, std::option::Option::None => { push_check( report, "slashing_report_exists", false, "violation report account does not exist", ); return std::result::Result::Ok(()); }, }; push_check( report, "slashing_report_owner_matches", account.owner.0 == kb_program_ids::SLASHING_PROGRAM_ID, "violation report must be owned by the Slashing program", ); push_check( report, "slashing_report_not_executable", !account.executable, "violation report state account must not be executable", ); if account.data.len() < SLASHING_REPORT_HEADER_SIZE { push_check( report, "slashing_report_decodes", false, "violation report is shorter than its 114-byte fixed header", ); return std::result::Result::Ok(()); } let version = account.data[0]; let retained_destination = match pubkey_text(&account.data[33..65]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { push_check(report, "slashing_report_decodes", false, error.to_string()); return std::result::Result::Ok(()); }, }; let report_epoch = match read_u64(account.data.as_slice(), 65, "Slashing report epoch") { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; push_fact(report, "slashing_report_version", version); push_fact(report, "slashing_report_epoch", report_epoch); push_check( report, "slashing_report_version_supported", version == 1, "violation report version must be 1", ); push_check( report, "slashing_report_destination_matches", retained_destination == destination.0, "close destination must match the destination retained in the violation report", ); let epoch = match epoch_info(pool, request).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; push_fact(report, "current_epoch", epoch.epoch); push_check( report, "slashing_report_retention_elapsed", epoch.epoch >= report_epoch.saturating_add(3), "violation report must remain on-chain for at least three epochs", ); return std::result::Result::Ok(()); } async fn inspect_zk_verify( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, proof_type: kb_lib::ExSolanaCoreZkElGamalProofType, proof_account: std::option::Option<(&kb_lib::MdPubkey, u32)>, context_state: std::option::Option<&kb_lib::ExSolanaCoreZkElGamalContextState>, ) -> kb_core::Result<()> { if let std::option::Option::Some((account_pubkey, offset)) = proof_account { let proof_result = match account_with_data(pool, request, account_pubkey).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, proof_result.context.slot); let proof = match proof_result.account { std::option::Option::Some(value) => value, std::option::Option::None => { push_check( report, "zk_proof_account_exists", false, "ZK proof account does not exist", ); return std::result::Result::Ok(()); }, }; push_check( report, "zk_proof_account_not_executable", !proof.executable, "ZK proof account must not be executable", ); let end = (offset as usize).checked_add(proof_type.proof_data_size()); push_check( report, "zk_proof_range_available", match end { std::option::Option::Some(end) => end <= proof.data.len(), std::option::Option::None => false, }, "ZK proof account must contain the complete proof POD at the requested offset", ); } let context = match context_state { std::option::Option::Some(value) => value, std::option::Option::None => { push_fact(report, "zk_context_state", "not_requested"); return std::result::Result::Ok(()); }, }; let context_result = match account_with_data(pool, request, &context.account).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, context_result.context.slot); let account = match context_result.account { std::option::Option::Some(value) => value, std::option::Option::None => { push_check( report, "zk_context_account_exists", false, "preallocated ZK context-state account does not exist", ); return std::result::Result::Ok(()); }, }; let expected_size = proof_type.context_state_size(); push_fact(report, "zk_context_expected_space", expected_size); push_fact(report, "zk_context_existing_space", account.space); push_check( report, "zk_context_owner_matches", account.owner.0 == kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID, "ZK context-state account must be owned by the ZK ElGamal Proof program", ); push_check( report, "zk_context_not_executable", !account.executable, "ZK context-state account must not be executable", ); push_check( report, "zk_context_space_exact", account.space == expected_size as u64 && account.data.len() == expected_size, "ZK context-state allocation must exactly match the official ProofContextState size", ); push_check( report, "zk_context_uninitialized", account.data.iter().all(|byte| return *byte == 0), "preallocated ZK context-state account must still be zero-initialized", ); let rent = match rent_minimum(pool, request, expected_size as u64).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; push_fact(report, "minimum_rent_lamports", rent); push_check( report, "zk_context_rent_exempt", account.lamports >= rent, "preallocated ZK context-state account must be rent exempt", ); return std::result::Result::Ok(()); } async fn inspect_zk_close( pool: &kb_onchain_transport::HttpEndpointPool, request: &crate::SolanaCoreStatefulReadinessRequest, report: &mut crate::SolanaCoreStatefulReadinessReport, context_state: &kb_lib::MdPubkey, authority: &kb_lib::MdPubkey, ) -> kb_core::Result<()> { let account_result = match account_with_data(pool, request, context_state).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; update_context_slot(report, account_result.context.slot); let account = match account_result.account { std::option::Option::Some(value) => value, std::option::Option::None => { push_check( report, "zk_context_account_exists", false, "ZK context-state account does not exist", ); return std::result::Result::Ok(()); }, }; push_check( report, "zk_context_owner_matches", account.owner.0 == kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID, "ZK context-state account must be owned by the ZK ElGamal Proof program", ); push_check( report, "zk_context_not_executable", !account.executable, "ZK context-state account must not be executable", ); if account.data.len() < ZK_CONTEXT_META_SIZE { push_check( report, "zk_context_meta_decodes", false, "ZK context-state account is shorter than its 33-byte generic metadata", ); return std::result::Result::Ok(()); } let retained_authority = match pubkey_text(&account.data[..32]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { push_check(report, "zk_context_meta_decodes", false, error.to_string()); return std::result::Result::Ok(()); }, }; let proof_type = proof_type_from_discriminator(account.data[32]); push_check( report, "zk_context_authority_matches", retained_authority == authority.0, "close authority must match the authority retained in ProofContextState", ); push_check( report, "zk_context_proof_type_supported", proof_type.is_some(), "ProofContextState must contain a supported proof type discriminator", ); if let std::option::Option::Some(value) = proof_type { let expected_size = value.context_state_size(); push_fact(report, "zk_context_proof_type", value.discriminator()); push_fact(report, "zk_context_expected_space", expected_size); push_check( report, "zk_context_space_exact", account.space == expected_size as u64 && account.data.len() == expected_size, "stored ZK context-state allocation must match its proof type", ); } return std::result::Result::Ok(()); } fn proof_type_from_discriminator( discriminator: u8, ) -> std::option::Option { return match discriminator { 1 => std::option::Option::Some(kb_lib::ExSolanaCoreZkElGamalProofType::ZeroCiphertext), 2 => std::option::Option::Some( kb_lib::ExSolanaCoreZkElGamalProofType::CiphertextCiphertextEquality, ), 3 => std::option::Option::Some( kb_lib::ExSolanaCoreZkElGamalProofType::CiphertextCommitmentEquality, ), 4 => std::option::Option::Some(kb_lib::ExSolanaCoreZkElGamalProofType::PubkeyValidity), 5 => std::option::Option::Some(kb_lib::ExSolanaCoreZkElGamalProofType::PercentageWithCap), 6 => { std::option::Option::Some(kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU64) }, 7 => { std::option::Option::Some(kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128) }, 8 => { std::option::Option::Some(kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU256) }, 9 => std::option::Option::Some( kb_lib::ExSolanaCoreZkElGamalProofType::GroupedCiphertext2HandlesValidity, ), 10 => std::option::Option::Some( kb_lib::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext2HandlesValidity, ), 11 => std::option::Option::Some( kb_lib::ExSolanaCoreZkElGamalProofType::GroupedCiphertext3HandlesValidity, ), 12 => std::option::Option::Some( kb_lib::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext3HandlesValidity, ), _ => std::option::Option::None, }; } fn duplicate_block_proof_length(data: &[u8], offset: u64) -> kb_core::Result { let start = match usize::try_from(offset) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_slashing_offset_invalid", "duplicate-block proof offset does not fit the local address space", )); }, }; let first_length = match read_u32(data, start, "first duplicate-block shred length") { std::result::Result::Ok(value) => value as usize, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if first_length == 0 || first_length > MAX_SHRED_SIZE { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_slashing_first_shred_length_invalid", "first duplicate-block shred length must be between 1 and 1232 bytes", )); } let second_length_offset = match start.checked_add(4).and_then(|value| return value.checked_add(first_length)) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_slashing_proof_overflow", "duplicate-block proof length arithmetic overflow", )); }, }; let second_length = match read_u32(data, second_length_offset, "second duplicate-block shred length") { std::result::Result::Ok(value) => value as usize, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if second_length == 0 || second_length > MAX_SHRED_SIZE { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_slashing_second_shred_length_invalid", "second duplicate-block shred length must be between 1 and 1232 bytes", )); } let total = 4_usize .checked_add(first_length) .and_then(|value| return value.checked_add(4)) .and_then(|value| return value.checked_add(second_length)); let total = match total { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_slashing_proof_overflow", "duplicate-block proof length arithmetic overflow", )); }, }; let end = match start.checked_add(total) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_slashing_proof_overflow", "duplicate-block proof end offset overflow", )); }, }; if end > data.len() { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_slashing_proof_truncated", "duplicate-block proof account does not contain both declared shreds", )); } return std::result::Result::Ok(total); } fn compact_u16_length(value: usize) -> usize { if value < 0x80 { return 1; } if value < 0x4000 { return 2; } return 3; } fn read_u32(data: &[u8], offset: usize, label: &str) -> kb_core::Result { let end = match offset.checked_add(4) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_integer_overflow", format!("{label} offset overflow"), )); }, }; let bytes = match data.get(offset..end) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_integer_truncated", format!("{label} is truncated"), )); }, }; let array = match <[u8; 4]>::try_from(bytes) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_integer_truncated", format!("{label} is not exactly four bytes"), )); }, }; return std::result::Result::Ok(u32::from_le_bytes(array)); } fn read_u64(data: &[u8], offset: usize, label: &str) -> kb_core::Result { let end = match offset.checked_add(8) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_integer_overflow", format!("{label} offset overflow"), )); }, }; let bytes = match data.get(offset..end) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_integer_truncated", format!("{label} is truncated"), )); }, }; let array = match <[u8; 8]>::try_from(bytes) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_integer_truncated", format!("{label} is not exactly eight bytes"), )); }, }; return std::result::Result::Ok(u64::from_le_bytes(array)); } fn pubkey_text(bytes: &[u8]) -> kb_core::Result { let array = match <[u8; 32]>::try_from(bytes) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(kb_core::Error::new( "execution_stateful_pubkey_length_invalid", "stateful account field is not exactly 32 bytes", )); }, }; return std::result::Result::Ok(solana_pubkey::Pubkey::new_from_array(array).to_string()); } #[cfg(test)] mod tests { #[test] fn cluster_policy_restricts_stateful_reads_to_localnet_and_devnet() { let devnet = kb_onchain_transport::GenesisHashResult { genesis_hash: std::string::String::from("devnet"), classified_cluster: std::option::Option::Some(kb_lib::ExApiExecutionCluster::Devnet), }; assert!(super::validate_cluster(kb_lib::ExApiExecutionCluster::Devnet, &devnet,).is_ok()); assert!( super::validate_cluster(kb_lib::ExApiExecutionCluster::Localnet, &devnet,).is_err() ); let local = kb_onchain_transport::GenesisHashResult { genesis_hash: std::string::String::from("local"), classified_cluster: std::option::Option::None, }; assert!(super::validate_cluster(kb_lib::ExApiExecutionCluster::Localnet, &local,).is_ok()); } #[test] fn alt_state_parser_preserves_authority_lifecycle_and_capacity() { let authority = solana_pubkey::Pubkey::new_unique(); let mut data = vec![0_u8; 56 + 64]; data[..4].copy_from_slice(&1_u32.to_le_bytes()); data[4..12].copy_from_slice(&u64::MAX.to_le_bytes()); data[21] = 1; let authority_bytes = authority.to_bytes(); data[22..54].copy_from_slice(&authority_bytes); let state = super::parse_alt_state(data.as_slice()) .unwrap_or_else(|error| panic!("unexpected ALT state error: {error}")); assert_eq!(state.deactivation_slot, u64::MAX); assert_eq!(state.authority, std::option::Option::Some(authority.to_string())); assert_eq!(state.address_count, 2); data.push(0); assert!(super::parse_alt_state(data.as_slice()).is_err()); } #[test] fn slashing_proof_shape_is_bounded_and_exact() { let mut data = std::vec::Vec::new(); data.extend_from_slice(&3_u32.to_le_bytes()); data.extend_from_slice(&[1, 2, 3]); data.extend_from_slice(&2_u32.to_le_bytes()); data.extend_from_slice(&[4, 5]); assert_eq!( super::duplicate_block_proof_length(data.as_slice(), 0) .unwrap_or_else(|error| panic!("unexpected proof shape error: {error}")), 13, ); assert!(super::duplicate_block_proof_length(&data[..12], 0).is_err()); let mut too_large = std::vec::Vec::new(); too_large.extend_from_slice(&1_233_u32.to_le_bytes()); assert!(super::duplicate_block_proof_length(too_large.as_slice(), 0,).is_err()); } #[test] fn proof_discriminators_map_to_exact_context_sizes() { for discriminator in 1_u8..=12 { let proof_type = super::proof_type_from_discriminator(discriminator) .unwrap_or_else(|| panic!("missing proof type {discriminator}")); assert_eq!(proof_type.discriminator(), discriminator); assert!(proof_type.context_state_size() > super::ZK_CONTEXT_META_SIZE); } assert_eq!( kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU64.context_state_size(), kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU256.context_state_size(), ); assert!(super::proof_type_from_discriminator(0).is_none()); } #[test] fn compact_u16_length_matches_config_key_boundaries() { assert_eq!(super::compact_u16_length(0), 1); assert_eq!(super::compact_u16_length(127), 1); assert_eq!(super::compact_u16_length(128), 2); assert_eq!(super::compact_u16_length(16_384), 3); } }