Từ chối sớm các BAL đối kháng

Bài viết này được dịch máy
Xem bản gốc

Từ chối sớm các BAL đối kháng

Xin chân thành cảm ơn Ansgar , Marius , Francesco , Carl , MariaJochem vì những đóng góp và sự hợp tác của họ.

Đề xuất cải tiến Ethereum (EIP)-7928 (Danh sách truy cập cấp khối, BAL) cho phép thực thi song song và I/O theo lô để xác thực Block bằng cách yêu cầu các nhà sản xuất khai báo tất cả các vị trí trạng thái được truy cập trong quá trình thực thi Block , cùng với các thay đổi trạng thái của chúng. Điều này cho phép các máy khách tải trước trạng thái và thực hiện các giao dịch song song.

Tuy nhiên, đặc tả BAL hiện tại có thể tạo ra sự bất đối xứng trong quá trình xác thực, điều này có thể bị lợi dụng để buộc thực hiện các công việc không cần thiết trong quá trình xác thực Block . Điều này không phá vỡ tính song song hóa thực thi, nhưng nó làm mất đi lợi ích của việc tìm nạp trước I/O theo lô trong khi vẫn gây ra chi phí băng thông và thực thi đáng kể.

Tóm lại: Các khối không hợp lệ có thể không được vô hiệu hóa nhanh bằng tốc độ xác thực các khối hợp lệ trong trường hợp xấu nhất. Điều này cản trở khả năng mở rộng của chúng ta. Vấn đề này có thể được giảm thiểu bởi các máy khách bằng cách kiểm tra ngân sách gas đơn giản. Bản dự thảo yêu cầu kéo (PR) đối với Đề xuất cải tiến Ethereum (EIP) có thể được tìm thấy ở đây .

Để hiểu rõ vấn đề và giải pháp của nó, chúng ta hãy bắt đầu bằng cách tìm hiểu cách thức hoạt động của BAL.

Bối cảnh: Cấu trúc BAL

Bảng cân đối kế BAL) được lập theo từng tài khoản:

