代码分析

版本:v2.0.14
核心代码:https://github.com/anza-xyz/agave/blob/v2.0.14/sdk/program/src/system_instruction.rs

SystemError

system程序调用中,常见的错误

错误类型 错误解释
AccountAlreadyInUse 具有相同地址的帐户已存在
ResultWithNegativeLamports 帐户没有足够的 SOL 来执行操作
InvalidProgramId 无法将帐户分配给此程序 ID
InvalidAccountDataLength 无法分配此长度的帐户数据
MaxSeedLengthExceeded 请求的Seed长度太长
AddressWithSeedMismatch 提供的地址与Seed得出的地址不匹配
NonceNoRecentBlockhashes 推进存储的随机数需要填充的RecentBlockhashes sysvar
NonceBlockhashNotExpired 存储的 nonce 仍在 recent_blockhashes 中
NonceUnexpectedBlockhashValue 指定的 nonce 与存储的 nonce 不匹配

固定变量

变量名 解释 默认值
MAX_PERMITTED_DATA_LENGTH 允许的最大帐户数据大小 10 MiB
MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION 每笔交易允许的新分配的最大大小,以字节为单位 MAX_PERMITTED_DATA_LENGTH x 2

数据结构

系统程序的指令和构造函数。
系统程序负责创建帐户和随机数帐户。它负责从系统程序拥有的帐户(包括典型的用户钱包帐户)转移 Lamport。
帐户创建通常涉及三个步骤:分配空间、转移 Lamport 以进行出租、分配给其拥有程序。create_account 函数一次完成所有三个步骤。所有新帐户必须包含足够的 Lamport 才能免除租金,否则创建指令将失败。
系统程序创建的帐户可以是用户控制的,其中密钥保存在区块链之外,也可以是程序派生的地址,其中拥有程序授予对帐户的写访问权限。
系统程序 ID 在 system_program 中定义。
此模块中的大多数函数都构造一个指令,必须将其提交给运行时执行,可以通过 RPC(通常使用 RpcClient)或通过跨程序调用。
通过 CPI 调用时,invoke 或invoke_signed 指令要求所有帐户引用都明确提供为 AccountInfo 值。所需的帐户引用在每个系统程序指令的 SystemInstruction 变体的文档中指定,并且这些变体与其构造函数的文档链接在一起。

pub enum SystemInstruction {
    /// 创建新账户
    /// # 账户引用
    /// 0. `[WRITE, SIGNER]` 资金账户
    /// 1. `[WRITE, SIGNER]` 新账户
    CreateAccount {
        /// 要转移到新帐户的lamports数量
        lamports: u64,

        /// 要分配的内存字节数
        space: u64,

        /// 所有者程序帐户地址
        owner: Pubkey,
    },

    /// 将账户分配给程序
    /// # 账户引用
    /// 0. `[WRITE, SIGNER]` 分配账户公钥
    Assign {
        /// 程序拥有者账户地址
        owner: Pubkey,
    },

    /// 转账 lampors
    ///
    /// # 账户引用
    /// 0. `[WRITE, SIGNER]` 资金账户
    /// 1. `[WRITE]` 收款账户
    Transfer { lamports: u64 },

    /// 在从基本公钥和种子派生的地址上创建新帐户
    ///
    /// # 帐户引用
    /// 0. `[WRITE, SIGNER]` 资金帐户
    /// 1. `[WRITE]` 创建帐户
    /// 2. `[SIGNER]` (可选) 基本帐户;必须将与以下基本公钥匹配的帐户
    /// 作为签名者提供,但可以与资金帐户相同
    /// 并作为帐户 0 提供
    CreateAccountWithSeed {
        /// 基本公钥
        base: Pubkey,

        /// ASCII 字符的字符串,不超过“Pubkey::MAX_SEED_LEN”
        seed: String,

        /// 要转移到新帐户的lamports数量
        lamports: u64,

        /// 要分配的内存字节数
        space: u64,

        /// 所有者程序帐户地址
        owner: Pubkey,
    },

    /// 使用存储的随机数,用后继者替换它
    ///
    /// # 帐户引用
    /// 0. `[WRITE]` 随机数帐户
    /// 1. `[]` 最近区块哈希系统变量
    /// 2. `[SIGNER]` 随机数权限
    AdvanceNonceAccount,

