Skip to content

Latest commit

 

History

History
154 lines (112 loc) · 16.8 KB

DHT.md

File metadata and controls

154 lines (112 loc) · 16.8 KB

DHT

DHT - Расшифровывается, как Distributed Hash Table, и по сути представляет из себя распределенную базу данных ключ-значение, где каждый участник сети может что-то сохранить, например, информацию о себе.

Реализация DHT в TON по своей сути похожа на реализацию Kademlia, который используется в IPFS. Любой участник сети может запустить DHT ноду, сгенерировать ключи и хранить данные. Для этого ему нужно сгенерировать случайный айди и сообщить о себе другим нодам.

Для определения, на какой ноде сохранить данные, используется алгоритм определения "расстояния" между нодой и ключом. Алгоритм прост: берем айди ноды и айди ключа, производим операцию XOR . Чем меньше получилось значение, тем ближе нода. Задача в том, чтобы сохранить ключ на максимально близким к ключу нодам, чтобы другие участники сети могли, используя тот же алгоритм, найти ноду, которая сможет отдать данные по этому ключу.

Поиск значения по ключу

Разберем пример с поиском ключа, подключимся к любой DHT ноде и установим соединение по ADNL UDP.

Например, мы хотим найти адрес и данные для подключения к RLDP ноде ТОН сайта foundation.ton. Допустим, мы уже получили ADNL адрес этого сайта, выполнив Get метод DNS контракта. ADNL адресом в hex представлении будет 516618cf6cbe9004f6883e742c9a2e3ca53ed02e3e36f4cef62a98ee1e449174. Теперь наша задача найти ip, порт и публичный ключ ноды, имеющей этот адрес.

Чтобы это сделать, нам нужно получить ID DHT ключа, сначала соберем сам DHT ключ:

dht.key id:int256 name:bytes idx:int = dht.Key

name - это тип ключа, для ADNL адресов используется слово address, а, например, для поиска нод шардчеина - nodes. Но типом ключа может быть любой массив байтов, зависит от искомого значения.

Заполнив эту схему, получим:

8fde67f6                                                           -- TL ID dht.key
516618cf6cbe9004f6883e742c9a2e3ca53ed02e3e36f4cef62a98ee1e449174   -- наш искомый ADNL адрес
07 61646472657373                                                  -- тип ключа, слово "address" в виде массива bytes
00000000                                                           -- индекс 0 т.к ключ всего 1

Далее - получим айди ключа, sha256 хеш от сериализованных выше байтов. Это будет b30af0538916421b46df4ce580bf3a29316831e0c3323a7f156df0236c5b2f75

Теперь мы можем приступить к поиску. Для этого нам нужно выполнить запрос, имеющий схему:

dht.findValue key:int256 k:int = dht.ValueResult

key - это айди нашего DHT ключа, а k - это "ширина" поиска, чем она меньше тем точнее, но меньше потенциальных нод для опроса. Максимальное k для нод в тоне равно 10, мы можем использовать 6.

Заполним эту структуру, сериализуем и отправим запрос, используя схему adnl.message.query. Подробнее об этом можно узнать в другой статье.

В ответ мы можем получить:

  • dht.valueNotFound - если значение не найдено.
  • dht.valueFound - если значение найдено на этой ноде.
dht.valueNotFound

Если мы получили dht.valueNotFound, ответ будет содержать список нод, которые известны запрошенной нами ноде и находятся максимально близко запрошенному нами ключу из списка известных ей нод. В этом случае нам нужно подключиться и добавить полученные ноды к списку известных нам. После этого, из списка всех известных нам нод выбрать самую близкую, доступную и еще не опрошенную, и сделать так же же запрос к ней. И так пока мы не попробуем все ноды в выбранном нами диапозоне или пока мы не перестанем получать новые ноды.

Разберем поля ответа подробнее, используемые схемы:

adnl.address.udp ip:int port:int = adnl.Address;
adnl.addressList addrs:(vector adnl.Address) version:int reinit_date:int priority:int expire_at:int = adnl.AddressList;

dht.node id:PublicKey addr_list:adnl.addressList version:int signature:bytes = dht.Node;
dht.nodes nodes:(vector dht.node) = dht.Nodes;

dht.valueNotFound nodes:dht.nodes = dht.ValueResult;

dht.nodes -> nodes - список DHT нод (массив).

У каждой ноды есть id, который является ее публичным ключом, обычно pub.ed25519, используется, как ключ сервера для подключения к ноде по ADNL. Также, каждая нода имеет список адресов addr_list:adnl.addressList, версию и подпись.

Нам нужно обязательно проверять подпись каждой ноды, для этого читаем значение signature и обнуляем поле (делаем пустым массивом bytes). После - сериализуем TL структуру dht.node с обнуленной подписью и проверяем signature, которая была до обнуления. Проверяем на полученных сериализованых байтах, используя ключ из id. [Пример реализации]

Из списка addrs:(vector adnl.Address) берем адрес и пробуем установить ADNL UDP соединение, в качестве ключа сервера используем id, который является публичным ключом.

Чтобы узнать "расстояние" до этой ноды - нам нужно взять айди ключа от ключа из поля id и проверить расстояние операцией XOR от айди ключа ноды и искомого ключа. Если расстояние достаточно небольшое - мы можем делать тот же запрос на эту ноду. И так далее, пока не найдем значение или не будет больше новых нод.

dht.valueFound

Ответ будет содержать само значение, полную информацию по ключу и, опционально, подпись.

