Cẩm nang lập trình Solana

Trong cẩm nang này, mình sẽ không đi qua về kiến trúc cốt lõi của Solana và sẽ trực tiếp đi đến Programming Model cũng như một vài ví dụ điển hình về hợp đồng trên Solana.

Cẩm nang lập trình Solana
Vài đặc điểm nổi bật của Solana

Solana trong những năm gần đây nổi lên như một ETH Killer tiềm năng có thể soán ngôi Ethereum bất cứ lúc nào. Và lý do cốt lõi khiến các Solana holder có một niềm tin mạnh mẽ đối với blockchain này là các khái cạnh vượt trội về mặt công nghệ. Với kiến trúc của Sealevel Runtime cho phép thực thi giao dịch song song (parallel processing), Solana mang đến khả năng thực thi giao dịch với tốc độ thực thi giao dịch cao (trung bình ~3000 giao dịch mỗi giây) có thể nói là đứng đầu trong mảng blockchain cùng với mức phí thấp.

Thông số trên Explorer của Solana

Sẽ có nhiều quan điểm và minh chứng cho thấy việc đẩy mạnh tốc độ giao dịch và giảm phí sẽ khiến Solana gặp nhiều sự cố về mặt bảo mặt dựa theo các lý thuyết về blockchain trilemma. Thời gian uptime khi mà trong nằm vừa qua Solana nhiều lần hứng chịu nhiều đợt sập nguồn do quá tải giới hạn giao dịch mạng lưới có thể chịu được.

Thời gian uptime của Solana blockchain trong Q1/2022. Nguồn: https://status.solana.com/uptime?page=3

Tuy nhiên, ở phương diện là một lập trình viên trên Solana, mình tự tin rằng các đợt sập nguồn của Solana sẽ không còn là vấn đề lớn khi QUIC protocol được thay thế trở thành giao thức chính để truyền các gói tin trong mạng lưới với tốc độ của UDP và tính bảo mật của TCP. Do đó, việc có một cẩm nang tiếng Việt để giúp các bạn tiếp cận dễ hơn với lập trình hợp đồng thông minh trên Solana mình nghĩ sẽ là điều cần thiết.

Trong cẩm nang này, mình sẽ không đi qua về kiến trúc cốt lõi của Solana và sẽ trực tiếp đi đến Programming Model cũng như một vài ví dụ điển hình về hợp đồng trên Solana.

Bắt đầu với Solana

Trước khi bắt đầu về các kiến thức nền tảng của Solana, bạn cần phải tải Solana CLI.

Install the Solana Tool Suite | Solana Docs
There are multiple ways to install the Solana tools on your computer

Ngôn ngữ cho hợp đồng thông minh

Khi làm việc với các hợp đồng trên Solana, hầu hết mọi người sẽ sử dụng Rust làm ngôn ngữ chủ đạo. Tuy nhiên, bạn cũng có thể sử dụng các ngôn ngữ khác như C hay C++ để viết hợp đồng thông minh. Về mặt kiến thức yêu cầu cho Rust, Solana không sử dụng quá nhiều tính năng mà Rust hỗ trợ. Tuy nhiên, việc nắm chắc các khái niệm về pointer, reference, ownership trong Rust cũng là một điểm mạnh để giúp bạn làm quen với Solana nhanh hơn.

Developing with Rust | Solana Docs
Solana supports writing on-chain programs using the

Ở phía client, Typescript sẽ là ngôn ngữ không thể nào phù hợp hơn để xử lý các dữ liệu và giao dịch từ phía mạng lưới.

Chương trình trên Solana: Native Program và On-chain Program

Trong Solana, các chương trình sẽ gắn với 1 mã định danh Program ID cụ thể. Chương trình (program) được chia làm 2 loại chính: Native Program và On-chain Program. Native Program là chương tình lõi của Solana hay các chương trình nằm ở nền hệ thống. Ví dụ của Native Program là

  • System Program (ID: 11111111111111111111111111111111): chương trình xử lý các cơ chế về khởi tạo account mới và các logic của account đó như chuyển native SOL
  • Stake Program (ID: Stake11111111111111111111111111111111111111): chương trình xử lý các cơ chế về stake và trả thưởng validator. Vì có cơ thế đồng thuận là Proof of History, có những nét tương đồng với Proof of Stake, để duy trì mạng lưới phải cần trả phí cho các validator trên mạng lưới.
  • Voting Program (ID: Vote111111111111111111111111111111111111111): Khởi tạo và quản lý các accounts để track hoạt động và trạng thái vote của validator.
  • BPF Loader Program (ID: BPFLoaderUpgradeab1e11111111111111111111111): Xử lý các cơ chế về deploy, nâng cấp hoặc thực thi các chương trình trên Solana.

