Библиотечные ячейки
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @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";
Использование в @тонн/тонн
В качестве альтернативы вы можете создать библиотечную ячейку полностью на уровне ts в Blueprint с помощью библиотеки @ton/ton
:
import { Cell, beginCell } from '@ton/core';
let lib_prep = beginCell().storeUint(2,8).storeBuffer(jwallet_code_raw.hash()).endCell();
jwallet_code = new Cell({ exotic:true, bits: lib_prep.bits, refs:lib_prep.refs});
- Изучите исходный код здесь.
Публикация обычной ячейки в контексте библиотеки мастерчейна
Практический пример доступен здесь. Ядром этого контракта является set_lib_code(lib_to_publish, 2);
- он принимает в качестве входных данных обычную ячейку, которую нужно опубликовать, и flag=2 (означает, что все могут ее использовать).
Обратите внимание, что этот контракт, который публикует ячейку, платит за свое хранение и хранение в мастерчейне в 1000 раз больше, чем в бейсчейне. Таким образом, использование библиотечной ячейки эффективно только для контрактов, используемых тысячами пользователей.
Тестирование в Blueprint
Чтобы проверить, как контракт, который использует библиотечные ячейки, работает в blueprint, вам нужно вручную добавить ссылочные ячейки в контекст библиотеки эмулятора blueprint. Это можно сделать следующим образом:
- вам нужно создать словарь контекста библиотеки (Hashmap)
uint256->Cell
, гдеuint256
- это хэш соответствующей ячейки. - установите контекст библиотеки в настройках эмулятора.
Пример того, как это можно сделать, показан здесь.
Обратите внимание, что текущая версия blueprint (@ton/blueprint:0.19.0
) не обновляет автоматически контекст библиотеки, если какой-либо контракт во время эмуляции публикует новую библиотеку, вам нужно сделать это вручную.
Get Методы для контрактов на основе библиотечных ячеек
У вас есть jetton-кошелек, код которого хранится в библиотечной ячейке, и вы хотите проверить баланс.
Чтобы проверить его баланс, вам нужно выполнить get метод получения в коде. Это включает в себя:
- доступ к библиотечной ячейке
- извлечение хеша указанной ячейки
- поиск ячейки с этим хешем в коллекции библиотеки мастерчейна
- выполнение кода оттуда.
В многоуровневых решениях (LS) все эти процессы происходят за кулисами, и пользователю не нужно знать о конкретном методе хранения кода.
Однако при локальной работе все по-другому. Например, если вы используете проводник или кошелек, вы можете взять состояние учетной записи и попытаться определить ее тип — будь то NFT, кошелек, токен или аукцион.
Для обычных контрактов вы можете посмотреть доступные get методы, т. е. интерфейс, чтобы понять его. Или вы можете "забрать" состояние учетной записи в локальной псевдосети и выполнить методы там.
Для библиотечной ячейки это невозможно, поскольку она сама по себе не содержит данных. Вы должны вручную обнаружить и извлечь необходимые ячейки из контекста. Это можно сделать через LS (хотя привязки пока не поддерживают это) или через DTon.
Получение библиотечной ячейки с помощью Liteserver
Liteserver при запуске get методов автоматически устанавливает правильный контекст библиотеки. Если вы хотите определить тип контракта с помощью get методов или запустить get методы локально, вам нужно загрузить соответствующие ячейки с помощью метода LS liteServer.getLibraries.
Получение библиотечной ячейки с помощью DTon
Вы также можете получить библиотеку с dton.io/graphql:
{
get_lib(
lib_hash: "<HASH>"
)
}
а также список библиотек для определенного блока мастерчейна:
{
blocks{
libs_publishers
libs_hash
}
}