ADNL TCP - Liteserver
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
Это низкоуровневый протокол, на котором построено все взаимодействие в сети TON, он может работать поверх любого протокола, но чаще всего используется поверх TCP и UDP. UDP используется для связи между узлами, а TCP - для связи с lite-серверами.
Теперь мы проанализируем ADNL, работающий поверх TCP, и узнаем, как напрямую взаимодействовать с lite-серверами.
В TCP-версии ADNL сетевые узлы используют открытые ключи ed25519 в качестве адресов и устанавливают соединение с помощью общего ключа, полученного с помощью процедуры Диффи-Хеллмана на эллиптических кривых - ECDH.
Структура пакета
Каждый пакет ADNL TCP, за исключением пакета рукопожатия, имеет следующую структуру:
- 4 байта размера пакета в порядке от младшего к старшему (N)
- 32 байта nonce (случайные байты для защиты от атак с контрольной суммой)
- (N - 64) байта полезной нагрузки
- 32 байта контрольной суммы SHA256 из nonce и полезной нагрузки
Весь пакет, включая размер, зашифрован AES-CTR. После расшифровки необходимо проверить, соответствует ли контрольная сумма данным, для проверки нужно просто самостоятельно посчитать контрольную сумму и сравнить результат с тем, что у нас в пакете.
Пакет подтверждения связи является исключением, он передается в частично незашифрованном виде и описан в следующей главе.
Установка соединения
Чтобы установить соединение, нам нужно знать IP, порт и открытый ключ сервера, а также сгенерировать собственный закрытый и открытый ключ ed25519.
Данные публичного сервера, такие как IP, порт и ключ, можно получить из глобальной конфигурации. IP в конфигурации в числовой форме, его можно привести к нормальной форме, используя, например, этот инструмент. Открытый ключ в конфигурации в формате base64.
Клиент генерирует 160 случайных байтов, некоторые из которых будут использоваться сторонами в качестве основы для шифрования AES.
Из них создаются 2 постоянных шифра AES-CTR, которые будут использоваться сторонами для шифрования/расшифровки сообщений после подтверждения связи.
- Шифр A - ключ 0 - 31 байт, iv 64 - 79 байт
- Шифр B - ключ 32 - 63 байта, iv 80 - 95 байт
Шифры применяются в следующем порядке:
- Шифр A используется сервером для шифрования отправляемых им сообщений.
- Шифр A используется клиентом для расшифровки полученных сообщений.
- Шифр B используется клиентом для шифрования отправляемых им сообщений.
- Шифр B используется сервером для расшифровки полученных сообщений.
Чтобы установить соединение, клиент должен отправить пакет подтверждения, содержащий:
- [32 байта] ID ключа сервера [Подробнее]
- [32 байта] Наш открытый ключ ed25519
- [32 байта] Хеш SHA256 из наших 160 байт
- [160 байт] Наши 160 байт зашифрованы [Подробнее]
При получении пакета подтверждения связи сервер выполнит те же действия, получит ключ ECDH, расшифрует 160 байт и создаст 2 постоянных ключа. Если все получится, сервер ответит пустым пакетом ADNL, без полезной нагрузки, для расшифровки которого (а также последующих) нам нужно использовать один из постоянных шифров.
С этого момента соединение можно считать установленным.
После установления соединения мы можем начать получать информацию; язык TL используется для сериализации данных.
Ping&Pong
Оптимально отправлять пакет ping каждые 5 сек унд. Это необходимо для поддержания соединения во время отсутствия передачи данных, в противном случае сервер может разорвать соединение.
Пакет ping, как и все остальные, строится по стандартной схеме, описанной выше, и несет в себе идентификатор запроса и идентификатор ping в качестве полезных данных.
Давайте найдем нужную схему для запроса ping здесь и вычислим идентификатор схемы как
crc32_IEEE("tcp.ping random_id:long = tcp.Pong")
. При преобразовании в байты с прямым порядком байтов получаем 9a2b084d.
Таким образом, наш пакет ping ADNL будет выглядеть так:
- 4 байта размера пакета в прямом порядке -> 64 + (4+8) = 76
- 32 байта nonce -> случайные 32 байта
- 4 байта схемы ID TL -> 9a2b084d
- 8 байт идентификатора запроса -> случайное число uint64
- 32 байта контрольной суммы SHA256 из nonce и полезной нагрузки
Мы отправляем наш пакет и ждем tcp.pong, random_id
будет равен тому, который мы отправили в пакете ping.
Получение информации от Liteserver
Все запросы, направленные на получение информации из блокчейна, упакованы в схему Liteserver запроса, которая в свою очередь упакована в схему ADNL запроса.
Lite запрос:
liteServer.query data:bytes = Object
, id df068c79
ADNL запрос:
adnl.message.query query_id:int256 query:bytes = adnl.Message
, id 7af98bb4
Lite запрос передается внутри ADNL запроса как query:bytes
, а окончательный запрос передается внутри Lite запроса как data:bytes
.
getMasterchainInfo
Теперь, поскольку мы уже знаем, как генерировать пакеты TL для API Lite, мы можем запросить информацию о текущем блоке мастерчейна TON. Блок мастерчейна используется во многих дальнейших запросах как входной параметр для указания состояния (момента), в котором нам нужна информация.
Мы ищем нужную нам схему TL, вычисляем ее идентификатор и создаем пакет:
- 4 байта размера пакета в прямом порядке -> 64 + (4+32+(1+4+(1+4+3)+3)) = 116
- 32 байта nonce -> случайные 32 байта
- 4 байта схемы ID ADNL запроса -> 7af98bb4
- 32 байта
query_id:int256
-> случайные 32 байта- 1 байт размера массива -> 12
- 4 байта схемы ID Lite запроса -> df068c79
- 1 байт размера массива -> 4
- 4 байта схемы ID getMasterchainInfo -> 2ee6b589
- 3 нулевых байта заполнения (выравнивание по 8)
- 3 нулевых байта заполнения (выравнивание по 16)
- 32 байта контрольной суммы SHA256 из одноразового nonce и полезной нагрузки
Пример пакета в шестнадцатеричном формате:
74000000 -> packet size (116)
5fb13e11977cb5cff0fbf7f23f674d734cb7c4bf01322c5e6b928c5d8ea09cfd -> nonce
7af98bb4 -> ADNLQuery
77c1545b96fa136b8e01cc08338bec47e8a43215492dda6d4d7e286382bb00c4 -> query_id
0c -> array size
df068c79 -> LiteQuery
04 -> array size
2ee6b589 -> getMasterchainInfo
000000 -> 3 bytes of padding
000000 -> 3 bytes of padding
ac2253594c86bd308ed631d57a63db4ab21279e9382e416128b58ee95897e164 -> sha256
В ответ мы ожидаем получить liteServer.masterchainInfo, состоящий из последнего: ton.blockIdExt state_root_hash:int256 и init:tonNode.zeroStateIdExt.
Полученный пакет десериализуется так же, как и отправленный — имеет тот же алгоритм, но в противоположном направлении, за исключением того, что ответ оборачивается только в ADNL запросе.
После расш ифровки ответа получаем пакет вида:
20010000 -> packet size (288)
5558b3227092e39782bd4ff9ef74bee875ab2b0661cf17efdfcd4da4e53e78e6 -> nonce
1684ac0f -> ADNLAnswer
77c1545b96fa136b8e01cc08338bec47e8a43215492dda6d4d7e286382bb00c4 -> query_id (identical to request)
b8 -> array size
81288385 -> liteServer.masterchainInfo
last:tonNode.blockIdExt
ffffffff -> workchain:int
0000000000000080 -> shard:long
27405801 -> seqno:int
e585a47bd5978f6a4fb2b56aa2082ec9deac33aaae19e78241b97522e1fb43d4 -> root_hash:int256
876851b60521311853f59c002d46b0bd80054af4bce340787a00bd04e0123517 -> file_hash:int256
8b4d3b38b06bb484015faf9821c3ba1c609a25b74f30e1e585b8c8e820ef0976 -> state_root_hash:int256
init:tonNode.zeroStateIdExt
ffffffff -> workchain:int
17a3a92992aabea785a7a090985a265cd31f323d849da51239737e321fb05569 -> root_hash:int256
5e994fcf4d425c0a6ce6a792594b7173205f740a39cd56f537defd28b48a0f6e -> file_hash:int256
000000 -> 3 bytes of padding
520c46d1ea4daccdf27ae21750ff4982d59a30672b3ce8674195e8a23e270d21 -> sha256