Ở phần dưới mình sẽ đưa ứng dụng của System Program khi lập tình trên Solana. Ở khía cạnh nhà phát triển, các hợp đồng thông minh sẽ là các chương trình được gọi là On-chain program. Các account này cũng sẽ có program ID định danh nhất định. Ngoài ra, Solana còn có một library tập hợp các Program gọi là Solana Program Library (SPL). Trong đó bao gồm các account ví dụ như Token Program (chương trình để làm việc với hệ thống token trên Solana) hay Token Swap Program (chương trình để xây dựng các hệ thống swap token trên Solana).

Introduction | Solana Program Library Docs
The Solana Program Library (SPL) is a collection of on-chain programs targeting

Về sự liên kết của các program với nhau, thương thì chúng ta sẽ làm việc với 3 program chính là System Program, BPF Loader Program, SPL và On-chain program.

Mẫu hình lập trình của Solana

Account

Nếu bạn đã có kinh nghiệm với Ethereum hoặc đã tìm hiểu về mạng blockchain này trước đó, bạn sẽ biết về khái niệm EOA (Externally Owned Account) và Contract Account. Trong ngữ cảnh của Solana, điều này cũng tương tự, Solana gồm 2 loại account chính là System Account và Program Derived Account. Cũng giống như trong hệ điều hành thông thường, có thể xem các account trên Solana là các file chứa dữ liệu kèm với các metadata để xác định xem ai là người có quyền điều chỉnh dữ liệu hoặc xoá file đó khỏi hệ thống.

Đơn vị tiền tệ trên Solana

Ta sẽ có 2 đơn vị phổ biến là lamport và SOL với 1 lamport tương ứng với 0.000000001 SOL

Một điều cần lưu ý là, để các account trên Solana có thể tồn tại, các account này cần một số lamport nhất định để duy trì sự tồn tại trên cơ sở dữ liệu của mạng lưới. Nếu các account này có đủ số lamport trong vòng 2 năm, account sẽ được miễn phí suốt khoảng thời gian còn lại.

Bạn cũng không cần quá quan tâm về vấn đề này vì kể từ các lần cập nhật gần đây, các account khi được khởi tạo bắt buộc đều phải có đủ phí trong vòng 2 năm. Ngoài ra, khi một account được đóng lại (close), phí rent của account đó sẽ được hoàn trả lại cho người trả phí khi khởi tạo (rent payer). Và khi rút tiền từ System Program account, bạn không được rút toàn bộ tiền từ account ra vì đó cũng là 1 cách để bạn giúp cho account đi vào hư không.

System Account

Là account được khởi tạo bởi System Program (là một Native Program). Và các account này cũng sẽ được sỡ hữu bởi System Program. Đối với các account thuộc dạng này, client được cấp quyền bởi Runtime system (Hệ thống thời gian chạy) để chuyển token và đặc biệt là bạn có thể chuyển quyền sỡ hữu của các account này. Lấy ví dụ phổ biến nhất là các tài khoản ví mà các bạn khởi tạo trên các ví Web3 như Phantom, Solfare hay Sollet.

Ví dụ của System Account aka tài khoản trên ví Phantom của mình

Để khởi tạo một System Program bằng Rust, bạn có thể dùng hàm SystemProgram::CreateAccount. Các account này có đặc điểm là nó sẽ gồm một cặp khoá Private Key / Public Key hoặc còn được gọi phổ biến trong mật mã học là Keypair. Nếu bạn là một crypto user, sẽ không quá xa lại với các cụm từ này, bất cứ khi nào bạn khởi tạo một ví mới thông qua Phantom hay Solflare, tương ứng với một cặp khoá mới cùng với Seed Phrase để khởi tạo.