    /// 从 nonce 账户中提取资金
    ///
    /// # 账户引用
    /// 0. `[WRITE]` Nonce 账户
    /// 1. `[WRITE]` 接收者账户
    /// 2. `[]` RecentBlockhashes 系统变量
    /// 3. `[]` Rent 系统变量
    /// 4. `[SIGNER]` Nonce 权限
    ///
    /// `u64` 参数是要提取的 lampor,它必须使
    /// 账户余额高于免租储备或为零。
    WithdrawNonceAccount(u64),

    /// 将 Uninitialized nonce 账户的状态驱动为 Initialized,设置 nonce 值
    ///
    /// # 账户引用
    /// 0. `[WRITE]` Nonce 账户
    /// 1. `[]` RecentBlockhashes 系统变量
    /// 2. `[]` Rent 系统变量
    ///
    /// `Pubkey` 参数指定有权在账户上执行 nonce 指令的实体
    ///
    /// 执行此指令不需要签名,从而可以派生
    /// nonce 账户地址
    InitializeNonceAccount(Pubkey),

    /// 更改授权在账户上执行 nonce 指令的实体
    ///
    /// # 账户引用
    /// 0. `[WRITE]` Nonce 账户
    /// 1. `[SIGNER]` Nonce 授权
    ///
    /// `Pubkey` 参数标识要授权的实体
    AuthorizeNonceAccount(Pubkey),

    /// 在(可能是新的)账户中分配空间,无需资金
    ///
    /// # 账户引用
    /// 0. `[WRITE, SIGNER]` 新账户
    Allocate {
        /// 要分配的内存字节数
        space: u64,
    },

    /// 为某个地址分配空间并指定一个账户
    /// 源自一个基本公钥和一个种子
    ///
    /// # 账户引用
    /// 0. `[WRITE]` 已分配账户
    /// 1. `[SIGNER]` 基本账户
    AllocateWithSeed {
        /// 基本公钥
        base: Pubkey,

        /// ASCII 字符的字符串,不超过“pubkey::MAX_SEED_LEN”
        seed: String,

        /// 要分配的内存字节数
        space: u64,

        /// 所有者程序帐户地址
        owner: Pubkey,
    },

    /// 根据种子将账户分配给程序
    ///
    /// # 账户引用
    /// 0. `[WRITE]` 分配账户
    /// 1. `[SIGNER]` 基本账户
    AssignWithSeed {
        /// 基本公钥
        base: Pubkey,

        /// ASCII 字符的字符串,不超过“pubkey::MAX_SEED_LEN”
        seed: String,

        /// 所有者程序帐户地址
        owner: Pubkey,
    },

    /// 从派生地址转移 lamport
    ///
    /// # 账户引用
    /// 0. `[WRITE]` 资金账户
    /// 1. `[SIGNER]` 资金账户基础
    /// 2. `[WRITE]` 接收账户
    TransferWithSeed {
        /// 转账金额
        lamports: u64,

        /// 用于获取资金账户地址的Seed
        from_seed: String,

        /// 所有者用来获取资金账户地址
        from_owner: Pubkey,
    },

    /// 对旧版 nonce 版本进行一次性幂等升级,以便将它们从链块哈希域中剔除。
    ///
    /// # 帐户引用
    /// 0. `[WRITE]` Nonce 帐户
    UpgradeNonceAccount,
}

Create an account

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::CreateAccount。
帐户创建通常涉及三个步骤:分配空间、转移 Lamport 以进行租赁、分配给其拥有程序。create_account 函数一次完成所有三个步骤。

必需的签名者

from_pubkey 和 to_pubkey 签名者必须签署交易。

示例

这些示例使用 SystemInstruction::CreateAccount 的单次调用来创建新帐户、分配一些空间、向其转移最低 Lamport 以进行租赁豁免,并将其分配给系统程序,

示例:客户端 RPC

此示例从 RPC 客户端提交指令。付款人和 new_account 是签名者。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    system_program,
    transaction::Transaction,
};
use anyhow::Result;

