2022-11-04 10:52:27 +01:00
|
|
|
|
when (NimMajor, NimMinor) < (1, 4):
|
|
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
|
else:
|
|
|
|
|
|
{.push raises: [].}
|
2021-03-26 10:49:51 +02:00
|
|
|
|
|
|
|
|
|
|
import
|
2023-01-23 21:24:46 +01:00
|
|
|
|
std/[tables, sequtils, sets, options, times, math],
|
|
|
|
|
|
chronos,
|
2021-06-09 16:37:08 +02:00
|
|
|
|
libp2p/builders,
|
2021-03-26 10:49:51 +02:00
|
|
|
|
libp2p/peerstore
|
|
|
|
|
|
|
2022-11-24 14:11:23 +01:00
|
|
|
|
import
|
|
|
|
|
|
../../utils/peers
|
|
|
|
|
|
|
2021-06-09 16:37:08 +02:00
|
|
|
|
export peerstore, builders
|
2021-03-26 10:49:51 +02:00
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
|
Connectedness* = enum
|
|
|
|
|
|
# NotConnected: default state for a new peer. No connection and no further information on connectedness.
|
|
|
|
|
|
NotConnected,
|
|
|
|
|
|
# CannotConnect: attempted to connect to peer, but failed.
|
|
|
|
|
|
CannotConnect,
|
|
|
|
|
|
# CanConnect: was recently connected to peer and disconnected gracefully.
|
|
|
|
|
|
CanConnect,
|
|
|
|
|
|
# Connected: actively connected to peer.
|
|
|
|
|
|
Connected
|
2022-11-24 14:11:23 +01:00
|
|
|
|
|
|
|
|
|
|
PeerOrigin* = enum
|
2022-11-29 17:35:25 +01:00
|
|
|
|
UnknownOrigin,
|
2022-11-24 14:11:23 +01:00
|
|
|
|
Discv5,
|
|
|
|
|
|
Static,
|
|
|
|
|
|
Dns
|
|
|
|
|
|
|
2023-01-23 21:24:46 +01:00
|
|
|
|
PeerDirection* = enum
|
2022-11-29 17:35:25 +01:00
|
|
|
|
UnknownDirection,
|
|
|
|
|
|
Inbound,
|
|
|
|
|
|
Outbound
|
|
|
|
|
|
|
2022-11-24 14:11:23 +01:00
|
|
|
|
# Keeps track of the Connectedness state of a peer
|
2022-06-01 11:49:41 +02:00
|
|
|
|
ConnectionBook* = ref object of PeerBook[Connectedness]
|
2021-03-26 10:49:51 +02:00
|
|
|
|
|
2023-01-23 21:24:46 +01:00
|
|
|
|
# Last failed connection attemp timestamp
|
|
|
|
|
|
LastFailedConnBook* = ref object of PeerBook[Moment]
|
|
|
|
|
|
|
|
|
|
|
|
# Failed connection attempts
|
|
|
|
|
|
NumberFailedConnBook* = ref object of PeerBook[int]
|
|
|
|
|
|
|
2022-11-24 14:11:23 +01:00
|
|
|
|
# Keeps track of when peers were disconnected in Unix timestamps
|
|
|
|
|
|
DisconnectBook* = ref object of PeerBook[int64]
|
|
|
|
|
|
|
|
|
|
|
|
# Keeps track of the origin of a peer
|
|
|
|
|
|
SourceBook* = ref object of PeerBook[PeerOrigin]
|
2021-04-21 11:36:56 +02:00
|
|
|
|
|
2022-11-29 17:35:25 +01:00
|
|
|
|
# Direction
|
2023-01-23 21:24:46 +01:00
|
|
|
|
DirectionBook* = ref object of PeerBook[PeerDirection]
|
2022-11-29 17:35:25 +01:00
|
|
|
|
|
2021-10-06 14:29:08 +02:00
|
|
|
|
StoredInfo* = object
|
2022-11-24 14:11:23 +01:00
|
|
|
|
# Taken from nim-libp2
|
|
|
|
|
|
peerId*: PeerId
|
2022-06-01 11:49:41 +02:00
|
|
|
|
addrs*: seq[MultiAddress]
|
|
|
|
|
|
protos*: seq[string]
|
2021-10-06 14:29:08 +02:00
|
|
|
|
publicKey*: PublicKey
|
2022-11-24 14:11:23 +01:00
|
|
|
|
agent*: string
|
|
|
|
|
|
protoVersion*: string
|
|
|
|
|
|
|
|
|
|
|
|
# Extended custom fields
|
|
|
|
|
|
connectedness*: Connectedness
|
|
|
|
|
|
disconnectTime*: int64
|
|
|
|
|
|
origin*: PeerOrigin
|
2023-01-23 21:24:46 +01:00
|
|
|
|
direction*: PeerDirection
|
|
|
|
|
|
lastFailedConn*: Moment
|
|
|
|
|
|
numberFailedConn*: int
|
2021-03-26 10:49:51 +02:00
|
|
|
|
|
2022-11-24 14:11:23 +01:00
|
|
|
|
##################
|
2021-06-15 10:55:47 +02:00
|
|
|
|
# Peer Store API #
|
|
|
|
|
|
##################
|
|
|
|
|
|
|
2023-01-23 21:24:46 +01:00
|
|
|
|
proc canBeConnected*(peerStore: PeerStore,
|
|
|
|
|
|
peerId: PeerId,
|
|
|
|
|
|
initialBackoffInSec: int,
|
|
|
|
|
|
backoffFactor: int): bool =
|
|
|
|
|
|
# Returns if we can try to connect to this peer, based on past failed attempts
|
|
|
|
|
|
# It uses an exponential backoff. Each connection attempt makes us
|
|
|
|
|
|
# wait more before trying again.
|
|
|
|
|
|
let failedAttempts = peerStore[NumberFailedConnBook][peerId]
|
|
|
|
|
|
|
|
|
|
|
|
# if it never errored, we can try to connect
|
|
|
|
|
|
if failedAttempts == 0:
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
# If it errored we wait an exponential backoff from last connection
|
|
|
|
|
|
# the more failed attemps, the greater the backoff since last attempt
|
|
|
|
|
|
let now = Moment.init(getTime().toUnix, Second)
|
|
|
|
|
|
let lastFailed = peerStore[LastFailedConnBook][peerId]
|
|
|
|
|
|
let backoff = chronos.seconds(initialBackoffInSec*(backoffFactor^(failedAttempts-1)))
|
|
|
|
|
|
if now >= (lastFailed + backoff):
|
|
|
|
|
|
return true
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
proc delete*(peerStore: PeerStore,
|
|
|
|
|
|
peerId: PeerId) =
|
|
|
|
|
|
# Delete all the information of a given peer.
|
|
|
|
|
|
peerStore.del(peerId)
|
|
|
|
|
|
|
2022-11-24 14:11:23 +01:00
|
|
|
|
proc get*(peerStore: PeerStore,
|
2021-06-15 10:55:47 +02:00
|
|
|
|
peerId: PeerID): StoredInfo =
|
|
|
|
|
|
## Get the stored information of a given peer.
|
|
|
|
|
|
StoredInfo(
|
2022-11-24 14:11:23 +01:00
|
|
|
|
# Taken from nim-libp2
|
2021-06-15 10:55:47 +02:00
|
|
|
|
peerId: peerId,
|
2022-11-24 14:11:23 +01:00
|
|
|
|
addrs: peerStore[AddressBook][peerId],
|
|
|
|
|
|
protos: peerStore[ProtoBook][peerId],
|
|
|
|
|
|
publicKey: peerStore[KeyBook][peerId],
|
|
|
|
|
|
agent: peerStore[AgentBook][peerId],
|
|
|
|
|
|
protoVersion: peerStore[ProtoVersionBook][peerId],
|
|
|
|
|
|
|
|
|
|
|
|
# Extended custom fields
|
|
|
|
|
|
connectedness: peerStore[ConnectionBook][peerId],
|
|
|
|
|
|
disconnectTime: peerStore[DisconnectBook][peerId],
|
|
|
|
|
|
origin: peerStore[SourceBook][peerId],
|
2022-11-29 17:35:25 +01:00
|
|
|
|
direction: peerStore[DirectionBook][peerId],
|
2023-01-23 21:24:46 +01:00
|
|
|
|
lastFailedConn: peerStore[LastFailedConnBook][peerId],
|
|
|
|
|
|
numberFailedConn: peerStore[NumberFailedConnBook][peerId]
|
2021-06-15 10:55:47 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
2022-11-29 17:35:25 +01:00
|
|
|
|
# TODO: Rename peers() to getPeersByProtocol()
|
2022-11-24 14:11:23 +01:00
|
|
|
|
proc peers*(peerStore: PeerStore): seq[StoredInfo] =
|
2021-06-15 10:55:47 +02:00
|
|
|
|
## Get all the stored information of every peer.
|
2022-11-24 14:11:23 +01:00
|
|
|
|
let allKeys = concat(toSeq(peerStore[AddressBook].book.keys()),
|
|
|
|
|
|
toSeq(peerStore[ProtoBook].book.keys()),
|
|
|
|
|
|
toSeq(peerStore[KeyBook].book.keys())).toHashSet()
|
2021-06-15 10:55:47 +02:00
|
|
|
|
|
|
|
|
|
|
return allKeys.mapIt(peerStore.get(it))
|
2022-11-24 14:11:23 +01:00
|
|
|
|
|
|
|
|
|
|
proc peers*(peerStore: PeerStore, proto: string): seq[StoredInfo] =
|
|
|
|
|
|
# Return the known info for all peers registered on the specified protocol
|
|
|
|
|
|
peerStore.peers.filterIt(it.protos.contains(proto))
|
|
|
|
|
|
|
|
|
|
|
|
proc peers*(peerStore: PeerStore, protocolMatcher: Matcher): seq[StoredInfo] =
|
|
|
|
|
|
# Return the known info for all peers matching the provided protocolMatcher
|
|
|
|
|
|
peerStore.peers.filterIt(it.protos.anyIt(protocolMatcher(it)))
|
|
|
|
|
|
|
|
|
|
|
|
proc toRemotePeerInfo*(storedInfo: StoredInfo): RemotePeerInfo =
|
|
|
|
|
|
RemotePeerInfo.init(peerId = storedInfo.peerId,
|
|
|
|
|
|
addrs = toSeq(storedInfo.addrs),
|
|
|
|
|
|
protocols = toSeq(storedInfo.protos))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc connectedness*(peerStore: PeerStore, peerId: PeerID): Connectedness =
|
|
|
|
|
|
# Return the connection state of the given, managed peer
|
|
|
|
|
|
# TODO: the PeerManager should keep and update local connectedness state for peers, redial on disconnect, etc.
|
|
|
|
|
|
# TODO: richer return than just bool, e.g. add enum "CanConnect", "CannotConnect", etc. based on recent connection attempts
|
|
|
|
|
|
return peerStore[ConnectionBook].book.getOrDefault(peerId, NotConnected)
|
|
|
|
|
|
|
2023-01-26 10:20:20 +01:00
|
|
|
|
proc isConnected*(peerStore: PeerStore, peerId: PeerID): bool =
|
|
|
|
|
|
# Returns `true` if the peer is connected
|
|
|
|
|
|
peerStore.connectedness(peerId) == Connected
|
|
|
|
|
|
|
2022-11-24 14:11:23 +01:00
|
|
|
|
proc hasPeer*(peerStore: PeerStore, peerId: PeerID, proto: string): bool =
|
|
|
|
|
|
# Returns `true` if peer is included in manager for the specified protocol
|
|
|
|
|
|
# TODO: What if peer does not exist in the peerStore?
|
|
|
|
|
|
peerStore.get(peerId).protos.contains(proto)
|
|
|
|
|
|
|
|
|
|
|
|
proc hasPeers*(peerStore: PeerStore, proto: string): bool =
|
|
|
|
|
|
# Returns `true` if the peerstore has any peer for the specified protocol
|
|
|
|
|
|
toSeq(peerStore[ProtoBook].book.values()).anyIt(it.anyIt(it == proto))
|
|
|
|
|
|
|
|
|
|
|
|
proc hasPeers*(peerStore: PeerStore, protocolMatcher: Matcher): bool =
|
|
|
|
|
|
# Returns `true` if the peerstore has any peer matching the protocolMatcher
|
|
|
|
|
|
toSeq(peerStore[ProtoBook].book.values()).anyIt(it.anyIt(protocolMatcher(it)))
|
|
|
|
|
|
|
2023-01-23 21:24:46 +01:00
|
|
|
|
proc getPeersByDirection*(peerStore: PeerStore, direction: PeerDirection): seq[StoredInfo] =
|
2023-01-18 15:17:56 +01:00
|
|
|
|
return peerStore.peers.filterIt(it.direction == direction)
|
|
|
|
|
|
|
|
|
|
|
|
proc getNotConnectedPeers*(peerStore: PeerStore): seq[StoredInfo] =
|
|
|
|
|
|
return peerStore.peers.filterIt(it.connectedness != Connected)
|
2023-01-26 10:20:20 +01:00
|
|
|
|
|
2023-02-09 16:59:29 +01:00
|
|
|
|
proc getConnectedPeers*(peerStore: PeerStore): seq[StoredInfo] =
|
|
|
|
|
|
return peerStore.peers.filterIt(it.connectedness == Connected)
|
|
|
|
|
|
|
2023-01-26 10:20:20 +01:00
|
|
|
|
proc getPeersByProtocol*(peerStore: PeerStore, proto: string): seq[StoredInfo] =
|
|
|
|
|
|
return peerStore.peers.filterIt(it.protos.contains(proto))
|