General
Інтроспекція інструкцій

Інтроспекція інструкцій

Інтроспекція інструкцій

Інтроспекція інструкцій

Інтроспекція інструкцій — це потужна можливість у Solana, яка дозволяє програмі аналізувати інші інструкції в межах тієї ж транзакції, включаючи ті, які ще не виконані. Це дає змогу динамічно реагувати на ці інструкції або розширювати їх — наприклад, додаючи захисні механізми, перевіряючи поведінку або інтегруючи інструкції від зовнішніх програм у вашу власну логіку.

Це стає можливим завдяки спеціальному системному обліковому запису, який називається Instructions sysvar. Sysvar — це облікові записи лише для читання, які підтримуються середовищем виконання Solana і надають програмам доступ до внутрішнього стану (наприклад, годинник, оренда, розклад епох тощо). Sysvar Instructions конкретно надає повний список інструкцій у поточній транзакції, разом з їхніми метаданими та серіалізованими даними.

Ось як Solana серіалізує цю інформацію під час виконання:

rust
// First encode the number of instructions:
//  0..2 - num_instructions
//
// Then a table of offsets of where to find them in the data
//  3..2 * num_instructions table of instruction offsets
//
// Each instruction is then encoded as:
//   0..2 - num_accounts
//   2 - meta_byte -> (bit 0 signer, bit 1 is_writable)
//   3..35 - pubkey - 32 bytes
//   35..67 - program_id
//   67..69 - data len - u16
//   69..data_len - data
#[cfg(not(target_os = "solana"))]
fn serialize_instructions(instructions: &[BorrowedInstruction]) -> Vec<u8> {
    // 64 bytes is a reasonable guess, calculating exactly is slower in benchmarks
    let mut data = Vec::with_capacity(instructions.len() * (32 * 2));
    append_u16(&mut data, instructions.len() as u16);
    for _ in 0..instructions.len() {
        append_u16(&mut data, 0);
    }
 
    for (i, instruction) in instructions.iter().enumerate() {
        let start_instruction_offset = data.len() as u16;
        let start = 2 + (2 * i);
        data[start..start + 2].copy_from_slice(&start_instruction_offset.to_le_bytes());
        append_u16(&mut data, instruction.accounts.len() as u16);
        for account_meta in &instruction.accounts {
            let mut account_meta_flags = InstructionsSysvarAccountMeta::empty();
            if account_meta.is_signer {
                account_meta_flags |= InstructionsSysvarAccountMeta::IS_SIGNER;
            }
            if account_meta.is_writable {
                account_meta_flags |= InstructionsSysvarAccountMeta::IS_WRITABLE;
            }
            append_u8(&mut data, account_meta_flags.bits());
            append_slice(&mut data, account_meta.pubkey.as_ref());
        }
 
        append_slice(&mut data, instruction.program_id.as_ref());
        append_u16(&mut data, instruction.data.len() as u16);
        append_slice(&mut data, instruction.data);
    }
    data
}

Це означає, що читаючи обліковий запис sysvar Instructions всередині вашої програми, ви можете отримати доступ до всіх інструкцій, включених у поточну транзакцію.

Вам не потрібно вручну аналізувати необроблені байти. Solana надає допоміжні функції:

  • load_current_index_checked: повертає індекс інструкції, яка зараз виконується.
  • load_instruction_at_checked: дозволяє завантажити конкретну інструкцію за її індексом у розібраному, десеріалізованому форматі.

Як це працює?

Кожна інструкція Solana містить:

  • ідентифікатор програми, на яку вона націлена,
  • облікові записи, з якими вона взаємодіє (включаючи метадані, такі як підписант/можливість запису),
  • та корисне навантаження даних (зазвичай дискримінатор + аргументи).

І саме це вам надає функція load_instruction_at_checked з sysvar Instructions:

  • program_id: програма, яку викликає ця інструкція.
  • accounts: список задіяних облікових записів з метаданими.
  • data: необроблене вхідне корисне навантаження (якщо ми проводимо інтроспекцію інструкції anchor, вона часто починається з 8-байтового дискримінатора, за яким ідуть параметри).

Щоб безпечно та ефективно досліджувати інші інструкції, дотримуйтесь таких кроків:

  • Визначте індекс поточної інструкції: Використовуйте load_current_index_checked для знаходження індексу інструкції, що виконується.

Майте на увазі: ваша інструкція може бути не першою (індекс 0) у транзакції.

  • Завантажте цільову інструкцію для аналізу: Маючи індекс, використовуйте load_instruction_at_checked(index) для завантаження іншої інструкції в тій самій транзакції — попередньої, наступної або за конкретною позицією.

Це корисно для перевірки поведінки, забезпечення очікуваних вхідних даних або безпечної композиції між програмами.

  • Створіть обмеження для запобігання зловмисній поведінці: Інтроспекція інструкцій — потужний інструмент, але вона створює нові вразливості. Обов'язково: перевіряйте, що досліджувана інструкція націлена на очікувану програму, підтверджуйте, що адреси облікових записів і дані відповідають очікуваним шаблонам, та уникайте припущень щодо порядку інструкцій, якщо це явно не забезпечено.

Дотримуючись цих запобіжних заходів, ви можете безпечно використовувати інтроспекцію інструкцій для створення потужних, компонованих і безпечних програм Solana.

Загальні обмеження для інтроспекції

При використанні інтроспекції інструкцій важливо застосовувати суворі обмеження для запобігання зловмисній або неочікуваній поведінці. Найпоширеніші запобіжні заходи включають:

  • Перевірка інструкції: Перевіряйте ідентифікатор програми та дискримінатор інструкції, щоб переконатися, що вона правильна. Дискримінатор (зазвичай перші 1, 4 або 8 байтів даних інструкції) унікально ідентифікує функцію, яка викликається.

Цей крок гарантує, що ви досліджуєте потрібну інструкцію, а не підроблену чи спотворену.

  • Перевірка змінних: Після дискримінатора проаналізуйте та перевірте критичні змінні, які використовуються інструкцією. Це можуть бути суми, напрямки (наприклад, довгі/короткі) або ідентифікатори, і ви завжди повинні підтверджувати, що ці поля відповідають очікуваній логіці вашої інтеграції чи захисту.
  • Перевірка облікових записів: Перевіряйте структуру та ідентичність облікових записів інструкції. Перевіряйте, що очікувані облікові записи з'являються на конкретних позиціях (наприклад, підписувач, сховище, застава) і переконайтеся, що ролі, такі як статус підписувача/можливість запису, відповідають вашим припущенням.

Застосовуючи ці обмеження, ви гарантуєте, що ваша програма реагує лише на дійсні та надійні інструкції, роблячи вашу логіку більш надійною, компонованою та безпечною.

Разом з атомарністю транзакцій, ці перевірки дозволяють вам створювати надійну, компоновану логіку, яка може безпечно взаємодіяти з іншими програмами та інструкціями в межах однієї транзакції.

Пам'ятайте, транзакції в Solana є атомарними. Якщо будь-яка інструкція не виконується, вся транзакція скасовується.

Blueshift © 2025Commit: 6d01265