cademlia proximity

- distance -> proximity
- proximity bin properly implemented
- fix update and adjust prox functions
- rename fields and methods
- comments
This commit is contained in:
zelig 2015-01-09 15:01:19 +00:00
parent 26c625d8e6
commit c55c67dbb4

View file

@ -2,6 +2,7 @@ package p2p
import (
"fmt"
"math"
"sync"
"time"
@ -11,8 +12,9 @@ import (
var cadlogger = ethlogger.NewLogger("CAD")
const (
hashBits = 160
hashBytes = 20
rowLength = 10
maxProx = 20
)
var maxAge = 180 * time.Nanosecond
@ -20,10 +22,11 @@ var purgeInterval = 300 * time.Second
type Cademlia struct {
Hash []byte
HashBits int
HashBytes int
RowLength int
MaxProxSize int
MaxProx int
MaxProxBinSize int
MaxAge time.Duration
PurgeInterval time.Duration
@ -37,12 +40,16 @@ type Cademlia struct {
quitC chan bool
}
// public constructor with compulsory arguments
// hash is a byte slice of length equal to self.HashBytes
func NewCademlia(hash []byte) *Cademlia {
return &Cademlia{
Hash: hash, // compulsory fields without default
}
}
// Start brings up a pool of peers potentially from an offline persisted source
// and sets default values for optional parameters
func (self *Cademlia) Start() error {
self.lock.Lock()
defer self.lock.Unlock()
@ -51,15 +58,18 @@ func (self *Cademlia) Start() error {
}
// these + self.Hash can and should be checked against the
// saved file/db
if self.HashBits == 0 {
self.HashBits = hashBits
if self.HashBytes == 0 {
self.HashBytes = hashBytes
}
if self.MaxProx == 0 {
self.MaxProx = maxProx
}
if self.RowLength == 0 {
self.RowLength = rowLength
}
// runtime parameters
if self.MaxProxSize == 0 {
self.MaxProxSize = self.RowLength
if self.MaxProxBinSize == 0 {
self.MaxProxBinSize = self.RowLength
}
if self.MaxAge == time.Duration(0) {
self.MaxAge = maxAge
@ -67,7 +77,7 @@ func (self *Cademlia) Start() error {
if self.PurgeInterval == time.Duration(0) {
self.PurgeInterval = purgeInterval
}
self.rows = make([]*row, self.HashBits)
self.rows = make([]*row, self.MaxProx)
for i, _ := range self.rows {
self.rows[i] = &row{} // will initialise row{int(0),[]*entry(nil),sync.Mutex}
}
@ -76,6 +86,7 @@ func (self *Cademlia) Start() error {
return nil
}
// Stop saves the routing table into a persistant form
func (self *Cademlia) Stop() {
self.lock.Lock()
defer self.lock.Unlock()
@ -86,10 +97,13 @@ func (self *Cademlia) Stop() {
self.quitC = nil
}
// AddPeer is the entry point where new peers are suggested for addition to the peer pool
// peers conform to the peerrInfo interface
// AddPeer(peer) returns an error if it deems the peer unworthy
func (self *Cademlia) AddPeer(peer peerInfo) (err error) {
self.lock.Lock()
defer self.lock.Unlock()
index := self.DistanceTo(peer.Hash())
index := self.ProximityBin(peer.Hash())
row := self.rows[index]
added := row.insert(&entry{peer: peer})
if added {
@ -104,35 +118,41 @@ func (self *Cademlia) AddPeer(peer peerInfo) (err error) {
return
}
// adjust Prox (proxLimit and proxSize after an insertion of add entries into row r)
func (self *Cademlia) adjustProx(r int, add int) {
if self.proxSize+add > self.MaxProxSize &&
self.lock.Lock()
defer self.lock.Unlock()
if r >= self.proxLimit &&
self.proxSize+add > self.MaxProxBinSize &&
self.rows[r].len() > 0 {
self.proxLimit--
self.proxLimit++
} else {
self.proxSize += add
}
}
// updates Prox (proxLimit and proxSize after purging entries)
func (self *Cademlia) updateProx() {
var sum, proxSize int
for _, r := range self.rows {
sum += r.len()
if sum <= self.MaxProxSize || r.len() == 0 {
proxSize = sum
}
}
self.lock.Lock()
self.proxSize = proxSize
self.lock.Unlock()
defer self.lock.Unlock()
var sum int
for i := self.MaxProx - 1; i >= 0; i-- {
l := self.rows[i].len()
sum += l
if sum <= self.MaxProxBinSize || l == 0 {
self.proxSize = sum
}
}
}
// GetPeers(target) returns the list of peers 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 rows in bin < proxLimit and 2) the sum of all items are the maximum possible but lower than MaxProxBinSize
func (self *Cademlia) GetPeers(target []byte) (peers []peerInfo) {
self.lock.RLock()
defer self.lock.RUnlock()
index := self.DistanceTo(target)
index := self.ProximityBin(target)
var entries []*entry
if index >= self.proxLimit {
for i := self.proxLimit; i < self.HashBits; i++ {
for i := self.proxLimit; i < self.MaxProx; i++ {
entries = append(entries, self.rows[i].row...)
}
} else {
@ -144,11 +164,13 @@ func (self *Cademlia) GetPeers(target []byte) (peers []peerInfo) {
return
}
// entry wrapper type for peer object adding potentially persisted metadata for offline permanent record
type entry struct {
peer peerInfo
// metadata
}
// in situ mutable row
type row struct {
length int
row []*entry
@ -161,6 +183,7 @@ func (self *row) len() int {
return self.length
}
// insert adds a peer to a row either by appending to existing items if row length does not exceed RowLength, or by replacing the worst entry in the row
func (self *row) insert(entry *entry) (added bool) {
self.lock.Lock()
defer self.lock.Unlock()
@ -176,6 +199,7 @@ func (self *row) insert(entry *entry) (added bool) {
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 *row) worst() (index int) {
var oldest time.Time
for i, entry := range self.row {
@ -187,6 +211,8 @@ func (self *row) worst() (index int) {
return
}
// expunges entries from a row that were last active more that MaxAge ago
// calls Disconnect on entry.peer
func (self *row) purge(recently time.Time) {
self.lock.Lock()
var newRow []*entry
@ -223,25 +249,39 @@ func (self *Cademlia) purgeLoop() {
}
}
func Xor(one, other []byte) (xor []byte) {
for i := 0; i < len(one); i++ {
xor[i] = one[i] ^ other[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 *Cademlia) ProximityBin(other []byte) (ret int) {
return int(math.Min(float64(self.MaxProx), float64(self.Proximity(self.Hash, other))))
}
func (self *Cademlia) DistanceTo(other []byte) (ret int) {
return self.Distance(self.Hash, other)
}
/*
The distance metric MSB(x, y) of two equal length bytesequences x an y is the value of the
binary integer cast of the xor-ed bytesequence most significant bit first.
Proximity(x, y) counts the common zeros in the front of this distance measure.
*/
func (self *Cademlia) Distance(one, other []byte) (ret int) {
func (self *Cademlia) Proximity(one, other []byte) (ret int) {
xor := Xor(one, other)
for i := 0; i < self.HashBits; i++ {
for i := 0; i < self.HashBytes; i++ {
for j := 0; j < 8; j++ {
if (xor[i]>>uint8(7-j))&0x1 != 0 {
return i*8 + j
}
}
}
return self.HashBits*8 - 1
return self.HashBytes*8 - 1
}
func Xor(one, other []byte) (xor []byte) {
for i := 0; i < len(one); i++ {
xor[i] = one[i] ^ other[i]
}
return
}