Разберем поля ответа подробнее, используемые схемы:

adnl.address.udp ip:int port:int = adnl.Address;
adnl.addressList addrs:(vector adnl.Address) version:int reinit_date:int priority:int expire_at:int = adnl.AddressList;

dht.key id:int256 name:bytes idx:int = dht.Key;

dht.updateRule.signature = dht.UpdateRule;
dht.updateRule.anybody = dht.UpdateRule;
dht.updateRule.overlayNodes = dht.UpdateRule;

dht.keyDescription key:dht.key id:PublicKey update_rule:dht.UpdateRule signature:bytes = dht.KeyDescription;

dht.value key:dht.keyDescription value:bytes ttl:int signature:bytes = dht.Value; 

dht.valueFound value:dht.Value = dht.ValueResult;

Для начала разберем key:dht.keyDescription, он представляет из себя полное описание ключа, сам ключ, информацию о том, кто и как может обновлять значение.

  • key:dht.key - ключ, должен полностью совпадать с тем, от чего мы брали айди ключа для поиска.
  • id:PublicKey - публичный ключ владельца записи.
  • update_rule:dht.UpdateRule - правило обновления записи
    • dht.updateRule.signature - обновлять запись может только владелец приватного ключа, signature как ключа, так и значения должна быть валидной
    • dht.updateRule.anybody - обновлять запись могут все, signature пуста и не проверяется
    • dht.updateRule.overlayNodes - обновлять ключ могут только ноды из одного оверлея (шарды воркчеина)
dht.updateRule.signature

После чтения описания ключа, мы действуем в зависимости от updateRule, для кейса с поиском ADNL адреса тип всегда - dht.updateRule.signature. Проверяем подпись ключа тем же способом, что и в прошлый раз, делаем подпись пустым массивом байтов, сериализуем и проверяем. После - повторяем то же самое для значения, т.е для всего объекта dht.value (подпись ключа при этом возвращаем на место).

[Пример реализации]

dht.updateRule.overlayNodes

Используется для ключей, содержащих информацию о других нодах-шардах воркчеина в сети, в значении всегда имеет TL структуру overlay.nodes. Подпись значения должна быть пустой.

overlay.node id:PublicKey overlay:int256 version:int signature:bytes = overlay.Node;
overlay.nodes nodes:(vector overlay.node) = overlay.Nodes;

Для проверки валидности мы должны проверить все nodes и для каждой проверить signature на соответствие ее id, сериализовав TL структуру:

overlay.node.toSign id:adnl.id.short overlay:int256 version:int = overlay.node.ToSign;

Как мы видим, id надо заменить на adnl.id.short, который является айди ключа id из оригинальной структуры. После сериализации - сверяем подпись с данными.

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

dht.updateRule.anybody

Подписей нет, обновлять может любой, но реального использования я не видел.

Использование значения

Когда все верифицировано и время жизни значения ttl:int не просрочено, мы можем начинать работать с самим значением, т.е value:bytes. Для ADNL адреса там внутри должна быть структура adnl.addressList. В ней будут ip адреса и порты серверов, соответствующих запрошенному ADNL адресу. В нашем случае там скорее всего будет 1 адрес RLDP-HTTP сервиса foundation.ton. В качестве ключа сервера мы будем использовать публичный ключ id:PublicKey из информации о ключе DHT.

После установки соединения - мы можем запрашивать страницы сайта, используя протокол RLDP. Задача DHT на этом этапе выполнена.

Поиск нод хранящих состояние блокчеина

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

Для того, чтобы получить данные, например, мастерчеина и его шарды, нам нужно заполнить TL структуру:

tonNode.shardPublicOverlayId workchain:int shard:long zero_state_file_hash:int256 = tonNode.ShardPublicOverlayId;

Где workchain в случае мастерчеина будет равен -1, его shard будет равен -922337203685477580, и zero state - хэш нулевого стейта чеина (file_hash), его, как и другие данные, можно взять в глобальном конфиге сети, в поле "validator"

"zero_state": {
  "workchain": -1,
  "shard": -9223372036854775808, 
  "seqno": 0,
  "root_hash": "F6OpKZKqvqeFp6CQmFomXNMfMj2EnaUSOXN+Mh+wVWk=",
  "file_hash": "XplPz01CXAps5qeSWUtxcyBfdAo5zVb1N979KLSKD24="
}

После того, как мы заполнили tonNode.shardPublicOverlayId, мы сериализуем его и получаем из него айди ключа, путем хеширования (как и всегда).

Полученный айди ключа нам нужно использовать, как name для заполнения структуры pub.overlay name:bytes = PublicKey, завернув его в bytes. Далее сериализуем его, и получаем айди ключа теперь уже из него.

Полученный айди будет ключом для использования в dht.findValue, а в качестве name будет слово nodes. Повторяем основной процесс из предыдущего раздела, все как и в прошлый раз, но updateRule будет dht.updateRule.overlayNodes.

После валидации - мы получим публичные ключи (id) нод имеющих информацию о нашем воркчеине и шарде. Чтобы получить ADNL адреса нод - нам нужно сделать из ключей айди (методом хеширования) и для каждого из ADNL адресов повторить процедуру описанную выше, как и с ADNL адресом домена foundation.ton.

В результате мы получим адреса нод, у которых, если захотим, сможем узнать адреса других нод этого чеина c помощью overlay.getRandomPeers. Также мы сможем получать от этих нод всю информацию о блоках.