Xin chào, mình là N.T.N từ GMO-Z.COM Vietnam Lab Center
Gần đây khi cả thế giới đang chao đảo bởi thị trường tiền mã hóa thì ở phía những nhà phát triển cũng đang cuốn theo cơn sóng Blockchain.
Các cụm từ web3, dapp, metaverse trở nên phổ biến và được nhiều sự quan tâm. Đặc biệt trong đó Facebook cũng đã đổi tên thành Meta để thể hiện sự khát khao của mình.


Tại GMO-Z.COM Vietnam Lab Center cũng đã hình thành các team để nghiên cứu các chủ đề về Blockchain; ứng dụng blockchain tuy vẫn còn trong giai đoạn sơ khai, chưa xuất hiện nhiều trong sinh hoạt của con người nhưng biết đâu được,  khi sự phát triển hạ tầng internet lên 1 tầng cao mới sẽ là tiền đề cho các ứng dụng blockchain bùng nổ.
Có thể nói thị trường Blockchain rất có tiềm năng.

Trong bài viết này, mình sẽ chia sẻ việc phát triển các ứng dụng trên Solana Blockchain bằng Anchor Framework

Tóm tắt nội dung

  • Cấu trúc một ứng dụng Blockchain trên solana
  • Giới thiệu Anchor Framework
  • Viết và deploy một Smart Contract lên DevNet
  • Viết test unit cho Smart Contract

I. Cấu trúc một ứng dụng trên Solana

Trước khi bắt đầu các bạn tìm hiểu về các viết một Smart Contract. Tôi sẽ tóm tắt lại mô hình một ứng dụng hoạt động trên mạng Solana, điều này sẽ giúp các bạn mường tượng ra được những công việc mà chúng ta sẽ phải làm đối với mảng công nghệ này.

Trên đây là mô hình mà một ứng dụng trên Solana sẽ hoạt động, gồm có 2 phần

  • Program: Chính là Smart Contract mà chúng ta sẽ deploy lên Solana Network
  • Client: Là các phần mềm tương tác với Smart Contract này

Smart Contract có thể được viết bởi Rust, C, C++. Sau khi được Deploy lên Solana Network, chúng ta có thể tương tác với các Method đã được định nghĩa thông qua một chương trình ở phía Client.

Để xây dựng ứng dụng trên Solana, chúng ta có thể tự phát triển từ A đến Z cả Program và Client hoặc sử dụng các Framework hỗ trợ quá trình này. Đơn cử ở đây là Anchor Framework.

II. Giới thiệu Anchor Framework

Anchor là một framework để xây dựng ứng dụng tương tác với Solana Smart Contract,
và viết Smart Contract nhanh chóng và an toàn hơn.

Ngoài ra nó bao gồm những tập lệnh CLI giúp tương tác với Solana network, và xây dựng bộ khung của chương trình Frontend.

Cụ thể như sau:
- Cung cấp thư viện Rust để viết Smart Contract nhanh hơn
- Cung cấp các API CLI hỗ trợ cho việc triển khai Smart Contract
- Cung cấp các API CLI generate những đoạn mã phù hợp đề tương tác với Smart Contract của bạn, kèm với đó là việc xử lý những kiểm tra bảo mật nhất định .

Điều đó có nghĩa là thay vì làm việc trên các phần tẻ nhạt của các chương trình Solana được xây dựng thô (bạn viết code từ a đến z) , bạn có thể dành nhiều thời gian hơn để làm việc với những gì quan trọng nhất, sản phẩm của bạn.

https://ww.anchor-lang.com

III. Bắt đầu với anchor

3.1 Cài đặt các gói phần mềm

Để bắt đầu sử dụng Anchor framework, chúng ta cần cài các gói phần mềm bao gồm Rust, Solana, Anchor.  Cụ thể bạn có thể click vào link hướng dẫn dưới đây để xem các bước cài đặt

https://www.anchor-lang.com/docs/installation

Sau đó, kiểm tra xem bạn đã có các gói phần mềm tương ứng chưa

$solana --version
solana-cli 1.9.30 (src:30e47c2b; feat:462418899)

$rustc --version
rustc 1.61.0 (fe5b13d68 2022-05-18)

$anchor --version
anchor-cli 0.25.0

3.2 Cấu trúc một project

Sau khi cài đặt các gói phần mềm theo hướng dẫn chúng ta đã có thể khởi tạo một dự án với cú pháp

    anchor init <new-workspace-name>

Anchor sẽ tạo thư mục chứa Project được tổ chức như sau:

.
├── Anchor.toml
├── app
├── Cargo.toml
├── migrations
├── node_modules
├── package.json
├── programs
├── tests
├── tsconfig.json
└── yarn.lock

