#account #instructions #tlv #interface #resolution #required #discriminator

solarti-tlv-account-resolution

Solarti程序库TLV账户解析接口

3个不稳定版本

0.5.1 2024年3月29日
0.5.0 2023年12月24日
0.4.0 2023年11月21日

#484 in 神奇豆

Download history 252/week @ 2024-03-13 330/week @ 2024-03-20 440/week @ 2024-03-27 218/week @ 2024-04-03 155/week @ 2024-04-10 151/week @ 2024-04-17 164/week @ 2024-04-24 179/week @ 2024-05-01 144/week @ 2024-05-08 139/week @ 2024-05-15 151/week @ 2024-05-22 158/week @ 2024-05-29 151/week @ 2024-06-05 155/week @ 2024-06-12 152/week @ 2024-06-19 98/week @ 2024-06-26

559 每月下载量
68 个crate中使用 (直接使用2个)

Apache-2.0

215KB
4K SLoC

TLV账户解析

定义了一个泛型状态接口的库,用于使用类型-长度-值结构来编码指令所需的其他账户。

示例用法

如果您想将指令所需的其他账户编码到账户中的TLV条目中,可以执行以下操作

use {
    solana_program::{account_info::AccountInfo, instruction::{AccountMeta, Instruction}, pubkey::Pubkey},
    spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
    spl_tlv_account_resolution::{
        account::ExtraAccountMeta,
        seeds::Seed,
        state::ExtraAccountMetaList
    },
};

struct MyInstruction;
impl SplDiscriminate for MyInstruction {
    // For ease of use, give it the same discriminator as its instruction definition
    const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]);
}

// Prepare the additional required account keys and signer / writable
let extra_metas = [
    AccountMeta::new(Pubkey::new_unique(), false).into(),
    AccountMeta::new_readonly(Pubkey::new_unique(), true).into(),
    ExtraAccountMeta::new_with_seeds(
        &[
            Seed::Literal {
                bytes: b"some_string".to_vec(),
            },
            Seed::InstructionData {
                index: 1,
                length: 1, // u8
            },
            Seed::AccountKey { index: 1 },
        ],
        false,
        true,
    ).unwrap(),
    ExtraAccountMeta::new_external_pda_with_seeds(
        0,
        &[Seed::AccountKey { index: 2 }],
        false,
        false,
    ).unwrap(),
];

// Allocate a new buffer with the proper `account_size`
let account_size = ExtraAccountMetaList::size_of(extra_metas.len()).unwrap();
let mut buffer = vec![0; account_size];

// Initialize the structure for your instruction
ExtraAccountMetaList::init::<MyInstruction>(&mut buffer, &extra_metas).unwrap();

// Off-chain, you can add the additional accounts directly from the account data
// You need to provide the resolver a way to fetch account data off-chain
let client = RpcClient::new_mock("succeeds".to_string());
let program_id = Pubkey::new_unique();
let mut instruction = Instruction::new_with_bytes(program_id, &[0, 1, 2], vec![]);
ExtraAccountMetaList::add_to_instruction::<_, _, MyInstruction>(
    &mut instruction,
    |address: &Pubkey| {
        client
            .get_account(address)
            .map_ok(|acct| Some(acct.data))
        },
    &buffer,
)
.await
.unwrap();

// On-chain, you can add the additional accounts *and* account infos
let mut cpi_instruction = Instruction::new_with_bytes(program_id, &[0, 1, 2], vec![]);

// Include all of the well-known required account infos here first
let mut cpi_account_infos = vec![]; 

// Provide all "remaining_account_infos" that are *not* part of any other known interface
let remaining_account_infos = &[]; 
ExtraAccountMetaList::add_to_cpi_instruction::<MyInstruction>(
    &mut cpi_instruction,
    &mut cpi_account_infos,
    &buffer,
    &remaining_account_infos,
).unwrap();

为了便于链上使用,还提供了 ExtraAccountMetaList::init,可以直接从一组给定的账户中初始化。

动机

Miraland账户模型对程序接口提出了独特的挑战。由于链上无法加载额外的账户,如果一个程序需要额外的账户来正确实现指令,客户端没有明确的方式来获取这些账户。

有两种主要方式来获取额外的账户,一种是动态通过程序模拟,另一种是静态地获取账户数据。这个库实现了静态的额外账户解析。您可以在附录中找到有关动态账户解析的更多信息。

静态账户解析

程序可以将所需的其他账户信息写入账户数据中,这样链上和链下客户端只需要读取数据就可以确定所需的其他账户。

而不是通过程序执行动态地暴露这些数据,这种方法使用静态账户数据。

例如,让我们想象有一个 Transferable 接口和 transfer 指令。一些实现 transfer 的程序可能需要的账户比接口中定义的还要多。链上或链下客户端如何确定所需的其他账户?

“静态”方法要求程序将额外的必要账户写入到指定地址定义的账户中。这可以直接在 mint 中进行,或者是从 mint 地址推导出的某个地址。

在链下,客户端必须获取这个额外的账户并读取其数据,以找到额外的必要账户,然后将它们包含在指令中。

在链上,程序必须能够访问包含特殊账户和所有其他必要账户的“剩余账户信息”,以正确创建 CPI 指令并给出正确的账户信息。

这种方法也可以称为“状态接口”。

必要账户类型

这个库能够存储两种额外必要账户的配置

  • 具有固定地址的账户
  • 具有从以下任一组合中生成的种子推导出的 动态程序推导地址 的账户
    • 硬编码的值,如字符串字面量或整数
    • 提供给转移钩子程序的指令数据的切片
    • 账户列表中的另一个账户的地址
    • 指令中另一个账户的程序 ID

当在额外必要账户中存储动态程序推导地址的配置时,PDA 本身在指令调用时被评估(或解析)。这发生在下面提到的链下和链上辅助程序中,它们利用 SPL TLV 账户解析库来自动执行此解析。

工作原理

这个库使用 solarti-type-length-value 从账户数据中读取和写入必要的指令账户。

接口指令必须有一个 8 字节的区分符,以便暴露的 ExtraAccountMetaList 类型可以使用指令区分符作为 ArrayDiscriminator,这允许该区分符作为唯一 TLV 区分符来识别与该特定指令对应的条目。

这可能会让人困惑。通常,类型实现 SplDiscriminate,以便类型可以写入 TLV 数据。在这种情况下,ExtraAccountMetaList 是对 SplDiscriminate 的泛型,这意味着程序可以将多个不同实例的 ExtraAccountMetaList 写入一个账户,使用不同的 ArrayDiscriminator

此外,它重用了指令区分符作为 TLV 区分符。例如,如果 transfer 指令的区分符为 [1, 2, 3, 4, 5, 6, 7, 8],那么账户使用 TLV 区分符 [1, 2, 3, 4, 5, 6, 7, 8] 来表示附加账户元数据的存储位置。

这不是必需的,但可以让客户端更容易找到指令的额外必要账户。

附录

动态账户解析

为了公开所需的额外账户,指令接口可以包括附加指令以返回所需的账户。

例如,在Transferable接口示例中,除了transfer指令外,还需要实现公开一个get_additional_accounts_for_transfer指令。

在程序实现中,该指令将额外账户写入返回数据,使得链上和链下客户端易于消费。

有关动态方法的信息,请参阅相关的sRFC

依赖项

~21–30MB
~477K SLoC