Skip to content

Commit

Permalink
dht
Browse files Browse the repository at this point in the history
  • Loading branch information
johankristianss committed Feb 23, 2024
1 parent e702125 commit c868dfd
Show file tree
Hide file tree
Showing 23 changed files with 953 additions and 0 deletions.
52 changes: 52 additions & 0 deletions pkg/dht/bucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package dht

import (
"container/list"
)

type bucket struct {
list *list.List
}

func newBucket() *bucket {
bucket := &bucket{}
bucket.list = list.New()
return bucket
}

func (bucket *bucket) AddContact(contact *Contact) {
var element *list.Element
for e := bucket.list.Front(); e != nil; e = e.Next() {
nodeID := e.Value.(*Contact).ID

if (contact).ID.Equals(nodeID) {
element = e
}
}

if element == nil {
if bucket.list.Len() < bucketSize {
bucket.list.PushFront(contact)
} else {
// Check if the last contact is alive
}
} else {
bucket.list.MoveToFront(element)
}
}

func (bucket *bucket) GetContactAndCalcDistance(target *KademliaID) []*Contact {
var contacts []*Contact

for elt := bucket.list.Front(); elt != nil; elt = elt.Next() {
contact := elt.Value.(*Contact)
contact.CalcDistance(target)
contacts = append(contacts, contact)
}

return contacts
}

func (bucket *bucket) Len() int {
return bucket.list.Len()
}
56 changes: 56 additions & 0 deletions pkg/dht/contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package dht

import (
"fmt"
"sort"
)

type Contact struct {
ID *KademliaID `json:"kademliaid"`
Addr string `json:"address"`
distance *KademliaID
}

func CreateContact(id *KademliaID, address string) *Contact {
return &Contact{id, address, nil}
}

func (contact *Contact) CalcDistance(target *KademliaID) {
contact.distance = contact.ID.CalcDistance(target)
}

func (contact *Contact) Less(otherContact *Contact) bool {
return contact.distance.Less(otherContact.distance)
}

func (contact *Contact) String() string {
return fmt.Sprintf(`contact("%s", "%s")`, contact.ID, contact.Addr)
}

type ContactCandidates struct {
contacts []*Contact
}

func (candidates *ContactCandidates) Append(contacts []*Contact) {
candidates.contacts = append(candidates.contacts, contacts...)
}

func (candidates *ContactCandidates) GetContacts(count int) []*Contact {
return candidates.contacts[:count]
}

func (candidates *ContactCandidates) Sort() {
sort.Sort(candidates)
}

func (candidates *ContactCandidates) Len() int {
return len(candidates.contacts)
}

func (candidates *ContactCandidates) Swap(i, j int) {
candidates.contacts[i], candidates.contacts[j] = candidates.contacts[j], candidates.contacts[i]
}

func (candidates *ContactCandidates) Less(i, j int) bool {
return candidates.contacts[i].Less(candidates.contacts[j])
}
90 changes: 90 additions & 0 deletions pkg/dht/examples/a/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"context"
"fmt"
"log"

"github.com/libp2p/go-libp2p"
dht "github.com/libp2p/go-libp2p-kad-dht"
record "github.com/libp2p/go-libp2p-record"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
)

// chatServiceValidator validates that the value associated with a key is a valid peer ID.
type chatServiceValidator struct{}

// Validate conforms to the record.Validator interface.
// It checks if the given value is a valid peer ID for the chat service keys.
func (v chatServiceValidator) Validate(key string, value []byte) error {
_, err := peer.Decode(string(value))
if err != nil {
return fmt.Errorf("invalid peer ID for key '%s'", key)
}
return nil
}

// Select conforms to the record.Validator interface.
// For this example, it just selects the first value, but you could implement more complex logic.
func (v chatServiceValidator) Select(key string, values [][]byte) (int, error) {
return 0, nil // Simplest implementation: always select the first value.
}

func setupHostAndDHT(ctx context.Context) (host.Host, *dht.IpfsDHT, error) {
// Create a new libp2p host
h, err := libp2p.New()
if err != nil {
return nil, nil, err
}

nsValidator := record.NamespacedValidator{
//"pk": record.PublicKeyValidator{}, // Validator for public keys
//"ipns": ipns.Validator{},
"chat": chatServiceValidator{}, // Use your namespace here
}

// Create a DHT instance with the custom validator
d, err := dht.New(ctx, h, dht.Validator(nsValidator))
// d, err := dht.New(ctx, h, dht.Validator(chatServiceValidator{}))
if err != nil {
h.Close()
return nil, nil, err
}

// Bootstrap the DHT (in a real-world scenario, you would connect to known peers)
if err := d.Bootstrap(ctx); err != nil {
h.Close()
return nil, nil, err
}

// Create a service key
serviceKey := fmt.Sprintf("/myapp/chat/%s", "serviceName")

// Advertise the service in the DHT
if err := d.PutValue(ctx, serviceKey, []byte(h.ID())); err != nil {
fmt.Println("err", err)
}

providers, err := d.GetValue(ctx, serviceKey)
if err != nil {
fmt.Println("err", err)
}

fmt.Println("providers", providers)

return h, d, nil
}

func main() {
ctx := context.Background()
h, _, err := setupHostAndDHT(ctx)
if err != nil {
log.Fatalf("Failed to set up host and DHT: %s", err)
}
defer h.Close()

// Your service advertisement and discovery logic here...

<-ctx.Done()
}
52 changes: 52 additions & 0 deletions pkg/dht/examples/get/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"context"
"fmt"
"log"

