Câu hỏi thường gặp
Đă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ớ
Bản đồ 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 bản đồ 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
AccessViolation
lỗi sẽ được trả về chứa địa chỉ và kích thước của sự 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 Pack
thì hãy thử đóng gói và giải nén kiểu chỉ thị T
để 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 phải đượ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ời gọi chương trình chéo 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ụ:
Error: Function _ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082E Stack offset of -30728 exceeded max offset of -4096 by 26632 bytes, please minimize large stack variables
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à:
rustfilt _ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082Ecurve25519_dalek::edwards::EdwardsBasepointTable::create
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 trong thời gian chạy, một lỗi AccessViolation
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
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.
Trong thực tế, các 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 của 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 về 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 epoch của 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?