Hiểu về RLP của Ethereum

Tram Ho

Nói ngắn gọn là RLP, RLP là gói Ethereum được sử dụng để tuần tự hóa tất cả các đối tượng thành mảng byte. Nó được mô tả trên Giấy vàng với nhiều công thức và rất khó hiểu.

Bởi vì Ethereum là một chuỗi khối phi tập trung, cho phép thực hiện các hợp đồng thông minh và lưu trữ dữ liệu trên chuỗi khối, nên nó cần phải được tuần tự hóa hoặc chuyển đổi thành định dạng nhị phân, được lưu trữ trong một lượng không gian tối thiểu trong chuỗi khối.

RLP là lược đồ mã hóa dựa trên tiền tố mã hóa dữ liệu nhị phân có cấu trúc tùy ý (mảng byte) theo cách dễ mã hóa và giải mã. Thuật toán RLP hoạt động bằng cách mã hóa đệ quy một danh sách các mục.

Một mục được định nghĩa như sau:

  • Một chuỗi (mảng byte)
  • Một danh sách các “mặt hàng” chính nó

Ví dụ:

  • Một chuỗi (mảng byte), bao gồm một chuỗi rỗng
  • Một danh sách chứa bất kỳ số chuỗi nào
  • Một cấu trúc dữ liệu phức tạp như ["cat", ["dog", "mouse"], [], ["""]]

Đi qua Sách vàng (Phụ lục B)

Chúng tôi có ba công thức mô tả dữ liệu nhị phân có cấu trúc tùy ý (mảng byte) : Công thức RLP

  • T: Dữ liệu nhị phân có cấu trúc tùy ý, là tập hợp các mảng byte và chuỗi cấu trúc
  • L: Tập hợp tất cả các cấu trúc dạng cây không phải là một lá
  • O: Tập hợp các byte 8 bit
  • B: Tập hợp tất cả các chuỗi byte (mảng byte hoặc lá trong cây)
  • Chúng tôi sử dụng liên kết rời rạc để phân biệt mảng byte trống (trong B) với danh sách trống (trong L).

Chúng tôi xác định chức năng RLP là RLP thông qua hai chức năng con:

  • Việc xử lý đầu tiên của các mảng byte
  • Cái thứ hai xử lý chuỗi các giá trị khác Chức năng RLP

Chúng ta sẽ đi sâu vào hàm đầu tiên xử lý mảng byte, RLP(B) . Nếu giá trị được sắp xếp theo thứ tự là một mảng byte, thì RLP(B) sẽ có một trong ba dạng:

  • Một byte nhỏ hơn 128 (thập phân), đầu ra giống với đầu vào
  • Nếu các byte mảng chứa ít hơn 56 byte, thì đầu ra bằng với đầu vào có tiền tố là byte bằng với độ dài của byte mảng + 128
  • Mặt khác, đầu ra bằng với biểu diễn đầu lớn của độ dài đầu vào ở phía trước đầu vào và sau đó là (183 + độ dài của đầu lớn của đầu vào) RLP(B)

Thứ hai, chúng ta sẽ xem cách hoạt động của RLP(L). Chúng tôi sử dụng RLP(L) để mã hóa từng mục, sau đó nối đầu ra.

  • Nếu chiều dài nhỏ hơn 56 thì xuất ra bằng: 192 + chiều dài mặt hàng + mặt hàng
  • Mặt khác, đầu ra bằng: 247 + chiều dài của phần cuối lớn của chiều dài mục + phần cuối lớn của chiều dài của mục + mục RLP(L)

Bạn có thể thấy s(x) là đệ quy của RLP với mỗi mục.

Thuật toán RLP dưới dạng mã như (ví dụ từ ethereum.org ):

Bạn có thể xem ví dụ trong tài liệu Ethereum với một số đầu vào:

  • Chuỗi “ethereum” => [“0x88”, “e”, “t”, “h”, “e”, “r”, “e”, “u”, “m”] => vì độ dài của chuỗi này có 8 ký tự, nhỏ hơn 56. Vì vậy, đầu ra là encoded_length(8, 128) + input = chr(136) + “ethereum” = [“0x88”, “e”, “t”, “h”, “e”, “r”, “e”, “u”, “m”]
  • Một danh sách [“ethereum”, “nền tảng”]:
    • rlp_encode("ethereum") = ["0x88", "e", "t", "h", "e", "r", "e", "u", "m"]
    • rlp_encode("foundation") = ["0x8A", "f", "o", "u", "n", "d", "a", "t", "i", "o", "n"]
    • rlp_encode(["ethereum", "foundation"]) = encode_length(20, 192) + ["0x88", "e", "t", "h", "e", "r", "e", "u", "m", "0x8A", "f", "o", "u", "n", "d", "a", "t", "i", "o", "n"] = ["0xD4", "0x88", "e", "t", "h", "e", "r", "e", "u", "m", "0x8A", "f", "o", "u", "n", "d", "a", "t", "i", "o", "n"]

giải mã RLP

Do các quy tắc mã hóa RLP, đầu vào của giải mã RLP là một mảng dữ liệu nhị phân.

  • Tùy thuộc vào byte đầu tiên trong đầu vào, chúng ta có thể xác định loại dữ liệu và độ dài của dữ liệu và độ lệch.
  • Tùy theo kiểu dữ liệu và offset của dữ liệu mà giải mã dữ liệu tương ứng.
  • Tiếp tục vòng lặp để giải mã phần còn lại của đầu vào.

Với các công thức RLP, chúng ta có thể xác định các quy tắc giải mã kiểu dữ liệu và bù đắp bằng cách sau:

  • Nếu phạm vi của byte đầu tiên là từ [0x00, 0x7f] và độ dài của đầu vào là 1, thì kiểu dữ liệu là một chuỗi và dữ liệu chính là chuỗi đó.
  • Nếu phạm vi của byte đầu tiên là từ [0x80, 0xb7], thì kiểu dữ liệu là một chuỗi và độ dài của chuỗi bằng byte đầu tiên trừ đi 0x80
  • Nếu phạm vi của byte đầu tiên là [0xb8, 0xbf] và độ dài của chuỗi có độ dài tính bằng byte bằng byte đầu tiên trừ 0xb7 theo sau byte đầu tiên và chuỗi theo sau độ dài của chuỗi;
  • Nếu phạm vi của byte đầu tiên là [0xc0, 0xf7] và phép nối mã hóa RLP của tất cả các mục trong danh sách có tổng tải trọng bằng byte đầu tiên trừ đi 0xc0 theo sau byte đầu tiên;
  • Nếu phạm vi của byte đầu tiên là [0xf8, 0xff] và tổng tải trọng của danh sách có độ dài bằng byte đầu tiên trừ đi 0xf7 theo sau byte đầu tiên và nối mã hóa RLP của tất cả các mục trong danh sách theo sau tổng trọng tải của danh sách;

Nguồn: Tài liệu Ethereum

Mã giả từ Tài liệu Ethereum:

Rất khó hiểu đầy đủ với các công thức này, vì vậy chúng ta cần gỡ lỗi để biết kết quả đầu ra khi chúng ta sử dụng RLP để mã hóa/giải mã dữ liệu nhị phân có cấu trúc tùy ý.

Go-ethereum RLP

Bạn nào chưa rành về Golang có thể đọc các bản khác do Typescript viết Với ​​mình thì bản này dễ hiểu hơn bản gốc do Golang viết

Cấu trúc gói RLP:

mã hóa.go

Đầu tiên chúng ta hãy xem giao diện Encode

Giao diện này chỉ có một chức năng, lấy io.Writer làm đầu vào và ghi trực tiếp đầu ra vào io.Writer . Mọi thứ được lưu trữ trên chuỗi khối Ethereum nên sử dụng RLP để mã hóa. Có rất nhiều triển khai: Triển khai mã hóa RLP

Tiếp theo, chúng ta có một hàm gọi là func Encode(w io.Writer, val interface{}) error , hàm này sẽ lấy wv để mã hóa thành dữ liệu nhị phân.

Đây là chức năng chính được gọi bởi các triển khai để mã hóa đầu vào. Go-ethereum sử dụng encBufferFromWriter để giảm phân bổ, chúng ta sẽ đi sâu vào vấn đề này.

Lý do chính vì nhóm tác giả đang sử dụng cấu trúc encBuffer để mã hóa dữ liệu đầu vào. Vì vậy, nếu w là loại encBuffer , chúng tôi sử dụng lại nó để mã hóa đầu vào.

Nhưng tôi có một câu hỏi, tại sao họ cần tạo một struct encBuffer mới để mã hóa? Hãy xem nào. Trong một số phiên bản trước của Go-ethereum, họ không sử dụng cấu trúc này. Cấu trúc encBuffer là:

và trong encbuffer.go , chúng tôi có một nhóm đồng bộ lưu trữ encBuffer

Tóm lại, chúng tôi có một số điểm:

  • go-ethereum sử dụng encBuffer để lưu trữ đầu ra nhị phân được mã hóa
  • Họ sử dụng a pool để lưu trữ encBuffer để tái sử dụng trong bước tiếp theo

Bước tiếp theo, chúng ta có một hàm buf.encode(val) để mã hóa val dựa trên loại val

Bạn có thể thấy, go-ethereum sử dụng sự phản chiếu và mã hóa RLP dựa trên loại Go của giá trị. writer là một hàm sẽ mã hóa dựa trên loại và ghi vào encBuffer

Chúng ta có một hàm cachedWriter khác sẽ trả về hàm writer với kiểu Go.

Họ sử dụng cấu trúc typeCache để lưu trữ trình ghi của loại Go. Chi tiết hơn bạn có thể vào typecache.go để xem.

Chúng ta có:

  • cur : để lưu trữ trình ghi bản đồ. Giá trị của curmap[typekey]*typeinfo
  • mu : sử dụng sync.Mutex để khóa trình ghi đồng bộ hóa
  • next : bản đồ lưu trữ bản đồ tiếp theo

go-ethereum sử dụng bản đồ để lưu trữ hàm ghi dựa trên loại Go. Bản đồ này sẽ lưu trữ trong mem khi geth đang chạy.

Đi qua nhiều chức năng hơn bên trong theTC.info(typ) , chúng ta sẽ thấy chức năng chính xác định chức năng của người viết dựa trên typ là gì

Tóm lại, chúng tôi có một số điểm:

  • go-ethereum sử dụng bản đồ trong mem để lưu trữ chức năng mã hóa người ghi dựa trên loại Go
  • go-etherem sử dụng cấu trúc encBuffer để lưu trữ dữ liệu nhị phân được mã hóa bởi writer
  • Sau khi chúng tôi mã hóa đầu vào, họ sẽ ghi lại đầu ra vào w

Gói RLP do Go viết rất khó hiểu hơn bản Typescript. Nhưng họ thêm nhiều kỹ thuật hơn vào đó để giảm phân bổ và giảm thời gian mã hóa bằng cách sử dụng bản đồ trong bộ nhớ để lưu trữ chức năng mã hóa của writer dựa trên loại Go.

giải mã.go

Chúng tôi có cách tiếp cận tương tự như encode với giao diện Decode, nhưng bây giờ, đầu vào của giải mã là Stream chứ không phải io.Writer như mã hóa.

Bởi vì decode hoạt động theo cùng một cách tiếp cận với encode . Tôi không đi sâu ngay bây giờ.  Nhưng tôi có một số điểm:

  • decode sử dụng bản đồ được lưu trong bộ nhớ cache
  • Chúng tôi có chức năng decoder dựa trên độ dài và phần tử đầu tiên của đầu vào

Bản tóm tắt

RLP là một thuật toán được sử dụng trong Ethereum để mã hóa/giải mã dữ liệu nhị phân có cấu trúc tùy ý. RLP được đề cập trong Yellow Paper nhưng lúc đầu rất khó hiểu đầy đủ.

Thẩm quyền giải quyết

Ghi chú

Một trong những bài viết public đầu tiên mình viết bằng tiếng Anh nên có lẽ bài viết này có sai sót gì về tiếng Anh hay kiến ​​thức, các bạn thấy có gì cứ góp ý cho mình nhé. Yêu tất cả các bạn.

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo