摘要
program account 存储可执行的 sBPF 代码。数据账户用于存储状态,由程序拥有。系统账户由 System Program 拥有。sysvar 在预定义地址提供集群范围的状态。
executable 字段决定了账户的类别:
- program account:
executable=true。包含可执行代码。 - 数据账户:
executable=false。用于存储状态或用户数据。
代码与可变状态的分离意味着程序只需部署一次,即可管理任意数量的数据账户。
program account
program account 用于存储可执行代码。每个 program account 都由 loader program 拥有。当 program 部署时,运行时会创建一个 program account 来存放其字节码。
program account 及其 4 个组成部分和 loader program 的示意图
程序数据账户
使用 loader-v3 部署的程序(参见
Loader programs)不会将可执行字节码存储在自己的
data 字段中,而是将其 data 指向一个单独的 program data
account,该账户包含程序代码。(见下图。)
带有数据的 program account。数据指向一个单独的 program data account
以下示例获取了 Token Program account。executable 字段为
true,确认其为 program account。
import { Address, createSolanaRpc } from "@solana/kit";const rpc = createSolanaRpc("https://api.mainnet.solana.com");const programId = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as Address;const accountInfo = await rpc.getAccountInfo(programId, { encoding: "base64" }).send();console.log(accountInfo);
数据账户
数据账户不包含可执行代码,而是用于存储程序自定义的状态。
程序状态账户
程序会将其状态存储在数据账户中。创建程序状态账户需要两个步骤:
- 调用 System Program 创建账户。System Program 会将账户所有权转移给指定的程序。
- 拥有该账户的程序根据其 instructions 初始化账户的
data字段。
由 program account 拥有的数据账户示意图
以下示例演示如何创建并获取由 Token 2022 program 拥有的 Token Mint account。
import {airdropFactory,appendTransactionMessageInstructions,createSolanaRpc,createSolanaRpcSubscriptions,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners} from "@solana/kit";import { getCreateAccountInstruction } from "@solana-program/system";import {getInitializeMintInstruction,getMintSize,TOKEN_2022_PROGRAM_ADDRESS,fetchMint} from "@solana-program/token-2022";// Create Connection, local validator in this exampleconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate keypairs for fee payerconst feePayer = await generateKeyPairSigner();// Fund fee payerawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: feePayer.address,lamports: lamports(1_000_000_000n),commitment: "confirmed"});// Generate keypair to use as address of mintconst mint = await generateKeyPairSigner();// Get default mint account size (in bytes), no extensions enabledconst space = BigInt(getMintSize());// Get minimum balance for rent exemptionconst rent = await rpc.getMinimumBalanceForRentExemption(space).send();// Instruction to create new account for mint (token 2022 program)// Invokes the system programconst createAccountInstruction = getCreateAccountInstruction({payer: feePayer,newAccount: mint,lamports: rent,space,programAddress: TOKEN_2022_PROGRAM_ADDRESS});// Instruction to initialize mint account data// Invokes the token 2022 programconst initializeMintInstruction = getInitializeMintInstruction({mint: mint.address,decimals: 9,mintAuthority: feePayer.address});const instructions = [createAccountInstruction, initializeMintInstruction];// Get latest blockhash to include in transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }), // Create transaction message(tx) => setTransactionMessageFeePayerSigner(feePayer, tx), // Set fee payer(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), // Set transaction blockhash(tx) => appendTransactionMessageInstructions(instructions, tx) // Append instructions);// Sign transaction message with required signers (fee payer and mint keypair)const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Send and confirm transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });// Get transaction signatureconst transactionSignature = getSignatureFromTransaction(signedTransaction);console.log("Mint Address:", mint.address);console.log("Transaction Signature:", transactionSignature);const accountInfo = await rpc.getAccountInfo(mint.address).send();console.log(accountInfo);const mintAccount = await fetchMint(rpc, mint.address);console.log(mintAccount);
System accounts
创建后仍由 System Program 拥有的账户称为 system account。首次向新地址发送 SOL 时,会在该地址创建一个由 System Program 拥有的新账户。
所有钱包账户都是 system account。交易的手续费支付者必须是 system account,因为只有 System Program 拥有的账户才能支付 交易手续费。
由 System Program 拥有、包含 1,000,000 lamports 的钱包示意图
以下示例生成一个新的 keypair,为其充值 SOL,并获取该账户。owner 字段为
11111111111111111111111111111111(System Program)。
import {airdropFactory,createSolanaRpc,createSolanaRpcSubscriptions,generateKeyPairSigner,lamports} from "@solana/kit";// Create a connection to Solana clusterconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate a new keypairconst keypair = await generateKeyPairSigner();console.log(`Public Key: ${keypair.address}`);// Funding an address with SOL automatically creates an accountconst signature = await airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: keypair.address,lamports: lamports(1_000_000_000n),commitment: "confirmed"});const accountInfo = await rpc.getAccountInfo(keypair.address).send();console.log(accountInfo);
Sysvar 账户
Sysvar 账户 是在预定义地址上的特殊账户,提供对集群状态数据的只读访问。它们会在每个 slot 动态更新。
| Sysvar | Address | Purpose |
|---|---|---|
| Clock | SysvarC1ock11111111111111111111111111111111 | 当前 slot、epoch 和 Unix 时间戳 |
| EpochSchedule | SysvarEpochSchedu1e111111111111111111111111 | 在创世块中设定的 epoch 调度常量 |
| EpochRewards | SysvarEpochRewards1111111111111111111111111 | epoch 奖励分配状态与进度 |
| Rent | SysvarRent111111111111111111111111111111111 | 租金费率与免租门槛 |
| SlotHashes | SysvarS1otHashes111111111111111111111111111 | slot 父银行的最新哈希值 |
| StakeHistory | SysvarStakeHistory1111111111111111111111111 | 每个 epoch 的质押激活与撤销记录 |
| LastRestartSlot | SysvarLastRestartS1ot1111111111111111111111 | 上一次集群重启的 slot |
| Instructions | Sysvar1nstructions1111111111111111111111111 | 当前交易的序列化指令 |
| SlotHistory | SysvarS1otHistory11111111111111111111111111 | 最近一个 epoch 内已产出的 slot 记录 |
以下示例演示如何获取并反序列化 Sysvar Clock 账户。
import { createSolanaRpc } from "@solana/kit";import { fetchSysvarClock, SYSVAR_CLOCK_ADDRESS } from "@solana/sysvars";const rpc = createSolanaRpc("https://api.mainnet.solana.com");const accountInfo = await rpc.getAccountInfo(SYSVAR_CLOCK_ADDRESS, { encoding: "base64" }).send();console.log(accountInfo);// Automatically fetch and deserialize the account dataconst clock = await fetchSysvarClock(rpc);console.log(clock);
Is this page helpful?