FAQ
Đăng câu hỏi của bạn trên StackExchange.
Berkeley Packet Filter (BPF)
Các chương trình onchain của Solana được biên dịch thông qua cơ sở hạ tầng trình biên dịch LLVM thành Định dạng Thực thi và Liên kết (ELF) chứa một biến thể của Berkeley Packet Filter (BPF) bytecode.
Vì Solana sử dụng cơ sở hạ tầng trình biên dịch LLVM, một chương trình có thể được viết bằng bất kỳ ngôn ngữ lập trình nào có thể nhắm đến backend BPF của LLVM.
BPF cung cấp một tập lệnh hiệu quả có thể được thực thi trong một máy ảo thông dịch hoặc như các lệnh gốc được biên dịch just-in-time hiệu quả.
Sơ đồ bộ nhớ
Sơ đồ bộ nhớ địa chỉ ảo được sử dụng bởi các chương trình SBF của Solana được cố định và bố trí như sau
- Mã chương trình bắt đầu tại 0x100000000
- Dữ liệu ngăn xếp bắt đầu tại 0x200000000
- Dữ liệu heap bắt đầu tại 0x300000000
- Tham số đầu vào chương trình bắt đầu tại 0x400000000
Các địa chỉ ảo trên là địa chỉ bắt đầu nhưng các chương trình được cấp quyền
truy cập vào một tập con của sơ đồ bộ nhớ. Chương trình sẽ báo lỗi nếu nó cố
gắng đọc hoặc ghi vào một địa chỉ ảo mà nó không được cấp quyền truy cập, và một
lỗi AccessViolation
sẽ được trả về chứa địa chỉ và kích thước của vi phạm đã
cố gắng thực hiện.
InvalidAccountData
Lỗi chương trình này có thể xảy ra vì nhiều lý do. Thông thường, nó được gây ra bởi việc truyền một tài khoản cho chương trình mà chương trình không mong đợi, hoặc ở vị trí sai trong lệnh hoặc một tài khoản không tương thích với lệnh đang được thực thi.
Một triển khai của chương trình cũng có thể gây ra lỗi này khi thực hiện một lệnh chéo giữa các chương trình và quên cung cấp tài khoản cho chương trình mà bạn đang gọi.
InvalidInstructionData
Lỗi chương trình này có thể xảy ra khi đang cố gắng giải tuần tự hóa
(deserialize) chỉ thị, hãy kiểm tra rằng cấu trúc được truyền vào khớp chính xác
với chỉ thị. Có thể có một số đệm giữa các trường. Nếu chương trình triển khai
trait Rust BorshSerialize
thì hãy thử đóng gói và giải nén loại chỉ thị
Instruction
để xác định mã hóa chính xác mà chương trình mong đợi.
MissingRequiredSignature
Một số chỉ thị yêu cầu tài khoản phải là người ký; lỗi này được trả về nếu một tài khoản được mong đợi sẽ được ký nhưng không được ký.
Việc triển khai một chương trình cũng có thể gây ra lỗi này khi thực hiện
lệnh gọi chéo chương trình yêu cầu một địa chỉ chương trình đã
ký, nhưng các seed người ký được truyền vào invoke_signed
không khớp với các
seed người ký được sử dụng để tạo địa chỉ chương trình
create_program_address
.
Stack
SBF sử dụng các khung ngăn xếp thay vì con trỏ ngăn xếp biến. Mỗi khung ngăn xếp có kích thước 4KB.
Nếu một chương trình vi phạm kích thước khung ngăn xếp đó, trình biên dịch sẽ báo cáo việc vượt quá dưới dạng cảnh báo.
Ví dụ:
warning: too many stack frames in function,the stack frame size is 0x400 but function is using 0x410
Thông báo xác định biểu tượng nào đang vượt quá khung ngăn xếp của nó, nhưng tên có thể bị làm rối.
Để giải mã một biểu tượng Rust, hãy sử dụng rustfilt.
Cảnh báo trên đến từ một chương trình Rust, vì vậy tên biểu tượng đã được giải mã là:
solana_escrow::processor::process_init_escrow::h3f6d86c08b4d54e9
Lý do tại sao một cảnh báo được báo cáo thay vì một lỗi là vì một số crate phụ
thuộc có thể bao gồm chức năng vi phạm các hạn chế khung ngăn xếp ngay cả khi
chương trình không sử dụng chức năng đó. Nếu chương trình vi phạm kích thước
ngăn xếp khi chạy, một lỗi CallDepth
sẽ được báo cáo.
Các khung ngăn xếp SBF chiếm một phạm vi địa chỉ ảo bắt đầu từ 0x200000000
.
Kích thước heap
Các chương trình có thể truy cập vào heap thời gian chạy thông qua các API
alloc
của Rust. Để tạo điều kiện cho việc cấp phát nhanh, một heap bump đơn
giản 32KB được sử dụng. Heap không hỗ trợ free
hoặc realloc
.
Về mặt nội bộ, các chương trình có quyền truy cập vào vùng bộ nhớ 32KB bắt đầu từ địa chỉ ảo 0x300000000 và có thể triển khai heap tùy chỉnh dựa trên nhu cầu cụ thể của chương trình.
Các chương trình Rust triển khai heap trực tiếp bằng cách định nghĩa một
global_allocator
tùy chỉnh
Loaders
Các chương trình được triển khai và thực thi bởi các loaders thời gian chạy, hiện tại có hai loaders được hỗ trợ BPF Loader và BPF loader deprecated
Các loaders có thể hỗ trợ các giao diện nhị phân ứng dụng khác nhau, vì vậy các
nhà phát triển phải viết chương trình của họ cho và triển khai chúng vào cùng
một loader. Nếu một chương trình được viết cho một loader nhưng lại được triển
khai vào một loader khác, kết quả thường là lỗi AccessViolation
do không khớp
khi giải mã các tham số đầu vào của chương trình.
Về mặt thực tế, chương trình nên luôn được viết để nhắm đến BPF loader mới nhất và loader mới nhất là mặc định cho giao diện dòng lệnh và các API javascript.
Triển khai
Triển khai chương trình SBF là quá trình tải một đối tượng chia sẻ BPF vào dữ
liệu tài khoản chương trình và đánh dấu tài khoản có thể thực thi. Một client
chia đối tượng chia sẻ SBF thành các phần nhỏ hơn và gửi chúng như dữ liệu hướng
dẫn của các hướng dẫn
Write
đến loader, nơi loader ghi dữ liệu đó vào dữ liệu tài khoản của chương trình.
Khi tất cả các phần đã được nhận, client gửi một hướng dẫn
Finalize
đến loader, sau đó loader xác nhận rằng dữ liệu SBF hợp lệ và đánh dấu tài khoản
chương trình là executable. Khi tài khoản chương trình đã được đánh dấu là có
thể thực thi, các giao dịch tiếp theo có thể đưa ra hướng dẫn cho chương trình
đó xử lý.
Khi một instruction được gửi đến một chương trình SBF thực thi, bộ tải cấu hình môi trường thực thi của chương trình, tuần tự hóa các tham số đầu vào của chương trình, gọi điểm vào của chương trình và báo cáo mọi lỗi gặp phải.
Để biết thêm thông tin, xem triển khai chương trình.
Tuần tự hóa tham số đầu vào
Bộ tải SBF tuần tự hóa các tham số đầu vào của chương trình thành một mảng byte sau đó được truyền đến điểm vào của chương trình, nơi chương trình chịu trách nhiệm giải tuần tự hóa trên chuỗi. Một trong những thay đổi giữa bộ tải đã lỗi thời và bộ tải hiện tại là các tham số đầu vào được tuần tự hóa theo cách khiến các tham số khác nhau rơi vào các vị trí căn chỉnh trong mảng byte đã căn chỉnh. Điều này cho phép các triển khai giải tuần tự hóa tham chiếu trực tiếp đến mảng byte và cung cấp các con trỏ đã căn chỉnh cho chương trình.
Bộ tải mới nhất tuần tự hóa các tham số đầu vào của chương trình như sau (tất cả mã hóa là little endian):
- 8 byte số không dấu của tài khoản
- Cho mỗi tài khoản
- 1 byte chỉ ra nếu đây là tài khoản trùng lặp, nếu không trùng lặp thì giá trị là 0xff, nếu không thì giá trị là chỉ số của tài khoản mà nó trùng lặp với.
- Nếu trùng lặp: 7 byte đệm
- Nếu không trùng lặp:
- 1 byte boolean, true nếu tài khoản là người ký
- 1 byte boolean, true nếu tài khoản có thể ghi
- 1 byte boolean, true nếu tài khoản có thể thực thi
- 4 byte đệm
- 32 byte của khóa công khai tài khoản
- 32 byte của khóa công khai chủ sở hữu tài khoản
- 8 byte số không dấu của lamport thuộc sở hữu của tài khoản
- 8 byte số không dấu của byte dữ liệu tài khoản
- x byte dữ liệu tài khoản
- 10k byte đệm, được sử dụng cho realloc
- đủ đệm để căn chỉnh offset thành 8 byte.
- 8 byte kỷ nguyên rent
- 8 byte của số không dấu của instruction data
- x byte của instruction data
- 32 byte của program id
Is this page helpful?