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". Bạn có thể hiểu 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. 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ỉ 32-byte duy nhất, thường được hiển thị dưới dạng chuỗi mã hóa base58 (ví dụ: 3Kj8xTnMjJVtpJCEhKSgL3KX1Qc2qDmKPpYdQrxXnCrJ).

Mối quan hệ giữa tài khoản và địa chỉ của nó hoạt động như một cặp khóa-giá trị, trong đó địa chỉ là khóa để định vị dữ liệu trên chuỗi 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);
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à Program Derived Addresses (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}`);
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:

  • data: Một mảng byte lưu trữ dữ liệu tùy ý cho tài khoản. Đố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. Đối với tài khoản chương trình (smart contract), trường này chứa mã chương trình có thể thực thi. Trường dữ liệu thường được gọi là "dữ liệu tài khoản."
  • executable: Cờ này cho biết liệu tài khoản có phải là một chương trình hay khô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).
  • owner: ID chương trình (khóa công khai) của chương trình sở hữu tài khoản này. Chỉ chương trình sở hữu mới có thể thay đổi dữ liệu tài khoản hoặc trừ số dư lamport của nó.
  • rent_epoch: Một trường cũ từ thời Solana có cơ chế định kỳ trừ lamport từ các tài khoản. Mặc dù trường này vẫn tồn tại trong loại Account, nó không còn được sử dụng kể từ khi việc thu rent bị loại bỏ.
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,
}
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);
Click to execute the code.

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, "smart contracts" đượ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

Chương trình hệ thống

Theo mặc định, tất cả tài khoản mới đều thuộc sở hữu của Chương trình hệ thống. Chương trình hệ thống thực hiện một số chức năng chính:

  • Tạo tài khoản mới: Chỉ Chương trình hệ thống mới có thể tạo tài khoản mới.
  • Phân bổ không gian: Thiết lập dung lượng byte cho trường dữ liệu của mỗi tài khoản.
  • Chuyển nhượng / Gán quyền sở hữu chương trình: Sau khi Chương trình hệ thống tạo tài khoản, nó có thể gán lại chủ sở hữu chương trình được chỉ định 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 nhận quyền sở hữu các tài khoản mới được tạo bởi Chương trình hệ thống.

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

Tài khoản Hệ thốngTài khoản Hệ thống

Tài khoản Sysvar

Tài khoản Sysvar là những tài khoản đặc biệt ở 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. Những 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.

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

Tài khoản Chương trình

Triển khai một chương trình Solana tạo ra một tài khoản chương trình có thể thực thi. Tài khoản chương trình lưu trữ mã thực thi của chương trình.

Tài khoản chương trình được sở hữu bởi một Chương trình Tải (Loader Program).

Tài khoản Chương trìnhTài khoản Chương trình

Để đơn giản, bạn có thể coi tài khoản chương trình 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ỉ tài khoản chương trình (thường được gọi là "Program ID").

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);
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 tài khoản chương trình. Tài khoản chương trình được sở hữu bởi một Chương trình Tải. Có nhiều phiên bản của chương 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 tài khoản chương trình. Loader-v3 lưu trữ mã thực thi trong một "tài khoản dữ liệu chương trình" riêng biệt và tài khoản chương trình chỉ trỏ đến nó. Khi bạn triển khai một chương trình mới, Solana CLI sử dụng phiên bản tải mới nhất theo mặc định.

Tài khoản Buffer

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 một chương trình trong quá trình triển khai hoặc triển khai lại/nâng cấp. Trong loader-v4, vẫn có buffer, nhưng chúng chỉ là các tài khoản chương trình thông thường.

Tài khoản Dữ liệu Chương trình

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

Tài khoản Dữ liệu Chương trìnhTài khoản Dữ liệu Chương trình

Đừng nhầm lẫn các tài khoản dữ liệu chương trình này với các tài khoản dữ liệu của chương trình (xem bên dưới).

Tài khoản Dữ liệu

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à họ 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.

Tài khoản Dữ liệuTài khoản Dữ liệu

Lưu ý rằng chỉ có Chương trình Hệ thống mới có thể tạo tài khoản mới. Sau khi Chương trình Hệ thống tạo một tài khoản, nó có thể chuyển giao hoặc 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 Chương trình Hệ thống để 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 như được định nghĩa bởi hướng dẫn của 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 việc hiểu quy trình cơ bản là rất hữu ích.

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
} from "@solana-program/token-2022";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://127.0.0.1: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);
Click to execute the code.

Is this page helpful?

Mục lục

Chỉnh sửa trang