Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type updates #118

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 2 additions & 43 deletions internal/kadtest/ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package kadtest

import (
"crypto/sha256"
"net"

"github.com/plprobelab/go-kademlia/kad"
"github.com/plprobelab/go-kademlia/key"
Expand All @@ -20,8 +19,8 @@ var _ kad.NodeID[key.Key8] = (*ID[key.Key8])(nil)
// NewID returns a new Kademlia identifier that implements the NodeID interface.
// Instead of deriving the Kademlia key from a NodeID, this method directly takes
// the Kademlia key.
func NewID[K kad.Key[K]](k K) *ID[K] {
return &ID[K]{key: k}
func NewID[K kad.Key[K]](k K) ID[K] {
return ID[K]{key: k}
}

// Key returns the Kademlia key that is used by, e.g., the routing table
Expand Down Expand Up @@ -65,43 +64,3 @@ func (s StringID) Equal(other string) bool {
func (s StringID) String() string {
return string(s)
}

type Info[K kad.Key[K], A kad.Address[A]] struct {
id *ID[K]
addrs []A
}

var _ kad.NodeInfo[key.Key8, net.IP] = (*Info[key.Key8, net.IP])(nil)

func NewInfo[K kad.Key[K], A kad.Address[A]](id *ID[K], addrs []A) *Info[K, A] {
return &Info[K, A]{
id: id,
addrs: addrs,
}
}

func (a *Info[K, A]) AddAddr(addr A) {
a.addrs = append(a.addrs, addr)
}

func (a *Info[K, A]) RemoveAddr(addr A) {
writeIndex := 0
// remove all occurrences of addr
for _, ad := range a.addrs {
if !ad.Equal(addr) {
a.addrs[writeIndex] = ad
writeIndex++
}
}
a.addrs = a.addrs[:writeIndex]
}

func (a *Info[K, A]) ID() kad.NodeID[K] {
return a.id
}

func (a *Info[K, A]) Addresses() []A {
addresses := make([]A, len(a.addrs))
copy(addresses, a.addrs)
return addresses
}
49 changes: 21 additions & 28 deletions internal/kadtest/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,77 +5,70 @@ import (
"github.com/plprobelab/go-kademlia/key"
)

// StrAddr is a simple implementation of kad.Address that uses a string to represent the address.
type StrAddr string

var _ kad.Address[StrAddr] = StrAddr("")

func (a StrAddr) Equal(b StrAddr) bool { return a == b }

type Request[K kad.Key[K]] struct {
type Request[K kad.Key[K], N kad.NodeID[K]] struct {
target K
id string
}

func NewRequest[K kad.Key[K]](id string, target K) *Request[K] {
return &Request[K]{
func NewRequest[K kad.Key[K], N kad.NodeID[K]](id string, target K) *Request[K, N] {
return &Request[K, N]{
target: target,
id: id,
}
}

func (r *Request[K]) Target() K {
func (r *Request[K, N]) Target() K {
return r.target
}

func (r *Request[K]) ID() string {
func (r *Request[K, N]) ID() string {
return r.id
}

func (r *Request[K]) EmptyResponse() kad.Response[K, StrAddr] {
return &Response[K]{}
func (r *Request[K, N]) EmptyResponse() kad.Response[K, N] {
return &Response[K, N]{}
}

type Response[K kad.Key[K]] struct {
type Response[K kad.Key[K], N kad.NodeID[K]] struct {
id string
closer []kad.NodeInfo[K, StrAddr]
closer []N
}

func NewResponse[K kad.Key[K]](id string, closer []kad.NodeInfo[K, StrAddr]) *Response[K] {
return &Response[K]{
func NewResponse[K kad.Key[K], N kad.NodeID[K]](id string, closer []N) *Response[K, N] {
return &Response[K, N]{
id: id,
closer: closer,
}
}

func (r *Response[K]) ID() string {
func (r *Response[K, N]) ID() string {
return r.id
}

func (r *Response[K]) CloserNodes() []kad.NodeInfo[K, StrAddr] {
func (r *Response[K, N]) CloserNodes() []N {
return r.closer
}

type (
// Request8 is a Request message that uses key.Key8
Request8 = Request[key.Key8]
Request8 = Request[key.Key8, ID[key.Key8]]

// Response8 is a Response message that uses key.Key8
Response8 = Response[key.Key8]
Response8 = Response[key.Key8, ID[key.Key8]]

// Request8 is a Request message that uses key.Key256
Request256 = Request[key.Key256]
Request256 = Request[key.Key256, ID[key.Key256]]

// Response256 is a Response message that uses key.Key256
Response256 = Response[key.Key256]
Response256 = Response[key.Key256, ID[key.Key256]]
)

var (
_ kad.Request[key.Key8, StrAddr] = (*Request8)(nil)
_ kad.Response[key.Key8, StrAddr] = (*Response8)(nil)
_ kad.Request[key.Key8, ID[key.Key8]] = (*Request8)(nil)
_ kad.Response[key.Key8, ID[key.Key8]] = (*Response8)(nil)
)

var (
_ kad.Request[key.Key256, StrAddr] = (*Request256)(nil)
_ kad.Response[key.Key256, StrAddr] = (*Response256)(nil)
_ kad.Request[key.Key256, ID[key.Key256]] = (*Request256)(nil)
_ kad.Response[key.Key256, ID[key.Key256]] = (*Response256)(nil)
)
40 changes: 4 additions & 36 deletions kad/kad.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package kad

import (
"context"
)

// Key is the interface all Kademlia key types support.
//
// A Kademlia key is defined as a bit string of arbitrary size. In practice, different Kademlia implementations use
Expand Down Expand Up @@ -96,24 +92,6 @@ type NodeID[K Key[K]] interface {
String() string
}

// NodeInfo is a container type that combines node identification information
// and network addresses at which the node is reachable.
type NodeInfo[K Key[K], A Address[A]] interface {
// ID returns the node identifier.
ID() NodeID[K]

// Addresses returns the network addresses associated with the given node.
Addresses() []A
}

// Address is an interface that any type must implement that can be used
// to address a node in the DHT network. This can be an IP/Port combination
// or in the case of libp2p a Multiaddress.
type Address[T any] interface {
// Equal re
Equal(T) bool
}

// Equal checks the equality of two NodeIDs.
// TODO: move somewhere else.
func Equal[K Key[K]](this, that NodeID[K]) bool {
Expand All @@ -122,31 +100,21 @@ func Equal[K Key[K]](this, that NodeID[K]) bool {

type Message interface{}

type Request[K Key[K], A Address[A]] interface {
type Request[K Key[K], N NodeID[K]] interface {
Message

// Target returns the target key and true, or false if no target key has been specfied.
Target() K

// EmptyResponse returns an empty response struct for this request message
// TODO: this is a weird patter, let's try to remove this.
EmptyResponse() Response[K, A]
EmptyResponse() Response[K, N]
}

type Response[K Key[K], A Address[A]] interface {
type Response[K Key[K], N NodeID[K]] interface {
Message

CloserNodes() []NodeInfo[K, A]
}

type RoutingProtocol[K Key[K], N NodeID[K], A Address[A]] interface {
FindNode(ctx context.Context, to N, target K) (NodeInfo[K, A], []N, error)
Ping(ctx context.Context, to N) error
}

type RecordProtocol[K Key[K], N NodeID[K]] interface {
Get(ctx context.Context, to N, target K) ([]Record, []N, error)
Put(ctx context.Context, to N, record Record) error
CloserNodes() []N
Copy link
Contributor

@iand iand Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the part that seems like we would want to change back one day. A useful Kademlia library needs to support getting addresses for nodes in the network.

Here's an alternative.

Keep Request/Response, Address and NodeInfo.

Create a new type that just represents a set of nodes assumed to be close to a key:

type KeyNodes[K Key[K], N NodeID[K]] struct {
    Key K
    Nodes []N
} 

Use this new type in all the routing and query state machines instead of Request. Response/Request can still be used in endpoint and simplequery.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea: let's rename NodeID[K] to Node[K] and let the implementation of Node[K] carry additional information. go-kademlia doesn't need to know anything about it.

Copy link
Contributor

@iand iand Sep 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion from a private conversation.

Define

type Node[K kad.Key, P any] interface {
  Key() K
  Preimage() P
}

So a PeerNode in libp2p is a Node[key.Key256, peer.ID]

It might even be better as a struct:

type Node[K kad.Key, P any] struct {
  Key K
  Preimage P
}

}

type Record any
18 changes: 9 additions & 9 deletions network/endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,31 +33,31 @@ type RequestHandlerFn[K kad.Key[K]] func(context.Context, kad.NodeID[K],

// ResponseHandlerFn defines a function that deals with the response to a
// request previously sent to a remote peer.
type ResponseHandlerFn[K kad.Key[K], A kad.Address[A]] func(context.Context, kad.Response[K, A], error)
type ResponseHandlerFn[K kad.Key[K], N kad.NodeID[K]] func(context.Context, kad.Response[K, N], error)

// Endpoint defines how Kademlia nodes interacts with each other.
type Endpoint[K kad.Key[K], A kad.Address[A]] interface {
type Endpoint[K kad.Key[K], N kad.NodeID[K]] interface {
// MaybeAddToPeerstore adds the given address to the peerstore if it is
// valid and if it is not already there.
// TODO: consider returning a status of whether the nodeinfo is a new node or contains a new address
MaybeAddToPeerstore(context.Context, kad.NodeInfo[K, A], time.Duration) error
MaybeAddToPeerstore(context.Context, N, time.Duration) error

// SendRequestHandleResponse attempts to sends a request to the given peer and handles
// the response with the given handler.
// An error is returned if the endpoint is unable to initiate sending the message for
// any reason. The handler will not be called if an error is returned.
SendRequestHandleResponse(context.Context, address.ProtocolID, kad.NodeID[K],
kad.Message, kad.Message, time.Duration,
ResponseHandlerFn[K, A]) error
ResponseHandlerFn[K, N]) error

// NetworkAddress returns the network address of the given peer (if known).
NetworkAddress(kad.NodeID[K]) (kad.NodeInfo[K, A], error)
NetworkAddress(kad.NodeID[K]) (N, error)
}

// ServerEndpoint is a Kademlia endpoint that can handle requests from remote
// peers.
type ServerEndpoint[K kad.Key[K], A kad.Address[A]] interface {
Endpoint[K, A]
type ServerEndpoint[K kad.Key[K], N kad.NodeID[K]] interface {
Endpoint[K, N]
// AddRequestHandler registers a handler for a given protocol ID.
AddRequestHandler(address.ProtocolID, kad.Message, RequestHandlerFn[K]) error
// RemoveRequestHandler removes a handler for a given protocol ID.
Expand All @@ -66,8 +66,8 @@ type ServerEndpoint[K kad.Key[K], A kad.Address[A]] interface {

// NetworkedEndpoint is an endpoint keeping track of the connectedness with
// known remote peers.
type NetworkedEndpoint[K kad.Key[K], A kad.Address[A]] interface {
Endpoint[K, A]
type NetworkedEndpoint[K kad.Key[K], N kad.NodeID[K]] interface {
Endpoint[K, N]
// Connectedness returns the connectedness of the given peer.
Connectedness(kad.NodeID[K]) (Connectedness, error)
}
Expand Down
44 changes: 20 additions & 24 deletions query/iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,45 @@ import (
)

// A NodeIter iterates nodes according to some strategy.
type NodeIter[K kad.Key[K]] interface {
type NodeIter[K kad.Key[K], N kad.NodeID[K]] interface {
// Add adds node information to the iterator
Add(*NodeStatus[K])
Add(*NodeStatus[K, N])

// Find returns the node information corresponding to the given Kademlia key
Find(K) (*NodeStatus[K], bool)
Find(K) (*NodeStatus[K, N], bool)

// Each applies fn to each entry in the iterator in order. Each stops and returns true if fn returns true.
// Otherwise Each returns false when there are no further entries.
Each(ctx context.Context, fn func(context.Context, *NodeStatus[K]) bool) bool
Each(ctx context.Context, fn func(context.Context, *NodeStatus[K, N]) bool) bool
}

// A ClosestNodesIter iterates nodes in order of ascending distance from a key.
type ClosestNodesIter[K kad.Key[K]] struct {
type ClosestNodesIter[K kad.Key[K], N kad.NodeID[K]] struct {
// target is the key whose distance to a node determines the position of that node in the iterator.
target K

// nodelist holds the nodes discovered so far, ordered by increasing distance from the target.
nodes *trie.Trie[K, *NodeStatus[K]]
nodes *trie.Trie[K, *NodeStatus[K, N]]
}

var _ NodeIter[key.Key8] = (*ClosestNodesIter[key.Key8])(nil)

// NewClosestNodesIter creates a new ClosestNodesIter
func NewClosestNodesIter[K kad.Key[K]](target K) *ClosestNodesIter[K] {
return &ClosestNodesIter[K]{
func NewClosestNodesIter[K kad.Key[K], N kad.NodeID[K]](target K) *ClosestNodesIter[K, N] {
return &ClosestNodesIter[K, N]{
target: target,
nodes: trie.New[K, *NodeStatus[K]](),
nodes: trie.New[K, *NodeStatus[K, N]](),
}
}

func (iter *ClosestNodesIter[K]) Add(ni *NodeStatus[K]) {
func (iter *ClosestNodesIter[K, N]) Add(ni *NodeStatus[K, N]) {
iter.nodes.Add(ni.NodeID.Key(), ni)
}

func (iter *ClosestNodesIter[K]) Find(k K) (*NodeStatus[K], bool) {
func (iter *ClosestNodesIter[K, N]) Find(k K) (*NodeStatus[K, N], bool) {
found, ni := trie.Find(iter.nodes, k)
return ni, found
}

func (iter *ClosestNodesIter[K]) Each(ctx context.Context, fn func(context.Context, *NodeStatus[K]) bool) bool {
func (iter *ClosestNodesIter[K, N]) Each(ctx context.Context, fn func(context.Context, *NodeStatus[K, N]) bool) bool {
// get all the nodes in order of distance from the target
// TODO: turn this into a walk or iterator on trie.Trie
entries := trie.Closest(iter.nodes, iter.target, iter.nodes.Size())
Expand All @@ -63,27 +61,25 @@ func (iter *ClosestNodesIter[K]) Each(ctx context.Context, fn func(context.Conte
}

// A SequentialIter iterates nodes in the order they were added to the iterator.
type SequentialIter[K kad.Key[K]] struct {
type SequentialIter[K kad.Key[K], N kad.NodeID[K]] struct {
// nodelist holds the nodes discovered so far, ordered by increasing distance from the target.
nodes []*NodeStatus[K]
nodes []*NodeStatus[K, N]
}

var _ NodeIter[key.Key8] = (*SequentialIter[key.Key8])(nil)

// NewSequentialIter creates a new SequentialIter
func NewSequentialIter[K kad.Key[K]]() *SequentialIter[K] {
return &SequentialIter[K]{
nodes: make([]*NodeStatus[K], 0),
func NewSequentialIter[K kad.Key[K], N kad.NodeID[K]]() *SequentialIter[K, N] {
return &SequentialIter[K, N]{
nodes: make([]*NodeStatus[K, N], 0),
}
}

func (iter *SequentialIter[K]) Add(ni *NodeStatus[K]) {
func (iter *SequentialIter[K, N]) Add(ni *NodeStatus[K, N]) {
iter.nodes = append(iter.nodes, ni)
}

// Find returns the node information corresponding to the given Kademlia key. It uses a linear
// search which makes it unsuitable for large numbers of entries.
func (iter *SequentialIter[K]) Find(k K) (*NodeStatus[K], bool) {
func (iter *SequentialIter[K, N]) Find(k K) (*NodeStatus[K, N], bool) {
for i := range iter.nodes {
if key.Equal(k, iter.nodes[i].NodeID.Key()) {
return iter.nodes[i], true
Expand All @@ -93,7 +89,7 @@ func (iter *SequentialIter[K]) Find(k K) (*NodeStatus[K], bool) {
return nil, false
}

func (iter *SequentialIter[K]) Each(ctx context.Context, fn func(context.Context, *NodeStatus[K]) bool) bool {
func (iter *SequentialIter[K, N]) Each(ctx context.Context, fn func(context.Context, *NodeStatus[K, N]) bool) bool {
for _, ns := range iter.nodes {
if fn(ctx, ns) {
return true
Expand Down
Loading