solana/system_instruction_processor.rs at master · solana-labs/solana
Web-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces. - solana/system_instruction_processor.rs at master · solana-labs/solana
Bạn có thể tham khảo code của hàm CreateAccount tại đây

Program Derived Address (PDA)

Khác với System Account, Program derived address hay PDA là account được khởi tạo và sở hữu bởi một account khác. Tuy nhiên, PDA không nằm trên vòng cung Ed25519 do đó PDA không có private key và chỉ có public key. Nhiều khi bạn sẽ gặp tình trạng không chuyển được token đến 1 PDA do bị off-curve. Hàm để tạo 1 PDA bằng Javascript sử dụng thư viện @solana/web3.js

https://solana-labs.github.io/solana-web3.js/classes/PublicKey.html#findProgramAddress

Tính ứng dụng của PDA trong lập tình Solana rất phổ biến và hầu như bất kì hợp đồng thông minh nào cũng sẽ sử dụng PDA. Khác với System Account, PDA không yêu cầu phí Rent do đó bạn có thể rút cạn SOL từ các PDA mà không lo account bị đóng lại. Một ví dụ cho việc sử dụng PDA vào thực tiện là mô hình thiết kế của các Vault. Hiểu nôm na Vault là chỗ bạn chỉ dùng để chứa tiền do đó việc thiết kế Vault là 1 PDA được sử dụng rất nhiều trên Solana. Và vì không có private key nên việc quản lý các PDA cũng sẽ dễ dàng hơn.

Transactions

Cấu trúc của 1 transaction hay 1 giao dịch trên Solana sẽ gồm tập hợp các instruction, Solana runtime sẽ thực thi giao dịch này theo thứ tự của các instruction được khai báo bên trong giao dịch. Bạn có thể hiểu instruction là bảng hướng dẫn để giúp cho Solana runtime biết cách thực thi chương trình và việc thực thi giao dịch trong Solana sẽ thực thi tập hợp nhiều chương trình. Đây là ví dụ của một giao dịch trên Solana khi xem qua explorer: https://solana.fm/tx/5iDyAtojTC4q5S9aBBym91sfM9WGL9SkL958EU5i5RPrFqHBNGAQk8FonVek4Uov2618KXpxptqLbjDfYr3ge1Hm?cluster=mainnet-qn1

Bạn sẽ dễ dàng thấy được giao dịch này gồm các phần mình đã nêu trên

Địa chỉ giao dịch và thời gian khởi tạo

Hash hay Signature là địa chỉ của giao dịch này để giúp bạn có thể truy vấn dữ liệu về giao dịch.

Các accounts liên quan

Ở dưới phần địa chỉ, bạn sẽ thấy một tập hợp các account có liên quan đến giao dịch. Mình sẽ giải thích chi tiết qua về phần này. Khi viết 1 instruction, bạn sẽ cần xác định trong chương trình các account nào sẽ được khai báo. Các Accounts này sẽ là các PDA hoặc các System Program. Mình lấy ví dụ như bạn có 1 instruction để chuyển tiền từ account A sang account B. Nếu vậy các account liên quan sẽ gồm account A và account B. Ngoài ra, một điểm lưu ý là account sẽ có các thuộc tính như isSigner, readOnlywritable.

  • isSigner: Account của bạn sẽ là signer của instruction cụ thể. Điều đó có nghĩa là, để thực thi thành công instruction đó yêu cầu chữ ký của account trên.
  • readOnly: Như tên gọi, đây là thuộc tính mặc định cho các account. Các account mang thuộc tính sẽ không thể bị thay đổi dữ liệu thông qua instruction được thực thi.
  • writable:  Trái ngược với readOnly, các account mang thuộc tính này có thể bị điều chỉnh thông qua instruction được thực thi.

Cross-program Invocation

Calling Between Programs | Solana Docs
Cross-Program Invocations

Solana hỗ trợ khả năng thực thi các chương trình của hợp đồng thông minh khác trong chính hợp đồng thông minh hiện tại. Trong Solana, khả năng này gọi là Cross-program invocation (thường được gọi là CPI). Để thực hiện CPI, sẽ có 2 hàm chính là invokeinvoke_signed. Với invoke, signer của transaction cũng sẽ là signer của instruction.

