Mô hình tài khoản Solana

Trên Solana, tất cả dữ liệu được lưu trữ trong cái gọi là "accounts" (tài khoản). Bạn có thể coi dữ liệu trên Solana như một cơ sở dữ liệu công khai với một bảng "Accounts" duy nhất, trong đó mỗi mục trong bảng này là một "account". Mọi tài khoản Solana đều chia sẻ cùng một kiểu Account cơ bản.

Tài khoảnTài khoản

Điểm chính

  • Tài khoản có thể lưu trữ tối đa 10MiB dữ liệu, chứa mã chương trình thực thi hoặc trạng thái chương trình.
  • Tài khoản yêu cầu một khoản đặt cọc rent bằng lamport (SOL) tỷ lệ thuận với lượng dữ liệu được lưu trữ, và bạn có thể thu hồi toàn bộ khi đóng tài khoản.
  • Mỗi tài khoản có một chương trình sở hữu. Chỉ chương trình sở hữu tài khoản mới có thể thay đổi dữ liệu hoặc trừ số dư lamport của nó. Nhưng bất kỳ ai cũng có thể tăng số dư.
  • Tài khoản Sysvar là những tài khoản đặc biệt lưu trữ trạng thái cụm mạng.
  • Tài khoản chương trình lưu trữ mã thực thi của các hợp đồng thông minh.
  • Tài khoản dữ liệu được tạo bởi các chương trình để lưu trữ và quản lý trạng thái chương trình.

Tài khoản

Mỗi tài khoản trên Solana có một địa chỉ duy nhất 32 byte, thường được hiển thị dưới dạng chuỗi mã hóa base58 (ví dụ: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo6NmXEyxm).

Mối quan hệ giữa tài khoản và địa chỉ của nó hoạt động như một cặp key-value, trong đó địa chỉ là khóa để định vị dữ liệu on-chain tương ứng của tài khoản. Địa chỉ tài khoản đóng vai trò như "ID duy nhất" cho mỗi mục trong bảng "Accounts".

Địa chỉ tài khoảnĐịa chỉ tài khoản

Hầu hết các tài khoản Solana sử dụng khóa công khai Ed25519 làm địa chỉ của chúng.

import { generateKeyPairSigner } from "@solana/kit";
// Kit does not enable extractable private keys
const keypairSigner = await generateKeyPairSigner();
console.log(keypairSigner);
Console
Click to execute the code.

Mặc dù các khóa công khai thường được sử dụng làm địa chỉ tài khoản, Solana cũng hỗ trợ một tính năng gọi là Địa chỉ phái sinh từ chương trình (PDAs). PDAs là những địa chỉ đặc biệt mà bạn có thể tạo ra một cách xác định từ ID chương trình và các đầu vào tùy chọn (seeds).

import { Address, getProgramDerivedAddress } from "@solana/kit";
const programAddress = "11111111111111111111111111111111" as Address;
const seeds = ["helloWorld"];
const [pda, bump] = await getProgramDerivedAddress({
programAddress,
seeds
});
console.log(`PDA: ${pda}`);
console.log(`Bump: ${bump}`);
Console
Click to execute the code.

Loại tài khoản

Tài khoản có kích thước tối đa là 10MiB và mọi tài khoản trên Solana đều chia sẻ cùng một loại Account cơ bản.

Loại tài khoảnLoại tài khoản

Mọi tài khoản trên Solana đều có các trường sau.

Base Account Type
pub struct Account {
/// lamports in the account
pub lamports: u64,
/// data held in this account
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
pub data: Vec<u8>,
/// the program that owns this account. If executable, the program that loads this account.
pub owner: Pubkey,
/// this account's data contains a loaded program (and is now read-only)
pub executable: bool,
/// the epoch at which this account will next owe rent
pub rent_epoch: Epoch,
}

Trường Lamports

Số dư tài khoản tính bằng lamport, đơn vị nhỏ nhất của SOL (1 SOL = 1 tỷ lamport). Số dư SOL của một tài khoản là số lượng trong trường lamports được chuyển đổi sang SOL.

Các tài khoản Solana phải có số dư lamport tối thiểu tỷ lệ thuận với lượng dữ liệu được lưu trữ trên tài khoản (tính bằng byte). Số dư tối thiểu này được gọi là "rent".

Số dư lamport được lưu trữ trong tài khoản có thể được khôi phục hoàn toàn khi tài khoản được đóng.

Trường dữ liệu

