
Mô hình lập trình Solana: Giới thiệu về phát triển trên Solana
Mục lục
- Bài viết này nói về điều gì?
- Cụm Solana là gì?
- Tài khoản là gì?
- Cấu trúc tài khoản
- Tiền thuê
- Địa chỉ trên Solana
- Tài khoản trên Solana khác tài khoản trên Ethereum như thế nào?
- Chương trình Solana là gì?
- Giao dịch là gì?
- Chỉ thị
- Giao dịch có phiên bản là gì?
- Cấu trúc của giao dịch có phiên bản
- Tích hợp mô hình lập trình với luồng giao dịch của Solana
- Kết luận
- Tài nguyên bổ sung / Đọc thêm
Bài viết này nói về điều gì?
Cách Solana tiếp cận điện toán phi tập trung bắt nguồn từ một nguyên tắc đơn giản: mọi thứ đều được lưu trữ trong vùng bộ nhớ riêng, được gọi là tài khoản. Solana hoạt động như một kho khóa/giá trị toàn cục, trong đó khóa công khai đóng vai trò là mã định danh duy nhất cho tài khoản tương ứng. Tài khoản là xương sống của Solana vì chúng lưu trữ trạng thái; chúng chứa mọi thứ, từ chương trình đến số dư token. Giao dịch được dùng để cập nhật tài khoản và phản ánh các thay đổi trạng thái.
Trong bài viết này, chúng ta sẽ khám phá sự phức tạp trong kiến trúc của Solana. Trước tiên là tổng quan về các cụm và khái niệm trạng thái, sau đó là vai trò của tài khoản và chương trình với tư cách các thành phần nền tảng của Solana. Tiếp theo, chúng ta sẽ xem cách giao dịch tạo ra các tương tác linh hoạt giữa tài khoản và chương trình.
Sau khi đọc xong bài viết này, bạn sẽ hiểu sâu về mô hình lập trình của Solana. Bạn sẽ nắm được kiến trúc của các cụm, vai trò thiết yếu của tài khoản trong việc lưu trữ dữ liệu và quy trình giao dịch cập nhật dữ liệu tài khoản. Ngoài ra, bạn cũng sẽ tìm hiểu các tính năng riêng của Solana như hệ thống tiền thuê và giao dịch có phiên bản.
Cụm Solana là gì?
Trọng tâm trong kiến trúc của Solana là các cụm — một tập hợp validator phối hợp xử lý giao dịch và duy trì một sổ cái duy nhất. Solana có một số cụm riêng biệt, mỗi cụm phục vụ một mục đích cụ thể:
- Localhost: cụm phát triển cục bộ tại cổng mặc định 8899. Giao diện dòng lệnh Solana (CLI) đi kèm một validator thử nghiệm tích hợp, có thể tùy chỉnh theo nhu cầu của từng nhà phát triển mà không cần airdrop hoặc chịu giới hạn tốc độ
- Devnet: môi trường sandbox không gây hậu quả để thử nghiệm trên Solana
- Testnet: môi trường để những người đóng góp cốt lõi cho Solana thử các bản cập nhật và tính năng mới trước khi chúng xuất hiện trên mainnet. Đây cũng là môi trường thử nghiệm dành cho các nhà phát triển muốn kiểm tra hiệu năng
- Mainnet Beta: cụm trực tiếp, không cần cấp quyền, nơi diễn ra các giao dịch thực tế. Đây là Solana “thực”, nơi người dùng, nhà phát triển, người nắm giữ token và validator tương tác hằng ngày
Mỗi cụm hoạt động độc lập và hoàn toàn không biết đến các cụm khác. Giao dịch được gửi đến sai cụm sẽ bị từ chối để bảo đảm tính toàn vẹn của từng môi trường vận hành.
Hãy hình dung các cụm như một heap dữ liệu nguyên khối. Trong khoa học máy tính, heap là vùng bộ nhớ nơi dữ liệu có thể được lưu trữ và sửa đổi linh hoạt. Tuy nhiên, cần lưu ý rằng các cụm không thực sự sử dụng cấu trúc dữ liệu heap. Phép so sánh này là một công cụ khái niệm giúp hiểu rằng các cụm gồm nhiều vùng bộ nhớ có thể được cấp phát và giải phóng khi cần. Hiểu cụm như một heap linh hoạt là chìa khóa để hiểu cách dữ liệu được quản lý, truy cập và bảo mật trong mạng.
Bạn cũng có thể xem heap dữ liệu nguyên khối này như một dạng nhà kho kỹ thuật số. Ở đây, dữ liệu giống như các hộp trên kệ, mỗi hộp có nhãn riêng và các quy tắc cụ thể về cách di chuyển cũng như thay đổi nội dung. Điều này bảo đảm một hệ thống an toàn, có trật tự, trong đó chỉ những hoạt động di chuyển hoặc thay đổi được cấp phép mới được thực hiện.
Hợp đồng thông minh, được gọi là chương trình trên Solana, được cấp phát phần riêng trong nhà kho hoặc heap để quản lý. Một chương trình có thể đọc bất kỳ phần nào trong không gian của nhà kho, nhưng cần một số quyền nhất định để thay đổi nội dung của không gian mà chương trình không sở hữu. Hành động duy nhất được cho phép trên toàn hệ thống là chuyển lamport, tiền mã hóa gốc của Solana, đến bất kỳ không gian nào trong nhà kho.
Mọi trạng thái đều nằm trong heap này, kể cả các chương trình. Mỗi vùng có một chương trình sở hữu và quản lý vùng đó. Ví dụ, các chương trình thuộc sở hữu của BPFLoader, chương trình chịu trách nhiệm tải, triển khai và nâng cấp các chương trình on-chain. Chúng ta gọi những vùng bộ nhớ này, tức các hộp trong nhà kho kỹ thuật số, là tài khoản.
Tài khoản là gì?
Mọi thứ trên Solana đều là tài khoản. Hãy xem tài khoản như các vùng chứa lưu giữ dữ liệu lâu dài, tương tự tệp trên máy tính. Đây là các khối xây dựng nên mô hình chương trình của Solana, dùng để lưu trữ trạng thái (tức số dư tài khoản, thông tin sở hữu, tài khoản có chứa chương trình hay không và thông tin tiền thuê).
Có ba loại tài khoản trên Solana:
- Tài khoản lưu trữ dữ liệu
- Tài khoản lưu trữ chương trình có thể thực thi
- Tài khoản lưu trữ chương trình gốc
Có thể phân biệt sâu hơn các loại tài khoản này theo khả năng của chúng:
- Tài khoản có thể thực thi — tài khoản có khả năng chạy mã
- Tài khoản không thể thực thi — tài khoản dùng để lưu trữ dữ liệu và không có khả năng thực thi mã (vì chúng không chứa mã!)
Trong hình trên, chúng ta có một vài ví dụ về tài khoản có thể thực thi và không thể thực thi. Với tài khoản có thể thực thi, Bubblegum là một ví dụ về Tài khoản chương trình. Đây là chương trình của Metaplex dùng để tạo và quản lý NFT nén. Vote Program là một ví dụ về Tài khoản chương trình gốc. Chương trình này được dùng để tạo và quản lý các tài khoản theo dõi trạng thái bỏ phiếu cùng phần thưởng của validator. Chúng ta sẽ trình bày sự khác biệt giữa Tài khoản chương trình và Tài khoản chương trình gốc trong phần Chương trình là gì?. Hiện tại, điều quan trọng cần biết là Solana có nhiều loại tài khoản có thể thực thi khác nhau.
Ngoài ra, mọi tài khoản không thể thực thi đều có thể được phân loại là Tài khoản dữ liệu. Ví dụ về Tài khoản dữ liệu gồm:
- Tài khoản token liên kết — tài khoản chứa thông tin về một token cụ thể, số dư và chủ sở hữu của token đó (ví dụ: Alice có 10 USDC)
- Tài khoản hệ thống — tài khoản do System Program tạo và sở hữu
- Tài khoản stake — tài khoản dùng để ủy quyền token cho validator nhằm có cơ hội nhận phần thưởng
Cấu trúc tài khoản
Tài khoản được cấu trúc theo struct AccountInfo:
pub struct AccountInfo<'a> {
pub key: &'a Pubkey,
pub lamports: Rc>,
pub data: Rc>,
pub owner: &'a Pubkey,
pub rent_epoch: Epoch,
pub is_signer: bool,
pub is_writable: bool,
pub executable: bool,
}Tài khoản được xác định bằng địa chỉ (key), là một khóa công khai 32 byte duy nhất.
Trường lamports chứa số lượng lamport thuộc sở hữu của tài khoản này. Một lamport bằng một phần tỷ SOL, token gốc của Solana.
data là mảng byte dữ liệu thô được tài khoản này lưu trữ. Trường này có thể lưu mọi thứ, từ siêu dữ liệu của tài sản kỹ thuật số đến số dư token, và có thể được các chương trình sửa đổi.
Trường owner chứa chủ sở hữu của tài khoản này, được biểu thị bằng địa chỉ của một tài khoản chương trình. Có một số quy tắc về quyền sở hữu tài khoản:
- Chỉ chủ sở hữu tài khoản mới có thể thay đổi dữ liệu và rút lamport
- Bất kỳ ai cũng có thể gửi lamport vào tài khoản
- Chủ sở hữu tài khoản có thể chuyển quyền sở hữu cho chủ sở hữu mới, với điều kiện dữ liệu của tài khoản được đặt lại về 0
Trường is_signer là một giá trị boolean cho biết giao dịch đã được chủ sở hữu của tài khoản liên quan ký hay chưa. Nói cách khác, trường này cho các chương trình tham gia giao dịch biết tài khoản có phải là bên ký hay không. Là bên ký có nghĩa là tài khoản nắm giữ khóa riêng tương ứng với khóa công khai và có quyền phê duyệt giao dịch được đề xuất.
Trường is_writable là một giá trị boolean cho biết dữ liệu của tài khoản có thể được sửa đổi hay không. Solana cho phép giao dịch chỉ định tài khoản ở chế độ chỉ đọc để hỗ trợ xử lý song song. Runtime cho phép các chương trình khác nhau đồng thời truy cập tài khoản chỉ đọc, đồng thời xử lý xung đột ghi tiềm ẩn đối với tài khoản có thể ghi bằng thứ tự xử lý giao dịch. Điều này bảo đảm chỉ những giao dịch không xung đột mới được xử lý song song.
Trường executable là một giá trị boolean cho biết tài khoản có thể xử lý chỉ thị hay không. Đúng vậy, điều này có nghĩa là chương trình được lưu trong tài khoản, và chúng ta sẽ tìm hiểu sâu hơn ở phần tiếp theo. Trước tiên, cần trình bày khái niệm tiền thuê.
Trường rent_epoch cho biết epoch tiếp theo mà tài khoản này sẽ phải trả tiền thuê. Một epoch là số slot mà lịch leader có hiệu lực. Không giống tệp truyền thống trong hệ điều hành, tài khoản trên Solana có vòng đời được biểu thị bằng một lượng lamport. Quan niệm rằng sự tồn tại liên tục của tài khoản phụ thuộc vào số dư lamport đưa chúng ta đến khái niệm tiền thuê.
Tiền thuê
Tiền thuê là chi phí lưu trữ phát sinh để duy trì tài khoản trên Solana và bảo đảm tài khoản được giữ trong bộ nhớ của validator. Tiền thuê được tính theo epoch, một đơn vị thời gian được xác định bằng các slot mà lịch leader có hiệu lực. Cơ chế tiền thuê hoạt động như sau:
- Thu tiền thuê — tiền thuê được thu một lần mỗi epoch. Khoản này cũng có thể được thu khi một giao dịch tham chiếu đến tài khoản
- Phân phối tiền thuê — một phần tiền thuê thu được sẽ bị đốt, nghĩa là bị loại vĩnh viễn khỏi lưu thông. Phần còn lại được phân phối cho các tài khoản bỏ phiếu sau mỗi slot
- Thanh toán tiền thuê — nếu tài khoản không có đủ lamport để trả tiền thuê, dữ liệu của tài khoản sẽ bị xóa và tài khoản được giải phóng trong một quy trình gọi là thu gom rác
- Miễn tiền thuê — tài khoản có thể được miễn tiền thuê nếu duy trì số dư tối thiểu tương đương hai năm thanh toán tiền thuê. Mọi tài khoản mới phải đạt ngưỡng miễn tiền thuê này, vốn phụ thuộc vào kích thước tài khoản
- Thu hồi tiền thuê — người dùng có thể đóng tài khoản để lấy lại số lamport còn lại. Điều này cho phép người dùng thu hồi khoản tiền thuê được lưu trong tài khoản
Có thể ước tính tiền thuê bằng endpoint RPC getMinimumBalanceForRentExemption cho một kích thước tài khoản cụ thể. Test Drive đơn giản hóa việc này bằng cách nhận độ dài dữ liệu của tài khoản ở dạng usize. Cũng có thể dùng lệnh con rent của Solana CLI để ước tính lượng SOL tối thiểu cần thiết để tài khoản được miễn tiền thuê. Ví dụ, tại thời điểm viết bài, chạy lệnh solana rent 20000 sẽ trả về Rent-exempt minimum: 0.14009088 SOL.
Địa chỉ trên Solana
Trên thực tế, Solana có hai “loại” địa chỉ. Solana sử dụng ed25519, một lược đồ chữ ký EdDSA dùng SHA-512 (SHA-2) và đường cong elliptic Curve22519, để tạo địa chỉ. Kết quả là các khóa công khai 32 byte, đóng vai trò định dạng địa chỉ chính. Chúng có thể được sử dụng trực tiếp vì không bị băm.
Để hợp lệ, địa chỉ phải là một điểm trên đường cong ed25519. Tuy nhiên, không phải mọi địa chỉ đều cần được dẫn xuất từ đường cong này. Program Derived Address (PDA) được tạo ngoài đường cong, nghĩa là chúng không có khóa riêng tương ứng và không thể dùng để ký. PDA được tạo thông qua System Program và được sử dụng khi chương trình cần quản lý tài khoản. Đây chỉ là phần giải thích thêm để bạn đọc biết về các loại địa chỉ khác nhau trên Solana. Chúng ta sẽ trình bày về PDA trong một bài viết sau.
Tài khoản trên Solana khác tài khoản trên Ethereum như thế nào?
Ethereum có hai loại tài khoản chính: tài khoản do bên ngoài sở hữu (EOA) và tài khoản hợp đồng. EOA được kiểm soát bằng khóa riêng, còn tài khoản hợp đồng chịu sự chi phối của mã hợp đồng và không thể tự khởi tạo giao dịch.
Cả EOA và tài khoản hợp đồng đều tuân theo cùng một cấu trúc tài khoản:
- Số dư — mọi tài khoản đều có số dư được tính bằng Ether
- Nonce — với EOA, đây là số giao dịch được gửi từ tài khoản. Với hợp đồng, đây là số hợp đồng do tài khoản tạo ra
- Gốc lưu trữ — giá trị băm 256 bit của nút gốc trong Merkle Patricia Trie, đại diện mã hóa cho nội dung lưu trữ của tài khoản
- CodeHash — giá trị băm của mã Ethereum Virtual Machine (EVM) trong hợp đồng. Giá trị này là bất biến, nghĩa là mã không thay đổi sau khi được tạo, dù trạng thái có thể thay đổi. Cần lưu ý rằng việc nâng cấp hợp đồng trên Ethereum có một số ngoại lệ, chẳng hạn sử dụng mẫu proxy, nhưng nội dung này nằm ngoài phạm vi bài viết. Với EOA, đây là giá trị băm của một chuỗi rỗng vì EOA không chứa mã
Solana áp dụng mô hình tài khoản đồng nhất hơn, trong đó bất kỳ tài khoản nào cũng có khả năng trở thành chương trình. Việc tách mã khỏi dữ liệu tạo ra một môi trường hiệu quả và linh hoạt hơn. Các chương trình Solana không có trạng thái và tương tác với nhiều tài khoản dữ liệu mà không cần triển khai trùng lặp. Điều này đặc biệt có lợi cho các ứng dụng tài chính phi tập trung (DeFi), nơi người dùng muốn tương tác với nhiều giao thức mà không cần chuyển tài sản giữa các chương trình khác nhau. Ngược lại, mô hình lập trình của Ethereum kết hợp mã và trạng thái thành một thực thể duy nhất. Điều này khiến các tương tác phức tạp hơn và có thể tốn kém hơn do yêu cầu gas khi thay đổi trạng thái.
Trước đây, tài khoản Solana phải trả tiền thuê và cần giữ số dư tối thiểu để tiếp tục hoạt động. Điều này bảo đảm các tài khoản không được sử dụng hoặc không đủ tiền cuối cùng sẽ được mạng thu hồi, qua đó giảm tình trạng phình to trạng thái. Các bản cập nhật gần đây đã loại bỏ tài khoản phải trả tiền thuê trên mainnet — tài khoản bắt buộc phải được miễn tiền thuê. Trong khi đó, Ethereum sử dụng gas để quản lý việc phân bổ tài nguyên. Theo mô hình này, dữ liệu lưu trữ của hợp đồng tồn tại vô thời hạn trừ khi được xóa rõ ràng. Cách tiếp cận của Solana cung cấp cấu trúc chi phí dễ dự đoán hơn cho việc lưu trữ trạng thái, trong khi chi phí trên Ethereum có thể biến động và trở nên quá cao khi mạng tắc nghẽn.
Trong phần tiếp theo, chúng ta sẽ xem xét cách Solana tách logic chương trình khỏi trạng thái. So với mô hình lập trình của Ethereum, bạn sẽ thấy cách tiếp cận mô-đun này hỗ trợ hoạt động on-chain hiệu quả hơn, đồng thời cung cấp cấu trúc chi phí minh bạch và dễ dự đoán cho nhà phát triển.
Chương trình Solana là gì?
Chương trình là các tài khoản có thể thực thi thuộc sở hữu của BPF Loader. Chúng được thực thi bởi Solana Runtime, vốn được thiết kế để xử lý giao dịch và logic chương trình.
Một trong những đặc điểm khác biệt của mô hình lập trình Solana là tách biệt mã và dữ liệu. Chương trình không có trạng thái, nghĩa là chúng không lưu trữ bất kỳ trạng thái nào bên trong. Thay vào đó, mọi dữ liệu chúng cần xử lý đều được lưu trong các tài khoản riêng biệt, được truyền đến chương trình theo tham chiếu thông qua giao dịch. Thiết kế này cho phép một bản triển khai chương trình chung duy nhất tương tác với nhiều tài khoản khác nhau.
Các chương trình trên Solana có khả năng:
- Sở hữu các tài khoản bổ sung
- Đọc hoặc ghi có cho các tài khoản khác
- Sửa đổi dữ liệu hoặc ghi nợ các tài khoản chúng sở hữu
Có hai loại chương trình:
- Chương trình on-chain — các chương trình do người dùng viết và triển khai trên Solana. Chúng có thể được nâng cấp bởi cơ quan có thẩm quyền nâng cấp, thường là tài khoản đã triển khai chương trình
- Chương trình gốc — các chương trình được tích hợp vào lõi của Solana. Chúng cung cấp chức năng nền tảng cần thiết để validator hoạt động. Chương trình gốc chỉ có thể được nâng cấp thông qua các bản cập nhật phần mềm trên toàn mạng. Các ví dụ phổ biến gồm System Program, BPF Loader Program và Vote Program.
Cả chương trình on-chain và chương trình gốc đều có thể được người dùng và chương trình khác gọi. Điểm khác biệt chính nằm ở cơ chế nâng cấp: chương trình on-chain có thể được cơ quan có thẩm quyền nâng cấp, còn chương trình gốc chỉ có thể được nâng cấp trong các bản cập nhật cụm.
Solana Labs tuyển chọn một nhóm chương trình on-chain gọi là Thư viện chương trình Solana. Thư viện này hỗ trợ nhiều hoạt động on-chain, bao gồm cho vay token và tạo pool stake. Chẳng hạn, Associated Token Account Program thiết lập tiêu chuẩn và cơ chế liên kết ví của người dùng với các tài khoản token tương ứng. Hơn nữa, SPL có tính linh hoạt. Các chương trình như Token-2022 xây dựng dựa trên và mở rộng chức năng do Token Program cung cấp.
Việc phát triển chương trình trên Solana thường được thực hiện bằng Rust với sự hỗ trợ của Anchor, một framework có quan điểm thiết kế rõ ràng giúp đơn giản hóa quá trình tạo chương trình bằng cách giảm mã mẫu và tinh gọn việc tuần tự hóa cũng như giải tuần tự hóa. Dù Rust được ưu tiên, nhà phát triển không bị giới hạn ở ngôn ngữ này — có thể dùng C, C++ và bất kỳ ngôn ngữ nào nhắm đến backend BPF của LLVM (tức một thành phần của LLVM cho phép biên dịch chương trình thành BPF bytecode). Những phát triển gần đây từ Solang và Neon Labs đã cho phép nhà phát triển sử dụng Solidity để phát triển chương trình.
Các chương trình thường được phát triển và kiểm thử trên Localhost cùng Devnet trước khi triển khai lên Testnet hoặc Mainnet Beta. Nhà phát triển có thể triển khai chương trình qua Solana CLI bằng lệnh solana program deploy <path to program>. Sau khi được biên dịch thành một đối tượng dùng chung ELF chứa BPF bytecode, chương trình được tải lên cụm Solana đã chỉ định. Các chương trình đã triển khai nằm trong tài khoản được đánh dấu là executable, với địa chỉ tài khoản đóng vai trò là program_id.
Ban đầu, các chương trình trên Solana được triển khai bằng tài khoản có kích thước gấp đôi chương trình. Bản cập nhật 1.16 của Solana bổ sung hỗ trợ cho tài khoản có thể thay đổi kích thước, mang lại khả năng phân bổ tài nguyên linh hoạt hơn cho nhà phát triển. Giờ đây, nhà phát triển có thể triển khai chương trình bằng một tài khoản nhỏ hơn rồi mở rộng kích thước sau đó.
Như đã đề cập ở trên, chương trình được xem là không có trạng thái vì mọi dữ liệu mà chúng tương tác đều được lưu trong các tài khoản riêng biệt và được truyền theo tham chiếu. Mọi chương trình đều có một điểm vào duy nhất để xử lý chỉ thị, nhận một program_id, một mảng tài khoản và dữ liệu chỉ thị dưới dạng mảng byte. Chương trình được Solana Runtime thực thi sau khi được giao dịch gọi.
Giao dịch là gì?
Giao dịch là xương sống của hoạt động on-chain. Chúng là cơ chế dùng để gọi chương trình và thực hiện thay đổi trạng thái. Một giao dịch trên Solana là gói các chỉ thị cho validator biết cần thực hiện hành động nào, trên tài khoản nào và có đủ quyền cần thiết hay không.
Một giao dịch gồm ba phần chính:
- Một mảng tài khoản để đọc hoặc ghi
- Một hoặc nhiều chỉ thị
- Một hoặc nhiều chữ ký
Giao dịch trên Solana tuân theo struct Transaction. Cấu trúc này cung cấp thông tin cần thiết để mạng xử lý và xác thực các hành động. Nó được định nghĩa như sau:
pub struct Transaction {
pub signatures: Vec,
pub message: Message,
}Trường signatures chứa một tập hợp chữ ký tương ứng với Message đã được tuần tự hóa. Mỗi chữ ký được liên kết với một khóa tài khoản trong danh sách account_keys của Message, bắt đầu bằng bên trả phí. Bên trả phí là tài khoản chịu trách nhiệm thanh toán phí giao dịch phát sinh khi giao dịch được xử lý. Đây thường là tài khoản khởi tạo giao dịch. Số chữ ký bắt buộc bằng num_required_signatures, được định nghĩa trong MessageHeader của thông điệp.
Bản thân message là một struct thuộc kiểu Message. Nó được định nghĩa như sau:
pub struct Message {
pub header: MessageHeader,
pub account_keys: Vec,
pub recent_blockhash: Hash,
pub instructions: Vec,
}header của thông điệp chứa ba số nguyên 8 bit không dấu: số chữ ký bắt buộc (tức num_required_signatures), số bên ký chỉ đọc và số bên không ký chỉ đọc.
Trường account_keys liệt kê mọi địa chỉ tài khoản tham gia giao dịch. Các tài khoản yêu cầu quyền đọc-ghi được liệt kê trước, sau đó là tài khoản chỉ đọc.
recent_blockhash là một blockhash gần đây, chứa giá trị băm SHA-256 dài 32 byte. Trường này cần thiết để cho biết thời điểm gần nhất client quan sát sổ cái và đóng vai trò là thời hạn tồn tại của các giao dịch gần đây. Validator sẽ từ chối giao dịch có blockhash cũ. Ngoài ra, việc đưa blockhash gần đây vào giúp ngăn giao dịch trùng lặp vì mọi giao dịch hoàn toàn giống giao dịch trước đó đều bị từ chối. Nếu vì bất kỳ lý do nào mà giao dịch cần được ký từ lâu trước khi gửi lên mạng, có thể dùng nonce giao dịch lâu dài thay cho blockhash gần đây để bảo đảm đó là một giao dịch duy nhất.
Trường instructions chứa một hoặc nhiều struct CompiledInstruction, mỗi struct quy định một hành động cụ thể mà các validator của mạng phải thực hiện.
Chỉ thị
Chỉ thị là lệnh cho một lần gọi chương trình Solana. Đây là đơn vị logic thực thi nhỏ nhất trong chương trình và là đơn vị vận hành cơ bản nhất trên Solana. Chương trình diễn giải dữ liệu được truyền từ chỉ thị và thao tác trên các tài khoản được chỉ định. Struct Instruction được định nghĩa như sau:
pub struct Instruction {
pub program_id: Pubkey,
pub accounts: Vec,
pub data: Vec,
}Trường program_id chỉ định khóa công khai của chương trình cần thực thi. Đây là địa chỉ của chương trình sẽ xử lý chỉ thị. Chủ sở hữu tài khoản chương trình, được biểu thị bằng khóa công khai này, xác định loader chịu trách nhiệm khởi tạo và thực thi chương trình. Loader đánh dấu các chương trình Solana Bytecode Format (SBF) on-chain là có thể thực thi sau khi triển khai. Runtime của Solana sẽ từ chối mọi giao dịch cố gọi tài khoản không được đánh dấu là có thể thực thi.
Trường accounts liệt kê các tài khoản mà chỉ thị có thể đọc hoặc ghi. Các tài khoản này phải được cung cấp dưới dạng giá trị AccountMeta. Mọi tài khoản có dữ liệu có thể bị chỉ thị thay đổi đều phải được chỉ định là có thể ghi, nếu không giao dịch sẽ thất bại. Nguyên nhân là chương trình không thể ghi vào tài khoản mà chúng không sở hữu hoặc không có đủ quyền truy cập. Quy tắc này cũng áp dụng khi thay đổi lamport của tài khoản: trừ lamport khỏi tài khoản không thuộc sở hữu của chương trình sẽ khiến giao dịch thất bại, còn thêm lamport vào bất kỳ tài khoản nào đều được phép. Trường accounts cũng có thể chỉ định các tài khoản mà chương trình không đọc hoặc ghi. Việc này nhằm tác động đến cách runtime lập lịch thực thi chương trình, ngoài ra các tài khoản này sẽ bị bỏ qua.
data là một vector đa dụng gồm các số nguyên 8 bit không dấu, đóng vai trò là đầu vào được truyền đến chương trình. Trường này rất quan trọng vì chứa các chỉ thị đã mã hóa mà chương trình sẽ thực thi.
Solana không phụ thuộc vào định dạng dữ liệu chỉ thị. Tuy nhiên, nền tảng có hỗ trợ tích hợp cho việc tuần tự hóa thông qua bincode và borsh (Binary Object Representation Serializer for Hashing). Tuần tự hóa là quá trình chuyển đổi cấu trúc dữ liệu phức tạp thành một chuỗi byte phẳng có thể truyền hoặc lưu trữ. Khi chọn cách mã hóa dữ liệu, cần cân nhắc chi phí giải mã vì toàn bộ quá trình diễn ra on-chain. Tuần tự hóa bằng Borsh thường được ưu tiên hơn bincode vì có đặc tả ổn định, bản triển khai JavaScript và nhìn chung hiệu quả hơn.
Chương trình sử dụng các hàm hỗ trợ để tinh gọn việc xây dựng chỉ thị được hỗ trợ. Ví dụ, System Program cung cấp một hàm hỗ trợ để xây dựng chỉ thị SystemInstruction::Assign:
pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
let account_metas = vec![AccountMeta::new(*pubkey, true)];
Instruction::new(
system_program::id(),
&SystemInstruction::Assign { owner: *owner },
account_metas,
)
}Hàm này tạo một chỉ thị mà khi được xử lý sẽ đổi chủ sở hữu của tài khoản được chỉ định thành chủ sở hữu mới đã cung cấp.
Một giao dịch có thể chứa nhiều chỉ thị, được thực thi tuần tự và nguyên tử theo thứ tự liệt kê. Điều này có nghĩa là tất cả chỉ thị đều thành công hoặc không chỉ thị nào thành công. Điều đó cũng đồng nghĩa thứ tự chỉ thị có thể mang tính quyết định. Chương trình phải được gia cố để xử lý an toàn mọi chuỗi chỉ thị có thể xảy ra, nhằm ngăn chặn các hành vi khai thác tiềm ẩn.
Ví dụ, trong quá trình hủy khởi tạo, chương trình có thể cố hủy khởi tạo tài khoản bằng cách đặt số dư lamport về 0. Cách làm này giả định rằng runtime Solana sẽ xóa tài khoản. Giả định đó đúng giữa các giao dịch, nhưng không đúng giữa các chỉ thị hoặc Cross-Program Invocation (chúng ta sẽ trình bày Cross-Program Invocation trong một bài viết sau). Chương trình nên chủ động đặt dữ liệu tài khoản về 0 để chống lại lỗ hổng tiềm ẩn này trong quy trình hủy khởi tạo. Nếu không, kẻ tấn công có thể đưa ra một chỉ thị tiếp theo để khai thác việc tài khoản được cho là đã bị xóa, chẳng hạn tái sử dụng tài khoản trước khi giao dịch hoàn tất.
Giao dịch có phiên bản là gì?
Giao dịch trên Solana sử dụng các tiêu chuẩn Đơn vị truyền tải tối đa (MTU) của IPv6 để bảo đảm truyền dữ liệu nhanh chóng và đáng tin cậy trên toàn cụm. Ngăn xếp mạng của Solana sử dụng kích thước MTU thận trọng là 1280 byte. Sau khi dành không gian cho header, còn 1232 byte cho dữ liệu gói tin. Do đó, giao dịch Solana bị giới hạn ở kích thước này.
Giới hạn kích thước này hỗ trợ nhiều cải tiến mạng nhưng cũng hạn chế độ phức tạp của các thao tác có thể thực hiện trong một giao dịch. Vì mỗi địa chỉ tài khoản chiếm 32 byte dung lượng lưu trữ, một giao dịch có thể chứa tối đa 35 tài khoản nếu không có chỉ thị nào. Hạn chế này gây khó khăn cho các trường hợp sử dụng cần hơn 35 tài khoản không yêu cầu chữ ký trong một giao dịch.
Để giải quyết vấn đề này, một định dạng giao dịch mới hỗ trợ nhiều phiên bản định dạng giao dịch đã được giới thiệu. Runtime Solana hiện hỗ trợ hai phiên bản giao dịch:
legacy— định dạng giao dịch ban đầu0(Phiên bản 0) — định dạng giao dịch mới nhất, hỗ trợ Address Lookup Table
Phiên bản 0 được phát hành để hỗ trợ Address Lookup Table (ALT). Về cơ bản, chúng lưu địa chỉ tài khoản trong một cấu trúc dữ liệu dạng bảng on-chain. Các bảng này là những tài khoản riêng biệt lưu địa chỉ tài khoản và cho phép tham chiếu đến chúng trong giao dịch bằng chỉ mục u8 dài 1 byte. Điều này làm giảm đáng kể kích thước giao dịch vì mỗi tài khoản được đưa vào chỉ cần dùng 1 byte thay vì 32 byte. ALT đặc biệt hữu ích cho các thao tác phức tạp liên quan đến nhiều tài khoản, chẳng hạn những thao tác phổ biến trong ứng dụng DeFi.
Sơ đồ này được điều chỉnh từ phần về Giao dịch có phiên bản trong Solana Cookbook
Thuật ngữ “giao dịch có phiên bản” đề cập đến cách Solana hỗ trợ cả định dạng giao dịch cũ lẫn Phiên bản 0. Cách tiếp cận này bảo đảm khả năng kết hợp trong khi vẫn tận dụng các cải tiến của runtime.
Cấu trúc của giao dịch có phiên bản
Một VersionedTransaction được định nghĩa như sau:
pub struct VersionedTransaction {
pub signatures: Vec,
pub message: VersionedMessage,
}Trường signatures là danh sách chữ ký của các bên ký giao dịch. Chúng dùng để xác thực và duy trì tính toàn vẹn của giao dịch. message là nội dung thực tế của giao dịch. Nội dung này được đóng gói bởi kiểu VersionedMessage, một enum wrapper mỏng xử lý cả thông điệp cũ và thông điệp Phiên bản 0:
pub enum VersionedMessage {
Legacy(Message),
V0(Message),
}Phiên bản thông điệp được xác định bằng bit đầu tiên trong quá trình tuần tự hóa. Nếu bit đầu tiên được đặt, 7 bit còn lại được dùng để xác định phiên bản Message nào được tuần tự hóa, bắt đầu từ Phiên bản 0. Nếu bit đầu tiên không được đặt, toàn bộ byte được dùng để mã hóa định dạng Message cũ. Nguyên nhân là có hai struct Message trùng tên nhưng nằm trong hai mô-đun khác nhau — legacy và v0.
Một Message đại diện cho định dạng nội bộ rút gọn của giao dịch. Định dạng này được dùng để truyền qua mạng và để runtime thao tác. Nó bao gồm danh sách tuyến tính của mọi tài khoản được các chỉ thị trong giao dịch sử dụng, một MessageHeader mô tả chi tiết cấu trúc mảng tài khoản, một blockhash gần đây và bản mã hóa nhỏ gọn các chỉ thị của thông điệp. Đây là cấu trúc của struct Message v0:
pub struct Message {
pub header: MessageHeader,
pub account_keys: Vec,
pub recent_blockhash: Hash,
pub instructions: Vec,
pub address_table_lookups: Vec,
}Điểm khác biệt giữa thông điệp cũ và thông điệp v0 là sự hiện diện của trường address_table_lookups.
Tích hợp mô hình lập trình với luồng giao dịch của Solana
Mô hình lập trình của Solana được tích hợp sâu với hệ thống tài khoản và giao dịch. Các khái niệm được kết nối với nhau như sau:
- Tài khoản dưới dạng trạng thái — Tài khoản trên Solana đóng vai trò là vùng chứa trạng thái cho chương trình. Mô hình lập trình xoay quanh việc sửa đổi dữ liệu được lưu trong các vùng chứa này để phản hồi chỉ thị
- Chỉ thị — Chương trình xác định logic xử lý các chỉ thị có trong giao dịch. Những chỉ thị này là các thành phần có thể thực thi tương tác với dữ liệu tài khoản
- Tuần tự hóa và xử lý — Khi một giao dịch được tuần tự hóa, các chỉ thị của chương trình quyết định những thay đổi đối với trạng thái tài khoản. Quá trình tuần tự hóa tuân theo thiết kế của chương trình, bất kể chương trình sử dụng định dạng giao dịch cũ hay Phiên bản 0
- Tính nguyên tử — Mô hình lập trình của Solana bảo đảm quá trình xử lý chỉ thị có tính nguyên tử. Chương trình phải được thiết kế để xử lý các giao dịch đồng thời một cách an toàn và hiệu quả
- Khả năng mở rộng — Mô hình lập trình của Solana hỗ trợ khả năng mở rộng thông qua các tính năng như Address Lookup Table (ALT). Các bảng này giảm kích thước giao dịch và tăng số tài khoản mà giao dịch có thể tham chiếu
Mô hình lập trình của Solana không chỉ liên quan đến việc viết mã mà còn đòi hỏi hiểu cách mã đó tương tác trong hệ sinh thái rộng lớn hơn. Tài khoản là thành phần thiết yếu của mô hình này, đóng vai trò là phương tiện chính để lưu trữ và sửa đổi dữ liệu trên mạng. Giao dịch hỗ trợ hoạt động on-chain bằng cách cho validator biết dữ liệu nào cần được tạo, cập nhật hoặc xóa. Hiểu rõ những khía cạnh này là điều thiết yếu để nhà phát triển xây dựng ứng dụng được tối ưu hóa về hiệu năng và khả năng phối hợp trong hệ sinh thái Solana.
Kết luận
Chúc mừng! Trong bài viết này, chúng ta đã tìm hiểu sự phức tạp trong kiến trúc hệ thống của Solana, đi sâu vào khái niệm cụm dưới dạng heap dữ liệu nguyên khối. Chúng ta đã khám phá cách heap này được tổ chức thành các vùng bộ nhớ riêng biệt gọi là tài khoản, tạo nên xương sống của mô hình lập trình Solana. Tài khoản lưu trữ mọi thứ, từ token của người dùng đến chính các chương trình xác định hành vi của mạng, và tất cả đều được sửa đổi thông qua giao dịch.
Đối với nhà phát triển, việc hiểu cách Solana tiếp cận điện toán phi tập trung là vô cùng quan trọng. Nắm vững sự phức tạp của tài khoản, chương trình và giao dịch là điều cần thiết để xây dựng ứng dụng khai thác đầy đủ khả năng của Solana. Cốt lõi là hiểu một hệ thống nơi mã được tách khỏi trạng thái. Kết quả là các chương trình không có trạng thái tương tác với dữ liệu thông qua tài khoản ở quy mô chưa từng có về khả năng kết hợp và nâng cấp.
Với cả nhà đầu tư và người dùng thông thường, việc hiểu cách thiết kế của Solana tạo ra một hệ sinh thái mạnh mẽ, linh hoạt và hiệu quả là điều thiết yếu để đánh giá tính khả thi của nền tảng cũng như khả năng thúc đẩy các ứng dụng đổi mới chỉ có thể xuất hiện trên Solana.
Nếu bạn đã đọc đến đây, cảm ơn bạn! Sẵn sàng tìm hiểu sâu hơn chưa? Hãy tham gia Discord của chúng tôi để bắt đầu lập trình trên Solana ngay hôm nay.
Tài nguyên bổ sung / Đọc thêm
Bài viết liên quan
Đăng ký nhận tin từ Helius
Luôn cập nhật những thông tin mới nhất về phát triển Solana và nhận thông báo khi chúng tôi đăng bài


