Sơ lược về lập trình Solana

Tôi đã viết bài này vào phút chót vào tháng 2 năm 2022 để cung cấp thêm hướng dẫn / tài nguyên trong quá trình Giới thiệu Solana của Illini Blockchain.
Nó đi kèm với dự án tiếng vang từ Jump Crypto x Solana Labs x Pyth Bootcamp mà tôi đã tham dự vào tháng 1 năm 2022.
Được biết, tôi sẽ đưa nó ra ngoài đó như một hồ sơ công khai. Điều này phác thảo những điều cơ bản về phát triển chương trình Solana được viết bằng Rust, không có Anchor khung phổ biến.
Điều kiện tiên quyết
Hiểu biết cơ bản về Mô hình lập trình Solana . Rust
cơ bản ( ví dụ ).
Cấu trúc tệp dự án
Đây là một ví dụ cơ bản về những gì một dự án Solana sẽ bao gồm.
project/
js/
program/
src/
entrypoint.rs
error.rs
instruction.rs
lib.rs
processor.rs
state.rs
tests/
Cargo.toml
js/
- các kịch bản khách hàng để thử nghiệm.entrypoint.rs
- trình bao bọc vềprocessor.rs
việc cung cấp một điểm truy cập trên blockchain.error.rs
- định nghĩa lỗi tùy chỉnh.instruction.rs
- các định nghĩa enum biểu thị các lệnh khác nhau và các tham số của chúng.lib.rs
- xuất các mô-đun để dễ dàng truy cập trong các thùng khác.processor.rs
- định nghĩa về mã đang chạy thực tế.state.rs
- định nghĩa cấu trúc cho việc tuần tự hóa dữ liệu tài khoản.Cargo.toml
- xác định siêu dữ liệu thùng và các yếu tố phụ thuộc.
- Sửa đổi chương trình và triển khai.
cargo build-bpf // builds your program - make sure you're in your program's root directory
solana program deploy [PATH_TO_DOT_SO_FILE]
// example: solana program deploy /Users/alecchen/Documents/Code/solana-onboarding/echo-skeleton/program/target/deploy/echo.so
// javascript
node [SCRIPT_FILENAME]
// example: node index.js
// example success output:
<https://explorer.solana.com/tx/85CVVw4CrmNbzqCXEmRDF3LEUFEymc4s1pUtDK1urcV6R3jbRjdJNWGjb24Trbt8bZjN5fJkqA55GjNvoBLubfE?cluster=devnet>
Echo Buffer Text: asdfff
Success
// example fail output:
Error: Transaction 4MDQWBNkuRAf59gjqgoFBDLq76UPVS73LUktTP1TJ8op1Qtvw2UsxcRw6tMHKo5mjsWEaZWC5CLePtV44YTFsUA3 failed ({"err":{"InstructionError":[1,"InvalidInstructionData"]}})
at sendAndConfirmTransaction (/Users/alecchen/Documents/Code/solana-onboarding/echo-skeleton/js/node_modules/@solana/web3.js/lib/index.cjs.js:2981:11)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async main (/Users/alecchen/Documents/Code/solana-onboarding/echo-skeleton/js/index.js:75:14)
// copy 4MDQWBNkuRAf59gjqgoFBDLq76UPVS73LUktTP1TJ8op1Qtvw2UsxcRw6t into a block explorer (make sure you're on devnet!)
Mã chương trình
Khi bạn triển khai hướng dẫn chương trình Solana, mã của bạn thường sẽ bao gồm 3 phần chính:
- Nhận tài khoản và giải mã hóa
- Xác Nhận Tài Khoản
- Logic và chức năng
Các tài khoản được chuyển vào dưới dạng một mảng AccountInfo
s. Để làm việc với những điều này, bạn sẽ xác định một trình lặp và truy xuất các tham chiếu đến các tài khoản.
Nhưng làm thế nào để bạn biết đó là những tài khoản nào? Bạn cần chỉ định ở đâu đó những tài khoản mà bạn mong đợi, sau đó 1) tùy thuộc vào khách hàng để chuyển đúng số lượng và thứ tự tài khoản và 2) tùy thuộc vào bạn với tư cách là nhà phát triển chương trình để đảm bảo rằng chương trình của bạn chỉ chạy nếu bạn nhận được đúng tài khoản (phần này là phần xác thực tài khoản). Bạn sẽ nhận thấy trong khung echo rằng các tài khoản được chuyển vào trong định nghĩa hướng dẫn trên máy khách khớp với những tài khoản được mong đợi / nêu trong instruction.rs
.
Dù sao, vào quá trình deserialization. Theo mặc định, dữ liệu tài khoản chỉ là một mảng byte. Nếu bạn mong đợi dữ liệu của tài khoản đến trong một cấu trúc nhất định, bạn có thể giải mã dữ liệu đó để truy cập dữ liệu đó một cách có tổ chức. Bạn sẽ cần phải xác định trước cấu trúc đó và bao gồm một số cách giải mã byte vào nó (các phương thức này thường được định nghĩa là đặc điểm trong Rust). Các chương trình SPL thường có các phương pháp giải không gian bằng cách sử dụng Pack
đặc điểm. Nếu bạn đang xác định các cấu trúc của riêng mình, Borsh là một thư viện có các phương thức được xác định trước cho các kiểu dữ liệu phổ biến.
Ví dụ với spl-token
tài khoản bạc hà chương trình:
Ví dụ với cấu trúc được xác định của riêng chúng tôi:
Xác Nhận Tài Khoản
Như đã đề cập trước đây, bạn đang dựa vào khách hàng để chuyển vào các tài khoản phù hợp với những gì bạn mong đợi. Điều này mở ra cơ hội cho khách hàng chuyển vào các tài khoản có thể làm rối loạn chức năng của mã của bạn.
Ví dụ: giả sử bạn có một chương trình hoán đổi cơ bản, trong đó chương trình yêu cầu 4 tài khoản này (trong số những tài khoản khác): kho tiền của bạn cho mã thông báo a, kho tiền của bạn cho mã thông báo b, kho tiền của chương trình hoán đổi cho mã thông báo a và kho tiền của chương trình hoán đổi cho mã thông báo b. Nó lấy một số lượng mã thông báo a từ kho tiền của bạn, gửi vào kho tiền của chính nó, sau đó lấy một số lượng mã thông báo b từ kho tiền của chính nó và gửi nó vào kho tiền của bạn. Bây giờ, điều gì sẽ xảy ra nếu bạn cung cấp lại mã thông báo của mình một kho tiền thay cho kho tiền của chương trình hoán đổi? Nếu nó không xác thực các tài khoản, thì nó sẽ gửi mã thông báo a vào kho tiền của bạn và bạn sẽ nhận được mã thông báo b miễn phí! Một ví dụ trong các chương trình Solana thực: việc khai thác Wormhole vào đầu tháng 2 là do việc sử dụng chức năng trợ giúp không dùng nữa, không xác thực tài khoản đúng cách.
Các tình huống phổ biến nhất mà bạn sẽ xác thực tài khoản là làm mất hiệu lực tài khoản bao gồm một số dữ liệu nhất định, đảm bảo tài khoản là một PDA nhất định và kiểm tra xem một chương trình có phải là một chương trình nhất định hay không.
Xác thực dữ liệu tài khoản
Để xác thực dữ liệu, có hai phần:
- đảm bảo rằng một tài khoản có thể được giải mã đúng cách
- đảm bảo rằng dữ liệu phù hợp với các ràng buộc nhất định.
Kể từ đây trở đi, tôi sẽ bỏ qua các phần nhập và nhận tài khoản để làm cho mọi thứ bớt lộn xộn hơn.
Xác thực PDA
Một cách phổ biến khác để xác thực tài khoản là sử dụng PDA. Nếu tài khoản là PDA, bạn có thể đảm bảo đó là tài khoản phù hợp bằng cách tự tìm PDA và kiểm tra xem các khóa có khớp nhau không. Bạn có thể làm điều này theo hai cách: 1) nếu bạn chỉ có hạt và không có vết sưng, find_program_address
và 2) nếu bạn có cả vết sưng và hạt , create_program_address
.
Kiểm tra id chương trình
Tất cả các chương trình đều là tài khoản, và như bạn biết, nếu chương trình của bạn đang tương tác với một tài khoản nhất định, nó phải được chuyển vào hướng dẫn. Vì vậy, nếu chương trình của bạn gọi một chương trình khác, khách hàng phải chuyển tài khoản chương trình đó vào.
Điều này mở ra một vấn đề bảo mật. Giờ đây, một ứng dụng khách có thể chuyển vào một chương trình triển khai cùng một hướng dẫn mà chương trình của bạn đang cố gắng truy cập, nhưng thay vào đó sẽ thêm vào một số hành vi độc hại. Vì lý do này, chúng tôi phải kiểm tra xem các tài khoản chương trình được chuyển vào có phải là tài khoản chính xác hay không.
Thường thì bạn sẽ làm việc với các chương trình nổi tiếng và chúng sẽ có các thùng Rust công khai hiển thị một số loại id()
chức năng cho phép bạn dễ dàng kiểm tra, ví dụ như chương trình gốc Solana, chương trình SPL. Những lần khác, bạn có thể chỉ phải viết mã id chương trình.
người trợ giúp khẳng định_msg
Một chức năng trợ giúp thú vị mà tôi đã được giới thiệu tại bootcamp là assert_msg
. Khi bạn gặp lỗi, nó sẽ chỉ in ra thông báo liên quan đến loại lỗi đó. Bạn có thể sử dụng cùng một lỗi trong các ngữ cảnh khác nhau, trong đó một thông báo bổ sung sẽ hữu ích cho việc gỡ lỗi. assert_msg
làm chính xác điều đó.
Logic và chức năng
Sau khi bạn đảm bảo rằng tất cả các tài khoản của mình đều chính xác, bạn thực sự có thể viết mã để thực hiện các công việc. Ở đây, bạn có thể làm bất cứ điều gì trái tim mình mong muốn, nhưng bằng cách này hay cách khác, bạn có thể sẽ làm một trong hai / cả hai điều sau:
- Sửa đổi dữ liệu tài khoản
- Gọi các chương trình khác
Hai cách để sửa đổi dữ liệu tài khoản: 1) sửa đổi trực tiếp các byte và 2) xác định cấu trúc và tuần tự hóa dữ liệu thành mảng tài khoản gồm các byte.
Sửa đổi trực tiếp:
Sắp xếp thứ tự dữ liệu (với Borsh):
Lời gọi chương trình chéo
Thông thường trong các chương trình, bạn viết bạn sẽ phải gọi các chương trình khác trên blockchain. Đối với những điều này, bạn sẽ chủ yếu sử dụng hai chức năng: [invoke](<https://docs.rs/solana-program/1.6.4/solana_program/program/fn.invoke.html>)
cho các cuộc gọi chương trình chéo thông thường và [invoke_signed](<https://docs.rs/solana-program/1.6.4/solana_program/program/fn.invoke_signed.html>)
cho các cuộc gọi chương trình chéo mà PDA cần phải ký.
Về cơ bản, chúng giống nhau ngoại trừ việc invoke_signed
bạn chuyển hạt giống (có gắn thêm vết sưng) cho PDA.
Ví dụ về cả hai có sẵn ở đây .
Bạn sẽ nhận thấy đối số đầu tiên có một Instruction
kiểu. Giống như tất cả các hướng dẫn, nó bao gồm tài khoản, id chương trình và dữ liệu đầu vào.
Tuy nhiên, như bạn thấy trong các ví dụ, thường thì các chương trình sẽ có các hàm wrapper để tạo các hướng dẫn, điều này giúp chúng ta làm điều đó dễ dàng hơn nhiều. Bạn có thể xem cách tạo hướng dẫn thô bằng cách kiểm tra mã nguồn của chúng.