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 ячейки.
Дальше контракт:
- Убеждается, что полученный блок действительно является ключевым блоком
- Извлекает
root_hashблока из Merkle proof - Проверяет подписи блока, используя
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.
- Из блока извлекается структура
account_blocks - По идентификатору выбирается нужный
account_blockдля конкретного аккаунта - Внутри
account_blockпо логическому времениltнаходится запись транзакции - Для найденной транзакции вычисляется хеш и сравнивается с переданным хешом
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 ячейки.
Примеры реализации
- Реализация контрактов для trustless взаимодействия: tonred/tvm-bridge
- Инструменты для сбора пруфов: broxus/tycho-l2
- Trustless UI: ChainConnect Bridge