代码分析
版本: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 生命周期是
- 使用 create_nonce_account 指令创建 nonce 帐户。
- 提交包含 advance_nonce_account 指令的特殊格式的交易。
- 通过使用 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,
)
}
版权属于:区块链中文技术社区 / 转载原创者
本文链接:https://bcskill.com/index.php/archives/2275.html
相关技术文章仅限于相关区块链底层技术研究,禁止用于非法用途,后果自负!本站严格遵守一切相关法律政策!