Discovery set up
This commit is contained in:
336
infrastructure/dht.go
Normal file
336
infrastructure/dht.go
Normal file
@@ -0,0 +1,336 @@
|
||||
package infrastructure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"oc-peer/conf"
|
||||
"time"
|
||||
|
||||
oclib "cloud.o-forge.io/core/oc-lib"
|
||||
"cloud.o-forge.io/core/oc-lib/models/peer"
|
||||
"cloud.o-forge.io/core/oc-lib/models/utils"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
"github.com/google/uuid"
|
||||
"github.com/libp2p/go-libp2p"
|
||||
dht "github.com/libp2p/go-libp2p-kad-dht"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
)
|
||||
|
||||
type DHTRecord struct {
|
||||
Name string
|
||||
State int
|
||||
PeerID string
|
||||
PubKey []byte
|
||||
URL string
|
||||
Signature []byte
|
||||
ExpiryDate time.Time
|
||||
}
|
||||
|
||||
type DHTService struct {
|
||||
Key string
|
||||
Host host.Host
|
||||
DHT *dht.IpfsDHT
|
||||
}
|
||||
|
||||
var singletonService *DHTService
|
||||
|
||||
func GetDHTService() *DHTService {
|
||||
return singletonService
|
||||
}
|
||||
|
||||
func Init(ctx context.Context) (*DHTService, error) {
|
||||
service := &DHTService{}
|
||||
priv, err := loadKeyFromFile(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := libp2p.New(
|
||||
libp2p.Identity(priv),
|
||||
libp2p.ListenAddrStrings(
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", conf.GetConfig().DHTEndpointPort),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service.Host = h
|
||||
service.DHT, err = dht.New(ctx, h, dht.MaxRecordAge(24*time.Hour)) // every day DHT will purge expired data... if not used.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
singletonService = service
|
||||
for {
|
||||
if VerifyPubWithPriv() {
|
||||
o := oclib.GetConfLoader()
|
||||
if _, err := singletonService.ClaimName(context.Background(), o.GetStringDefault("NAME", "local"), o.GetStringDefault("HOSTNAME", "http://localhost")); err == nil {
|
||||
go func() {
|
||||
for {
|
||||
singletonService.RefreshName(context.Background())
|
||||
time.Sleep(59 * time.Minute)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return service, service.DHT.Bootstrap(ctx)
|
||||
}
|
||||
|
||||
// Create your peer.
|
||||
func (d *DHTService) ClaimName(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
endPoint string,
|
||||
) (*peer.Peer, error) {
|
||||
if endPoint == "" {
|
||||
return nil, errors.New("no endpoint found for peer" + name)
|
||||
}
|
||||
|
||||
pub := d.Host.Peerstore().PubKey(d.Host.ID())
|
||||
pubBytes, _ := pub.Raw()
|
||||
|
||||
now := time.Now()
|
||||
expiry := now.Add(1 * time.Hour)
|
||||
rec := DHTRecord{
|
||||
Name: name,
|
||||
PeerID: d.Host.ID().String(),
|
||||
PubKey: pubBytes,
|
||||
}
|
||||
|
||||
payload, _ := json.Marshal(rec)
|
||||
sig, _ := sign(d.Host.Peerstore().PrivKey(d.Host.ID()), payload)
|
||||
rec.Signature = sig
|
||||
|
||||
rec.URL = endPoint
|
||||
rec.State = peer.ONLINE.EnumIndex()
|
||||
rec.ExpiryDate = expiry
|
||||
|
||||
data, _ := json.Marshal(rec)
|
||||
|
||||
key := name
|
||||
|
||||
// retrieve your key name in standard
|
||||
existing, err := d.DHT.GetValue(ctx, key)
|
||||
if err == nil && existing != nil {
|
||||
var old DHTRecord
|
||||
_ = json.Unmarshal(existing, &old)
|
||||
|
||||
if old.PeerID != d.Host.ID().String() { // check if someone claims your name before
|
||||
return nil, errors.New("name already claimed by another peer")
|
||||
}
|
||||
if now.After(old.ExpiryDate) {
|
||||
payload, _ := json.Marshal(rec)
|
||||
d.DHT.PutValue(ctx, key, payload)
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.DHT.PutValue(ctx, key, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubStr := base64.StdEncoding.EncodeToString(pubBytes)
|
||||
d.Key = key
|
||||
access := peer.NewAccessor(&tools.APIRequest{Admin: true})
|
||||
p := &peer.Peer{
|
||||
AbstractObject: utils.AbstractObject{
|
||||
UUID: uuid.New().String(),
|
||||
Name: name,
|
||||
},
|
||||
State: peer.ONLINE,
|
||||
Relation: peer.SELF,
|
||||
PeerID: d.Host.ID().String(),
|
||||
PublicKey: pubStr,
|
||||
Url: endPoint,
|
||||
WalletAddress: "my-wallet",
|
||||
}
|
||||
if founded, _, err := access.Search(nil, fmt.Sprintf("%v", peer.SELF.EnumIndex()), false); err != nil || len(founded) == 0 {
|
||||
access.StoreOne(p)
|
||||
} else if f, _, err := access.LoadOne(founded[0].GetID()); err == nil {
|
||||
f.(*peer.Peer).Name = name
|
||||
f.(*peer.Peer).PeerID = d.Host.ID().String()
|
||||
f.(*peer.Peer).State = peer.ONLINE
|
||||
f.(*peer.Peer).Url = endPoint
|
||||
access.UpdateOne(f, f.GetID())
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (d *DHTService) treatPeer(ctx context.Context, key string, data []byte) (*peer.Peer, error) {
|
||||
var rec DHTRecord
|
||||
if err := json.Unmarshal(data, &rec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubKey, err := crypto.UnmarshalPublicKey(rec.PubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dht := DHTRecord{
|
||||
Name: rec.Name,
|
||||
PeerID: rec.PeerID,
|
||||
PubKey: rec.PubKey,
|
||||
}
|
||||
payload, _ := json.Marshal(dht)
|
||||
|
||||
if ok, _ := verify(pubKey, payload, rec.Signature); !ok {
|
||||
return nil, errors.New("invalid signature")
|
||||
}
|
||||
pubBytes, _ := pubKey.Raw()
|
||||
pubStr := base64.StdEncoding.EncodeToString(pubBytes)
|
||||
|
||||
rel := peer.NONE
|
||||
if d.Key == key {
|
||||
rel = peer.SELF
|
||||
}
|
||||
|
||||
p := &peer.Peer{
|
||||
AbstractObject: utils.AbstractObject{
|
||||
UUID: uuid.New().String(),
|
||||
Name: rec.Name,
|
||||
},
|
||||
State: peer.ONLINE,
|
||||
Relation: rel,
|
||||
PeerID: rec.PeerID,
|
||||
PublicKey: pubStr,
|
||||
Url: rec.URL,
|
||||
}
|
||||
access := peer.NewAccessor(&tools.APIRequest{Admin: true})
|
||||
if now.After(rec.ExpiryDate) {
|
||||
rec.State = peer.OFFLINE.EnumIndex()
|
||||
p.State = peer.OFFLINE
|
||||
payload, _ := json.Marshal(rec)
|
||||
d.DHT.PutValue(ctx, key, payload)
|
||||
if founded, _, err := access.Search(nil, p.Name, false); err != nil || len(founded) == 0 {
|
||||
access.StoreOne(p)
|
||||
} else if f, _, err := access.LoadOne(founded[0].GetID()); err == nil {
|
||||
f.(*peer.Peer).State = peer.OFFLINE
|
||||
access.UpdateOne(f, f.GetID())
|
||||
}
|
||||
return nil, errors.New("peer " + key + " is expired")
|
||||
}
|
||||
if rec.State == peer.OFFLINE.EnumIndex() {
|
||||
if founded, _, err := access.Search(nil, p.Name, false); err != nil || len(founded) == 0 {
|
||||
pp, _, err := access.StoreOne(p)
|
||||
if err == nil {
|
||||
return pp.(*peer.Peer), nil
|
||||
}
|
||||
} else {
|
||||
f, ok := peer.CheckPeerStatus(founded[0].GetID(), "")
|
||||
if ok {
|
||||
f.State = peer.ONLINE
|
||||
} else {
|
||||
f.State = peer.OFFLINE
|
||||
}
|
||||
pp, _, err := access.UpdateOne(f, f.GetID())
|
||||
if err == nil && ok {
|
||||
return pp.(*peer.Peer), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("peer " + key + " is offline")
|
||||
}
|
||||
|
||||
func (d *DHTService) GetPeer(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
) (*peer.Peer, error) {
|
||||
key := name
|
||||
data, err := d.DHT.GetValue(ctx, key)
|
||||
if err != nil {
|
||||
return nil, errors.New("no DHT peer not found")
|
||||
}
|
||||
return d.treatPeer(ctx, key, data)
|
||||
}
|
||||
|
||||
// Discover a specific Peer
|
||||
func (d *DHTService) DiscoverPeers(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
) ([]*peer.Peer, error) {
|
||||
peers := []*peer.Peer{}
|
||||
key := name
|
||||
datas, err := d.DHT.SearchValue(ctx, key)
|
||||
if err != nil {
|
||||
return nil, errors.New("no DHT peer not found")
|
||||
}
|
||||
for data := range datas {
|
||||
if p, err := d.treatPeer(ctx, key, data); err == nil {
|
||||
peers = append(peers, p)
|
||||
}
|
||||
}
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
func (d *DHTService) existsDHT(ctx context.Context) (*DHTRecord, error) {
|
||||
if d.Key == "" {
|
||||
return nil, errors.New("no self peer found")
|
||||
}
|
||||
data, err := d.DHT.GetValue(ctx, d.Key)
|
||||
if err != nil {
|
||||
return nil, errors.New("no DHT peer found")
|
||||
}
|
||||
|
||||
var rec DHTRecord
|
||||
if err := json.Unmarshal(data, &rec); err != nil {
|
||||
return &rec, err
|
||||
}
|
||||
|
||||
pubKey, err := crypto.UnmarshalPublicKey(rec.PubKey)
|
||||
if err != nil {
|
||||
return &rec, err
|
||||
}
|
||||
|
||||
dht := DHTRecord{
|
||||
Name: rec.Name,
|
||||
PeerID: rec.PeerID,
|
||||
PubKey: rec.PubKey,
|
||||
URL: rec.URL,
|
||||
}
|
||||
payload, _ := json.Marshal(dht)
|
||||
|
||||
if ok, _ := verify(pubKey, payload, rec.Signature); !ok {
|
||||
return &rec, err
|
||||
}
|
||||
return &rec, nil
|
||||
}
|
||||
|
||||
func (d *DHTService) RefreshName( // peer should regulary refresh your host to not be settings up offline.
|
||||
ctx context.Context,
|
||||
) error {
|
||||
rec, err := d.existsDHT(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
expiry := now.Add(1 * time.Hour)
|
||||
rec.State = peer.ONLINE.EnumIndex()
|
||||
rec.ExpiryDate = expiry
|
||||
payload, _ := json.Marshal(rec)
|
||||
d.DHT.PutValue(ctx, d.Key, payload)
|
||||
access := peer.NewAccessor(&tools.APIRequest{Admin: true})
|
||||
p := &peer.Peer{
|
||||
AbstractObject: utils.AbstractObject{
|
||||
UUID: uuid.New().String(),
|
||||
Name: rec.Name,
|
||||
},
|
||||
State: peer.ONLINE, // online
|
||||
Relation: peer.SELF, // self
|
||||
PeerID: rec.PeerID,
|
||||
PublicKey: string(rec.PubKey),
|
||||
Url: rec.URL,
|
||||
}
|
||||
if founded, _, err := access.Search(nil, rec.Name, false); err != nil || len(founded) == 0 {
|
||||
access.StoreOne(p)
|
||||
} else if f, _, err := access.LoadOne(founded[0].GetID()); err == nil {
|
||||
f.(*peer.Peer).State = peer.ONLINE
|
||||
access.UpdateOne(f, f.GetID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user