use {
crate::{Error, Result},
solana_account::Account,
solana_account_decoder::parse_address_lookup_table::{
LookupTableAccountType,
parse_address_lookup_table,
},
solana_message::AddressLookupTableAccount,
solana_pubkey::Pubkey,
solana_rpc_client::nonblocking::rpc_client::RpcClient,
std::str::FromStr,
tracing::debug,
};
async fn get_multiple_accts(
lookup_tables: &[Pubkey],
rpc: impl AsRef<RpcClient>,
) -> Result<Vec<Option<Account>>> {
rpc.as_ref()
.get_multiple_accounts(lookup_tables)
.await
.map_err(|e| Error::SolanaRpcError(format!("failed to get lookup table accounts: {e}")))
}
fn process_lookup_tables(
lookup_tables: &[Pubkey],
accounts: Vec<Option<Account>>,
) -> Result<Vec<AddressLookupTableAccount>> {
let mut lookup_tables_state = Vec::with_capacity(accounts.len());
for (i, maybe_account) in accounts.iter().enumerate() {
match maybe_account {
None => tracing::warn!("lookup table account {} not found", lookup_tables[i]),
Some(account) => {
let table_type = parse_address_lookup_table(account.data.as_ref())?;
match table_type {
LookupTableAccountType::Uninitialized => {
tracing::warn!("lookup table {} is uninitialized", lookup_tables[i])
}
LookupTableAccountType::LookupTable(table) => {
if table.addresses.is_empty() {
tracing::warn!(
"lookup table addresses are empty for account {}",
lookup_tables[i]
);
continue;
}
let mut addresses = Vec::with_capacity(table.addresses.len());
for a in table.addresses.iter() {
addresses.push(Pubkey::from_str(a)?);
}
lookup_tables_state.push(AddressLookupTableAccount {
key: lookup_tables[i],
addresses,
});
}
}
}
}
}
debug!(
"found {} valid lookup table state accounts",
lookup_tables_state.len()
);
Ok(lookup_tables_state)
}
pub async fn fetch_lookup_tables(
lookup_tables: &[Pubkey],
rpc: impl AsRef<RpcClient>,
) -> Result<Vec<AddressLookupTableAccount>> {
if lookup_tables.is_empty() {
return Ok(Vec::with_capacity(0));
}
debug!(lookup_tables =? lookup_tables.len(), "fetching lookup tables");
let accounts = get_multiple_accts(lookup_tables, rpc).await?;
process_lookup_tables(lookup_tables, accounts)
}
#[cfg(test)]
mod tests {
use {
super::*,
base64::prelude::*,
solana_account::{AccountSharedData, WritableAccount},
solana_address_lookup_table_interface::{
program::ID as LOOKUP_TABLE_PROGRAM_ID,
state::AddressLookupTable,
},
};
const NOT_INITIALIZED: Pubkey =
solana_pubkey::pubkey!("3W6YcoQyFcrSo6K9vixhM2Cfvtjv4KeKSH1FaEKJF1Ug");
const NOT_INITIALIZED_DATA: &str =
"AQAAAP//////////AAAAAAAAAAAAAQ7hKwIsEP/WoRK4pP1+ELuGsEsjs8K1lkPsgzEtmy39AAA=";
const INITIALIZED: Pubkey =
solana_pubkey::pubkey!("FNK9gB5E3cntDRiy3LHwtwQC6qhbVgdBLBMqjRZLEYiK");
const INITIALIZED_DATA: &str = r#"AQAAAP//////////xWHAGAAAAAAAAQ7hKwIsEP/WoRK4pP1+ELuGsEsjs8K1lkPsgzEtmy39AAAGm4hX/quBhPtof2NGGMA12sQ53BrrO1WYoPAAAAAAATtELLORIVfxOpM9ATQoLQMrX/7NAaLb8bd5BgjfAC6nc0qlNf/0kfkCG8En13LFGRXgKeyZs/EkFm0noup/XR8="#;
const EXPECTED_TABLE: [Pubkey; 3] = [
solana_pubkey::pubkey!("So11111111111111111111111111111111111111112"),
solana_pubkey::pubkey!("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"),
solana_pubkey::pubkey!("8m3uKEn4fMPNVr7nv6RmQYktT4zRqEZzhuZDpG8hQZT4"),
];
fn convert(data: &str) -> anyhow::Result<Account> {
let data: Vec<u8> = BASE64_STANDARD.decode(data)?;
let address_lookup = AddressLookupTable::deserialize(&data)?;
let account_shared_data = AccountSharedData::create(
1,
address_lookup.serialize_for_tests()?,
LOOKUP_TABLE_PROGRAM_ID,
false,
0,
);
Ok(account_shared_data.into())
}
#[test]
fn test_empty_table() -> anyhow::Result<()> {
let result = process_lookup_tables(&[], vec![])?;
assert!(result.is_empty());
Ok(())
}
#[test]
fn test_not_initialized() -> anyhow::Result<()> {
let account: Account = convert(NOT_INITIALIZED_DATA)?;
let result = process_lookup_tables(&[NOT_INITIALIZED], vec![Some(account)])?;
assert!(result.is_empty());
Ok(())
}
#[test]
fn test_initialized() -> anyhow::Result<()> {
let account: Account = convert(INITIALIZED_DATA)?;
let result = process_lookup_tables(&[INITIALIZED], vec![Some(account)])?;
assert_eq!(1, result.len());
assert_eq!(result[0].key, INITIALIZED);
assert_eq!(result[0].addresses, EXPECTED_TABLE);
Ok(())
}
#[test]
fn test_mixed() -> anyhow::Result<()> {
let accounts: Vec<Option<Account>> = vec![
Some(convert(INITIALIZED_DATA)?),
Some(convert(NOT_INITIALIZED_DATA)?),
];
let result = process_lookup_tables(&[INITIALIZED, NOT_INITIALIZED], accounts)?;
assert_eq!(1, result.len());
assert_eq!(result[0].key, INITIALIZED);
assert_eq!(result[0].addresses, EXPECTED_TABLE);
Ok(())
}
}