Một mảng byte lưu trữ dữ liệu tùy ý cho một tài khoản. Trường dữ liệu này thường được gọi là "dữ liệu tài khoản."

  • Đối với tài khoản program (hợp đồng thông minh), trường này chứa mã chương trình thực thi hoặc địa chỉ của một tài khoản khác lưu trữ mã chương trình thực thi.
  • Đối với tài khoản không thực thi, trường này thường lưu trữ trạng thái được dùng để đọc.

Đọc dữ liệu từ một tài khoản Solana bao gồm hai bước:

  1. Lấy tài khoản bằng địa chỉ của nó (khóa công khai)
  2. Giải mã trường dữ liệu của tài khoản từ các byte thô thành cấu trúc dữ liệu phù hợp, được xác định bởi program sở hữu tài khoản

Trường Owner

ID của program (khóa công khai) của program sở hữu tài khoản này.

Mỗi tài khoản Solana có một program được chỉ định làm chủ sở hữu. Chỉ program sở hữu tài khoản mới có thể thay đổi dữ liệu của tài khoản hoặc trừ số dư lamport của nó.

Các chỉ thị được định nghĩa trong một program xác định cách thức dữ liệu và số dư lamport của tài khoản có thể được thay đổi.

Trường Executable

Trường này chỉ ra liệu một tài khoản có phải là một program thực thi hay không.

  • Nếu true, tài khoản là một program Solana thực thi.
  • Nếu false, tài khoản là một tài khoản dữ liệu lưu trữ trạng thái.

Đối với tài khoản thực thi, trường owner chứa ID program của một program tải. Các program tải là các program tích hợp sẵn chịu trách nhiệm tải và quản lý các tài khoản program thực thi.

Trường Rent Epoch

Trường rent_epoch là một trường cũ không còn được sử dụng nữa.

Ban đầu, trường này theo dõi khi nào một tài khoản cần phải trả rent (bằng lamport) để duy trì dữ liệu của nó trên mạng. Tuy nhiên, cơ chế thu rent này đã bị loại bỏ.

Trường Lamports

Số dư tài khoản tính bằng lamport, đơn vị nhỏ nhất của SOL (1 SOL = 1 tỷ lamport). Số dư SOL của một tài khoản là số lượng trong trường lamports được chuyển đổi sang SOL.

Các tài khoản Solana phải có số dư lamport tối thiểu tỷ lệ thuận với lượng dữ liệu được lưu trữ trên tài khoản (tính bằng byte). Số dư tối thiểu này được gọi là "rent".

Số dư lamport được lưu trữ trong tài khoản có thể được khôi phục hoàn toàn khi tài khoản được đóng.

Trường dữ liệu

Một mảng byte lưu trữ dữ liệu tùy ý cho một tài khoản. Trường dữ liệu này thường được gọi là "dữ liệu tài khoản."

  • Đối với tài khoản program (hợp đồng thông minh), trường này chứa mã chương trình thực thi hoặc địa chỉ của một tài khoản khác lưu trữ mã chương trình thực thi.
  • Đối với tài khoản không thực thi, trường này thường lưu trữ trạng thái được dùng để đọc.

Đọc dữ liệu từ một tài khoản Solana bao gồm hai bước:

  1. Lấy tài khoản bằng địa chỉ của nó (khóa công khai)
  2. Giải mã trường dữ liệu của tài khoản từ các byte thô thành cấu trúc dữ liệu phù hợp, được xác định bởi program sở hữu tài khoản

Trường Owner

ID của program (khóa công khai) của program sở hữu tài khoản này.

Mỗi tài khoản Solana có một program được chỉ định làm chủ sở hữu. Chỉ program sở hữu tài khoản mới có thể thay đổi dữ liệu của tài khoản hoặc trừ số dư lamport của nó.

Các chỉ thị được định nghĩa trong một program xác định cách thức dữ liệu và số dư lamport của tài khoản có thể được thay đổi.

Trường Executable

Trường này chỉ ra liệu một tài khoản có phải là một program thực thi hay không.

  • Nếu true, tài khoản là một program Solana thực thi.
  • Nếu false, tài khoản là một tài khoản dữ liệu lưu trữ trạng thái.

Đối với tài khoản thực thi, trường owner chứa ID program của một program tải. Các program tải là các program tích hợp sẵn chịu trách nhiệm tải và quản lý các tài khoản program thực thi.

Trường Rent Epoch

Trường rent_epoch là một trường cũ không còn được sử dụng nữa.

Ban đầu, trường này theo dõi khi nào một tài khoản cần phải trả rent (bằng lamport) để duy trì dữ liệu của nó trên mạng. Tuy nhiên, cơ chế thu rent này đã bị loại bỏ.

