DHT
DHT代表分布式哈希表,本质上是一个分布式的键值数据库,其中网络的每个成员都可以存储某些内容,例如,关于自己的信息。
TON中的DHT实现与IPFS中使用的Kademlia的实现本质上类似。任何网络成员都可以运行一个DHT节点,生成密钥并存储数据。为此,他需要生成一个随机ID并通知其他节点自己的存在。
为了确定在哪个节点上存储数据,使用了一种确定节点与密钥之间“距离”的算法。算法很简单:我们取节点的ID和密钥的ID,执行XOR操作。值越小,节点越近。任务是尽可能接近密钥的节点上存储密钥,以便其他网络参与者可以使用同样的算法,找到可以给出此密钥数据的节点。
通过密钥查找值
让我们看一个查找密钥的示例,连接到任何DHT节点并通过ADNL UDP建立连接。
例如,我们想找到托管foundation.ton站点的节点的地址和公钥。假设我们已经通过执行DNS合约的Get方法获得了该站点的ADNL地址。ADNL地址的十六进制表示为516618cf6cbe9004f6883e742c9a2e3ca53ed02e3e36f4cef62a98ee1e449174
。现在我们的目标是找到拥有此地址的节点的ip、端口和公钥。
为此,我们需要获取DHT密钥的ID,首先我们将填充DHT密钥模式:
dht.key id:int256 name:bytes idx:int = dht.Key
name
是密钥类型,对于ADNL地址使用“address”,例如,要搜索分片链节点 - 使用“nodes”。但密钥类型可以是任何字节数组,取决于您正在查找的值。
填写此模式,我们得到:
8fde67f6 -- TL ID dht.key
516618cf6cbe9004f6883e742c9a2e3ca53ed02e3e36f4cef62a98ee1e449174 -- our searched ADNL address
07 61646472657373 -- key type, the word "address" as an TL array of bytes
00000000 -- index 0 because there is only 1 key
接下来 - 从上面序列化的字节获取DHT密钥ID的sha256哈希。它将是b30af0538916421b46df4ce580bf3a29316831e0c3323a7f156df0236c5b2f75
现在我们可以开始搜索。为此,我们需要执行一个具有模式的查询:
dht.findValue key:int256 k:int = dht.ValueResult
key
是我们DHT密钥的id,k
是搜索的“宽度”,它越小,搜索越精确,但可查询的潜在节点越少。TON节点的最大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
的值并将该字段置零(我们使其成为空字节数组)。之后 - 我们序列化TL结构dht.node
并检查空签名。之后 - 我们使用id
字段中的公钥检查清空之前的signature
字段。[实现示例]
从列表addrs:(vector adnl.Address)
中,我们取地址并尝试建立ADNL UDP连接,作为服务器密钥我们使用id
,即公钥。
为了找出与该节点的“距离” - 我们需要从id
字段的密钥中取出key id并通过节点密钥id和所需密钥的XOR操作检查距离。如果距离足够小,我们可以对此节点发出相同的请求。依此类推,直到我们找到值或没有更多新节点。