Библиотечные ячейки
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
Введение
Одной из характерных особенностей того, как TON хранит данные в ячейках, является дедупликация: в хранилище сообщения, блоки, транзакции и т. д. дублирующиеся ячейки сохраняются только один раз. Это значительно уменьшает размер сериализованных данных и позволяет эффективно хранить поэтапно обновляемые данные.
По той же причине многие структуры в TON одновременно являются богатыми, удобными и эффективными: структура блока содержит одинаковые копии каждого сообщения в различных местах: в очереди сообщений, списке транзакций, обновлениях дерева Меркла и так далее: поскольку дублирование не имеет накладных расходов, мы можем хранить данные несколько раз там, где они нам нужны, не беспокоясь об эффективности.
Библиотечные ячейки используют механизм дедупликации в цепочке, что позволяет интегрировать эту технологию в пользовательские смарт-контракты.
Если вы храните код jetton-wallet в виде библиотечной ячейки (1 ячейка и 256+8 бит вместо ~20 ячеек и 6000 бит), например, то сборы за пересылку сообщения, содержащего init_code
, будут снижены с 0,011 до 0,003 TON.
Основная информация
Рассмотрим шаг бейсчейна от блока 1'000'000 до блока 1'000'001. Хотя каждый блок содержит небольшой объем данных (обычно менее 1000 транзакций), все состояние бейсчейна содержит миллионы учетных записей, и поскольку блокчейн должен поддерживать целостность данных (в частности, для фиксации хэша корня Меркла всего состояния в блоке), все дерево состояния должно быть обновлено.
Для блокчейнов предыдущих поколений это означает, что обычно вы отслеживаете только последние состояния, поскольку хра нение отдельных состояний цепи для каждого блока потребует слишком много места. Но в блокчейне TON из-за дедупликации для каждого блока вы добавляете в хранилище только новые ячейки. Это не только ускоряет обработку, но и позволяет эффективно работать с историей: проверять балансы, состояния и даже запускать методы get для любой точки истории без особых накладных расходов!
В случае, когда у нас есть семейство похожих контрактов (например, jetton-wallets), узел сохраняет дублирующие данные (одинаковый код каждого jetton-wallet) только один раз. Библиотечные ячейки позволяют использовать механизм дедупликации для таких контрактов, чтобы уменьшить плату за хранение и пересылку.
Вы можете рассматривать библиотечную ячейку как указатель C++: одна маленькая ячейка, которая указывает на большую ячейку с (возможно) большим количеством ссылок. Ссылоч ная ячейка (ячейка, на которую указывает библиотечная ячейка) должна существовать и быть зарегистрирована в публичном контексте ("published").
Структура библиотечных ячеек
Библиотечная ячейка - это экзотическая ячейка, которая содержит ссылку на некоторую другую статическую ячейку. В частности, она содержит 256 бит хеша указанной ячейки.
Для TVM библиотечные ячейки работают следующим образом: всякий раз, когда TVM получает команду открыть ячейку для фрагмента (инструкция TVM: CTOS
, функциональный метод: .begin_parse()
), он выполняет поиск ячейки с соответствующим хэшем из библиотечной ячейки в контексте библиотеки мастерчейна. Если она найдена, она открывает указанную ячейку и возвращает ее срез.
Открытие библиотечной ячейки стоит столько же, сколько открытие обычной ячейки, поэтому ее можно использовать в качестве прозрачной замены для статических ячеек, которые, однако, занимают гораздо меньше места (и, следовательно, требуют меньших затрат на хранение и отправку).
Обратите внимание, что можно создать библиотечную ячейку, которая ссылается на другую библиотечную ячейк, которая, в свою очередь, ссылается на другую, и так далее. В таком случае .begin_parse()
вызовет исключение. Однако такая библиотека может быть развернута пошагово с помощью opcode XLOAD
.
Еще одной важной особенностью библиотечной ячейки является то, что, поскольку она содержит хэш указанной ячейки, она в конечном итоге ссылается на некоторые статические данные. Вы не можете изменить данные, на которые ссылается эта библиотечная ячейка.
Чтобы быть найденной в контексте библиотеки мастерчейна и, следовательно, на которую ссылается библиотечная ячейка, исходная ячейка должна быть опубликована в мастерчейне. Это означает, что смарт-контракт, существующий в мастерчейне, должен добавить эту ячейку в свое состояние с флагом public=true
. Это можно сделать с opcode SETLIBCODE
.
Использование в смарт-контрактах
Поскольку библиотечная ячейка ведет себя так же, как и обычная ячейка, на которую она ссылается, во всех контекстах, за исключением расчета платы, вы можете просто использовать ее вместо любой ячейки со статическими данными. Например, вы можете хранить код jetton-wallet в виде библиотечной ячейки (то есть 1 ячейка и 256+8 бит вместо обычных ~20 ячеек и 6000 бит), что приведет к уменьшению на порядок комиссий за хранение и пересылку. В частности, комиссии за пересылку сообщения internal_transfer
, содержащего init_code
, будут снижены с 0,011 до 0,003 TON.
Хранение данных в библиотечной ячейке
Давайте рассмотрим пример хранения кода jetton-wallet в виде библиотечной ячейки для уменьшения комиссий. Сначала нам нужно скомпилировать jetton-wallet в обычную ячейку, содержащую его код.
Затем вам нужно создать библиотечную ячейку со ссылкой на обычную ячейку. Библиотечная ячейка содержит 8-битный тег библиотеки 0x02
, за которым следует 256-битный хэш указанной ячейки.
Использование в Fift
По сути, вам нужно поместить тег и хэш в конструктор, а затем "закрыть конструктор как экзотическую ячейку".
Это можно сделать в конструкции Fift-asm, как эта, пример компиляции некоторого контракта непосредственно в библиотечную ячейку здесь.
;; https://docs.ton.org/tvm.pdf, page 30
;; Library reference cell — Always has level 0, and contains 8+256 data bits, including its 8-bit type integer 2
;; and the representation hash Hash(c) of the library cell being referred to. When loaded, a library
;; reference cell may be transparently replaced by the cell it refers to, if found in the current library context.
cell order_code() asm "<b 2 8 u, 0x6305a8061c856c2ccf05dcb0df5815c71475870567cab5f049e340bcf59251f3 256 u, b>spec PUSHREF";