kademlia bootstrap and refresh

- hive: refersh loop kad.getNodeRecord -> connectPeer
- backend: stop dpa and netstore
- netstore: stop
- kademlia: getNodeRecord implements kademlia integrity based priority
using minBucketSize =1
- bootstrap test
- change kademlia adjust prox - proxbinsize minimum
- random address generation for buckets farther than bootstrap
This commit is contained in:
zelig 2015-05-15 04:34:00 +02:00
parent b8f6d32a45
commit 33d8ddbe52
5 changed files with 257 additions and 98 deletions

View file

@ -3,6 +3,7 @@ package bzz
import ( import (
// "fmt" // "fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/kademlia" "github.com/ethereum/go-ethereum/common/kademlia"
) )
@ -24,8 +25,10 @@ type peer struct {
// to keep the nodetable uptodate // to keep the nodetable uptodate
type hive struct { type hive struct {
addr kademlia.Address
kad *kademlia.Kademlia kad *kademlia.Kademlia
path string path string
ping chan bool
} }
func newHive(hivepath string) *hive { func newHive(hivepath string) *hive {
@ -35,32 +38,55 @@ func newHive(hivepath string) *hive {
} }
} }
func (self *hive) start(address kademlia.Address) (err error) { func (self *hive) start(address kademlia.Address, connectPeer func(string) error) (err error) {
self.ping = make(chan bool)
self.addr = address
self.kad.Start(address) self.kad.Start(address)
err = self.kad.Load(self.path) err = self.kad.Load(self.path)
if err != nil { if err != nil {
dpaLogger.Warnf("Warning: error reading kademlia node db (skipping): %v", err) dpaLogger.Warnf("Warning: error reading kademlia node db (skipping): %v", err)
err = nil err = nil
} }
// go func() { go func() {
// for { for _ = range self.ping {
// select { node, full := self.kad.GetNodeRecord()
// case <-timer: if node != nil {
// case <-subscr: if len(node.Url) > 0 {
// } connectPeer(node.Url)
// maxpeers := 4 } else if !full {
// self.getPeerEntries(maxpeers) // a random peer is taken
// } peers := self.kad.GetNodes(kademlia.RandomAddress(), 1)
// }() if len(peers) > 0 {
req := &retrieveRequestMsgData{
Key: Key(common.Hash(kademlia.RandomAddressAt(self.addr, 0)).Bytes()),
}
peers[0].(peer).retrieve(req)
}
}
}
}
}()
return return
} }
func (self *hive) stop() error {
close(self.ping)
return self.kad.Stop(self.path)
}
func (self *hive) addPeer(p peer) { func (self *hive) addPeer(p peer) {
self.kad.AddNode(p) self.kad.AddNode(p)
// self lookup
req := &retrieveRequestMsgData{
Key: Key(common.Hash(self.addr).Bytes()),
}
p.retrieve(req)
self.ping <- true
} }
func (self *hive) removePeer(p peer) { func (self *hive) removePeer(p peer) {
self.kad.RemoveNode(p) self.kad.RemoveNode(p)
self.ping <- false
} }
// Retrieve a list of live peers that are closer to target than us // Retrieve a list of live peers that are closer to target than us
@ -91,14 +117,3 @@ func (self *hive) addPeerEntries(req *peersMsgData) {
} }
self.kad.AddNodeRecords(nrs) self.kad.AddNodeRecords(nrs)
} }
// called to ask periodically for preferences
// Kademlia ideally maintains a queue of prioritized nodes
func (self *hive) getPeerEntries(max int) (resp *peersMsgData, err error) {
nrs, err := self.kad.GetNodeRecords(max)
for _, n := range nrs {
_ = n
// resp // build response from kademlia noderecords
}
return
}

View file