"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/peer"
dht "github.com/libp2p/go-libp2p-kad-dht"
)

func main() {
ctx := context.Background()

// Create a new libp2p Host that listens on a random TCP port
h, err := libp2p.New()
if err != nil {
log.Fatalf("Failed to create host: %s", err)
}

// Set up a DHT for peer discovery
kadDHT, err := dht.New(ctx, h)
if err != nil {
log.Fatalf("Failed to create the DHT: %s", err)
}

// Bootstrap the DHT. In the real world, we would need to connect to bootstrap nodes here.
if err = kadDHT.Bootstrap(ctx); err != nil {
log.Fatalf("Failed to bootstrap the DHT: %s", err)
}

// Connect to the other libp2p instance using its multiaddress
// Replace <peerMultiAddr> with the actual multiaddress of the peer that stored the value
peerMultiAddr := "<peerMultiAddr>"
peerAddr, err := peer.AddrInfoFromP2pAddr(peerMultiAddr)
if err != nil {
log.Fatalf("Failed to parse peer multiaddr: %s", err)
}
if err := h.Connect(ctx, *peerAddr); err != nil {
log.Fatalf("Failed to connect to peer: %s", err)
}

// Fetch the value from the DHT
key := "myKey"
value, err := kadDHT.GetValue(ctx, key)
if err != nil {
log.Fatalf("Failed to get value from DHT: %s", err)
}

fmt.Printf("Fetched value from DHT: %s\n", string(value))
}
83 changes: 83 additions & 0 deletions pkg/dht/examples/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package main

import (
"context"
"fmt"
"log"
"time"
"unicode/utf8"

"github.com/libp2p/go-libp2p"
dht "github.com/libp2p/go-libp2p-kad-dht"
"github.com/libp2p/go-libp2p/core/record"
)

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Create a new libp2p Host
h, err := libp2p.New()
if err != nil {
log.Fatalf("Failed to create libp2p host: %s", err)
}
defer h.Close()

// Create a custom validator that includes namespaced validation logic
customValidator := &record.ValidChecker{
Validator: map[string]record.Validator{
"utf8": &utf8Validator{},
},
Selector: map[string]record.Selector{
"utf8": &record.CurrentSelector{},
},
}

// Create a new DHT instance with the custom validator
d, err := dht.New(ctx, h, dht.Validator(customValidator))
if err != nil {
log.Fatalf("Failed to create DHT with custom validator: %s", err)
}

// Bootstrap the DHT
if err := d.Bootstrap(ctx); err != nil {
log.Fatalf("Failed to bootstrap DHT: %s", err)
}

// Define the key and value to store in the DHT
key := "/utf8/validKey"
value := "Hello, world!"

// Store the value in the DHT
if err := storeValue(ctx, d, key, value); err != nil {
log.Fatalf("Failed to store value: %s", err)
}

// Retrieve the value from the DHT
retrievedValue, err := d.GetValue(ctx, key)
if err != nil {
log.Fatalf("Failed to retrieve value: %s", err)
}
fmt.Printf("Retrieved value: %s\n", string(retrievedValue))
}

// utf8Validator is a custom validator that checks for valid UTF-8 encoding.
type utf8Validator struct{}

func (v *utf8Validator) Validate(key string, value []byte) error {
if !utf8.Valid(value) {
return fmt.Errorf("invalid UTF-8 value for key %s", key)
}
return nil
}

func (v *utf8Validator) Select(key string, values [][]byte) (int, error) {
return 0, nil // Always select the first value
}

// storeValue stores a key-value pair in the DHT.
func storeValue(ctx context.Context, d *dht.IpfsDHT, key, value string) error {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
return d.PutValue(ctx, key, []byte(value))
}
60 changes: 60 additions & 0 deletions pkg/dht/examples/put/put.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"log"

"github.com/libp2p/go-libp2p"
dht "github.com/libp2p/go-libp2p-kad-dht"
)

func formatKey(originalKey string) string {
hash := sha256.Sum256([]byte(originalKey))
return hex.EncodeToString(hash[:])
}

func main() {
ctx := context.Background()

// Create a new libp2p Host that listens on a random TCP port
h, err := libp2p.New()
if err != nil {
log.Fatalf("Failed to create h: %s", err)
}

// Set up a DHT for peer discovery
kadDHT, err := dht.New(ctx, h)
if err != nil {
log.Fatalf("Failed to create the DHT: %s", err)
}

// Bootstrap the DHT. In the real world, we would need to connect to bootstrap nodes here.
if err = kadDHT.Bootstrap(ctx); err != nil {
log.Fatalf("Failed to bootstrap the DHT: %s", err)
}

fmt.Println("This node's addresses:")
for _, addr := range h.Addrs() {
fmt.Printf("%s/p2p/%s\n", addr, h.ID())
}

// Use a hash function to convert the key to a suitable format for the DHT
key := "myKey"
//hashedKey := sha256.Sum256([]byte(key))
//keyString := fmt.Sprintf("/record/%x", hashedKey)
formattedKey := formatKey(key)

value := []byte("Hello World")
err = kadDHT.PutValue(ctx, formattedKey, value)
if err != nil {
log.Fatalf("Failed to put value in DHT: %s", err)
}

fmt.Println("Successfully stored value in DHT")

// Keep the host alive until the user interrupts the program
select {}
}
Loading

0 comments on commit c868dfd

Please sign in to comment.