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

Trustless L2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. Убеждается, что полученный блок действительно является ключевым блоком
  2. Извлекает root_hash блока из Merkle proof
  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 ячейки.

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