Skip to content

Commit

Permalink
Add support for address table lookups from Accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed Jan 2, 2022
1 parent 75a294c commit ff20b52
Show file tree
Hide file tree
Showing 7 changed files with 416 additions and 3 deletions.
6 changes: 5 additions & 1 deletion accountsdb-plugin-postgres/scripts/create_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ Create TYPE "TransactionErrorCode" AS ENUM (
'WouldExceedMaxBlockCostLimit',
'UnsupportedVersion',
'InvalidWritableAccount',
'WouldExceedMaxAccountDataCostLimit'
'WouldExceedMaxAccountDataCostLimit',
'AddressLookupTableNotFound',
'InvalidAddressLookupTableOwner',
'InvalidAddressLookupTableData',
'InvalidAddressLookupTableIndex',
);

CREATE TYPE "TransactionError" AS (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,10 @@ pub enum DbTransactionErrorCode {
UnsupportedVersion,
InvalidWritableAccount,
WouldExceedMaxAccountDataCostLimit,
AddressLookupTableNotFound,
InvalidAddressLookupTableOwner,
InvalidAddressLookupTableData,
InvalidAddressLookupTableIndex,
}

impl From<&TransactionError> for DbTransactionErrorCode {
Expand Down Expand Up @@ -362,6 +366,14 @@ impl From<&TransactionError> for DbTransactionErrorCode {
TransactionError::WouldExceedMaxAccountDataCostLimit => {
Self::WouldExceedMaxAccountDataCostLimit
}
TransactionError::AddressLookupTableNotFound => Self::AddressLookupTableNotFound,
TransactionError::InvalidAddressLookupTableOwner => {
Self::InvalidAddressLookupTableOwner
}
TransactionError::InvalidAddressLookupTableData => Self::InvalidAddressLookupTableData,
TransactionError::InvalidAddressLookupTableIndex => {
Self::InvalidAddressLookupTableIndex
}
}
}
}
Expand Down
147 changes: 147 additions & 0 deletions programs/address-lookup-table/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use {
instruction::InstructionError,
pubkey::Pubkey,
slot_hashes::{SlotHashes, MAX_ENTRIES},
transaction::AddressLookupError,
},
std::borrow::Cow,
};
Expand Down Expand Up @@ -133,6 +134,39 @@ impl<'a> AddressLookupTable<'a> {
Ok(())
}

/// Lookup addresses for provided table indexes. Since lookups are performed on
/// tables which are not read-locked, this implementation needs to be careful
/// about resolving addresses consistently.
pub fn lookup(
&self,
current_slot: Slot,
indexes: &[u8],
slot_hashes: &SlotHashes,
) -> Result<Vec<Pubkey>, AddressLookupError> {
if !self.meta.is_active(current_slot, slot_hashes) {
// Once a lookup table is no longer active, it can be closed
// at any point, so returning a specific error for deactivated
// lookup tables could result in a race condition.
return Err(AddressLookupError::LookupTableAccountNotFound);
}

// If the address table was extended in the same slot in which it is used
// to lookup addresses for another transaction, the recently extended
// addresses are not considered active and won't be accessible.
let active_addresses_len = if current_slot > self.meta.last_extended_slot {
self.addresses.len()
} else {
self.meta.last_extended_slot_start_index as usize
};

let active_addresses = &self.addresses[0..active_addresses_len];
indexes
.iter()
.map(|idx| active_addresses.get(*idx as usize).cloned())
.collect::<Option<_>>()
.ok_or(AddressLookupError::InvalidLookupIndex)
}

/// Serialize an address table including its addresses
pub fn serialize_for_tests(self, data: &mut Vec<u8>) -> Result<(), InstructionError> {
data.resize(LOOKUP_TABLE_META_SIZE, 0);
Expand Down Expand Up @@ -322,4 +356,117 @@ mod tests {
test_case(case);
}
}

#[test]
fn test_lookup_from_empty_table() {
let lookup_table = AddressLookupTable {
meta: LookupTableMeta::default(),
addresses: Cow::Owned(vec![]),
};

assert_eq!(
lookup_table.lookup(0, &[], &SlotHashes::default()),
Ok(vec![])
);
assert_eq!(
lookup_table.lookup(0, &[0], &SlotHashes::default()),
Err(AddressLookupError::InvalidLookupIndex)
);
}

#[test]
fn test_lookup_from_deactivating_table() {
let current_slot = 1;
let slot_hashes = SlotHashes::default();
let addresses = vec![Pubkey::new_unique()];
let lookup_table = AddressLookupTable {
meta: LookupTableMeta {
deactivation_slot: current_slot,
last_extended_slot: current_slot - 1,
..LookupTableMeta::default()
},
addresses: Cow::Owned(addresses.clone()),
};

assert_eq!(
lookup_table.meta.status(current_slot, &slot_hashes),
LookupTableStatus::Deactivating {
remaining_blocks: MAX_ENTRIES + 1
}
);

assert_eq!(
lookup_table.lookup(current_slot, &[0], &slot_hashes),
Ok(vec![addresses[0]]),
);
}

#[test]
fn test_lookup_from_deactivated_table() {
let current_slot = 1;
let slot_hashes = SlotHashes::default();
let lookup_table = AddressLookupTable {
meta: LookupTableMeta {
deactivation_slot: current_slot - 1,
last_extended_slot: current_slot - 1,
..LookupTableMeta::default()
},
addresses: Cow::Owned(vec![]),
};

assert_eq!(
lookup_table.meta.status(current_slot, &slot_hashes),
LookupTableStatus::Deactivated
);
assert_eq!(
lookup_table.lookup(current_slot, &[0], &slot_hashes),
Err(AddressLookupError::LookupTableAccountNotFound)
);
}

#[test]
fn test_lookup_from_table_extended_in_current_slot() {
let current_slot = 0;
let addresses: Vec<_> = (0..2).map(|_| Pubkey::new_unique()).collect();
let lookup_table = AddressLookupTable {
meta: LookupTableMeta {
last_extended_slot: current_slot,
last_extended_slot_start_index: 1,
..LookupTableMeta::default()
},
addresses: Cow::Owned(addresses.clone()),
};

assert_eq!(
lookup_table.lookup(current_slot, &[0], &SlotHashes::default()),
Ok(vec![addresses[0]])
);
assert_eq!(
lookup_table.lookup(current_slot, &[1], &SlotHashes::default()),
Err(AddressLookupError::InvalidLookupIndex),
);
}

#[test]
fn test_lookup_from_table_extended_in_previous_slot() {
let current_slot = 1;
let addresses: Vec<_> = (0..10).map(|_| Pubkey::new_unique()).collect();
let lookup_table = AddressLookupTable {
meta: LookupTableMeta {
last_extended_slot: current_slot - 1,
last_extended_slot_start_index: 1,
..LookupTableMeta::default()
},
addresses: Cow::Owned(addresses.clone()),
};

assert_eq!(
lookup_table.lookup(current_slot, &[0, 3, 1, 5], &SlotHashes::default()),
Ok(vec![addresses[0], addresses[3], addresses[1], addresses[5]])
);
assert_eq!(
lookup_table.lookup(current_slot, &[10], &SlotHashes::default()),
Err(AddressLookupError::InvalidLookupIndex),
);
}
}
Loading

0 comments on commit ff20b52

Please sign in to comment.