Skip to content

Commit

Permalink
Merge unstable (#518)
Browse files Browse the repository at this point in the history
* Address Book POC implementation (#499)

* Address Book POC implementation

* Feat/peerstore impl (#505)

Co-authored-by: Hanno Cornelius <68783915+jm-clius@users.noreply.github.com>
  • Loading branch information
dryajov and jm-clius authored Feb 8, 2021
1 parent 4dea23c commit 2658181
Show file tree
Hide file tree
Showing 2 changed files with 393 additions and 0 deletions.
170 changes: 170 additions & 0 deletions libp2p/peerstore.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
## Nim-LibP2P
## Copyright (c) 2021 Status Research & Development GmbH
## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.

{.push raises: [Defect].}

import
std/[tables, sets, sequtils],
./crypto/crypto,
./peerid,
./multiaddress

type
#################
# Handler types #
#################

PeerBookChangeHandler*[T] = proc(peerId: PeerID, entry: T)

AddrChangeHandler* = PeerBookChangeHandler[HashSet[MultiAddress]]
ProtoChangeHandler* = PeerBookChangeHandler[HashSet[string]]
KeyChangeHandler* = PeerBookChangeHandler[PublicKey]

#########
# Books #
#########

# Each book contains a book (map) and event handler(s)
PeerBook*[T] = object of RootObj
book*: Table[PeerID, T]
changeHandlers: seq[PeerBookChangeHandler[T]]

AddressBook* = object of PeerBook[HashSet[MultiAddress]]
ProtoBook* = object of PeerBook[HashSet[string]]
KeyBook* = object of PeerBook[PublicKey]

####################
# Peer store types #
####################

PeerStore* = ref object of RootObj
addressBook*: AddressBook
protoBook*: ProtoBook
keyBook*: KeyBook

StoredInfo* = object
# Collates stored info about a peer
peerId*: PeerID
addrs*: HashSet[MultiAddress]
protos*: HashSet[string]
publicKey*: PublicKey

## Constructs a new PeerStore with metadata of type M
proc new*(T: type PeerStore): PeerStore =
var p: PeerStore
new(p)
return p

#########################
# Generic Peer Book API #
#########################

proc get*[T](peerBook: PeerBook[T],
peerId: PeerID): T =
## Get all the known metadata of a provided peer.

peerBook.book.getOrDefault(peerId)

proc set*[T](peerBook: var PeerBook[T],
peerId: PeerID,
entry: T) =
## Set metadata for a given peerId. This will replace any
## previously stored metadata.

peerBook.book[peerId] = entry

# Notify clients
for handler in peerBook.changeHandlers:
handler(peerId, peerBook.get(peerId))

proc delete*[T](peerBook: var PeerBook[T],
peerId: PeerID): bool =
## Delete the provided peer from the book.

if not peerBook.book.hasKey(peerId):
return false
else:
peerBook.book.del(peerId)
return true

####################
# Address Book API #
####################

proc add*(addressBook: var AddressBook,
peerId: PeerID,
multiaddr: MultiAddress) =
## Add known multiaddr of a given peer. If the peer is not known,
## it will be set with the provided multiaddr.

addressBook.book.mgetOrPut(peerId,
initHashSet[MultiAddress]()).incl(multiaddr)

# Notify clients
for handler in addressBook.changeHandlers:
handler(peerId, addressBook.get(peerId))

#####################
# Protocol Book API #
#####################

proc add*(protoBook: var ProtoBook,
peerId: PeerID,
protocol: string) =
## Adds known protocol codec for a given peer. If the peer is not known,
## it will be set with the provided protocol.

protoBook.book.mgetOrPut(peerId,
initHashSet[string]()).incl(protocol)

# Notify clients
for handler in protoBook.changeHandlers:
handler(peerId, protoBook.get(peerId))

##################
# Peer Store API #
##################

proc addHandlers*(peerStore: PeerStore,
addrChangeHandler: AddrChangeHandler,
protoChangeHandler: ProtoChangeHandler,
keyChangeHandler: KeyChangeHandler) =
## Register event handlers to notify clients of changes in the peer store

peerStore.addressBook.changeHandlers.add(addrChangeHandler)
peerStore.protoBook.changeHandlers.add(protoChangeHandler)
peerStore.keyBook.changeHandlers.add(keyChangeHandler)

proc delete*(peerStore: PeerStore,
peerId: PeerID): bool =
## Delete the provided peer from every book.

peerStore.addressBook.delete(peerId) and
peerStore.protoBook.delete(peerId) and
peerStore.keyBook.delete(peerId)

proc get*(peerStore: PeerStore,
peerId: PeerID): StoredInfo =
## Get the stored information of a given peer.

StoredInfo(
peerId: peerId,
addrs: peerStore.addressBook.get(peerId),
protos: peerStore.protoBook.get(peerId),
publicKey: peerStore.keyBook.get(peerId)
)

proc peers*(peerStore: PeerStore): seq[StoredInfo] =
## Get all the stored information of every peer.

let allKeys = concat(toSeq(keys(peerStore.addressBook.book)),
toSeq(keys(peerStore.protoBook.book)),
toSeq(keys(peerStore.keyBook.book))).toHashSet()

return allKeys.mapIt(peerStore.get(it))
223 changes: 223 additions & 0 deletions tests/testpeerstore.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import
std/[unittest, tables, sequtils, sets],
../libp2p/crypto/crypto,
../libp2p/multiaddress,
../libp2p/peerid,
../libp2p/peerstore,
./helpers

suite "PeerStore":
# Testvars
let
# Peer 1
keyPair1 = KeyPair.random(ECDSA, rng[]).get()
peerId1 = PeerID.init(keyPair1.secKey).get()
multiaddrStr1 = "/ip4/127.0.0.1/udp/1234/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"
multiaddr1 = MultiAddress.init(multiaddrStr1).get()
testcodec1 = "/nim/libp2p/test/0.0.1-beta1"
# Peer 2
keyPair2 = KeyPair.random(ECDSA, rng[]).get()
peerId2 = PeerID.init(keyPair2.secKey).get()
multiaddrStr2 = "/ip4/0.0.0.0/tcp/1234/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"
multiaddr2 = MultiAddress.init(multiaddrStr2).get()
testcodec2 = "/nim/libp2p/test/0.0.2-beta1"

test "PeerStore API":
# Set up peer store
var
peerStore = PeerStore.new()

peerStore.addressBook.add(peerId1, multiaddr1)
peerStore.addressBook.add(peerId2, multiaddr2)
peerStore.protoBook.add(peerId1, testcodec1)
peerStore.protoBook.add(peerId2, testcodec2)
peerStore.keyBook.set(peerId1, keyPair1.pubKey)
peerStore.keyBook.set(peerId2, keyPair2.pubKey)

# Test PeerStore::get
let
peer1Stored = peerStore.get(peerId1)
peer2Stored = peerStore.get(peerId2)
check:
peer1Stored.peerId == peerId1
peer1Stored.addrs == toHashSet([multiaddr1])
peer1Stored.protos == toHashSet([testcodec1])
peer1Stored.publicKey == keyPair1.pubkey
peer2Stored.peerId == peerId2
peer2Stored.addrs == toHashSet([multiaddr2])
peer2Stored.protos == toHashSet([testcodec2])
peer2Stored.publicKey == keyPair2.pubkey

# Test PeerStore::peers
let peers = peerStore.peers()
check:
peers.len == 2
peers.anyIt(it.peerId == peerId1 and
it.addrs == toHashSet([multiaddr1]) and
it.protos == toHashSet([testcodec1]) and
it.publicKey == keyPair1.pubkey)
peers.anyIt(it.peerId == peerId2 and
it.addrs == toHashSet([multiaddr2]) and
it.protos == toHashSet([testcodec2]) and
it.publicKey == keyPair2.pubkey)

# Test PeerStore::delete
check:
# Delete existing peerId
peerStore.delete(peerId1) == true
peerStore.peers().anyIt(it.peerId == peerId1) == false

# Now try and delete it again
peerStore.delete(peerId1) == false

test "PeerStore listeners":
# Set up peer store with listener
var
peerStore = PeerStore.new()
addrChanged = false
protoChanged = false
keyChanged = false

proc addrChange(peerId: PeerID, addrs: HashSet[MultiAddress]) =
addrChanged = true

proc protoChange(peerId: PeerID, protos: HashSet[string]) =
protoChanged = true

proc keyChange(peerId: PeerID, publicKey: PublicKey) =
keyChanged = true

peerStore.addHandlers(addrChangeHandler = addrChange,
protoChangeHandler = protoChange,
keyChangeHandler = keyChange)

# Test listener triggered on adding multiaddr
peerStore.addressBook.add(peerId1, multiaddr1)
check:
addrChanged == true

# Test listener triggered on setting addresses
addrChanged = false
peerStore.addressBook.set(peerId2,
toHashSet([multiaddr1, multiaddr2]))
check:
addrChanged == true

# Test listener triggered on adding proto
peerStore.protoBook.add(peerId1, testcodec1)
check:
protoChanged == true

# Test listener triggered on setting protos
protoChanged = false
peerStore.protoBook.set(peerId2,
toHashSet([testcodec1, testcodec2]))
check:
protoChanged == true

# Test listener triggered on setting public key
peerStore.keyBook.set(peerId1,
keyPair1.pubkey)
check:
keyChanged == true

# Test listener triggered on changing public key
keyChanged = false
peerStore.keyBook.set(peerId1,
keyPair2.pubkey)
check:
keyChanged == true

test "AddressBook API":
# Set up address book
var
addressBook = PeerStore.new().addressBook

# Test AddressBook::add
addressBook.add(peerId1, multiaddr1)

check:
toSeq(keys(addressBook.book))[0] == peerId1
toSeq(values(addressBook.book))[0] == toHashSet([multiaddr1])

# Test AddressBook::get
check:
addressBook.get(peerId1) == toHashSet([multiaddr1])

# Test AddressBook::delete
check:
# Try to delete peerId that doesn't exist
addressBook.delete(peerId2) == false

# Delete existing peerId
addressBook.book.len == 1 # sanity
addressBook.delete(peerId1) == true
addressBook.book.len == 0

# Test AddressBook::set
# Set peerId2 with multiple multiaddrs
addressBook.set(peerId2,
toHashSet([multiaddr1, multiaddr2]))
check:
toSeq(keys(addressBook.book))[0] == peerId2
toSeq(values(addressBook.book))[0] == toHashSet([multiaddr1, multiaddr2])

test "ProtoBook API":
# Set up protocol book
var
protoBook = PeerStore.new().protoBook

# Test ProtoBook::add
protoBook.add(peerId1, testcodec1)

check:
toSeq(keys(protoBook.book))[0] == peerId1
toSeq(values(protoBook.book))[0] == toHashSet([testcodec1])

# Test ProtoBook::get
check:
protoBook.get(peerId1) == toHashSet([testcodec1])

# Test ProtoBook::delete
check:
# Try to delete peerId that doesn't exist
protoBook.delete(peerId2) == false

# Delete existing peerId
protoBook.book.len == 1 # sanity
protoBook.delete(peerId1) == true
protoBook.book.len == 0

# Test ProtoBook::set
# Set peerId2 with multiple protocols
protoBook.set(peerId2,
toHashSet([testcodec1, testcodec2]))
check:
toSeq(keys(protoBook.book))[0] == peerId2
toSeq(values(protoBook.book))[0] == toHashSet([testcodec1, testcodec2])

test "KeyBook API":
# Set up key book
var
keyBook = PeerStore.new().keyBook

# Test KeyBook::set
keyBook.set(peerId1,
keyPair1.pubkey)
check:
toSeq(keys(keyBook.book))[0] == peerId1
toSeq(values(keyBook.book))[0] == keyPair1.pubkey

# Test KeyBook::get
check:
keyBook.get(peerId1) == keyPair1.pubkey

# Test KeyBook::delete
check:
# Try to delete peerId that doesn't exist
keyBook.delete(peerId2) == false

# Delete existing peerId
keyBook.book.len == 1 # sanity
keyBook.delete(peerId1) == true
keyBook.book.len == 0

0 comments on commit 2658181

Please sign in to comment.