fn create_account(
    client: &RpcClient,
    payer: &Keypair,
    new_account: &Keypair,
    space: u64,
) -> Result<()> {
    let rent = client. get_minimum_balance_for_rent_exemption(space. try_into()?)?;
    let instr = system_instruction::create_account(
        &payer. pubkey(),
        &new_account. pubkey(),
        rent,
        space,
        &system_program::ID,
    );

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &[instr],
        Some(&payer. pubkey()),
        &[payer, new_account],
        blockhash,
    );

    let _sig = client. send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例从链上 Solana 程序提交指令。创建的帐户是程序派生的地址。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program::invoke_signed,
    pubkey::Pubkey,
    system_instruction,
    system_program,
    sysvar::rent::Rent,
    sysvar::Sysvar,
};

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CreateAccountInstruction {
    /// The PDA seed used to distinguish the new account from other PDAs
    pub new_account_seed: [u8; 16],
    /// The PDA bump seed
    pub new_account_bump_seed: u8,
    /// The amount of space to allocate for `new_account_pda`
    pub space: u64,
}

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;

    let account_info_iter = &mut accounts. iter();

    let payer = next_account_info(account_info_iter)?;
    let new_account_pda = next_account_info(account_info_iter)?;
    let system_account = next_account_info(account_info_iter)?;

    assert!(payer. is_signer);
    assert!(payer. is_writable);
    // Note that `new_account_pda` is not a signer yet.
    // This program will sign for it via `invoke_signed`.
    assert!(!new_account_pda. is_signer);
    assert!(new_account_pda. is_writable);
    assert!(system_program::check_id(system_account. key));

    let new_account_seed = &instr. new_account_seed;
    let new_account_bump_seed = instr. new_account_bump_seed;

    let rent = Rent::get()?
        .minimum_balance(instr. space. try_into().expect("overflow"));

    invoke_signed(
        &system_instruction::create_account(
            payer. key,
            new_account_pda. key,
            rent,
            instr. space,
            &system_program::ID
        ),
        &[payer. clone(), new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    Ok(())
}
程序代码
pub fn create_account(
    from_pubkey: &Pubkey,
    to_pubkey: &Pubkey,
    lamports: u64,
    space: u64,
    owner: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*from_pubkey, true),
        AccountMeta::new(*to_pubkey, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::CreateAccount {
            lamports,
            space,
            owner: *owner,
        },
        account_metas,
    )
}

// we accept `to` as a parameter so that callers do their own error handling when
//   calling create_with_seed()
pub fn create_account_with_seed(
    from_pubkey: &Pubkey,
    to_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner)
    base: &Pubkey,
    seed: &str,
    lamports: u64,
    space: u64,
    owner: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*from_pubkey, true),
        AccountMeta::new(*to_pubkey, false),
        AccountMeta::new_readonly(*base, true),
    ];

    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::CreateAccountWithSeed {
            base: *base,
            seed: seed.to_string(),
            lamports,
            space,
            owner: *owner,
        },
        account_metas,
    )
}

从系统程序分配帐户所有权。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::Assign。
必需的签名者公钥签名者必须签署交易。

示例

这些示例为帐户分配空间,向其转移最低余额以免除租金,并将帐户分配给程序。

示例:客户端 RPC

此示例从 RPC 客户端提交指令。它将帐户分配给提供的程序帐户。付款人和新帐户是签名者。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn create_account(
    client: &RpcClient,
    payer: &Keypair,
    new_account: &Keypair,
    owning_program: &Pubkey,
    space: u64,
) -> Result<()> {
    let rent = client. get_minimum_balance_for_rent_exemption(space. try_into()?)?;

    let transfer_instr = system_instruction::transfer(
        &payer. pubkey(),
        &new_account. pubkey(),
        rent,
    );

    let allocate_instr = system_instruction::allocate(
        &new_account. pubkey(),
        space,
    );

    let assign_instr = system_instruction::assign(
        &new_account. pubkey(),
        owning_program,
    );

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &[transfer_instr, allocate_instr, assign_instr],
        Some(&payer. pubkey()),
        &[payer, new_account],
        blockhash,
    );

    let _sig = client. send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例提交来自链上 Solana 程序的指令。创建的帐户是程序派生的地址,由付款人提供资金,并分配给正在运行的程序。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program::invoke_signed,
    pubkey::Pubkey,
    system_instruction,
    system_program,
    sysvar::rent::Rent,
    sysvar::Sysvar,
};

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CreateAccountInstruction {
    /// The PDA seed used to distinguish the new account from other PDAs
    pub new_account_seed: [u8; 16],
    /// The PDA bump seed
    pub new_account_bump_seed: u8,
    /// The amount of space to allocate for `new_account_pda`
    pub space: u64,
}

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;

    let account_info_iter = &mut accounts. iter();

    let payer = next_account_info(account_info_iter)?;
    let new_account_pda = next_account_info(account_info_iter)?;
    let system_account = next_account_info(account_info_iter)?;

    assert!(payer. is_signer);
    assert!(payer. is_writable);
    // Note that `new_account_pda` is not a signer yet.
    // This program will sign for it via `invoke_signed`.
    assert!(!new_account_pda. is_signer);
    assert!(new_account_pda. is_writable);
    assert!(system_program::check_id(system_account. key));

    let new_account_seed = &instr. new_account_seed;
    let new_account_bump_seed = instr. new_account_bump_seed;

    let rent = Rent::get()?
        .minimum_balance(instr. space. try_into().expect("overflow"));

    invoke_signed(
        &system_instruction::transfer(
            payer. key,
            new_account_pda. key,
            rent,
        ),
        &[payer. clone(), new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::allocate(
            new_account_pda. key,
            instr. space,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::assign(
            new_account_pda. key,
            &program_id,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    Ok(())
}

程序代码

pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
    let account_metas = vec![AccountMeta::new(*pubkey, true)];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::Assign { owner: *owner },
        account_metas,
    )
}

pub fn assign_with_seed(
    address: &Pubkey, // must match create_with_seed(base, seed, owner)
    base: &Pubkey,
    seed: &str,
    owner: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*address, false),
        AccountMeta::new_readonly(*base, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::AssignWithSeed {
            base: *base,
            seed: seed.to_string(),
            owner: *owner,
        },
        account_metas,
    )
}

