Skip to content

Commit

Permalink
Parse address-lookup-table instructions (solana-labs#27316)
Browse files Browse the repository at this point in the history
* Parse address-lookup-table instructions

* Finish extend instruction handling

* Rename payer, recipient

* Update docs parsing status
  • Loading branch information
Tyera Eulberg authored Aug 24, 2022
1 parent ed463dd commit 62eebe6
Show file tree
Hide file tree
Showing 7 changed files with 373 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/src/developing/clients/jsonrpc-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ JSON parsing for the following native and SPL programs:

| Program | Account State | Instructions |
| --- | --- | --- |
| Address Lookup | v1.12.0 | |
| Address Lookup | v1.12.0 | v1.12.0 |
| BPF Loader | n/a | stable |
| BPF Upgradeable Loader | stable | stable |
| Config | stable | |
Expand Down
1 change: 1 addition & 0 deletions programs/bpf/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions transaction-status/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ serde = "1.0.143"
serde_derive = "1.0.103"
serde_json = "1.0.83"
solana-account-decoder = { path = "../account-decoder", version = "=1.12.0" }
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.12.0" }
solana-measure = { path = "../measure", version = "=1.12.0" }
solana-metrics = { path = "../metrics", version = "=1.12.0" }
solana-sdk = { path = "../sdk", version = "=1.12.0" }
Expand Down
1 change: 1 addition & 0 deletions transaction-status/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ extern crate serde_derive;

pub mod extract_memos;
pub mod parse_accounts;
pub mod parse_address_lookup_table;
pub mod parse_associated_token;
pub mod parse_bpf_loader;
pub mod parse_instruction;
Expand Down
358 changes: 358 additions & 0 deletions transaction-status/src/parse_address_lookup_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,358 @@
use {
crate::parse_instruction::{
check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
},
bincode::deserialize,
serde_json::json,
solana_address_lookup_table_program::instruction::ProgramInstruction,
solana_sdk::{instruction::CompiledInstruction, message::AccountKeys},
};

pub fn parse_address_lookup_table(
instruction: &CompiledInstruction,
account_keys: &AccountKeys,
) -> Result<ParsedInstructionEnum, ParseInstructionError> {
let address_lookup_table_instruction: ProgramInstruction = deserialize(&instruction.data)
.map_err(|_| {
ParseInstructionError::InstructionNotParsable(ParsableProgram::AddressLookupTable)
})?;
match instruction.accounts.iter().max() {
Some(index) if (*index as usize) < account_keys.len() => {}
_ => {
// Runtime should prevent this from ever happening
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::AddressLookupTable,
));
}
}
match address_lookup_table_instruction {
ProgramInstruction::CreateLookupTable {
recent_slot,
bump_seed,
} => {
check_num_address_lookup_table_accounts(&instruction.accounts, 4)?;
Ok(ParsedInstructionEnum {
instruction_type: "createLookupTable".to_string(),
info: json!({
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
"payerAccount": account_keys[instruction.accounts[2] as usize].to_string(),
"systemProgram": account_keys[instruction.accounts[3] as usize].to_string(),
"recentSlot": recent_slot,
"bumpSeed": bump_seed,
}),
})
}
ProgramInstruction::FreezeLookupTable => {
check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
Ok(ParsedInstructionEnum {
instruction_type: "freezeLookupTable".to_string(),
info: json!({
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
}),
})
}
ProgramInstruction::ExtendLookupTable { new_addresses } => {
check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
let new_addresses: Vec<String> = new_addresses
.into_iter()
.map(|address| address.to_string())
.collect();
let mut value = json!({
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
"newAddresses": new_addresses,
});
let map = value.as_object_mut().unwrap();
if instruction.accounts.len() >= 4 {
map.insert(
"payerAccount".to_string(),
json!(account_keys[instruction.accounts[2] as usize].to_string()),
);
map.insert(
"systemProgram".to_string(),
json!(account_keys[instruction.accounts[3] as usize].to_string()),
);
}
Ok(ParsedInstructionEnum {
instruction_type: "extendLookupTable".to_string(),
info: value,
})
}
ProgramInstruction::DeactivateLookupTable => {
check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
Ok(ParsedInstructionEnum {
instruction_type: "deactivateLookupTable".to_string(),
info: json!({
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
}),
})
}
ProgramInstruction::CloseLookupTable => {
check_num_address_lookup_table_accounts(&instruction.accounts, 3)?;
Ok(ParsedInstructionEnum {
instruction_type: "closeLookupTable".to_string(),
info: json!({
"lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
"recipient": account_keys[instruction.accounts[2] as usize].to_string(),
}),
})
}
}
}