Base Account Type
pub struct Account {
/// lamports in the account
pub lamports: u64,
}
// Example Token Mint Account
Account {
lamports: 1461600,
}
// Example Token Program Account
Account {
lamports: 4513200894,
}

Rent

Để lưu trữ dữ liệu trên chuỗi, các tài khoản cũng phải duy trì số dư lamport (SOL) tỷ lệ thuận với lượng dữ liệu được lưu trữ trên tài khoản (tính bằng byte). Số dư này được gọi là "rent", nhưng nó hoạt động giống như một khoản đặt cọc vì bạn có thể lấy lại toàn bộ số tiền khi đóng tài khoản. Bạn có thể tìm thấy cách tính toán tại đây sử dụng các hằng số.

Thuật ngữ "rent" bắt nguồn từ một cơ chế đã lỗi thời trước đây thường xuyên khấu trừ lamport từ các tài khoản có số dư thấp hơn ngưỡng rent. Cơ chế này hiện không còn hoạt động nữa.

Chủ sở hữu chương trình

Trên Solana, "hợp đồng thông minh" được gọi là chương trình. Quyền sở hữu chương trình là một phần quan trọng trong Mô hình Tài khoản Solana. Mỗi tài khoản có một chương trình được chỉ định làm chủ sở hữu. Chỉ chương trình chủ sở hữu mới có thể:

  • Thay đổi trường data của tài khoản
  • Khấu trừ lamport từ số dư của tài khoản

Mỗi chương trình xác định cấu trúc dữ liệu được lưu trữ trong trường data của tài khoản. Các hướng dẫn của chương trình xác định cách thức dữ liệu này và số dư lamports của tài khoản có thể được thay đổi.

System Program

Theo mặc định, tất cả tài khoản mới đều thuộc sở hữu của System Program. System Program thực hiện các chức năng chính sau đây:

Chức năngMô tả
Tạo tài khoản mớiChỉ System Program mới có thể tạo tài khoản mới.
Phân bổ không gianThiết lập dung lượng byte cho trường dữ liệu của mỗi tài khoản.
Gán quyền sở hữu chương trìnhSau khi System Program tạo tài khoản, nó có thể gán lại chương trình chủ sở hữu cho một tài khoản chương trình khác. Đó là cách các chương trình tùy chỉnh tiếp quản quyền sở hữu các tài khoản mới được tạo bởi System Program.
Chuyển SOLChuyển lamport (SOL) từ các tài khoản System sang các tài khoản khác.

Lưu ý rằng tất cả các tài khoản "ví" trên Solana đều là "System Accounts" thuộc sở hữu của System Program. Số dư lamport trong các tài khoản này thể hiện lượng SOL mà ví sở hữu. Chỉ có System Accounts mới có thể thanh toán phí giao dịch.

System AccountSystem Account

Khi SOL được gửi đến một địa chỉ mới lần đầu tiên, một tài khoản sẽ tự động được tạo tại địa chỉ đó thuộc sở hữu của System Program.

Trong ví dụ dưới đây, một keypair mới được tạo và nạp SOL. Chạy mã để xem kết quả. Lưu ý rằng trường owner của tài khoản là System Program với địa chỉ 11111111111111111111111111111111.

import {
airdropFactory,
createSolanaRpc,
createSolanaRpcSubscriptions,
generateKeyPairSigner,
lamports
} from "@solana/kit";
// Create a connection to Solana cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate a new keypair
const keypair = await generateKeyPairSigner();
console.log(`Public Key: ${keypair.address}`);
// Funding an address with SOL automatically creates an account
const 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);
Console
Click to execute the code.

Tài khoản Sysvar

Tài khoản Sysvar là các tài khoản đặc biệt tại các địa chỉ được xác định trước, cung cấp quyền truy cập vào dữ liệu trạng thái của cụm mạng. Các tài khoản này cập nhật động với dữ liệu về cụm mạng. Bạn có thể tìm thấy danh sách đầy đủ các tài khoản Sysvar tại đây.

Ví dụ sau đây cho thấy cách lấy và giải mã dữ liệu từ tài khoản Sysvar Clock.

import { createSolanaRpc } from "@solana/kit";
import { fetchSysvarClock, SYSVAR_CLOCK_ADDRESS } from "@solana/sysvars";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const accountInfo = await rpc
.getAccountInfo(SYSVAR_CLOCK_ADDRESS, { encoding: "base64" })
.send();
console.log(accountInfo);
// Automatically fetch and deserialize the account data
const clock = await fetchSysvarClock(rpc);
console.log(clock);
Console
Click to execute the code.

Program Account

Triển khai một chương trình Solana sẽ tạo ra một program account có thể thực thi. Program account lưu trữ mã thực thi của chương trình. Program accounts thuộc sở hữu của Loader Program.