从系统程序拥有的帐户转移 lamport。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::Transfer。
必需的签名者from_pubkey 签名者必须签署交易。

示例

这些示例为帐户分配空间,向其转移最低余额以免除租金,并将帐户分配给程序。

示例:客户端 RPC

此示例从 RPC 客户端提交指令。它将帐户分配给提供的程序帐户。付款人和 new_account 是签名者。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn create_account(
    client: &RpcClient,
    payer: &Keypair,
    new_account: &Keypair,
    owning_program: &Pubkey,
    space: u64,
) -> Result<()> {
    let rent = client.get_minimum_balance_for_rent_exemption(space. try_into()?)?;

    let transfer_instr = system_instruction::transfer(
        &payer. pubkey(),
        &new_account. pubkey(),
        rent,
    );

    let allocate_instr = system_instruction::allocate(
        &new_account. pubkey(),
        space,
    );

    let assign_instr = system_instruction::assign(
        &new_account. pubkey(),
        owning_program,
    );

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &[transfer_instr, allocate_instr, assign_instr],
        Some(&payer. pubkey()),
        &[payer, new_account],
        blockhash,
    );

    let _sig = client.send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例提交来自链上 Solana 程序的指令。创建的帐户是程序派生的地址,由付款人提供资金,并分配给正在运行的程序。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program::invoke_signed,
    pubkey::Pubkey,
    system_instruction,
    system_program,
    sysvar::rent::Rent,
    sysvar::Sysvar,
};

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CreateAccountInstruction {
    /// The PDA seed used to distinguish the new account from other PDAs
    pub new_account_seed: [u8; 16],
    /// The PDA bump seed
    pub new_account_bump_seed: u8,
    /// The amount of space to allocate for `new_account_pda`
    pub space: u64,
}

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;

    let account_info_iter = &mut accounts. iter();

    let payer = next_account_info(account_info_iter)?;
    let new_account_pda = next_account_info(account_info_iter)?;
    let system_account = next_account_info(account_info_iter)?;

    assert!(payer. is_signer);
    assert!(payer. is_writable);
    // Note that `new_account_pda` is not a signer yet.
    // This program will sign for it via `invoke_signed`.
    assert!(!new_account_pda. is_signer);
    assert!(new_account_pda. is_writable);
    assert!(system_program::check_id(system_account. key));

    let new_account_seed = &instr. new_account_seed;
    let new_account_bump_seed = instr. new_account_bump_seed;

    let rent = Rent::get()?
        .minimum_balance(instr. space. try_into().expect("overflow"));

    invoke_signed(
        &system_instruction::transfer(
            payer. key,
            new_account_pda. key,
            rent,
        ),
        &[payer. clone(), new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::allocate(
            new_account_pda. key,
            instr. space,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::assign(
            new_account_pda. key,
            &program_id,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    Ok(())
}

程序代码

pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*from_pubkey, true),
        AccountMeta::new(*to_pubkey, false),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::Transfer { lamports },
        account_metas,
    )
}

pub fn transfer_with_seed(
    from_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner)
    from_base: &Pubkey,
    from_seed: String,
    from_owner: &Pubkey,
    to_pubkey: &Pubkey,
    lamports: u64,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*from_pubkey, false),
        AccountMeta::new_readonly(*from_base, true),
        AccountMeta::new(*to_pubkey, false),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::TransferWithSeed {
            lamports,
            from_seed,
            from_owner: *from_owner,
        },
        account_metas,
    )
}

