Assembly Timeout

Trong bài học này, chúng ta sẽ sử dụng sBPF Assembly để tạo một instruction kiểm tra dựa trên thời gian để đảm bảo rằng một giao dịch chỉ được thực hiện trước một thời điểm nhất định.
Bằng cách chèn instruction này vào trong giao dịch của bạn, bạn sẽ tạo ra một cơ chế an toàn để ngăn chặn việc thực hiện giao dịch sau một thời điểm nhất định, bảo vệ trước trường hợp thực hiện giao dịch bị trễ hoặc phát lại các lệnh cũ.
Một số lý do khiến kiểm tra timeout trở thành một ứng cử viên lý tưởng cho assembly:
Các trường hợp đơn hoặc bị ràng buộc
Sử dụng hiệu quả các biến hệ thống
Không cần thực hiện các kiểm tra signer/account
Chỉ cần cải thiện tính bảo mật
Nếu bạn không quen thuộc với việc viết chương trình assembly, bạn nên bắt đầu bằng cách đọc bài Giới thiệu về Assembly.
Thiết kế chương trình
Chương trình của chúng ta sẽ triển khai việc kiểm tra một hoạt động quan trọng: xác minh rằng slot hiện tại không vượt quá một deadline nhất định. Mô hình này rất cần thiết cho các hoạt động DeFi nhạy cảm với thời gian - từ việc ngăn chặn các nỗ lực sử dụng chênh lệnh giá cũ đến việc thực thi thời hạn của một đấu giá.
Chương trình của chúng ta sẽ cần:
Một số 8-byte thể hiện độ cao slot-height trong dữ liệu instruction.
Truy cập vào biến hệ thống Clock của Solana thông qua
sol_get_clock_sysvar.Trả về thành công nếu slot hiện tại ≤ slot tối đa, ngược lại trả về lỗi.
Độ lệch bộ nhớ
Chương trình sBPF nhận dữ liệu tài khoản dưới dạng các vùng nhớ liên tục. Các hằng số này xác định các độ lệch byte
Vì chương trình của chúng ta không nhận bất kỳ tài khoản nào, chúng ta có thể tính toán vị trí của MAX_SLOT_HEIGHT (được truyền trong dữ liệu của instruction). Chúng ta cũng sẽ lưu trữ dữ liệu của biến hệ thống Clock trên stack, vì lý do này CURRENT_SLOT_HEIGHT sẽ là số âm.
Các hằng số định nghĩa bố cục bộ nhớ của chúng ta như sau:
.equ NUM_ACCOUNTS, 0x0000
.equ MAX_SLOT_HEIGHT, 0x0010
.equ CURRENT_SLOT_HEIGHT, -0x0028NUM_ACCOUNTS(0x0000): Trỏ đến số lượng tài khoản trong header của dữ liệu instruction để xác thực.MAX_SLOT_HEIGHT(0x0010): Xác định vị trí của 8-byte slot-height tối đa trong dữ liệu của instruction.CURRENT_SLOT_HEIGHT(-0x0028): Xác định độ lệch stack của trường dữ liệu slot được lưu trong Clock sysvar. Vì slot là trường đầu tiên trong cấu trúc Clock, độ lệch này trỏ trực tiếp đến nó.
Không giống như những ngôn ngữ bậc cao khác trừu tượng hóa bố cục bộ nhớ, assembly yêu cầu bạn phải biết chính xác vị trí của từng dữ liệu.
Điểm đầu vào và Xác thực ban đầu
.globl entrypoint
entrypoint:
ldxdw r0, [r1+NUM_ACCOUNTS] // Veto if any accounts are included
ldxdw r2, [r1+MAX_SLOT_HEIGHT] // Store target slot heightMỗi chương trình sBPF bắt đầu bởi một ký hiệu toàn cục .entrypoint. Runtime Solana cung cấp các tài khoản và dữ liệu instruction thông qua thanh ghi r1.
instruction ldxdw tải lên (ldx) một giá trị 8-byte (double word, dw) từ bộ nhớ vào thanh ghi. Đây là những gì xảy ra:
ldxdw r0, [r1+NUM_ACCOUNTS]: tải số lượng tài khoản vàor0. Vìr0là thanh ghi mà máy ảo đọc khi thoát, bất kỳ giá trị khác không phải 0 sẽ tự động làm thất bại chương trình (chính xác như chúng ta muốn nếu có tài khoản được truyền).ldxdw r2, [r1+MAX_SLOT_HEIGHT]: trỏ đến slot-height tối đa được truyền trong dữ liệu của instruction. Giá trị 64-bit này được lưu trongr2.
Cả 2 hành động đều là zero-copy: chúng ta đang đọc trực tiếp từ dữ liệu tài khoản mà không cần deserialize.
Clock Sysvar
mov64 r1, r10
add64 r1, CURRENT_SLOT_HEIGHT
call sol_get_clock_sysvar
ldxdw r1, [r1+0x0000]Việc gọi sol_get_clock_sysvar syscall sẽ ghi dữ liệu Clock hiện tại vào địa chỉ ô nhớ được chỉ định trong r1.
Bởi vì cấu trúc của Clock là 40 bytes (nó quá lớn cho các thanh ghi chỉ có thể lưu trữ 8 byte mỗi lần), chúng ta sử dụng stack cho việc lưu trữ nhanh chóng, không cần cấp phát và được dọn dẹp tự động.
Bởi vì thanh ghi r10 là chỉ đọc, để hoạt động trên stack, chúng ta cần sao chép địa chỉ bộ nhớ của nó vào một thanh ghi: mov64 r1, r10
Sau đó chúng ta thêm CURRENT_SLOT_HEIGHT (-0x0028) vào thanh ghi r1. Bởi vì hằng số này là số âm, nó thực tế là phép trừ: r1 = r10 - 40 bytes, cấp phát 40 bytes trên stack.
Sau khi gọi hàm sol_get_clock_sysvar, thanh ghi r1 chứa địa chỉ bộ nhớ của dữ liệu Clock được ghi vào, không phải là giá trị của slot. Vì lý do này, chúng ta tiếp tục tải giá trị slot thực tế bằng cách sử dụng ldxdw r1, [r1+0x0000].
Logic so sánh thời gian
jle r1, r2, end // If current slot <= max slot, success
lddw r0, 1 // Otherwise, set error codeLogic chính của chúng ta là một lệnh nhảy:
Temporal validation: lệnh
jle(jump if less or equal) (nhảy nếu nhỏ hơn hoặc bằng) so sánh slot hiện tại ở thanh ghi (r1) với deadline của chúng ta ở thanh ghi (r2). Nếu chúng ta còn trong cửa sổ thời gian, chúng ta nhảy đến nhãnend.Timeout handling: Nếu deadline đã qua, thực thi tiếp tục (
lddw) để tải mã lỗi 1 vào thanh ghir0để trả về lỗi.
Kết luận
Chương trình này thực hiện kiểm tra thời gian với chi phí tính toán cực kỳ thấp.
Sự đánh đổi ở đây là phải hiểu các giao diện syscall, quản lý ngăn xếp, và bố cục nhị phân của biến hệ thống Clock. Nhưng đối với các xác minh thời gian quan trọng về hiệu suất, assembly cung cấp nó một cách hiệu quả mà không có gì so sánh được.