invoke(&instruction, accounts)?;

Với invoke_signed, signer sẽ được phía lập trình viên khái báo seed và instruction chỉ được thực thi nếu seed tương ứng.

  invoke_signed(
   &instruction,
   accounts,
   &[&["First addresses seed"],
   &["Second addresses first seed", "Second addresses secon seed"]],
  )?;

Kiến trúc của một chương trình

GitHub - coral-xyz/anchor: ⚓ Solana Sealevel Framework
⚓ Solana Sealevel Framework. Contribute to coral-xyz/anchor development by creating an account on GitHub.

Có 2 cách phát triển chương trình phổ biến trong Solana: Viết chay chương trình và viết chương trình bằng Anchor framework. Nếu bạn đã quá quen với phát triển giao diện người dùng thì điều này cũng giống như phát triển frontend bằng HTML, CSS, JS thuần và phát triển với React framework. Điểm lợi và hại của 2 hương phát triển này là viết chay chương trình yêu cầu bạn thực hiện khá nhiều các tác vụ nhỏ nhặt trong khi Anchor framework sẽ giúp bạn phát triển các chương trình với tốc độ nhanh hơn và gọn gàng hơn. Tuy nhiên, vì phát triển ứng dụng sử dụng 1 framework có sẵn, sẽ có nhiều logic bị hạn chế về thiết kế của Anchor mặc dù các hạn chế này là không đáng kể.

Trong bài viết này, mình sẽ sử dụng Anchor framework để đưa ra ví dụ của một hợp đồng thông minh

Ngày từ những dòng đầu tiên, chúng ta có declare_id là nơi khai báo Program ID của chương trình. Module hello_anchor có macro #[program] sẽ là endpoint chính của chương trình, nơi bạn khai báo các phương thức chính của chương trình. Chúng ta có account MyAccount với macro #[account] để khai báo các account trong hợp đồng thông minh. Và cuối cùng Context<SetData> để khai báo những account được dùng trong instruction đó và các thuộc tính của account đầu vào. Và đó là các phần chính của 1 chương trình trên Solana. Chúng ta có: Account, Instruction, Program ID và Context. Nắm được các khai niệm nêu trên, các bạn sẽ có thể viết được bất kì chương trình trên Solana.

Solana RPC endpoint

Solana mặc định có các public RPC mà bạn có thể dùng để deploy hợp đồng thông minh là devnet, testnet và mainnet. Mainnet là production cluster chính của Solana. Devnet và Testnet sẽ là các chain để bạn thử nghiệm.

Quy trình phát triển

Để phát triển ứng dụng phi tập trung hoàn chỉnh trên Solana, chúng ta sẽ có quy trình như sau: Bạn viết và deploy chương trình lên mạng Solana. Ở phía client, chúng ta sẽ gọi đến các instruction được khai báo trong program thông qua Program ID. Sau khi nhận được dữ liệu, bạn cần phải qua bước Deserialize data. Ngược lại, để gửi dữ liệu lên chain, chúng ta cần Serialize data.

Khác với khi làm việc với các thư viện HTTP như Axios để GET hoặc POST data, dữ liệu đầu vào và đầu ra của bạn sẽ được parse trực tiếp bởi các thư viện đó sang định dạng phù hợp như JSON. Các dữ liệu được truyền đi trong Solana cần qua bước serialize / deserialize data sang dữ liệu byte tương ứng. Thường mình sẽ sử dụng thư viện Borsh để xử lý việc này.

GitHub - near/borsh-js: TypeScript/JavaScript implementation of Binary Object Representation Serializer for Hashing
TypeScript/JavaScript implementation of Binary Object Representation Serializer for Hashing - GitHub - near/borsh-js: TypeScript/JavaScript implementation of Binary Object Representation Serializer...

Tới đây, bài viết cũng khá là dài và mình cũng đã bao quát hầu hết các phần cốt lõi của lập trình trong Solana. Hi vọng bài viết này sẽ giúp ích được cho các bạn muốn tìm hiểu thêm về hợp đồng thông minh trên Solana.