为帐户分配空间。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::Allocate。
如果帐户的大小已大于 0,或者请求的大小大于 MAX_PERMITTED_DATA_LENGTH,则交易将失败。
必需的签名者公钥签名者必须签署交易。

示例

这些示例为帐户分配空间,向其转移租金豁免的最低余额,并将帐户分配给程序。

示例:客户端 RPC

此示例从 RPC 客户端提交指令。它将帐户分配给提供的程序帐户。付款人和新帐户是签名者。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn create_account(
    client: &RpcClient,
    payer: &Keypair,
    new_account: &Keypair,
    owning_program: &Pubkey,
    space: u64,
) -> Result<()> {
    let rent = client. get_minimum_balance_for_rent_exemption(space. try_into()?)?;

    let transfer_instr = system_instruction::transfer(
        &payer. pubkey(),
        &new_account. pubkey(),
        rent,
    );

    let allocate_instr = system_instruction::allocate(
        &new_account. pubkey(),
        space,
    );

    let assign_instr = system_instruction::assign(
        &new_account. pubkey(),
        owning_program,
    );

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &[transfer_instr, allocate_instr, assign_instr],
        Some(&payer. pubkey()),
        &[payer, new_account],
        blockhash,
    );

    let _sig = client. send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例提交来自链上 Solana 程序的指令。创建的帐户是程序派生的地址,由付款人提供资金,并分配给正在运行的程序。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program::invoke_signed,
    pubkey::Pubkey,
    system_instruction,
    system_program,
    sysvar::rent::Rent,
    sysvar::Sysvar,
};

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CreateAccountInstruction {
    /// The PDA seed used to distinguish the new account from other PDAs
    pub new_account_seed: [u8; 16],
    /// The PDA bump seed
    pub new_account_bump_seed: u8,
    /// The amount of space to allocate for `new_account_pda`
    pub space: u64,
}

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;

    let account_info_iter = &mut accounts. iter();

    let payer = next_account_info(account_info_iter)?;
    let new_account_pda = next_account_info(account_info_iter)?;
    let system_account = next_account_info(account_info_iter)?;

    assert!(payer. is_signer);
    assert!(payer. is_writable);
    // Note that `new_account_pda` is not a signer yet.
    // This program will sign for it via `invoke_signed`.
    assert!(!new_account_pda. is_signer);
    assert!(new_account_pda. is_writable);
    assert!(system_program::check_id(system_account. key));

    let new_account_seed = &instr. new_account_seed;
    let new_account_bump_seed = instr. new_account_bump_seed;

    let rent = Rent::get()?
        .minimum_balance(instr. space. try_into().expect("overflow"));

    invoke_signed(
        &system_instruction::transfer(
            payer. key,
            new_account_pda. key,
            rent,
        ),
        &[payer. clone(), new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::allocate(
            new_account_pda. key,
            instr. space,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    invoke_signed(
        &system_instruction::assign(
            new_account_pda. key,
            &program_id,
        ),
        &[new_account_pda. clone()],
        &[&[payer. key. as_ref(), new_account_seed, &[new_account_bump_seed]]],
    )?;

    Ok(())
}

程序代码

pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction {
    let account_metas = vec![AccountMeta::new(*pubkey, true)];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::Allocate { space },
        account_metas,
    )
}

pub fn allocate_with_seed(
    address: &Pubkey, // must match create_with_seed(base, seed, owner)
    base: &Pubkey,
    seed: &str,
    space: u64,
    owner: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*address, false),
        AccountMeta::new_readonly(*base, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::AllocateWithSeed {
            base: *base,
            seed: seed.to_string(),
            space,
            owner: *owner,
        },
        account_metas,
    )
}

将 lamport 从系统程序拥有的帐户转移到多个帐户。

此函数生成一个指令向量,该向量必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::Transfers。
所需签名者from_pubkey 签名者必须签署交易。

示例

示例:客户端 RPC

此示例在单个交易中执行多次转移。

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn transfer_lamports_to_many(
    client: &RpcClient,
    from: &Keypair,
    to_and_amount: &[(Pubkey, u64)],
) -> Result<()> {
    let instrs = system_instruction::transfer_many(&from. pubkey(), to_and_amount);

    let blockhash = client. get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &instrs,
        Some(&from. pubkey()),
        &[from],
        blockhash,
    );

    let _sig = client. send_and_confirm_transaction(&tx)?;

    Ok(())
}
示例:链上程序

