Как это работает

Trustless L2

Trustless-взаимодействие между TON и Tycho L2 не требует доверия. Транзакция предъявляется смарт-контракту на принимающей стороне. Смарт-контракт проверяет доказательства и убеждается, что транзакция действительно была выполнена в исходной сети. Это работает в обе стороны, Tycho L2 может проверять события из TON, и TON может проверять события из Tycho L2.

Это возможно за счёт:

  • общей структуры данных
  • общей логики проверок, которая собирает цепочку от транзакции до подтвержденного блока
  • использования доказательств Меркла и подписей валидаторов с порогом по весам

Данные, необходимые для проверки транзакции

Чтобы проверить транзакцию из TON внутри Tycho L2, контракту нужны две группы данных.

  • Данные о наборе валидаторов
  • Данные о транзакции

Данные о наборе валидаторов

Чтобы понять, какие именно данные нужны для проверки транзакции, нужно сначала разобраться, как устроена смена валидаторов в протоколе. В TON есть мастерчейн, где каждый мастер-блок подписывается валидаторами текущего набора.

Когда набор валидаторов меняется, в мастерчейне выпускается ключевой блок. В нём лежит информация о новом наборе валидаторов, включая их публичные ключи и веса. При этом сам ключевой блок подписан старыми валидаторами, поэтому новая конфигурация привязывается к уже подтверждённой истории.

На стороне L2 хранится цепочка данных, созданная из таких ключевых блоков, включая информацию о текущей эпохе валидаторов и о нескольких прошлых эпохах. Это нужно, чтобы для любого проверяемого мастер-блока можно было восстановить актуальный на тот момент набор валидаторов и понять, какие подписи требуется проверить.

Какие данные из ключевого блока переносятся в L2

Когда появляется новый ключевой блок, он переносится в L2 как пакет данных для проверки и обновления локального состояния. Эти данные включают:

  • доказательство ключевого блока в виде доказательства Меркла для ячейки
  • file_hash как отдельное поле, которое передаётся вместе с этим ключевым блоком
  • подписи этого ключевого блока

Нам не нужно переносить весь ключевой блок. Передаются только нужные данные, а лишние заменяются на pruned-ячейки. Передаются:

  • seq_no — порядковый номер мастер-блока
  • gen_utime — время генерации мастер-блока
  • prev_key_block — ссылка на предыдущий ключевой блок через его номер
  • конфигурация валидаторов из параметров 34 и при необходимости 32

Остальные части блока сворачиваются в pruned-ячейки.

Дальше контракт:

  1. Убеждается, что полученный блок действительно является ключевым блоком
  2. Извлекает root_hash блока из доказательства Меркла
  3. Проверяет подписи блока, используя root_hash, file_hash и переданные подписи. Проверка делается относительно набора валидаторов, который был актуален при выпуске этого ключевого блока.

После успешной проверки контракт извлекает из McBlockExtra параметры конфигурации 32 и 34 и обновляет данные о валидаторах. В L2 сохраняется только то, что нужно для будущих проверок мастер-блоков:

  • epoch_id — время начала действия этого набора валидаторов
  • период времени, в котором новый набор валидаторов считается актуальным
  • список валидаторов с публичными ключами и весами в формате, удобном для перебора в контракте
  • порог по суммарному весу подписей, который нужен, чтобы признать блок подтвержденным

Данные о транзакции

Для проверки транзакции необходимо предоставить пакет доказательств, который связывает транзакцию с мастер-блоком, подписанным валидаторами, и содержит данные для поиска транзакции в блоке.

check_transaction#ddab5b88
  proof_chain:^(MERKLE_PROOF ProofChainRoot)
  tx_proof:^TransactionProof
= InternalMsgBody;

Цепочка доказательств, связывающая транзакцию с мастер-блоком:

  • epoch_id мастер-блока, чтобы выбрать правильный набор валидаторов для проверки подписей
  • file_hash мастер-блока как отдельное поле
  • mc_block — мастер-блок
  • signatures — подписи для мастер-блока
  • блок рабочей цепочки, если транзакция находится в нём, и при необходимости цепочка таких блоков, если мастер-блок ссылается на нужный блок рабочей цепочки через промежуточные блоки
proof_chain_root#_ {n:#}
  file_hash:uint256
  epoch_id:uint32
  mc_block:^Cell
  signatures:^(Hashmap 16 bits512)
  head:(ProofChainHead ~n)
= ProofChainRoot;

proof_chain_head_empty#_ = ProofChainHead ~0;
proof_chain_head_1#_ sc_block1:^Cell = ProofChainHead ~1;
proof_chain_head_2#_ {n:#} sc_block1:^Cell tail:^(ProofChainTail ~n) = ProofChainHead ~2;

proof_chain_tail_empty#_ = ProofChainTail ~0;
proof_chain_tail_1#_ sc_block1:^Cell = ProofChainTail ~1;
proof_chain_tail_2#_ sc_block1:^Cell sc_block2:^Cell = ProofChainTail ~2;
proof_chain_tail_3#_ sc_block1:^Cell sc_block2:^Cell sc_block3:^Cell = ProofChainTail ~3;
proof_chain_tail_3tail#_ {n:#} sc_block1:^Cell sc_block2:^Cell sc_block3:^Cell tail:^(ProofChainTail ~n) = ProofChainTail ~4;

Информация о транзакции:

  • идентификатор блока аккаунта, в котором лежат транзакции этого аккаунта
  • логическое время транзакции
  • хеш транзакции
tx_proof#_ account_block:uint256 lt:uint64 tx_hash:uint256 = TransactionProof;

Проверка транзакции

Контракт распаковывает цепочку доказательств в поисках блока, который содержит транзакцию.

  • Если блоков рабочей цепочки нет, берётся mc_block
  • Иначе выбирается самый последний из приложенных блоков рабочей цепочки.

В найденном блоке производим поиск искомой транзакции tx_hash, используя данные из TransactionProof.

  1. Из блока извлекается структура account_blocks
  2. По идентификатору выбирается нужный account_block для конкретного аккаунта
  3. Внутри account_block по логическому времени lt находится запись транзакции
  4. Для найденной транзакции вычисляется хеш и сравнивается с переданным хешом tx_hash

Последним шагом проверяется подпись мастер-блока.

  • по epoch_id получаем публичные ключи и веса валидаторов этой эпохи
  • получаем root_hash из mc_block
  • по root_hash и file_hash проверяем signatures, чтобы определить, был ли этот мастер-блок подписан правильным набором валидаторов

Нам не нужно переносить полные блоки. Передаются только нужные данные, а лишние заменяются на pruned-ячейки. Передаются:

  • Для мастер-блока — Block info в минимальном виде и McBlockExtra с shard-hashes
  • Для блока рабочей цепочки в связанной цепочке — Block info prev_ref
  • Для блока с транзакцией — нужный account_block из Block extra account_blocks и только одна запись транзакции по lt

Остальные части блока сворачиваются в pruned-ячейки.

Примеры реализации