Три хеша одного сообщения
TON-сообщение передаётся как bag of cells — BoC, и «хешем сообщения» называют три разных хеша в зависимости от того, кто говорит. Мемпул-стрим TONNode доставляет все три для каждого ожидающего сообщения под такими именами:
cell_hash— representation hash корневой ячейки. Именно его обозреватели называют хешем сообщения, а после включения сообщения в блок он равен хешуin_msgтой транзакции. Он зависит только от ячеек, а не от того, как они сериализованы в байты.norm_hash— нормализованный хеш по TEP-467: хеш того же внешнего входящего сообщения, переписанного в фиксированную форму —ext_in_msg_info$10 src:addr_none dest:<unchanged> import_fee:0 init:nothing body:right$1 ^body. Он не меняется при пересериализации сообщения и при изменении полейsrc,import_feeилиinit. Если сообщение уже в этой форме,norm_hashравенcell_hash.sha256— SHA-256 байтов BoC ровно в том виде, в каком они доставлены. Одни и те же ячейки можно сериализовать несколькими допустимыми способами (с индексом или без, с CRC32C или без), и у каждой сериализации свой SHA-256, тогда как два хеша ячеек не меняются.
Инструмент выводит каждый из них в hex и в base64, потому что API расходятся: обозреватели и протокол liteserver используют hex, ряд HTTP API возвращает base64.
Зачем нужен нормализованный хеш
Внешнее сообщение подписывает кошелёк, а в сеть его передаёт тот, кто ретранслирует. По пути его могут перекодировать, а ретранслятор — добавить или убрать адрес src, import_fee или прикреплённый init, ничего из этого кошелёк не подписывал. Каждое изменение даёт новую корневую ячейку и новый cell_hash, так что нода, отсеивающая дубликаты по хешу ячейки, приняла бы один и тот же подписанный payload несколько раз. TEP-467 фиксирует форму, по которой считается хеш, чтобы все эти варианты сводились к одной сущности, и собственная функция ноды get_ext_in_msg_hash_norm (в validator/impl/external-message.cpp) вычисляет его именно так.
Конструкция проста: взять биты получателя как есть, обнулить источник, обнулить комиссию, отбросить init и положить тело в ссылку независимо от того, было ли оно там. Нормализованный хеш есть только у внешнего входящего сообщения; для внутреннего или внешнего исходящего инструмент показывает два других хеша и прямо об этом говорит.
Что несёт мемпул-стрим
Каждый кадр msg мемпул-стрима TONNode несёт сам boc, его size, а также cell_hash, norm_hash и sha256 в определённом выше смысле. Отсеивайте дубликаты в своей обработке по cell_hash: одно и то же сообщение может прийти дважды — сначала как early (непроверенное), затем как checked, — и ещё раз после переподключения. sha256 — ключ, по которому нода отсеивает дубликаты полученных байтов, поэтому он считается по доставленным байтам, а не по перекодировке.
Проверено на реальных сообщениях
Реализация здесь строит нормализованную ячейку в точности так, как это делает собственный парсер стрима, а набор тестов репозитория пропускает через неё 72 реальных внешних сообщения mainnet — набор фикстур стрима, для каждого из которых известны cell_hash, norm_hash и sha256, доставленные стримом, — и проверяет совпадение всех трёх. Пример сообщения из документации мемпула — одно из них; вставьте его, чтобы увидеть три значения рядом.