此示例从“银行”账户(由调用程序拥有的程序派生地址)进行多次转账。此示例提交来自链上 Solana 程序的指令。创建的帐户是程序派生的地址,并分配给正在运行的程序。付款人和 new_account_pda 是签名者,其中 new_account_pda 由程序本身通过invoke_signed 虚拟签名,付款人由提交交易的客户端签名。

use solana_program::{
    account_info::{next_account_info, next_account_infos, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program::invoke_signed,
    pubkey::Pubkey,
    system_instruction,
    system_program,
};

/// # Accounts 
/// 
/// - 0: bank_pda - writable 
/// - 1: system_program - executable 
/// - *: to - writable
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct TransferLamportsToManyInstruction {
    pub bank_pda_bump_seed: u8,
    pub amount_list: Vec<u64>,
}

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let instr = TransferLamportsToManyInstruction::deserialize(&mut &instruction_data[..])?;

    let account_info_iter = &mut accounts. iter();

    let bank_pda = next_account_info(account_info_iter)?;
    let bank_pda_bump_seed = instr. bank_pda_bump_seed;
    let system_account = next_account_info(account_info_iter)?;

    assert!(system_program::check_id(system_account. key));

    let to_accounts = next_account_infos(account_info_iter, account_info_iter. len())?;

    for to_account in to_accounts {
         assert!(to_account. is_writable);
         // ... do other verification ...
    }

    let to_and_amount = to_accounts
        .iter()
        .zip(instr. amount_list. iter())
        .map(|(to, amount)| (*to. key, *amount))
        .collect::<Vec<(Pubkey, u64)>>();

    let instrs = system_instruction::transfer_many(bank_pda. key, to_and_amount. as_ref());

    for instr in instrs {
        invoke_signed(&instr, accounts, &[&[b"bank", &[bank_pda_bump_seed]]])?;
    }

    Ok(())
}

程序代码

pub fn transfer_many(from_pubkey: &Pubkey, to_lamports: &[(Pubkey, u64)]) -> Vec<Instruction> {
    to_lamports
        .iter()
        .map(|(to_pubkey, lamports)| transfer(from_pubkey, to_pubkey, *lamports))
        .collect()
}

创建一个包含持久交易 nonce 的帐户。

此函数生成一个指令向量,该向量必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::CreateAccount 和 SystemInstruction::InitializeNonceAccount。
持久交易 nonce 是一个特殊帐户,可以执行过去已签名的交易。
标准 Solana 交易包括最近的区块哈希(有时称为 nonce)。在执行过程中,Solana 运行时会验证最近的区块哈希大约不到两分钟,并且在这两分钟内没有执行具有相同区块哈希的其他相同交易。这些检查可防止意外重放交易。因此,不可能签署交易,等待两分钟以上,然后成功执行该交易。
持久交易 nonce 是标准最近区块哈希 nonce 的替代方案。它们存储在链上的帐户中,每次使用时,它们的值都会更改为新值以供下次使用。运行时会验证每个持久 nonce 值是否仅使用一次,并且对 nonce 的“旧”程度没有任何限制。由于它们存储在链上并需要额外的指令才能使用,因此使用持久交易 nonce 进行交易比使用标准交易更昂贵。
持久 nonce 的值本身就是一个区块哈希,可通过 nonce::state::Data 的区块哈希字段访问,该字段从 nonce 帐户数据反序列化而来。
基本的持久交易 nonce 生命周期是

  1. 使用 create_nonce_account 指令创建 nonce 帐户。
  2. 提交包含 advance_nonce_account 指令的特殊格式的交易。
  3. 通过使用 withdraw_nonce_account 指令撤回其 lamports 来销毁 nonce 帐户。

Nonce 帐户有一个关联的授权帐户,该帐户存储在其帐户数据中,可以使用 authorize_nonce_account 指令进行更改。授权机构必须签署包含 advance_nonce_account、authorize_nonce_account 和 withdraw_nonce_account 指令的交易。
Nonce 帐户归系统程序所有。
此构造函数创建 SystemInstruction::CreateAccount 指令和 SystemInstruction::InitializeNonceAccount 指令。
必需的签名者
from_pubkey 和 nonce_pubkey 签名者必须签署交易。

示例

从链下客户端创建 nonce 帐户:

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
    nonce::State,
};
use anyhow::Result;