[address,storage_changes, # slot → [(block_access_index, post_value)] storage_reads, # [slot] balance_changes,nonce_changes,code_changes]

BAL chứa hai loại thông tin khác nhau:

  1. Sự khác biệt về trạng thái sau giao dịchCác thao tác ghi được đánh dấu bằng block_access_index , nhờ đó chúng ta biết chính xác giao dịch nào thay đổi một phần cụ thể của trạng thái.

  2. Vị trí trạng thái sau khối — Các thao tác đọc ( storage_reads và địa chỉ không có *_changes ) không được ánh xạ tới chỉ mục giao dịch. Chúng ta biết chúng được truy cập ở đâu đó trong Block, nhưng không biết bởi giao dịch nào.

Tại sao lại bỏ qua chỉ mục giao dịch cho các thao tác đọc? Điều này giúp tiết kiệm băng thông. Khi một giao dịch vừa đọc vừa ghi vào một vị trí lưu trữ, mục ghi trong storage_changes sẽ bao gồm cả thao tác đọc, tránh trùng lặp. Vì việc song song hóa chỉ yêu cầu biết vị trí trạng thái nào được truy cập (không cần biết khi nào), nên chỉ mục giao dịch là một chi phí không cần thiết.

Sự bất đối xứng này rất quan trọng để hiểu được lỗ hổng: các thao tác ghi có thể được xác thực sớm, nhưng các thao tác đọc thì không.

Xác thực Block bằng BALs

Sau khi nhận được Block, khách hàng sử dụng BAL để:

  • Thực hiện song song các giao dịch đồng thời trì hoãn việc xác thực BAL đầy đủ cho đến sau khi thực thi xong.
  • Tải trước trạng thái cần thiết thông qua I/O theo lô để giảm độ trễ ổ đĩa.
  • Tính toán gốc trạng thái sau song song với quá trình thực thi.

Trước khi có BAL, việc xác thực Block khá đơn giản: trường hợp xấu nhất là một Block tiêu thụ hết toàn bộ gas có sẵn. Bạn không thể làm cho mọi thứ tồi tệ hơn bằng cách bỏ qua tính hợp lệ — các máy khách sẽ hủy bỏ khi đạt đến Gas Limit và đánh dấu Block đó là không hợp lệ. Với BAL, điều này thay đổi: chúng ta phải đảm bảo rằng thời gian thực thi trong trường hợp xấu nhất của các khối hợp lệ không tệ hơn thời gian thực thi của các khối không hợp lệ.

Những gì có thể được xác thực sớm

Sự khác biệt về trạng thái sau giao dịch có thể được thực thi trong quá trình thực hiện giao dịch. Vì các thao tác ghi được ánh xạ tới các chỉ mục giao dịch, mỗi giao dịch có thể được xác thực độc lập so với các thay đổi trạng thái dự kiến ​​của nó.

Ảnh chụp màn hình từ ngày 30/01/2026 lúc 08:41/01
Ảnh chụp màn hình từ ngày 30/01/2026 lúc 08:41/01, kích thước 941×427, dung lượng 47.7 KB.

Những gì không thể được xác thực sớm

Vị trí trạng thái sau khi chặn không thể được xác thực trong quá trình thực thi giao dịch riêng lẻ.

Một ô nhớ được đọc bởi giao dịch x có thể được ghi bởi giao dịch y . Khi tổng hợp BAL cuối cùng , các thao tác ghi sẽ tiêu thụ các thao tác đọc (ô nhớ chỉ xuất hiện trong storage_changes , chứ không phải storage_reads ). Việc một ô nhớ có được khai báo là thao tác đọc hay không phụ thuộc vào toàn bộ các thao tác ghi trên tất cả các giao dịch.

Do đó, việc xác thực các lệnh đọc đã khai báo yêu cầu phải thực hiện tất cả các giao dịch trước.

Nghiên cứu trường hợp: Geth

Ví dụ, Geth sử dụng nhiều goroutine worker thực thi các giao dịch và xác thực sự khác biệt về trạng thái của chúng song song. Đối với các vị trí trạng thái (đọc), việc xác thực được hoãn lại cho một goroutine ResultHandler tổng hợp kết quả sau khi tất cả các giao dịch hoàn tất.

Ảnh chụp màn hình từ 2026-01-29 18-18-41
Ảnh chụp màn hình từ 2026-01-29 18-18-41 1014×682 58.8 KB

Trên thực tế:

  • Các nhân viên thực hiện giao dịch và xác thực sự khác biệt về trạng thái một cách độc lập.
  • Kết quả được truyền đến ResultHandler , nơi thu thập các ô lưu trữ đã được truy cập.
  • Chỉ sau khi tất cả các giao dịch hoàn tất, trình xử lý mới có thể xác minh các lần đọc đã khai báo so với các lần truy cập thực tế.

Việc trì hoãn xác thực này tạo ra cơ hội tấn công.

Cuộc tấn công

Giờ chúng ta hãy xem xét những gì mà một kẻ xây dựng Block độc hại có thể lợi dụng.

Cài đặt

Cho phép:

  • G G = Gas Limit Block
  • G_{\mathrm{tx}} G t x = Gas Limit tối đa cho mỗi giao dịch (≈ 2^{24} 2 24 theo Đề xuất cải tiến Ethereum (EIP)-7825 )
  • g_{\mathrm{sload}} = 2100 g s l o a d = 2100 = chi phí gas tối thiểu của một SLOAD lạnh

Tấn công xây dựng

Bên đề xuất đối địch xây dựng một Block theo hai bước:

Bước 1: Khai báo các lần đọc bộ nhớ ảo

Khai báo một tập hợp S các khe lưu trữ trong storage_reads :

|S| = \left\lfloor \frac{G - G_{\mathrm{tx}}}{g_{\mathrm{sload}}} \right\rfloor
| S | = G G t x g s l o a d

Đây là số lượng tối đa các thao tác đọc dữ liệu Ví lạnh riêng biệt có thể chứa trong Block nếu toàn bộ gas còn lại (sau khi dành riêng một giao dịch max-gas) được sử dụng cho SLOADs .

Bước 2: Bao gồm các giao dịch chỉ dùng để tính toán

Bao gồm một hoặc nhiều giao dịch mà việc thực hiện của chúng đáp ứng các điều kiện sau:

  • Lượng gas ≈ G_{\mathrm{tx}} G t x
  • Không có quyền truy cập vào bất kỳ vị trí nào trong S S
  • Tính toán thuần túy (ví dụ: vòng lặp số học, băm)

Kết quả BAL hợp lệ về mặt cú pháp và tuân thủ tất cả các giới hạn gas , tuy nhiên các thao tác đọc bộ nhớ đã khai báo không bao giờ được thực thi trong quá trình thực hiện.

Vì sao điều này lại gây ra vấn đề

Các máy khách sẽ bắt đầu tải trước dữ liệu trong khi thực hiện các giao dịch song song. Từ góc nhìn của bất kỳ giao dịch đơn lẻ nào, chúng không thể vô hiệu hóa Block trước khi hoàn tất việc thực hiện tất cả các giao dịch.

Kết quả: một Block không hợp lệ làm vô hiệu hóa hoàn toàn việc tìm nạp trước I/O hữu ích, làm phình to BAL lên hàng trăm KiB (0,6 MiB ở Gas Limit Block 60M) và gây gánh nặng cho mạng — tất cả trong khi việc vô hiệu hóa nó diễn ra quá muộn trong quy trình.

Ảnh chụp màn hình từ 2026-01-30 11-07-55
Ảnh chụp màn hình từ 2026-01-30 11-07-55 1228×421 39.8 KB

Điều này có nghĩa là chúng ta không thể mở rộng quy mô tối đa như các khối hợp lệ trong trường hợp xấu nhất cho phép, mà lại bị giới hạn bởi các khối không hợp lệ.

Tại sao không khai báo quá mức các lệnh đọc? Nếu kẻ tấn công khai báo \left\lfloor \frac{G}{g_{\mathrm{sload}}} \right\rfloor G g s l o a d khe lưu trữ (sử dụng toàn bộ ngân sách gas ), các máy khách có thể vô hiệu hóa Block ngay khi bất kỳ giao dịch nào lãng phí gas vào các thao tác không truy cập vào bộ nhớ. Nhưng bằng cách để lại không gian cho ít nhất một giao dịch có kích thước tối đa, không một giao dịch nào có thể tự mình vô hiệu hóa Block sớm.

Hãy xem BALrog , một proxy đơn giản cho API Engine, có chức năng chèn các BAL trường hợp xấu nhất vào các khối, rất hữu ích cho việc thử nghiệm.

Giải pháp: Kiểm tra tính khả thi của ngân sách khí đốt

Cuộc tấn công lợi dụng hai sự thật:

  1. Threads riêng lẻ không biết những gì đang xảy ra trên Threads khác.
  2. Vị trí của tiểu bang chỉ có thể được xác thực sau khi hoàn tất tất cả các giao dịch.

Tuy nhiên, chúng ta có thể khai thác một ràng buộc quan trọng: lần truy cập đầu tiên vào một khe lưu trữ có chi phí ít nhất bằng chi phí SLOAD lạnh (2100 gas, hoặc 2000 gas với Danh sách truy cập Đề xuất cải tiến Ethereum (EIP)-2930 , giá trị cuối cùng phụ thuộc vào Đề xuất cải tiến Ethereum (EIP)-7981 ).

Bất biến

Định kỳ trong quá trình thực thi (ví dụ: cứ sau 8 giao dịch), máy khách sẽ xác minh:

  • Đã sử dụng bao nhiêu gas?
  • Những khe lưu trữ nào đã được khai báo và đã được truy cập
  • Còn lại bao nhiêu lượt đọc đã được công bố?

Sau đó, giả sử:

  • R_{\mathrm{declared}} R d e c l a r e d = số lần đọc trạng thái đã khai báo trong BAL
  • R_{\mathrm{seen}} R s e e n = số lần truy cập
  • R_{\mathrm{remaining}} = R_{\mathrm{declared}} - R_{\mathrm{seen}} R r e m a i n i n g = R d e c l a r e d R s e e n
  • G_{\mathrm{remaining}} G r e m a i n i n g = gas còn lại Block

Điều kiện cần thiết để BAL có hiệu lực là:

G_{\mathrm{remaining}} \ge R_{\mathrm{remaining}} \cdot 2100
Green còn lại Red còn lại 2100

Nếu bất đẳng thức này không đúng, Block có thể bị bác bỏ ngay lập tức.

Ảnh chụp màn hình từ ngày 30/01/2026 lúc 07:33:54
Ảnh chụp màn hình từ ngày 30/01/2026 lúc 07:33/05/2026, kích thước 1014×682, dung lượng 65.1 KB.

Bạn có thể xem bản dự thảo yêu cầu kéo (PR) cho đặc tả kỹ thuật này tại đây .

Những lợi ích

Việc kiểm tra ngân sách gas cho phép loại bỏ sớm các khối độc hại chỉ sau một giao dịch tối đa gas duy nhất (~16M gas / ~1 giây). Việc thực thi song song vẫn không thay đổi, bảo toàn tất cả các lợi ích của việc song song hóa. Định dạng BAL không yêu cầu thay đổi và I/O theo lô được khôi phục hoàn toàn cho các khối hợp lệ. Độ phức tạp triển khai là tối thiểu—chỉ cần tính toán đơn giản trong trình xử lý kết quả hiện có—với chi phí hiệu năng không đáng kể từ các kiểm tra số học định kỳ.

Các phương án thay thế đã được xem xét

Một cách tiếp cận khác là chú thích các truy cập chỉ đọc bằng chỉ mục giao dịch truy cập đầu tiên, điều này sẽ làm cho BAL tự mô tả và đơn giản hóa logic xác thực. Cách tiếp cận dựa trên ngân sách gas đạt được các đặc tính từ chối sớm tương tự nhưng cần thêm tính toán trong quá trình thực thi thay vì thêm dữ liệu vào BAL. Việc ánh xạ chỉ mục vào các lần đọc sẽ thêm trung bình 4% dữ liệu (đã nén) vào BAL.

Một phương án khác là sắp xếp các vị trí trạng thái trong BAL theo thời điểm chúng được truy cập trong Block. Tuy nhiên, điều này sẽ làm tăng thêm độ phức tạp, cũng như gây khó khăn hơn trong việc chứng minh các điều liên quan đến BAL.

Việc kiểm tra tính khả thi của ngân sách gas cung cấp một biện pháp giảm thiểu đơn giản và hiệu quả: bằng cách xác minh rằng gas còn lại có thể đáp ứng được các yêu cầu đọc đã khai báo còn lại, các máy khách có thể từ chối các khối độc hại sớm mà không cần thay đổi định dạng BAL hoặc hy sinh lợi ích song song hóa.

Ví dụ về logic xử lý kết quả

MIN_GAS_PER_READ = 2100 # cold SLOAD cost CHECK_EVERY_N_TXS = 8 def result_handler ( block, bal, tx_results_channel ): # Count expected storage reads from the BAL (once, upfront) expected_reads = sum ( len (acc.storage_reads) for acc in bal.accounts)accessed_slots = set ()total_gas_used = 0 for i, result in enumerate (tx_results_channel, start= 1 ): # Merge this transaction's accessed slots accessed_slots.update(result.accessed_slots)total_gas_used += result.gas_used # Periodic feasibility check if i % CHECK_EVERY_N_TXS == 0 :remaining_gas = block.gas_limit - total_gas_usedunaccessed_reads = expected_reads - len (accessed_slots)min_gas_needed = unaccessed_reads * MIN_GAS_PER_READ if min_gas_needed > remaining_gas: raise Exception( "BAL infeasible: " f" {unaccessed_reads} reads need {min_gas_needed} gas, " f"only {remaining_gas} left" )

Tài nguyên


Nguồn
Tuyên bố từ chối trách nhiệm: Nội dung trên chỉ là ý kiến của tác giả, không đại diện cho bất kỳ lập trường nào của Followin, không nhằm mục đích và sẽ không được hiểu hay hiểu là lời khuyên đầu tư từ Followin.
Thích
84
Thêm vào Yêu thích
14
Bình luận