From 762c672e8c0ea56d8bd6731e55c6c4b5c5d065ce Mon Sep 17 00:00:00 2001 From: 0xl3on <8888destiny@proton.me> Date: Mon, 27 Apr 2026 14:02:19 -0300 Subject: [PATCH] fix(rpc): strip LoaderV4State prefix when downloading v4 programs --- src/main.rs | 20 ++++++++++++++++---- src/rpc.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index a99c8f8..78d10bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,8 @@ use std::sync::{Arc, Mutex}; use rpc::{ ProgramAccount, derive_programdata_address, fetch_all_programdata_accounts, - fetch_programs_for_loader, loader_version_name, parse_programdata, + fetch_programs_for_loader, loader_version_name, parse_loaderv4_program, + parse_programdata, }; /// Dummy context object for static analysis (no execution needed) @@ -273,7 +274,7 @@ fn download_programs( |(program_id, exec_pubkey_opt, stored_slot, loader_version)| { // Determine which account to fetch from // v3 (BPFUpgradeable): fetch from derived executable account - // v1, v2: fetch from program account directly + // v1, v2, v4: fetch from program account directly let account_to_fetch = if *loader_version == 3 { // BPFUpgradeable - use derived executable account match exec_pubkey_opt { @@ -286,7 +287,7 @@ fn download_programs( } } } else { - // v0, v1, v2 - use program account directly + // v1, v2, v4 - use program account directly program_id.clone() }; @@ -337,10 +338,21 @@ fn download_programs( }; } } + } else if *loader_version == 4 { + // LoaderV4 - strip 48-byte LoaderV4State prefix to get the ELF + // (see solana_loader_v4_interface::state::LoaderV4State) + match parse_loaderv4_program(&account_data) { + Ok(data) => data, + Err(_) => { + return DownloadResult { + program_id: program_id.clone(), + status: DownloadStatus::Error, + }; + } + } } else { // v1, v2 - raw program data (no slot tracking, no upgrade authority) // Use 1 as a dummy slot since these loaders don't have slot tracking - // v0 is already filtered out above (1, None, account_data) }; diff --git a/src/rpc.rs b/src/rpc.rs index 2abf50c..94619dc 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -467,6 +467,39 @@ fn parse_loaderv4_header(data: &[u8]) -> Result<(u64, Option)> { Ok((status, sbpf_version)) } +/// Parse a complete LoaderV4 program account: 48-byte state header + ELF. +/// Layout: slot `u64` (0..8) + authority `Pubkey` (8..40) + status `u64` +/// (40..48); ELF begins at offset 48. Authority is returned as `None` for +/// `Finalized` (status=2) since the field then carries a next-version address. +/// Source: `solana_loader_v4_interface::state::LoaderV4State`. +pub fn parse_loaderv4_program(data: &[u8]) -> Result<(u64, Option, Vec)> { + if data.len() < LOADERV4_STATE_SIZE { + anyhow::bail!("LoaderV4 account too small"); + } + let slot = u64::from_le_bytes(data[0..8].try_into()?); + let authority_bytes: [u8; 32] = data[8..40].try_into()?; + let status = u64::from_le_bytes(data[40..48].try_into()?); + + // Finalized: field is the next-version address, not an upgrade authority. + let authority = match status { + 2 => None, + _ => Some(Pubkey::new_from_array(authority_bytes)), + }; + + if data.len() < LOADERV4_STATE_SIZE + 4 { + anyhow::bail!("No ELF data found after LoaderV4 state header"); + } + if &data[LOADERV4_STATE_SIZE..LOADERV4_STATE_SIZE + 4] != ELF_MAGIC { + anyhow::bail!( + "Invalid ELF magic after LoaderV4 state header: {:02x?}", + &data[LOADERV4_STATE_SIZE..LOADERV4_STATE_SIZE + 4] + ); + } + + let elf_data = data[LOADERV4_STATE_SIZE..].to_vec(); + Ok((slot, authority, elf_data)) +} + /// Finalization classification for a program entry. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FinalizationStatus {