fn submit_create_nonce_account_tx(
    client: &RpcClient,
    payer: &Keypair,
) -> Result<()> {

    let nonce_account = Keypair::new();

    let nonce_rent = client. get_minimum_balance_for_rent_exemption(State::size())?;
    let instr = system_instruction::create_nonce_account(
        &payer. pubkey(),
        &nonce_account. pubkey(),
        &payer. pubkey(), // Make the fee payer the nonce account authority
        nonce_rent,
    );

    let mut tx = Transaction::new_with_payer(&instr, Some(&payer. pubkey()));

    let blockhash = client. get_latest_blockhash()?;
    tx. try_sign(&[&nonce_account, payer], blockhash)?;

    client. send_and_confirm_transaction(&tx)?;

    Ok(())
}

程序代码

pub fn create_nonce_account(
    from_pubkey: &Pubkey,
    nonce_pubkey: &Pubkey,
    authority: &Pubkey,
    lamports: u64,
) -> Vec<Instruction> {
    vec![
        create_account(
            from_pubkey,
            nonce_pubkey,
            lamports,
            nonce::State::size() as u64,
            &system_program::id(),
        ),
        Instruction::new_with_bincode(
            system_program::id(),
            &SystemInstruction::InitializeNonceAccount(*authority),
            vec![
                AccountMeta::new(*nonce_pubkey, false),
                #[allow(deprecated)]
                AccountMeta::new_readonly(recent_blockhashes::id(), false),
                AccountMeta::new_readonly(rent::id(), false),
            ],
        ),
    ]
}

pub fn create_nonce_account_with_seed(
    from_pubkey: &Pubkey,
    nonce_pubkey: &Pubkey,
    base: &Pubkey,
    seed: &str,
    authority: &Pubkey,
    lamports: u64,
) -> Vec<Instruction> {
    vec![
        create_account_with_seed(
            from_pubkey,
            nonce_pubkey,
            base,
            seed,
            lamports,
            nonce::State::size() as u64,
            &system_program::id(),
        ),
        Instruction::new_with_bincode(
            system_program::id(),
            &SystemInstruction::InitializeNonceAccount(*authority),
            vec![
                AccountMeta::new(*nonce_pubkey, false),
                #[allow(deprecated)]
                AccountMeta::new_readonly(recent_blockhashes::id(), false),
                AccountMeta::new_readonly(rent::id(), false),
            ],
        ),
    ]
}

提高持久交易 nonce 的值。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::AdvanceNonceAccount。
每个依赖于持久交易 nonce 的交易都必须包含一个 SystemInstruction::AdvanceNonceAccount 指令作为消息中的第一个指令,由此函数创建。当包含在第一个位置时,Solana 运行时会将该交易识别为依赖于持久交易 nonce 的交易并对其进行相应处理。Message::new_with_nonce 函数可用于构造正确格式的消息,而无需直接调用 advance_nonce_account。
在构建包含 AdvanceNonceInstruction 的交易时,必须以不同的方式处理 recent_blockhash — 不是将其设置为最近的区块哈希,而是必须从 nonce 帐户中检索并反序列化 nonce 的值,并将该值指定为“最近的区块哈希”。可以使用 solana_rpc_client_nonce_utils::data_from_account 函数反序列化 nonce 帐户。
有关持久事务 nonce 的进一步描述,请参阅 create_nonce_account。
所需签名者
authorized_pubkey 签名者必须签署交易。

示例

使用持久 nonce 创建和签署交易:

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    message::Message,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use std::path::Path;
use anyhow::Result;

fn create_transfer_tx_with_nonce(
    client: &RpcClient,
    nonce_account_pubkey: &Pubkey,
    payer: &Keypair,
    receiver: &Pubkey,
    amount: u64,
    tx_path: &Path,
) -> Result<()> {

    let instr_transfer = system_instruction::transfer(
        &payer. pubkey(),
        receiver,
        amount,
    );

    // In this example, `payer` is `nonce_account_pubkey`'s authority
    let instr_advance_nonce_account = system_instruction::advance_nonce_account(
        nonce_account_pubkey,
        &payer. pubkey(),
    );

    // The `advance_nonce_account` instruction must be the first issued in
    // the transaction.
    let message = Message::new(
        &[
            instr_advance_nonce_account,
            instr_transfer
        ],
        Some(&payer. pubkey()),
    );

    let mut tx = Transaction::new_unsigned(message);

    // Sign the tx with nonce_account's `blockhash` instead of the
    // network's latest blockhash.
    let nonce_account = client. get_account(nonce_account_pubkey)?;
    let nonce_data = solana_rpc_client_nonce_utils::data_from_account(&nonce_account)?;
    let blockhash = nonce_data. blockhash();

    tx. try_sign(&[payer], blockhash)?;

    // Save the signed transaction locally for later submission.
    save_tx_to_file(&tx_path, &tx)?;

    Ok(())
}

