Program Derived Address
Trong phần này, bạn sẽ học cách xây dựng một chương trình Tạo, Đọc, Cập nhật, Xóa (CRUD) cơ bản.
Hướng dẫn này trình bày một chương trình đơn giản nơi người dùng có thể tạo, cập nhật và xóa tin nhắn. Mỗi tin nhắn tồn tại trong một tài khoản với địa chỉ xác định được tạo ra từ chính chương trình (Program Derived Address hay PDA).
Hướng dẫn này đưa bạn qua quá trình xây dựng và kiểm tra một chương trình Solana sử dụng framework Anchor đồng thời minh họa Program Derived Addresses (PDAs). Để biết thêm chi tiết, tham khảo trang Program Derived Addresses.
Để tham khảo, bạn có thể xem mã nguồn cuối cùng sau khi hoàn thành cả hai phần PDA và Cross Program Invocation (CPI).
Mã khởi đầu
Bắt đầu bằng cách mở đường dẫn Solana Playground với mã khởi đầu. Sau đó nhấp vào nút "Import" để thêm chương trình vào các dự án Solana Playground của bạn.
Import
Trong tệp lib.rs
, bạn sẽ thấy một chương trình với các hướng dẫn
create
, update
, và delete
để thêm vào các bước sau.
use anchor_lang::prelude::*;declare_id!("8KPzbM2Cwn4Yjak7QYAEH9wyoQh86NcBicaLuzPaejdw");#[program]pub mod pda {use super::*;pub fn create(_ctx: Context<Create>) -> Result<()> {Ok(())}pub fn update(_ctx: Context<Update>) -> Result<()> {Ok(())}pub fn delete(_ctx: Context<Delete>) -> Result<()> {Ok(())}}#[derive(Accounts)]pub struct Create {}#[derive(Accounts)]pub struct Update {}#[derive(Accounts)]pub struct Delete {}#[account]pub struct MessageAccount {}
Trước khi bắt đầu, chạy build
trong terminal Playground để kiểm tra
chương trình khởi đầu có biên dịch thành công không.
$build
Định nghĩa kiểu tài khoản tin nhắn
Đầu tiên, định nghĩa cấu trúc cho tài khoản tin nhắn mà chương trình tạo ra. Cấu trúc này xác định dữ liệu để lưu trữ trong tài khoản được tạo bởi chương trình.
Trong lib.rs
, cập nhật struct MessageAccount
với nội dung sau:
#[account]pub struct MessageAccount {pub user: Pubkey,pub message: String,pub bump: u8,}
Xây dựng chương trình một lần nữa bằng cách chạy build
trong terminal.
$build
Đoạn mã này xác định dữ liệu nào sẽ được lưu trữ trên tài khoản tin nhắn. Tiếp theo, bạn sẽ thêm các chỉ thị chương trình.
Thêm chỉ thị Create
Bây giờ, hãy thêm chỉ thị create
để tạo và khởi tạo MessageAccount
.
Bắt đầu bằng cách xác định các tài khoản cần thiết cho chỉ thị bằng cách cập
nhật cấu trúc Create
với nội dung sau:
#[derive(Accounts)]#[instruction(message: String)]pub struct Create<'info> {#[account(mut)]pub user: Signer<'info>,#[account(init,seeds = [b"message", user.key().as_ref()],bump,payer = user,space = 8 + 32 + 4 + message.len() + 1)]pub message_account: Account<'info, MessageAccount>,pub system_program: Program<'info, System>,}
Tiếp theo, thêm logic nghiệp vụ cho lệnh create
bằng cách cập nhật hàm
create
với nội dung sau:
pub fn create(ctx: Context<Create>, message: String) -> Result<()> {msg!("Create Message: {}", message);let account_data = &mut ctx.accounts.message_account;account_data.user = ctx.accounts.user.key();account_data.message = message;account_data.bump = ctx.bumps.message_account;Ok(())}
Xây dựng lại chương trình.
$build
Thêm lệnh cập nhật
Tiếp theo, thêm lệnh update
để thay đổi MessageAccount
với một tin nhắn mới.
Giống như bước trước, trước tiên hãy chỉ định các tài khoản cần thiết cho lệnh
update
.
Cập nhật cấu trúc Update
với nội dung sau:
#[derive(Accounts)]#[instruction(message: String)]pub struct Update<'info> {#[account(mut)]pub user: Signer<'info>,#[account(mut,seeds = [b"message", user.key().as_ref()],bump = message_account.bump,realloc = 8 + 32 + 4 + message.len() + 1,realloc::payer = user,realloc::zero = true,)]pub message_account: Account<'info, MessageAccount>,pub system_program: Program<'info, System>,}
Tiếp theo, thêm logic cho lệnh update
.
pub fn update(ctx: Context<Update>, message: String) -> Result<()> {msg!("Update Message: {}", message);let account_data = &mut ctx.accounts.message_account;account_data.message = message;Ok(())}
Xây dựng lại chương trình
$build
Thêm lệnh xóa
Tiếp theo, thêm lệnh delete
để đóng MessageAccount
.
Cập nhật cấu trúc Delete
với nội dung sau:
#[derive(Accounts)]pub struct Delete<'info> {#[account(mut)]pub user: Signer<'info>,#[account(mut,seeds = [b"message", user.key().as_ref()],bump = message_account.bump,close = user,)]pub message_account: Account<'info, MessageAccount>,}
Tiếp theo, thêm logic cho lệnh delete
.
pub fn delete(_ctx: Context<Delete>) -> Result<()> {msg!("Delete Message");Ok(())}
Xây dựng lại chương trình.
$build
Triển khai chương trình
Bạn đã hoàn thành chương trình CRUD cơ bản. Triển khai chương trình bằng cách
chạy deploy
trong terminal của Playground.
Trong ví dụ này, bạn sẽ triển khai chương trình lên devnet, một cụm Solana dành cho kiểm thử phát triển.
Ví Playground kết nối với devnet theo mặc định. Đảm bảo ví Playground của bạn có devnet SOL để thanh toán cho việc triển khai chương trình. Nhận devnet SOL từ Solana Faucet.
$deploy
Thiết lập tệp kiểm thử
Mã khởi đầu cũng bao gồm một tệp kiểm thử trong anchor.test.ts
.
import { PublicKey } from "@solana/web3.js";describe("pda", () => {it("Create Message Account", async () => {});it("Update Message Account", async () => {});it("Delete Message Account", async () => {});});
Thêm mã dưới đây vào bên trong describe()
, nhưng trước các phần
it()
.
const program = pg.program;const wallet = pg.wallet;const [messagePda, messageBump] = PublicKey.findProgramAddressSync([Buffer.from("message"), wallet.publicKey.toBuffer()],program.programId);
Chạy tệp kiểm thử bằng cách chạy test
trong terminal Playground để kiểm
tra xem nó có chạy như mong đợi không. Các bước tiếp theo sẽ thêm các bài kiểm
thử thực tế.
$test
Gọi lệnh Create
Cập nhật bài kiểm thử đầu tiên với nội dung sau:
it("Create Message Account", async () => {const message = "Hello, World!";const transactionSignature = await program.methods.create(message).accounts({messageAccount: messagePda}).rpc({ commitment: "confirmed" });const messageAccount = await program.account.messageAccount.fetch(messagePda,"confirmed");console.log(JSON.stringify(messageAccount, null, 2));console.log("Transaction Signature:",`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`);});
Gọi lệnh cập nhật
Cập nhật bài kiểm thử thứ hai với nội dung sau:
it("Update Message Account", async () => {const message = "Hello, Solana!";const transactionSignature = await program.methods.update(message).accounts({messageAccount: messagePda}).rpc({ commitment: "confirmed" });const messageAccount = await program.account.messageAccount.fetch(messagePda,"confirmed");console.log(JSON.stringify(messageAccount, null, 2));console.log("Transaction Signature:",`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`);});
Gọi lệnh xóa
Cập nhật bài kiểm thử thứ ba với nội dung sau:
it("Delete Message Account", async () => {const transactionSignature = await program.methods.delete().accounts({messageAccount: messagePda}).rpc({ commitment: "confirmed" });const messageAccount = await program.account.messageAccount.fetchNullable(messagePda,"confirmed");console.log("Expect Null:", JSON.stringify(messageAccount, null, 2));console.log("Transaction Signature:",`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`);});
Chạy kiểm thử
Sau khi chuẩn bị các bài kiểm thử, chạy tệp kiểm thử với test
trong
terminial của Playground. Lệnh này chạy các bài kiểm thử đối với chương trình đã
triển khai trên devnet và ghi lại các liên kết đến SolanaFM để xem chi tiết giao
dịch.
$test
Kiểm tra các liên kết SolanaFM để xem chi tiết giao dịch.
Lưu ý rằng trong ví dụ này, nếu bạn chạy lại bài kiểm thử, lệnh create
sẽ
thất bại vì messageAccount
đã tồn tại như một tài khoản. Chỉ một tài khoản
có thể tồn tại cho một PDA nhất định.
Is this page helpful?