Program AccountProgram Account

Để đơn giản, bạn có thể coi program account là chính chương trình đó. Khi bạn gọi các lệnh của một chương trình, bạn chỉ định địa chỉ của program account (thường được gọi là "Program ID").

Ví dụ sau đây lấy Token Program account để cho thấy rằng program accounts có cùng kiểu cơ sở Account, ngoại trừ trường executable được đặt thành true. Vì program accounts chứa mã thực thi trong trường dữ liệu của chúng, chúng ta không giải mã dữ liệu.

import { Address, createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const programId = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as Address;
const accountInfo = await rpc
.getAccountInfo(programId, { encoding: "base64" })
.send();
console.log(accountInfo);
Console
Click to execute the code.

Khi bạn triển khai một chương trình Solana, nó được lưu trữ trong một program account. Program accounts được sở hữu bởi một Chương trình tải. Có nhiều phiên bản của trình tải, nhưng tất cả ngoại trừ loader-v3 đều lưu trữ mã thực thi trực tiếp trong program account. Loader-v3 lưu trữ mã thực thi trong một "program data account" riêng biệt và program account chỉ trỏ đến nó. Khi bạn triển khai một chương trình mới, Solana CLI sẽ sử dụng phiên bản trình tải mới nhất theo mặc định.

Tài khoản đệm (Buffer Account)

Loader-v3 có một loại tài khoản đặc biệt để tạm thời lưu trữ việc tải lên chương trình trong quá trình triển khai hoặc nâng cấp. Trong loader-v4, vẫn có các bộ đệm, nhưng chúng chỉ là các program account thông thường.

Tài khoản dữ liệu chương trình (Program Data Account)

Loader-v3 hoạt động khác với tất cả các chương trình BPF Loader khác. Program account chỉ chứa địa chỉ của một program data account, nơi lưu trữ mã thực thi thực sự:

Program Data AccountProgram Data Account

Đừng nhầm lẫn các program data account này với các data account của chương trình (xem bên dưới).

Tài khoản dữ liệu (Data Account)

Trên Solana, mã thực thi của một chương trình được lưu trữ trong một tài khoản khác với trạng thái của chương trình. Điều này giống như cách các hệ điều hành thường có các tệp riêng biệt cho chương trình và dữ liệu của chúng.

Để duy trì trạng thái, các chương trình định nghĩa các hướng dẫn để tạo các tài khoản riêng biệt mà chúng sở hữu. Mỗi tài khoản này có địa chỉ duy nhất riêng và có thể lưu trữ bất kỳ dữ liệu tùy ý nào được định nghĩa bởi chương trình.

Data AccountData Account

Lưu ý rằng chỉ có System Program mới có thể tạo tài khoản mới. Sau khi System Program tạo một tài khoản, nó có thể gán quyền sở hữu của tài khoản mới cho một chương trình khác.

Nói cách khác, việc tạo một tài khoản dữ liệu cho một chương trình tùy chỉnh cần hai bước:

  1. Gọi System Program để tạo một tài khoản, sau đó chuyển quyền sở hữu cho chương trình tùy chỉnh
  2. Gọi chương trình tùy chỉnh, hiện đang sở hữu tài khoản, để khởi tạo dữ liệu tài khoản theo định nghĩa của hướng dẫn chương trình

Quá trình tạo tài khoản này thường được trừu tượng hóa thành một bước duy nhất, nhưng hiểu rõ quy trình cơ bản là rất hữu ích.

Ví dụ sau đây cho thấy cách tạo và lấy một tài khoản Token Mint được sở hữu bởi chương trình Token 2022.

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 example
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate keypairs for fee payer
const feePayer = await generateKeyPairSigner();
// Fund fee payer
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: feePayer.address,
lamports: lamports(1_000_000_000n),
commitment: "confirmed"
});
// Generate keypair to use as address of mint
const mint = await generateKeyPairSigner();
// Get default mint account size (in bytes), no extensions enabled
const space = BigInt(getMintSize());
// Get minimum balance for rent exemption
const rent = await rpc.getMinimumBalanceForRentExemption(space).send();
// Instruction to create new account for mint (token 2022 program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token 2022 program
const initializeMintInstruction = getInitializeMintInstruction({
mint: mint.address,
decimals: 9,
mintAuthority: feePayer.address
});
const instructions = [createAccountInstruction, initializeMintInstruction];
// Get latest blockhash to include in transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Create transaction message
const 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 transaction
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
// Get transaction signature
const 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);
Console
Click to execute the code.

Is this page helpful?