程序代码

pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*nonce_pubkey, false),
        #[allow(deprecated)]
        AccountMeta::new_readonly(recent_blockhashes::id(), false),
        AccountMeta::new_readonly(*authorized_pubkey, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::AdvanceNonceAccount,
        account_metas,
    )
}

从持久交易 nonce 账户中提取 lamport。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::WithdrawNonceAccount。
提取 nonce 账户的全部余额将导致运行时在成功完成交易后销毁它。
否则,nonce 账户必须保持大于或等于租金豁免所需的最低余额。如果此指令的结果使 nonce 账户的余额低于租金豁免所需的余额,但也大于零,则交易将失败。
此构造函数创建一个 SystemInstruction::WithdrawNonceAccount 指令。
必需的签名者
authorized_pubkey 签名者必须签署交易。

示例

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn submit_withdraw_nonce_account_tx(
    client: &RpcClient,
    nonce_account_pubkey: &Pubkey,
    authorized_account: &Keypair,
) -> Result<()> {

    let nonce_balance = client. get_balance(nonce_account_pubkey)?;

    let instr = system_instruction::withdraw_nonce_account(
        &nonce_account_pubkey,
        &authorized_account. pubkey(),
        &authorized_account. pubkey(),
        nonce_balance,
    );

    let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account. pubkey()));

    let blockhash = client. get_latest_blockhash()?;
    tx. try_sign(&[authorized_account], blockhash)?;

    client. send_and_confirm_transaction(&tx)?;

    Ok(())
}

程序代码

pub fn withdraw_nonce_account(
    nonce_pubkey: &Pubkey,
    authorized_pubkey: &Pubkey,
    to_pubkey: &Pubkey,
    lamports: u64,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*nonce_pubkey, false),
        AccountMeta::new(*to_pubkey, false),
        #[allow(deprecated)]
        AccountMeta::new_readonly(recent_blockhashes::id(), false),
        AccountMeta::new_readonly(rent::id(), false),
        AccountMeta::new_readonly(*authorized_pubkey, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::WithdrawNonceAccount(lamports),
        account_metas,
    )
}

更改持久交易 nonce 帐户的权限。

此函数生成一个指令,该指令必须在交易中提交或调用才能生效,其中包含序列化的 SystemInstruction::AuthorizeNonceAccount。
此构造函数创建一个 SystemInstruction::AuthorizeNonceAccount 指令。
必需的签名者
authorized_pubkey 签名者必须签署交易。

示例

use solana_rpc_client::rpc_client::RpcClient;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use anyhow::Result;

fn authorize_nonce_account_tx(
    client: &RpcClient,
    nonce_account_pubkey: &Pubkey,
    authorized_account: &Keypair,
    new_authority_pubkey: &Pubkey,
) -> Result<()> {

    let instr = system_instruction::authorize_nonce_account(
        &nonce_account_pubkey,
        &authorized_account. pubkey(),
        &new_authority_pubkey,
    );

    let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account. pubkey()));

    let blockhash = client. get_latest_blockhash()?;
    tx. try_sign(&[authorized_account], blockhash)?;

    client. send_and_confirm_transaction(&tx)?;

    Ok(())
}

程序代码

pub fn authorize_nonce_account(
    nonce_pubkey: &Pubkey,
    authorized_pubkey: &Pubkey,
    new_authority: &Pubkey,
) -> Instruction {
    let account_metas = vec![
        AccountMeta::new(*nonce_pubkey, false),
        AccountMeta::new_readonly(*authorized_pubkey, true),
    ];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::AuthorizeNonceAccount(*new_authority),
        account_metas,
    )
}

对旧版 nonce 版本进行一次性幂等升级,以将其排除在链块哈希域之外。

pub fn upgrade_nonce_account(nonce_pubkey: Pubkey) -> Instruction {
    let account_metas = vec![AccountMeta::new(nonce_pubkey, /*is_signer:*/ false)];
    Instruction::new_with_bincode(
        system_program::id(),
        &SystemInstruction::UpgradeNonceAccount,
        account_metas,
    )
}