指令內省

指令內省是 Solana 上一項強大的功能,允許程式分析同一交易中的其他指令,包括尚未執行的指令。這使您能夠動態地回應或增強這些指令,例如注入安全措施、驗證行為,或將外部程式的指令整合到您自己的邏輯中。
這是通過一個名為 Instructions 的特殊系統帳戶(sysvar)實現的。Sysvar 是由 Solana 運行時維護的只讀帳戶,向程式公開內部狀態(例如時鐘、租金、時代計劃等)。Instructions sysvar 專門公開當前交易中的完整指令列表,以及它們的元數據和序列化數據。
以下是 Solana 在運行時如何序列化這些資訊:
// 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
}這意味著通過在您的程式中讀取 Instructions sysvar 帳戶,您可以訪問當前交易中包含的所有指令。
您不需要手動解析原始位元組。Solana 提供了輔助函數:
load_current_index_checked:返回當前執行指令的索引。load_instruction_at_checked:允許您按索引以解析、反序列化的格式載入特定指令。
如何運作?
每個 Solana 指令包含:
它目標的程式 ID,
它互動的帳戶(包括簽名者/可寫等元數據),
以及數據負載(通常是識別碼 + 參數)。
而這正是 Instructions sysvar 中的 load_instruction_at_checked 函數提供的內容:
program_id:此指令調用的程式。
accounts:包含元數據的相關帳戶列表。
data:原始輸入負載(如果我們正在內省一個 Anchor 指令,它通常以 8 字節的識別碼開頭,後跟參數)。
為了安全且高效地檢查其他指令,請按照以下步驟進行:
確定當前指令的索引:使用
load_current_index_checked來找到當前執行指令的索引。
加載目標指令進行分析:獲得索引後,使用
load_instruction_at_checked(index)加載同一交易中的另一個指令——可以是前一個、後一個或特定位置的指令。
建立約束以防止惡意行為:指令檢查功能非常強大,但也引入了新的攻擊面。請確保:驗證被檢查的指令是否針對預期的程序,確認帳戶地址和數據是否符合預期模式,並避免對指令順序做出假設,除非有明確的強制規定。
通過採取這些預防措施,您可以安全地利用指令檢查功能來構建強大、可組合且安全的Solana程序。
Common Constraints for Introspection
在使用指令檢查時,必須強制執行嚴格的約束以防止惡意或意外行為。最常見的安全措施包括:
指令驗證:驗證指令的程序ID和識別碼,以確保其正確。識別碼(通常是指令數據的前1、4或8個字節)唯一標識正在調用的功能。
變量驗證:在識別碼之後,解析並檢查指令使用的關鍵變量。這些可能包括金額、方向(例如多/空)或ID,您應始終確認這些字段是否符合您的集成或安全措施的預期邏輯。
帳戶驗證:驗證指令帳戶的結構和身份。檢查預期帳戶是否出現在特定位置(例如簽署者、保管庫、抵押品),並確保角色如簽署者/可寫狀態符合您的假設。
通過應用這些限制,您可以確保您的程式僅回應有效且受信任的指令,從而使您的邏輯更加穩健、可組合和安全。
結合交易的原子性,這些檢查允許您構建穩健、可組合的邏輯,能夠在同一筆交易中安全地與其他程式和指令互動。