fn check_num_address_lookup_table_accounts(
accounts: &[u8],
num: usize,
) -> Result<(), ParseInstructionError> {
check_num_accounts(accounts, num, ParsableProgram::AddressLookupTable)
}

#[cfg(test)]
mod test {
use {
super::*,
solana_address_lookup_table_program::instruction,
solana_sdk::{message::Message, pubkey::Pubkey, system_program},
std::str::FromStr,
};

#[test]
fn test_parse_create_address_lookup_table_ix() {
let from_pubkey = Pubkey::new_unique();
// use explicit key to have predicatble bump_seed
let authority = Pubkey::from_str("HkxY6vXdrKzoCQLmdJ3cYo9534FdZQxzBNWTyrJzzqJM").unwrap();
let slot = 42;

let (instruction, lookup_table_pubkey) =
instruction::create_lookup_table(authority, from_pubkey, slot);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "createLookupTable".to_string(),
info: json!({
"lookupTableAccount": lookup_table_pubkey.to_string(),
"lookupTableAuthority": authority.to_string(),
"payerAccount": from_pubkey.to_string(),
"systemProgram": system_program::id().to_string(),
"recentSlot": slot,
"bumpSeed": 254,
}),
}
);
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..3], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&keys, None)
)
.is_err());
}

#[test]
fn test_parse_freeze_lookup_table_ix() {
let lookup_table_pubkey = Pubkey::new_unique();
let authority = Pubkey::new_unique();

let instruction = instruction::freeze_lookup_table(lookup_table_pubkey, authority);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "freezeLookupTable".to_string(),
info: json!({
"lookupTableAccount": lookup_table_pubkey.to_string(),
"lookupTableAuthority": authority.to_string(),
}),
}
);
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..1], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&keys, None)
)
.is_err());
}

#[test]
fn test_parse_extend_lookup_table_ix() {
let lookup_table_pubkey = Pubkey::new_unique();
let authority = Pubkey::new_unique();
let from_pubkey = Pubkey::new_unique();
let no_addresses = vec![];
let address0 = Pubkey::new_unique();
let address1 = Pubkey::new_unique();
let some_addresses = vec![address0, address1];

// No payer, no addresses
let instruction =
instruction::extend_lookup_table(lookup_table_pubkey, authority, None, no_addresses);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "extendLookupTable".to_string(),
info: json!({
"lookupTableAccount": lookup_table_pubkey.to_string(),
"lookupTableAuthority": authority.to_string(),
"newAddresses": [],
}),
}
);
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..1], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&keys, None)
)
.is_err());

// Some payer, some addresses
let instruction = instruction::extend_lookup_table(
lookup_table_pubkey,
authority,
Some(from_pubkey),
some_addresses,
);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "extendLookupTable".to_string(),
info: json!({
"lookupTableAccount": lookup_table_pubkey.to_string(),
"lookupTableAuthority": authority.to_string(),
"payerAccount": from_pubkey.to_string(),
"systemProgram": system_program::id().to_string(),
"newAddresses": [
address0.to_string(),
address1.to_string(),
],
}),
}
);
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..1], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
message.instructions[0].accounts.pop();
message.instructions[0].accounts.pop();
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&keys, None)
)
.is_err());
}

#[test]
fn test_parse_deactivate_lookup_table_ix() {
let lookup_table_pubkey = Pubkey::new_unique();
let authority = Pubkey::new_unique();

let instruction = instruction::deactivate_lookup_table(lookup_table_pubkey, authority);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "deactivateLookupTable".to_string(),
info: json!({
"lookupTableAccount": lookup_table_pubkey.to_string(),
"lookupTableAuthority": authority.to_string(),
}),
}
);
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..1], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&keys, None)
)
.is_err());
}

#[test]
fn test_parse_close_lookup_table_ix() {
let lookup_table_pubkey = Pubkey::new_unique();
let authority = Pubkey::new_unique();
let recipient = Pubkey::new_unique();

let instruction =
instruction::close_lookup_table(lookup_table_pubkey, authority, recipient);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "closeLookupTable".to_string(),
info: json!({
"lookupTableAccount": lookup_table_pubkey.to_string(),
"lookupTableAuthority": authority.to_string(),
"recipient": recipient.to_string(),
}),
}
);
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..2], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
assert!(parse_address_lookup_table(
&message.instructions[0],
&AccountKeys::new(&keys, None)
)
.is_err());
}
}
Loading

0 comments on commit 62eebe6

Please sign in to comment.