Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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()
};

Expand Down Expand Up @@ -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)
};

Expand Down
33 changes: 33 additions & 0 deletions src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,39 @@ fn parse_loaderv4_header(data: &[u8]) -> Result<(u64, Option<u32>)> {
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<Pubkey>, Vec<u8>)> {
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 {
Expand Down