Trong đó:

  • app: Một thư mục trống mà bạn có thể sử dụng để chứa giao diện người dùng của mình
  • programs: Thư mục này chứa các chương trình của bạn. Nó có thể chứa nhiều chương trình nhưng ban đầu chỉ chứa một chương trình có cùng tên với <new-workspace-name>.
  • tests: Thư mục chứa các bài kiểm tra E2E của bạn. Nó sẽ bao gồm một tệp kiểm tra mã mẫu trong programs / <new-workspace-name>.
  • migrations: Trong thư mục này, bạn có thể lưu các tập lệnh deploy và migrate cho chương trình của mình.

IV. Xây dựng một Smart Contract và deploy lên DevNet

4.1 Các loại account trên mạng lưới Solana

Để hiểu cách vận hành của Smart Contract trên solana chúng ta cần phải nắm sơ lượt về các loại account trên mạng lưới này.

Trên mạng lưới Solana có 3 loại Account riêng biệt như sau:

- Program Account -  Các tài khoản này lưu trữ mã thực thi Smart Contract.
- Storage Account - Các tài khoản này lưu trữ dữ liệu được kết nối với Program.
- Token Account - Các tài khoản này theo dõi số dư tài khoản của mã thông báo và cho phép chuyển hoặc nhận token giữa các tài khoản. (có thể hiểu là tài khoản giao dịch)

Trong chuỗi khối Solana, có sự tách biệt giữa chương trình và dữ liệu/trạng thái của chương trình. Cả hai đều được chỉ định các tài khoản riêng biệt nhưng được kết nối với nhau.

Nếu bạn định tạo một chương trình đếm số lần mà một chương trình đã thực hiện, bạn cần:

  • Một Account để thực hiện việc thực thi (Program Account).
  • Một Account khác để lưu trữ số lần chuyển (Storage Account).
  • Account dùng để deploy được Smart Contract trên mạng solana hoặc trả phí cho những giao dịch (Token Account)

4.2. Tạo một Smart Contract đơn giản

Khởi tạo project the-begin bằng command

anchor init the-begin

Trong thư mục programs/the-begin/src/lib.rs. Chúng ta sẽ viết một Smart Contract đơn giản thực hiện 2 công việc

  • Tạo một Account chứa Data trong chương trình của mình
  • Update Data của Account đó
use anchor_lang::prelude::*;

declare_id!("XXXXXXXXX");

#[program]
mod the_begin {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data = data;
        Ok(())
    }

    pub fn update(ctx: Context<Update>, data: u64) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data = data;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 8 + 8)]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut)]
    pub my_account: Account<'info, MyAccount>,
}

#[account]
pub struct MyAccount {
    pub data: u64,
}

4.3. Tạo các Account và setup môi trường

Tạo Token Account dùng để trả phí cho việc deploy và thực thi các giao dịch

Nếu bạn chưa có Token Account bạn có thể tạo bằng command sau

solana-keygen new 

Mặc định thì keypair được lưu tại ~/.config/solana/id.json

Chỉ định DevNet để deploy Smart Contract

solana config set --url devnet

Airdop cho Token Account của bạn 1 ít Sol

solana airdrop 2

Kiểm tra các thông số cài đặt

$solana config set --url devnet
Config File: /home/lucas/.config/solana/cli/config.yml
RPC URL: https://api.devnet.solana.com 
WebSocket URL: wss://api.devnet.solana.com/ (computed)
Keypair Path: /home/lucas/.config/solana/id.json 
Commitment: confirmed 
$solana balance  
2.63203032 SOL

4.4 Deploy Smart Contract

Build Smart Contract

Bây giờ thực thi command dưới đây, Anchor sẽ tự động build 1 chương trình viết bằng Rust sang Bytecode để deploy lên mạng Solana

anchor build

Một chương trình sẽ được tạo ra với đường dẫn sau target/deploy/the_begin.so

Kèm với đó là Anchor cũng generate cho ta 1 Program Account tại `target/deploy/the_begin-keypair.json`

Deploy Smart Contract

Anchor cũng sẽ hiện hướng dẫn bạn deploy chương trình này trên cửa sổ terminal

To deploy this program:
  $ solana program deploy /home/lucas/Research/Solana/the-begin/target/deploy/the_begin.so
The program address will default to this keypair (override with --program-id):
  /home/lucas/Research/Solana/the-begin/target/deploy/the_begin-keypair.json

Solana CLI sẽ mặc định chọn keypair nằm cùng thư mục với Smart Contract mà chúng ta sẽ deploy

ví dụ:

$solana program deploy /home/lucas/Research/Solana/the-begin/target/deploy/the_begin.so 

Program Id: EUm7VJBMPkoExd8XLXKheKS8b7ktgdGPaUTs4CqyR7xu

Sau khi deploy, chúng ta có thể thấy được chương trình tồn tại trên mạng Solana bằng công cụ Explorer

https://explorer.solana.com/address/EUm7VJBMPkoExd8XLXKheKS8b7ktgdGPaUTs4CqyR7xu?cluster=devnet