@ -61,15 +61,19 @@ func NewNetStore(path, hivepath string) (netstore *NetStore, err error) {
return return
} }
func (self *NetStore) Start(node *discover.Node) (err error) { func (self *NetStore) Start(node *discover.Node, connectPeer func(string) error) (err error) {
self.self = node self.self = node
err = self.hive.start(kademlia.Address(node.Sha())) err = self.hive.start(kademlia.Address(node.Sha()), connectPeer)
if err != nil { if err != nil {
return return
} }
return return
} }
func (self *NetStore) Stop() (err error) {
return self.hive.stop()
}
func (self *NetStore) Put(entry *Chunk) { func (self *NetStore) Put(entry *Chunk) {
chunk, err := self.localStore.Get(entry.Key) chunk, err := self.localStore.Get(entry.Key)
dpaLogger.Debugf("NetStore.Put: localStore.Get returned with %v.", err) dpaLogger.Debugf("NetStore.Put: localStore.Get returned with %v.", err)

View file

@ -1,15 +1,14 @@
package kademlia package kademlia
import ( import (
"fmt"
"sort"
// "math"
"encoding/json" "encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"math/rand"
"os" "os"
"sort"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -19,8 +18,9 @@ import (
var kadlogger = logger.NewLogger("KΛÐ") var kadlogger = logger.NewLogger("KΛÐ")
const ( const (
bucketSize = 20 minBucketSize = 1
maxProx = 255 bucketSize = 20
maxProx = 255
) )
type Kademlia struct { type Kademlia struct {
@ -28,12 +28,12 @@ type Kademlia struct {
addr Address addr Address
// adjustable parameters // adjustable parameters
MaxProx int MaxProx int
MaxProxBinSize int ProxBinSize int
BucketSize int BucketSize int
currentMaxBucketSize int MinBucketSize int
nodeDB [][]*NodeRecord nodeDB [][]*NodeRecord
nodeIndex map[Address]*NodeRecord nodeIndex map[Address]*NodeRecord
GetNode func(int) GetNode func(int)
@ -108,9 +108,12 @@ func (self *Kademlia) Start(addr Address) error {
if self.BucketSize == 0 { if self.BucketSize == 0 {
self.BucketSize = bucketSize self.BucketSize = bucketSize
} }
if self.MinBucketSize == 0 {
self.MinBucketSize = minBucketSize
}
// runtime parameters // runtime parameters
if self.MaxProxBinSize == 0 { if self.ProxBinSize == 0 {
self.MaxProxBinSize = self.BucketSize self.ProxBinSize = self.BucketSize
} }
self.buckets = make([]*bucket, self.MaxProx+1) self.buckets = make([]*bucket, self.MaxProx+1)
@ -159,7 +162,7 @@ func (self *Kademlia) RemoveNode(node Node) (err error) {
if len(bucket.nodes) < bucket.size { if len(bucket.nodes) < bucket.size {
err = fmt.Errorf("insufficient nodes (%v) in bucket %v", len(bucket.nodes), index) err = fmt.Errorf("insufficient nodes (%v) in bucket %v", len(bucket.nodes), index)
} }
if len(bucket.nodes) == 0 { if len(bucket.nodes) == 0 || index >= self.proxLimit {
self.adjustProx(index, -1) self.adjustProx(index, -1)
} }
// async callback to notify user that bucket needs filling // async callback to notify user that bucket needs filling
@ -214,22 +217,24 @@ func (self *Kademlia) AddNode(node Node) (err error) {
// adjust Prox (proxLimit and proxSize after an insertion of add nodes into bucket r) // adjust Prox (proxLimit and proxSize after an insertion of add nodes into bucket r)
func (self *Kademlia) adjustProx(r int, add int) { func (self *Kademlia) adjustProx(r int, add int) {
var i int
switch { switch {
case add > 0 && r == self.proxLimit: 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 self.proxSize += add
for i = self.proxLimit; i < self.MaxProx && len(self.buckets[i].nodes) > 0 && self.proxSize > self.ProxBinSize; i++ {
self.proxSize -= len(self.buckets[i].nodes)
}
self.proxLimit = i
case add < 0 && r < self.proxLimit && len(self.buckets[r].nodes) == 0: case add < 0 && r < self.proxLimit && len(self.buckets[r].nodes) == 0:
for i := self.proxLimit - 1; i > r; i-- { for i = self.proxLimit - 1; i > r; i-- {
self.proxSize += len(self.buckets[i].nodes) self.proxSize += len(self.buckets[i].nodes)
} }
self.proxLimit = r self.proxLimit = r
case add < 0 && self.proxLimit > 0 && r >= self.proxLimit-1:
for i = self.proxLimit - 1; len(self.buckets[i].nodes)+self.proxSize <= self.ProxBinSize; i-- {
self.proxSize += len(self.buckets[i].nodes)
}
self.proxLimit = i
} }
} }
@ -238,7 +243,7 @@ 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 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 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 empty buckets in bin < proxLimit and 2) the sum of all items are the maximum
possible but lower than MaxProxBinSize possible but lower than ProxBinSize
*/ */
func (self *Kademlia) GetNodes(target Address, max int) []Node { func (self *Kademlia) GetNodes(target Address, max int) []Node {
return self.getNodes(target, max).nodes return self.getNodes(target, max).nodes
@ -298,49 +303,58 @@ func (self *Kademlia) AddNodeRecords(nrs []*NodeRecord) {
_, found := self.nodeIndex[node.Address] _, found := self.nodeIndex[node.Address]
if !found { if !found {
self.nodeIndex[node.Address] = node self.nodeIndex[node.Address] = node
index := self.proximityBin(node.Address) index := proximity(self.addr, node.Address)
self.nodeDB[index] = append(self.nodeDB[index], node) self.nodeDB[index] = append(self.nodeDB[index], node)
} }
} }
} }
/* /*
GetNodeRecords gives back an at most max length slice of node records GetNodeRecord gives back a node record with the highest priority for desired
in order of decreasing priority for desired connection connection
Used to pick candidates for live nodes to satisfy Kademlia network for Swarm Used to pick candidates for live nodes to satisfy Kademlia network for Swarm
Does a round robin on buckets starting from 0 to proxLimit then back if len(nodes) < MinBucketSize, then take ith element in corresponding
on each round i we inspect if live-nodes fill the bucket
if len(nodes) + i < currentMaxBucketSize, then take ith element in corresponding
db row ordered by reputation (active time?) db row ordered by reputation (active time?)
node record a is more favoured to b a > b iff
|proxBin(a)| < |proxBin(b)|
|| proxBin(a) < proxBin(b) && |proxBin(a)| < MinBucketSize
|| lastActive(a) < lastActive(b)
This has double role. Starting as naive node with empty db, this implements This has double role. Starting as naive node with empty db, this implements
Kademlia bootstrapping Kademlia bootstrapping
As a mature node, it manages quickly fill in blanks or short lines As a mature node, it manages quickly fill in blanks or short lines
All on demand All on demand
*/ */
func (self *Kademlia) GetNodeRecords(max int) (nrs []*NodeRecord, err error) { func (self *Kademlia) GetNodeRecord() (*NodeRecord, bool) {
var round int full := true
for max > 0 { for i, b := range self.nodeDB {
for i, b := range self.buckets { if i >= self.MaxProx {
if len(b.nodes)+round < self.currentMaxBucketSize { break
if nr := self.getNodeRecord(i, round); nr != nil { }
nrs = append(nrs) if len(self.buckets[i].nodes) < self.MinBucketSize {
full = false
for _, node := range b {
if node.node == nil {
return node, full
} }
} }
} }
round++
max--
} }
return for i, b := range self.nodeDB {
} if i > self.MaxProx {
break
func (self *Kademlia) getNodeRecord(row, col int) (nr *NodeRecord) { }
if row >= 0 && row < len(self.nodeDB) && if len(self.buckets[i].nodes) < self.BucketSize {
col >= 0 && col < len(self.nodeDB[row]) { full = false
nr = self.nodeDB[row][col] for _, node := range b {
if node.node == nil {
return node, full
}
}
}
} }
return return nil, full
} }
// in situ mutable bucket // in situ mutable bucket
@ -401,21 +415,19 @@ func (self *bucket) insert(node Node) (err error) {
self.lock.Lock() self.lock.Lock()
defer self.lock.Unlock() defer self.lock.Unlock()
if len(self.nodes) >= self.size { // >= allows us to add peers beyond the bucketsize limitation if len(self.nodes) >= self.size { // >= allows us to add peers beyond the bucketsize limitation
worst := self.worstNode() self.worstNode().Drop() // assumes self.size > 0
self.nodes[worst] = node
} else {
self.nodes = append(self.nodes, node)
} }
self.nodes = append(self.nodes, node)
return return
} }
// worst expunges the single worst entry in a row, where worst entry is with a peer that has not been active the longests // 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) { func (self *bucket) worstNode() (node Node) {
var oldest time.Time var oldest time.Time
for i, node := range self.nodes { for _, n := range self.nodes {
if (oldest == time.Time{}) || node.LastActive().Before(oldest) { if (oldest == time.Time{}) || node.LastActive().Before(oldest) {
oldest = node.LastActive() oldest = n.LastActive()
index = i node = n
} }
} }
return return
@ -452,6 +464,9 @@ 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 value of the binary integer cast of the xor-ed byte sequence (most significant
bit first). bit first).
proximity(x, y) counts the common zeros in the front of this distance measure. proximity(x, y) counts the common zeros in the front of this distance measure.
which is equivalent to the reverse rank of the integer part of the base 2
logarithm of the distance
called proximity belt (0 farthest, 255 closest, 256 self)
*/ */
func proximity(one, other Address) (ret int) { func proximity(one, other Address) (ret int) {
for i := 0; i < len(one); i++ { for i := 0; i < len(one); i++ {
@ -485,16 +500,6 @@ func (self *Kademlia) DB() [][]*NodeRecord {
return self.nodeDB 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 // save persists all peers encountered
func (self *Kademlia) Save(path string) error { func (self *Kademlia) Save(path string) error {
@ -528,9 +533,38 @@ func (self *Kademlia) Load(path string) (err error) {
if err != nil { if err != nil {
return return
} }
self.nodeDB = kad.Nodes
if self.addr != kad.Address { if self.addr != kad.Address {
return fmt.Errorf("invalid kad db: address mismatch, expected %v, got %v", self.addr, kad.Address) return fmt.Errorf("invalid kad db: address mismatch, expected %v, got %v", self.addr, kad.Address)
} }
self.nodeDB = kad.Nodes
return return
} }
// randomAddressAt(address, prox) generates a random address
// at proximity order prox relative to address
// if prox is negative a random address is generated
func RandomAddressAt(self Address, prox int) (addr Address) {
addr = self
var pos int
if prox >= 0 {
pos = prox / 8
trans := prox % 8
transbytea := byte(0)
for j := 0; j <= trans; j++ {
transbytea |= 1 << uint8(7-j)
}
flipbyte := byte(1 << uint8(7-trans))
transbyteb := transbytea ^ byte(255)
randbyte := byte(rand.Intn(255))
addr[pos] = ((addr[pos] & transbytea) ^ flipbyte) | randbyte&transbyteb
}
for i := pos + 1; i < len(addr); i++ {
addr[i] = byte(rand.Intn(255))
}
return
}
// randomAddressAt() generates a random address
func RandomAddress() Address {
return RandomAddressAt(Address{}, -1)
}

View file

@ -15,8 +15,9 @@ import (
) )
var ( var (
quickrand = rand.New(rand.NewSource(time.Now().Unix())) quickrand = rand.New(rand.NewSource(time.Now().Unix()))
quickcfg = &quick.Config{MaxCount: 5000, Rand: quickrand} quickcfgGetNodes = &quick.Config{MaxCount: 5000, Rand: quickrand}
quickcfgBootStrap = &quick.Config{MaxCount: 1000, Rand: quickrand}
) )
var once sync.Once var once sync.Once
@ -67,6 +68,82 @@ func TestAddNode(t *testing.T) {
_ = err _ = err
} }
func TestBootstrap(t *testing.T) {
t.Parallel()
LogInit(logger.DebugLevel)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
test := func(test *bootstrapTest) bool {
// for any node kad.le, Target and N
kad := New()
kad.MaxProx = test.MaxProx
kad.MinBucketSize = test.MinBucketSize
kad.BucketSize = test.BucketSize
kad.Start(test.Self)
var err error
t.Logf("bootstapTest MaxProx: %v MinBucketSize: %v BucketSize: %v\n", test.MaxProx, test.MinBucketSize, test.BucketSize)
addr := gen(Address{}, r).(Address)
prox := proximity(addr, test.Self)
for p := 0; p <= prox; p++ {
var nrs []*NodeRecord
for i := 0; i < test.BucketSize; i++ {
nrs = append(nrs, &NodeRecord{
Address: RandomAddressAt(test.Self, p),
})
}
kad.AddNodeRecords(nrs)
}
node := &testNode{addr}
n := 0
for n < 100 {
err = kad.AddNode(node)
if err != nil {
t.Errorf("backend not accepting node")
return false
}
var nrs []*NodeRecord
prox := proximity(node.addr, test.Self)
for i := 0; i < test.BucketSize; i++ {
nrs = append(nrs, &NodeRecord{
Address: RandomAddressAt(test.Self, prox+1),
})
}
kad.AddNodeRecords(nrs)
var lens []int
for i := 0; i <= test.MaxProx; i++ {
lens = append(lens, len(kad.buckets[i].nodes))
}
record, _ := kad.GetNodeRecord()
if record == nil {
t.Logf("after round %d, no more node records needed", n)
break
}
node = &testNode{record.Address}
n++
}
exp := test.BucketSize * (test.MaxProx + 1)
if kad.Count() != exp {
t.Errorf("incorrect number of peers, expceted %d, got %d", exp, kad.Count())
}
return true
if n < 1 {
t.Errorf("incorrect number of rounds, expceted %d, got %d", 0, n)
}
return true
}
if err := quick.Check(test, quickcfgBootStrap); err != nil {
t.Error(err)
}
}
func TestGetNodes(t *testing.T) { func TestGetNodes(t *testing.T) {
t.Parallel() t.Parallel()
LogInit(logger.DebugLevel) LogInit(logger.DebugLevel)
@ -131,7 +208,7 @@ func TestGetNodes(t *testing.T) {
} }
return true return true
} }
if err := quick.Check(test, quickcfg); err != nil { if err := quick.Check(test, quickcfgGetNodes); err != nil {
t.Error(err) t.Error(err)
} }
} }
@ -178,7 +255,7 @@ func TestProxAdjust(t *testing.T) {
} }
return kad.proxCheck(t) return kad.proxCheck(t)
} }
if err := quick.Check(test, quickcfg); err != nil { if err := quick.Check(test, quickcfgGetNodes); err != nil {
t.Error(err) t.Error(err)
} }
} }
@ -285,7 +362,7 @@ func (self *Kademlia) proxCheck(t *testing.T) bool {
} }
// check if merged high prox bucket does not exceed size // check if merged high prox bucket does not exceed size
if sum > 0 { if sum > 0 {
if sum > self.MaxProxBinSize { if sum > self.ProxBinSize {
t.Errorf("bucket %d is empty, yet proxSize is %d", i, self.proxSize) t.Errorf("bucket %d is empty, yet proxSize is %d", i, self.proxSize)
return false return false
} }
@ -293,10 +370,32 @@ func (self *Kademlia) proxCheck(t *testing.T) bool {
t.Errorf("proxSize incorrect, expected %v, got %v", sum, self.proxSize) t.Errorf("proxSize incorrect, expected %v, got %v", sum, self.proxSize)
return false return false
} }
if self.proxLimit > 0 && sum+len(self.buckets[self.proxLimit-1].nodes) < self.ProxBinSize {
t.Errorf("proxBinSize incorrect, expected %v got %v", sum, self.proxSize)
return false
}
} }
return true return true
} }
type bootstrapTest struct {
MaxProx int
MinBucketSize int
BucketSize int
Self Address
}
func (*bootstrapTest) Generate(rand *rand.Rand, size int) reflect.Value {
m := rand.Intn(2) + 1
t := &bootstrapTest{
Self: gen(Address{}, rand).(Address),
MaxProx: 10 + rand.Intn(3),
MinBucketSize: m,
BucketSize: rand.Intn(3) + m,
}
return reflect.ValueOf(t)
}
type getNodesTest struct { type getNodesTest struct {
Self Address Self Address
Target Address Target Address

View file

@ -468,7 +468,7 @@ func (s *Ethereum) Start() error {
if s.DPA != nil { if s.DPA != nil {
s.DPA.Start() s.DPA.Start()
s.netStore.Start(s.net.Self()) s.netStore.Start(s.net.Self(), s.AddPeer)
go bzz.StartHttpServer(s.DPA) go bzz.StartHttpServer(s.DPA)
} }
@ -543,6 +543,13 @@ func (s *Ethereum) Stop() {
s.whisper.Stop() s.whisper.Stop()
} }
if s.DPA != nil {
s.DPA.Stop()
}
if s.netStore != nil {
s.netStore.Stop()
}
glog.V(logger.Info).Infoln("Server stopped") glog.V(logger.Info).Infoln("Server stopped")
close(s.shutdownChan) close(s.shutdownChan)
} }