Add deactivation cooldown before address lookup tables can be closed (#22011)
This commit is contained in:
@@ -31,7 +31,7 @@ pub enum ProgramInstruction {
|
||||
bump_seed: u8,
|
||||
},
|
||||
|
||||
/// Permanently freeze a address lookup table, making it immutable.
|
||||
/// Permanently freeze an address lookup table, making it immutable.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[WRITE]` Address lookup table account to freeze
|
||||
@@ -47,6 +47,14 @@ pub enum ProgramInstruction {
|
||||
/// 3. `[]` System program for CPI.
|
||||
ExtendLookupTable { new_addresses: Vec<Pubkey> },
|
||||
|
||||
/// Deactivate an address lookup table, making it unusable and
|
||||
/// eligible for closure after a short period of time.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[WRITE]` Address lookup table account to deactivate
|
||||
/// 1. `[SIGNER]` Current authority
|
||||
DeactivateLookupTable,
|
||||
|
||||
/// Close an address lookup table account
|
||||
///
|
||||
/// # Account references
|
||||
@@ -127,6 +135,23 @@ pub fn extend_lookup_table(
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs an instruction that deactivates an address lookup
|
||||
/// table so that it cannot be extended again and will be unusable
|
||||
/// and eligible for closure after a short amount of time.
|
||||
pub fn deactivate_lookup_table(
|
||||
lookup_table_address: Pubkey,
|
||||
authority_address: Pubkey,
|
||||
) -> Instruction {
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&ProgramInstruction::DeactivateLookupTable,
|
||||
vec![
|
||||
AccountMeta::new(lookup_table_address, false),
|
||||
AccountMeta::new_readonly(authority_address, true),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an instruction that closes an address lookup table
|
||||
/// account. The account will be deallocated and the lamports
|
||||
/// will be drained to the recipient address.
|
||||
|
@@ -47,6 +47,9 @@ pub fn process_instruction(
|
||||
ProgramInstruction::ExtendLookupTable { new_addresses } => {
|
||||
Processor::extend_lookup_table(invoke_context, first_instruction_account, new_addresses)
|
||||
}
|
||||
ProgramInstruction::DeactivateLookupTable => {
|
||||
Processor::deactivate_lookup_table(invoke_context, first_instruction_account)
|
||||
}
|
||||
ProgramInstruction::CloseLookupTable => {
|
||||
Processor::close_lookup_table(invoke_context, first_instruction_account)
|
||||
}
|
||||
@@ -152,7 +155,6 @@ impl Processor {
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account)?;
|
||||
lookup_table_account.set_state(&ProgramState::LookupTable(LookupTableMeta::new(
|
||||
authority_key,
|
||||
derivation_slot,
|
||||
)))?;
|
||||
|
||||
Ok(())
|
||||
@@ -187,6 +189,10 @@ impl Processor {
|
||||
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
||||
return Err(InstructionError::IncorrectAuthority);
|
||||
}
|
||||
if lookup_table.meta.deactivation_slot != Slot::MAX {
|
||||
ic_msg!(invoke_context, "Deactivated tables cannot be frozen");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
if lookup_table.addresses.is_empty() {
|
||||
ic_msg!(invoke_context, "Empty lookup tables cannot be frozen");
|
||||
return Err(InstructionError::InvalidInstructionData);
|
||||
@@ -244,6 +250,10 @@ impl Processor {
|
||||
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
||||
return Err(InstructionError::IncorrectAuthority);
|
||||
}
|
||||
if lookup_table.meta.deactivation_slot != Slot::MAX {
|
||||
ic_msg!(invoke_context, "Deactivated tables cannot be extended");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
if lookup_table.addresses.len() >= LOOKUP_TABLE_MAX_ADDRESSES {
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
@@ -320,6 +330,56 @@ impl Processor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deactivate_lookup_table(
|
||||
invoke_context: &mut InvokeContext,
|
||||
first_instruction_account: usize,
|
||||
) -> Result<(), InstructionError> {
|
||||
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
||||
|
||||
let lookup_table_account =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account)?;
|
||||
if lookup_table_account.owner()? != crate::id() {
|
||||
return Err(InstructionError::InvalidAccountOwner);
|
||||
}
|
||||
|
||||
let authority_account =
|
||||
keyed_account_at_index(keyed_accounts, checked_add(first_instruction_account, 1)?)?;
|
||||
if authority_account.signer_key().is_none() {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
let lookup_table_account_ref = lookup_table_account.try_account_ref()?;
|
||||
let lookup_table_data = lookup_table_account_ref.data();
|
||||
let lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
|
||||
|
||||
if lookup_table.meta.authority.is_none() {
|
||||
ic_msg!(invoke_context, "Lookup table is frozen");
|
||||
return Err(InstructionError::Immutable);
|
||||
}
|
||||
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
||||
return Err(InstructionError::IncorrectAuthority);
|
||||
}
|
||||
if lookup_table.meta.deactivation_slot != Slot::MAX {
|
||||
ic_msg!(invoke_context, "Lookup table is already deactivated");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
|
||||
let mut lookup_table_meta = lookup_table.meta;
|
||||
drop(lookup_table_account_ref);
|
||||
|
||||
let clock: Clock = invoke_context.get_sysvar(&clock::id())?;
|
||||
lookup_table_meta.deactivation_slot = clock.slot;
|
||||
|
||||
AddressLookupTable::overwrite_meta_data(
|
||||
lookup_table_account
|
||||
.try_account_ref_mut()?
|
||||
.data_as_mut_slice(),
|
||||
lookup_table_meta,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close_lookup_table(
|
||||
invoke_context: &mut InvokeContext,
|
||||
first_instruction_account: usize,
|
||||
@@ -353,16 +413,24 @@ impl Processor {
|
||||
let lookup_table = AddressLookupTable::deserialize(lookup_table_data)?;
|
||||
|
||||
if lookup_table.meta.authority.is_none() {
|
||||
ic_msg!(invoke_context, "Lookup table is frozen");
|
||||
return Err(InstructionError::Immutable);
|
||||
}
|
||||
if lookup_table.meta.authority != Some(*authority_account.unsigned_key()) {
|
||||
return Err(InstructionError::IncorrectAuthority);
|
||||
}
|
||||
if lookup_table.meta.deactivation_slot == Slot::MAX {
|
||||
ic_msg!(invoke_context, "Lookup table is not deactivated");
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
|
||||
// Assert that the slot used in the derivation path of the lookup table address
|
||||
// is no longer recent and can't be reused to initialize an account at the same address.
|
||||
// Assert that the deactivation slot is no longer recent to give in-flight transactions
|
||||
// enough time to land and to remove indeterminism caused by transactions loading
|
||||
// addresses in the same slot when a table is closed. This enforced delay has a side
|
||||
// effect of not allowing lookup tables to be recreated at the same derived address
|
||||
// because tables must be created at an address derived from a recent slot.
|
||||
let slot_hashes: SlotHashes = invoke_context.get_sysvar(&slot_hashes::id())?;
|
||||
if let Some(position) = slot_hashes.position(&lookup_table.meta.derivation_slot) {
|
||||
if let Some(position) = slot_hashes.position(&lookup_table.meta.deactivation_slot) {
|
||||
let expiration = MAX_ENTRIES.saturating_sub(position);
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
|
@@ -22,12 +22,11 @@ pub enum ProgramState {
|
||||
}
|
||||
|
||||
/// Address lookup table metadata
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, AbiExample)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, AbiExample)]
|
||||
pub struct LookupTableMeta {
|
||||
/// The slot used to derive the table's address. The table cannot
|
||||
/// be closed until the derivation slot is no longer "recent"
|
||||
/// (not accessible in the `SlotHashes` sysvar).
|
||||
pub derivation_slot: Slot,
|
||||
/// Lookup tables cannot be closed until the deactivation slot is
|
||||
/// no longer "recent" (not accessible in the `SlotHashes` sysvar).
|
||||
pub deactivation_slot: Slot,
|
||||
/// The slot that the table was last extended. Address tables may
|
||||
/// only be used to lookup addresses that were extended before
|
||||
/// the current bank's slot.
|
||||
@@ -43,10 +42,21 @@ pub struct LookupTableMeta {
|
||||
// the account's data, starting from `LOOKUP_TABLE_META_SIZE`.
|
||||
}
|
||||
|
||||
impl Default for LookupTableMeta {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
deactivation_slot: Slot::MAX,
|
||||
last_extended_slot: 0,
|
||||
last_extended_slot_start_index: 0,
|
||||
authority: None,
|
||||
_padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LookupTableMeta {
|
||||
pub fn new(authority: Pubkey, derivation_slot: Slot) -> Self {
|
||||
pub fn new(authority: Pubkey) -> Self {
|
||||
LookupTableMeta {
|
||||
derivation_slot,
|
||||
authority: Some(authority),
|
||||
..LookupTableMeta::default()
|
||||
}
|
||||
|
Reference in New Issue
Block a user