V. Viết một test unit cho Smart Contract program

Trong thư mục test ta viết một test file the-begin.ts để test các phương thức của Smart Contract

import { Program } from "@project-serum/anchor";
import { TheBegin } from "../target/types/the_begin";

const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;

describe("the-begin", () => {
  let _myAccount = null;
  // Use a local provider.
  const provider = anchor.AnchorProvider.env();

  // Configure the client to use the local cluster.
  anchor.setProvider(provider);
  it("Creates and initializes an account in a single atomic transaction (simplified)", async () => {
    // #region code-simplified
    // The program to execute.
    const program = anchor.workspace.TheBegin as Program<TheBegin>;


    // The Account to create.
    const myAccount = anchor.web3.Keypair.generate();

    // Create the new account and initialize it with the program.
    // #region code-simplified
    await program.rpc.initialize(new anchor.BN(1234), {
      accounts: {
        myAccount: myAccount.publicKey,
        user: provider.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      },
      signers: [myAccount],
    });

    // #endregion code-simplified

    // Fetch the newly created account from the cluster.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was initialized.
    assert.ok(account.data.eq(new anchor.BN(1234)));

    _myAccount = myAccount;
  });

  it("Updates a previously created account", async () => {
    const myAccount = _myAccount;

    // #region update-test

    // The program to execute.
    const program = anchor.workspace.TheBegin as Program<TheBegin>;

    // Invoke the update rpc.
    await program.rpc.update(new anchor.BN(4321), {
      accounts: {
        myAccount: myAccount.publicKey,
      },
    });

    // Fetch the newly updated account.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was mutated.
    assert.ok(account.data.eq(new anchor.BN(4321)));

    // #endregion update-test
  });
});

Ngoài ra ta cũng phải config file Anchor.toml để truyền các biến môi trường

[features]
seeds = false
skip-lint = false
[programs.localnet]
the_begin = "EUm7VJBMPkoExd8XLXKheKS8b7ktgdGPaUTs4CqyR7xu"

[programs.devnet]
the_begin = "EUm7VJBMPkoExd8XLXKheKS8b7ktgdGPaUTs4CqyR7xu"

[registry]
url = "https://api.apr.dev"

[provider]
cluster = "devnet"
wallet = "/home/lucas/.config/solana/id.json"

[scripts]
# test = "yarn run mocha -t 1000000 tests/"
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

Sau khi chuẩn bị Test và môi trường, command thực thi file test như sau:

$anchor test --skip-deploy
BPF SDK: /home/lucas/.local/share/solana/install/releases/stable-30e47c2b39853ed967815258efaca05e347e6290/solana-release/bin/sdk/bpf
cargo-build-bpf child: rustup toolchain list -v
cargo-build-bpf child: cargo +bpf build --target bpfel-unknown-unknown --release
    Finished release [optimized] target(s) in 0.18s
cargo-build-bpf child: /home/lucas/.local/share/solana/install/releases/stable-30e47c2b39853ed967815258efaca05e347e6290/solana-release/bin/sdk/bpf/dependencies/bpf-tools/llvm/bin/llvm-readelf --dyn-symbols /home/lucas/Research/Solana/the-begin/target/deploy/the_begin.so

To deploy this program:
  $ solana program deploy /home/lucas/Research/Solana/the-begin/target/deploy/the_begin.so
The program address will default to this keypair (override with --program-id):
  /home/lucas/Research/Solana/the-begin/target/deploy/the_begin-keypair.json

Found a 'test' script in the Anchor.toml. Running it as a test suite!

Running test suite: "/home/lucas/Research/Solana/the-begin/Anchor.toml"

yarn run v1.22.19
warning package.json: No license field
$ /home/lucas/Research/Solana/the-begin/node_modules/.bin/ts-mocha -p ./tsconfig.json -t 1000000 'tests/**/*.ts'


  the-begin
    ✔ Creates and initializes an account in a single atomic transaction (simplified) (2881ms)
    ✔ Updates a previously created account (2298ms)


  2 passing (5s)

Done in 10.97s.

Trên màn hình terminal ta có thể thấy là đã thực thi thành công initializes. Ngoài ra, ta cũng có thể theo dõi được thông tin của Account vừa tạo trên Solana Explorer

https://explorer.solana.com/address/695VSP2UeC4RaNPD24A47FBcQYAJpTaGSLmbxQnKTdst?cluster=devnet

Account vừa tạo đã thực hiện hai giao dịch là Create và Update

VI. Kết bài

Cảm ơn các bạn đã theo dõi đến đây, hy vọng bài viết này sẽ giúp các bạn dễ dàng tiếp cận với Solana và việc phát triển ứng dụng trên blockchain này

Toàn bộ source-code của bài viết này các bạn có thể tìm thấy tại github của mình

https://github.com/info-namnt/the-begin

Hẹn gặp lại các bạn ở bài viết tiếp theo trong loạt series về blockchain