mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 20:56:42 +00:00
465 lines
11 KiB
Go
465 lines
11 KiB
Go
package kademlia
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
// "math"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/logger"
|
|
)
|
|
|
|
var kadlogger = logger.NewLogger("KΛÐ")
|
|
|
|
const (
|
|
bucketSize = 20
|
|
maxProx = 255
|
|
)
|
|
|
|
type Kademlia struct {
|
|
// immutable baseparam
|
|
addr Address
|
|
|
|
// adjustable parameters
|
|
BucketSize int
|
|
MaxProx int
|
|
MaxProxBinSize int
|
|
nodeDB [][]*nodeRecord
|
|
nodeIndex map[Address]*nodeRecord
|
|
|
|
// state
|
|
proxLimit int
|
|
proxSize int
|
|
|
|
//
|
|
count int
|
|
buckets []*bucket
|
|
|
|
lock sync.RWMutex
|
|
quitC chan bool
|
|
}
|
|
|
|
type Address common.Hash
|
|
|
|
type Node interface {
|
|
Addr() Address
|
|
// Url()
|
|
LastActive() time.Time
|
|
}
|
|
|
|
type nodeRecord struct {
|
|
Address Address `json:address`
|
|
Active int64 `json:active`
|
|
node Node
|
|
}
|
|
|
|
func (self *nodeRecord) setActive() {
|
|
if self.node != nil {
|
|
self.Active = self.node.LastActive().UnixNano()
|
|
}
|
|
}
|
|
|
|
type kadDB struct {
|
|
Address Address `json:address`
|
|
Nodes [][]*nodeRecord `json:nodes`
|
|
}
|
|
|
|
// public constructor with compulsory arguments
|
|
// hash is a byte slice of length equal to self.HashBytes
|
|
func New(a Address) *Kademlia {
|
|
return &Kademlia{
|
|
addr: a, // compulsory fields without default
|
|
}
|
|
}
|
|
|
|
// accessor for KAD self address
|
|
func (self *Kademlia) Addr() Address {
|
|
return self.addr
|
|
}
|
|
|
|
// accessor for KAD self count
|
|
func (self *Kademlia) Count() int {
|
|
return self.count
|
|
}
|
|
|
|
// Start brings up a pool of entries potentially from an offline persisted source
|
|
// and sets default values for optional parameters
|
|
func (self *Kademlia) Start() error {
|
|
self.lock.Lock()
|
|
defer self.lock.Unlock()
|
|
if self.quitC != nil {
|
|
return nil
|
|
}
|
|
if self.MaxProx == 0 {
|
|
self.MaxProx = maxProx
|
|
}
|
|
if self.BucketSize == 0 {
|
|
self.BucketSize = bucketSize
|
|
}
|
|
// runtime parameters
|
|
if self.MaxProxBinSize == 0 {
|
|
self.MaxProxBinSize = self.BucketSize
|
|
}
|
|
|
|
self.buckets = make([]*bucket, self.MaxProx+1)
|
|
for i, _ := range self.buckets {
|
|
self.buckets[i] = &bucket{size: self.BucketSize} // will initialise bucket{int(0),[]Node(nil),sync.Mutex}
|
|
}
|
|
|
|
self.nodeDB = make([][]*nodeRecord, 8*len(self.addr))
|
|
self.nodeIndex = make(map[Address]*nodeRecord)
|
|
|
|
self.quitC = make(chan bool)
|
|
return nil
|
|
}
|
|
|
|
// Stop saves the routing table into a persistant form
|
|
func (self *Kademlia) Stop(path string) (err error) {
|
|
self.lock.Lock()
|
|
defer self.lock.Unlock()
|
|
if self.quitC == nil {
|
|
return
|
|
}
|
|
close(self.quitC)
|
|
self.quitC = nil
|
|
|
|
if len(path) > 0 {
|
|
err = self.Save(path)
|
|
if err != nil {
|
|
kadlogger.Warnf("unable to save node records: %v", err)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// RemoveNode is the entrypoint where nodes are taken offline
|
|
func (self *Kademlia) RemoveNode(node Node) (err error) {
|
|
self.lock.Lock()
|
|
defer self.lock.Unlock()
|
|
index := self.proximityBin(node.Addr())
|
|
bucket := self.buckets[index]
|
|
for i := 0; i < len(bucket.nodes); i++ {
|
|
if node.Addr() == bucket.nodes[i].Addr() {
|
|
bucket.nodes = append(bucket.nodes[:i], bucket.nodes[(i+1):]...)
|
|
}
|
|
}
|
|
self.count--
|
|
if len(bucket.nodes) < bucket.size {
|
|
err = fmt.Errorf("insufficient nodes (%v) in bucket %v", len(bucket.nodes), index)
|
|
}
|
|
if len(bucket.nodes) == 0 {
|
|
self.adjustProx(index, -1)
|
|
}
|
|
// async callback to notify user that bucket needs filling
|
|
// action is left to the user
|
|
// go self.getNode(index)
|
|
return
|
|
}
|
|
|
|
// AddNode is the entry point where new nodes are registered
|
|
func (self *Kademlia) AddNode(node Node) (err error) {
|
|
|
|
self.lock.Lock()
|
|
defer self.lock.Unlock()
|
|
|
|
index := self.proximityBin(node.Addr())
|
|
kadlogger.Debugf("bin %d, len: %d\n", index, len(self.buckets))
|
|
|
|
bucket := self.buckets[index]
|
|
err = bucket.insert(node)
|
|
if err != nil {
|
|
return
|
|
}
|
|
self.count++
|
|
if index >= self.proxLimit {
|
|
self.adjustProx(index, 1)
|
|
}
|
|
|
|
go func() {
|
|
record, found := self.nodeIndex[node.Addr()]
|
|
if found {
|
|
record.node = node
|
|
} else {
|
|
record = &nodeRecord{
|
|
Address: node.Addr(),
|
|
// Url: node.Url(),
|
|
Active: node.LastActive().UnixNano(),
|
|
node: node,
|
|
}
|
|
self.nodeIndex[node.Addr()] = record
|
|
self.nodeDB[index] = append(self.nodeDB[index], record)
|
|
}
|
|
}()
|
|
|
|
kadlogger.Infof("add peer %v...", node)
|
|
return
|
|
|
|
}
|
|
|
|
// adjust Prox (proxLimit and proxSize after an insertion of add nodes into bucket r)
|
|
func (self *Kademlia) adjustProx(r int, add int) {
|
|
switch {
|
|
case add > 0 && r == self.proxLimit:
|
|
self.proxLimit += add
|
|
for ; self.proxLimit < self.MaxProx && len(self.buckets[self.proxLimit].nodes) > 0; self.proxLimit++ {
|
|
self.proxSize -= len(self.buckets[self.proxLimit].nodes)
|
|
}
|
|
case add > 0 && r > self.proxLimit && self.proxSize+add > self.MaxProxBinSize:
|
|
self.proxLimit++
|
|
self.proxSize -= len(self.buckets[r].nodes) - add
|
|
case add > 0 && r > self.proxLimit:
|
|
self.proxSize += add
|
|
case add < 0 && r < self.proxLimit && len(self.buckets[r].nodes) == 0:
|
|
for i := self.proxLimit - 1; i > r; i-- {
|
|
self.proxSize += len(self.buckets[i].nodes)
|
|
}
|
|
self.proxLimit = r
|
|
}
|
|
}
|
|
|
|
/*
|
|
GetNodes(target) returns the list of nodes belonging to the same proximity bin
|
|
as the target. The most proximate bin will be the union of the bins between
|
|
proxLimit and MaxProx. proxLimit is dynamically adjusted so that 1) there is no
|
|
empty buckets in bin < proxLimit and 2) the sum of all items are the maximum
|
|
possible but lower than MaxProxBinSize
|
|
*/
|
|
func (self *Kademlia) GetNodes(target Address, max int) (r nodesByDistance) {
|
|
self.lock.RLock()
|
|
defer self.lock.RUnlock()
|
|
r.target = target
|
|
index := self.proximityBin(target)
|
|
start := index
|
|
var down bool
|
|
if index >= self.proxLimit {
|
|
start = self.MaxProx
|
|
down = true
|
|
}
|
|
var n int
|
|
limit := max
|
|
if max == 0 {
|
|
limit = 1000
|
|
}
|
|
for {
|
|
bucket := self.buckets[start].nodes
|
|
for i := 0; i < len(bucket); i++ {
|
|
r.push(bucket[i], limit)
|
|
n++
|
|
}
|
|
if max == 0 && start == index ||
|
|
max > 0 && down && start <= index && (n >= max || n == self.Count() || start == 0) {
|
|
break
|
|
}
|
|
if down {
|
|
start--
|
|
} else {
|
|
if start == self.MaxProx {
|
|
if index == 0 {
|
|
break
|
|
}
|
|
start = index - 1
|
|
down = true
|
|
} else {
|
|
start++
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// in situ mutable bucket
|
|
type bucket struct {
|
|
size int
|
|
nodes []Node
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
func (a Address) Bin() string {
|
|
var bs []string
|
|
for _, b := range a[:] {
|
|
bs = append(bs, fmt.Sprintf("%08b", b))
|
|
}
|
|
return strings.Join(bs, "")
|
|
}
|
|
|
|
// nodesByDistance is a list of nodes, ordered by distance to target.
|
|
type nodesByDistance struct {
|
|
nodes []Node
|
|
target Address
|
|
}
|
|
|
|
func sortedByDistanceTo(target Address, slice []Node) bool {
|
|
var last Address
|
|
for i, node := range slice {
|
|
if i > 0 {
|
|
if proxCmp(target, node.Addr(), last) < 0 {
|
|
return false
|
|
}
|
|
}
|
|
last = node.Addr()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// push(node, max) adds the given node to the list, keeping the total size
|
|
// below max elements.
|
|
func (h *nodesByDistance) push(node Node, max int) {
|
|
// returns the firt index ix such that func(i) returns true
|
|
ix := sort.Search(len(h.nodes), func(i int) bool {
|
|
return proxCmp(h.target, h.nodes[i].Addr(), node.Addr()) >= 0
|
|
})
|
|
|
|
if len(h.nodes) < max {
|
|
h.nodes = append(h.nodes, node)
|
|
}
|
|
if ix < len(h.nodes) {
|
|
copy(h.nodes[ix+1:], h.nodes[ix:])
|
|
h.nodes[ix] = node
|
|
}
|
|
}
|
|
|
|
// insert adds a peer to a bucket either by appending to existing items if
|
|
// bucket length does not exceed bucketLength, or by replacing the worst
|
|
// Node in the bucket
|
|
func (self *bucket) insert(node Node) (err error) {
|
|
self.lock.Lock()
|
|
defer self.lock.Unlock()
|
|
if len(self.nodes) >= self.size { // >= allows us to add peers beyond the bucketsize limitation
|
|
worst := self.worstNode()
|
|
self.nodes[worst] = node
|
|
} else {
|
|
self.nodes = append(self.nodes, node)
|
|
}
|
|
return
|
|
}
|
|
|
|
// worst expunges the single worst entry in a row, where worst entry is with a peer that has not been active the longests
|
|
func (self *bucket) worstNode() (index int) {
|
|
var oldest time.Time
|
|
for i, node := range self.nodes {
|
|
if (oldest == time.Time{}) || node.LastActive().Before(oldest) {
|
|
oldest = node.LastActive()
|
|
index = i
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
/*
|
|
Taking the proximity value relative to a fix point x classifies the points in
|
|
the space (n byte long byte sequences) into bins the items in which are each at
|
|
most half as distant from x as items in the previous bin. Given a sample of
|
|
uniformly distrbuted items (a hash function over arbitrary sequence) the
|
|
proximity scale maps onto series of subsets with cardinalities on a negative
|
|
exponential scale.
|
|
|
|
It also has the property that any two item belonging to the same bin are at
|
|
most half as distant from each other as they are from x.
|
|
|
|
If we think of random sample of items in the bins as connections in a network of interconnected nodes than relative proximity can serve as the basis for local
|
|
decisions for graph traversal where the task is to find a route between two
|
|
points. Since in every step of forwarding, the finite distance halves, there is
|
|
a guaranteed constant maximum limit on the number of hops needed to reach one
|
|
node from the other.
|
|
*/
|
|
|
|
func (self *Kademlia) proximityBin(other Address) (ret int) {
|
|
ret = proximity(self.addr, other)
|
|
if ret > self.MaxProx {
|
|
ret = self.MaxProx
|
|
}
|
|
return
|
|
}
|
|
|
|
/*
|
|
The distance metric MSB(x, y) of two equal length byte sequences x an y is the
|
|
value of the binary integer cast of the xor-ed byte sequence (most significant
|
|
bit first).
|
|
proximity(x, y) counts the common zeros in the front of this distance measure.
|
|
*/
|
|
func proximity(one, other Address) (ret int) {
|
|
for i := 0; i < len(one); i++ {
|
|
oxo := one[i] ^ other[i]
|
|
for j := 0; j < 8; j++ {
|
|
if (oxo>>uint8(7-j))&0x1 != 0 {
|
|
return i*8 + j
|
|
}
|
|
}
|
|
}
|
|
return len(one)*8 - 1
|
|
}
|
|
|
|
// proxCmp compares the distances a->target and b->target.
|
|
// Returns -1 if a is closer to target, 1 if b is closer to target
|
|
// and 0 if they are equal.
|
|
func proxCmp(target, a, b Address) int {
|
|
for i := range target {
|
|
da := a[i] ^ target[i]
|
|
db := b[i] ^ target[i]
|
|
if da > db {
|
|
return 1
|
|
} else if da < db {
|
|
return -1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (self *Kademlia) DB() [][]*nodeRecord {
|
|
return self.nodeDB
|
|
}
|
|
|
|
func (n *nodeRecord) bumpActive() {
|
|
stamp := time.Now().Unix()
|
|
atomic.StoreInt64(&n.Active, stamp)
|
|
}
|
|
|
|
func (n *nodeRecord) LastActive() time.Time {
|
|
stamp := atomic.LoadInt64(&n.Active)
|
|
return time.Unix(stamp, 0)
|
|
}
|
|
|
|
// save persists all peers encountered
|
|
func (self *Kademlia) Save(path string) error {
|
|
|
|
kad := kadDB{
|
|
Address: self.addr,
|
|
Nodes: self.nodeDB,
|
|
}
|
|
for _, b := range kad.Nodes {
|
|
for _, node := range b {
|
|
node.setActive()
|
|
}
|
|
}
|
|
data, err := json.MarshalIndent(&kad, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ioutil.WriteFile(path, data, os.ModePerm)
|
|
}
|
|
|
|
// loading the idle node record from disk
|
|
func (self *Kademlia) Load(path string) (err error) {
|
|
var data []byte
|
|
data, err = ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var kad kadDB
|
|
err = json.Unmarshal(data, &kad)
|
|
if err != nil {
|
|
return
|
|
}
|
|
self.nodeDB = kad.Nodes
|
|
if self.addr != kad.Address {
|
|
return fmt.Errorf("invalid kad db: address mismatch, expected %v, got %v", self.addr, kad.Address)
|
|
}
|
|
return
|
|
}
|