nimbus-eth1/fluffy/network/state/state_distance.nim

80 lines
2.9 KiB
Nim

# Nimbus
# Copyright (c) 2021 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
eth/p2p/discoveryv5/routing_table,
stint
const MID* = u256(2).pow(u256(255))
const MAX* = high(Uint256)
# Custom distance function described in: https://notes.ethereum.org/h58LZcqqRRuarxx4etOnGQ#Storage-Layout
# The implementation looks different than in spec, due to the fact that in practice
# we are operating on unsigned 256bit integers instead of signed big ints.
# Thanks to this we do not need to use:
# - modulo operations
# - abs operation
# and the results are eqivalent to function described in spec.
#
# The way it works is as follows. Let say we have integers modulo 8:
# [0, 1, 2, 3, 4, 5, 6, 7]
# and we want to calculate minimal distance between 0 and 5.
# Raw difference is: 5 - 0 = 5, which is larger than mid point which is equal to 4.
# From this we know that the shorter distance is the one wraping around 0, which
# is equal to 3
func stateDistance*(node_id: UInt256, content_id: UInt256): UInt256 =
let rawDiff =
if node_id > content_id:
node_id - content_id
else:
content_id - node_id
if rawDiff > MID:
# If rawDiff is larger than mid this means that distance between node_id and
# content_id is smaller when going from max side.
MAX - rawDiff + UInt256.one
else:
rawDiff
# TODO we do not have Uint256 log2 implementation. It would be nice to implement
# it in stint library in some more performant way. This version has O(n) complexity.
func log2DistanceImpl(value: UInt256): uint16 =
# Logarithm is not defined for zero values. Implementation in stew for builtin
# types return -1 in that case, but here it is just internal function so just make sure
# 0 is never provided.
doAssert(not value.isZero())
if value == UInt256.one:
return 0'u16
var comp = value
var ret = 0'u16
while (comp > 1):
comp = comp shr 1
ret = ret + 1
return ret
func stateIdAtDistance*(id: UInt256, dist: uint16): UInt256 =
# TODO With current distance function there are always two ids at given distance
# so we might as well do: id - u256(dist), maybe it is worth discussing if every client
# should use the same id in this case.
id + u256(2).pow(dist)
func stateLogDistance*(a, b: UInt256): uint16 =
let distance = stateDistance(a, b)
if distance.isZero():
return 0
else:
return log2DistanceImpl(distance)
const stateDistanceCalculator* =
DistanceCalculator(
calculateDistance: stateDistance,
calculateLogDistance: stateLogDistance,
calculateIdAtDistance: stateIdAtDistance
)