Skip to content

Commit

Permalink
kademlia: add unsaturated bins to depth calculation (#1553)
Browse files Browse the repository at this point in the history
  • Loading branch information
acud authored Apr 13, 2021
1 parent 63f2ef3 commit 78aba90
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 84 deletions.
35 changes: 30 additions & 5 deletions pkg/kademlia/kademlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,32 @@ func recalcDepth(peers *pslice.PSlice) uint8 {
shallowestEmpty, noEmptyBins = peers.ShallowestEmpty()
)

shallowestUnsaturated := uint8(0)
binCount := 0
_ = peers.EachBinRev(func(_ swarm.Address, bin uint8) (bool, bool, error) {
if bin == shallowestUnsaturated {
binCount++
return false, false, nil
}
if bin > shallowestUnsaturated && binCount < saturationPeers {
// this means we have less than saturationPeers in the previous bin
// therefore we can return assuming that bin is the unsaturated one.
return true, false, nil
}
// bin > shallowestUnsaturated && binCount >= saturationPeers
shallowestUnsaturated = bin
binCount = 1

return false, false, nil
})

// if there are some empty bins and the shallowestEmpty is
// smaller than the shallowestUnsaturated then set shallowest
// unsaturated to the empty bin.
if !noEmptyBins && shallowestEmpty < shallowestUnsaturated {
shallowestUnsaturated = shallowestEmpty
}

_ = peers.EachBin(func(_ swarm.Address, po uint8) (bool, bool, error) {
peersCtr++
if peersCtr >= nnLowWatermark {
Expand All @@ -563,12 +589,11 @@ func recalcDepth(peers *pslice.PSlice) uint8 {
}
return false, false, nil
})

if noEmptyBins || shallowestEmpty > candidate {
if shallowestUnsaturated > candidate {
return candidate
}

return shallowestEmpty
return shallowestUnsaturated
}

// connect connects to a peer and gossips its address to our connected peers,
Expand Down Expand Up @@ -889,8 +914,8 @@ func (k *Kad) ClosestPeer(addr swarm.Address, skipPeers ...swarm.Address) (swarm
}

// IsWithinDepth returns if an address is within the neighborhood depth of a node.
func (k *Kad) IsWithinDepth(adr swarm.Address) bool {
return swarm.Proximity(k.base.Bytes(), adr.Bytes()) >= k.NeighborhoodDepth()
func (k *Kad) IsWithinDepth(addr swarm.Address) bool {
return swarm.Proximity(k.base.Bytes(), addr.Bytes()) >= k.NeighborhoodDepth()
}

// // EachNeighbor iterates from closest bin to farthest of the neighborhood peers.
Expand Down
160 changes: 81 additions & 79 deletions pkg/kademlia/kademlia_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,132 +49,134 @@ func TestNeighborhoodDepth(t *testing.T) {
var (
conns int32 // how many connect calls were made to the p2p mock
base, kad, ab, _, signer = newTestKademlia(&conns, nil, kademlia.Options{})
peers []swarm.Address
binEight []swarm.Address
)

if err := kad.Start(context.Background()); err != nil {
t.Fatal(err)
}
defer kad.Close()

for i := 0; i < 8; i++ {
addr := test.RandomAddressAt(base, i)
peers = append(peers, addr)
}

// add 2 peers in bin 8
for i := 0; i < 2; i++ {
addr := test.RandomAddressAt(base, 8)
binEight = append(binEight, addr)
}

// check empty kademlia depth is 0
kDepth(t, kad, 0)
addOne(t, signer, kad, ab, addr)

// add two bin 8 peers, verify depth still 0
add(t, signer, kad, ab, binEight, 0, 2)
// wait for one connection
waitConn(t, &conns)
}
// depth is 0
kDepth(t, kad, 0)

var shallowPeers []swarm.Address
// add two first peers (po0,po1)
add(t, signer, kad, ab, peers, 0, 2)
for i := 0; i < 2; i++ {
addr := test.RandomAddressAt(base, i)
addOne(t, signer, kad, ab, addr)
shallowPeers = append(shallowPeers, addr)

// wait for 4 connections
waitCounter(t, &conns, 4)
// wait for one connection
waitConn(t, &conns)
}

// depth 2 (shallowest empty bin)
kDepth(t, kad, 2)
for _, a := range shallowPeers {
if !kad.IsWithinDepth(a) {
t.Fatal("expected address to be within depth")
}
}

for i := 2; i < len(peers)-1; i++ {
addOne(t, signer, kad, ab, peers[i])
// depth 0 - bin 0 is unsaturated
kDepth(t, kad, 0)

for i := 2; i < 8; i++ {
addr := test.RandomAddressAt(base, i)
addOne(t, signer, kad, ab, addr)

// wait for one connection
waitConn(t, &conns)
}
// still zero
kDepth(t, kad, 0)

// depth is i+1
// now add peers from bin 0 and expect the depth
// to shift. the depth will be that of the shallowest
// unsaturated bin.
for i := 0; i < 7; i++ {
for j := 0; j < 3; j++ {
addr := test.RandomAddressAt(base, i)
addOne(t, signer, kad, ab, addr)
waitConn(t, &conns)
}
kDepth(t, kad, i+1)
}

// the last peer in bin 7 which is empty we insert manually,
addOne(t, signer, kad, ab, peers[len(peers)-1])
waitConn(t, &conns)
// depth is 7 because bin 7 is unsaturated (1 peer)
kDepth(t, kad, 7)

// depth is 8 because we have nnLowWatermark neighbors in bin 8
kDepth(t, kad, 8)
// expect shallow peers not in depth

// now add another ONE peer at depth+1, and expect the depth to still
for _, a := range shallowPeers {
if kad.IsWithinDepth(a) {
t.Fatal("expected address to outside of depth")
}
}

// now add another ONE peer at depth, and expect the depth to still
// stay 8, because the counter for nnLowWatermark would be reached only at the next
// depth iteration when calculating depth
addr := test.RandomAddressAt(base, 9)
addr := test.RandomAddressAt(base, 8)
addOne(t, signer, kad, ab, addr)
waitConn(t, &conns)
kDepth(t, kad, 8)
kDepth(t, kad, 7)

// fill the rest up to the bin before last and check that everything works at the edges
for i := 10; i < int(swarm.MaxBins)-1; i++ {
addr := test.RandomAddressAt(base, i)
// now fill bin 7 so that it is saturated, expect depth 8
for i := 0; i < 3; i++ {
addr := test.RandomAddressAt(base, 7)
addOne(t, signer, kad, ab, addr)
waitConn(t, &conns)
kDepth(t, kad, i-1)
}
kDepth(t, kad, 8)

// add a whole bunch of peers in bin 13, expect depth to stay at 13
// saturate bin 8
addr = test.RandomAddressAt(base, 8)
addOne(t, signer, kad, ab, addr)
waitConn(t, &conns)
kDepth(t, kad, 8)

var addrs []swarm.Address
// fill the rest up to the bin before last and check that everything works at the edges
for i := 9; i < int(swarm.MaxBins); i++ {
for j := 0; j < 4; j++ {
addr := test.RandomAddressAt(base, i)
addOne(t, signer, kad, ab, addr)
waitConn(t, &conns)
addrs = append(addrs, addr)
}
kDepth(t, kad, i)
}

// add a whole bunch of peers in bin 15, expect depth to stay at 15
for i := 0; i < 15; i++ {
addr = test.RandomAddressAt(base, 13)
addr = test.RandomAddressAt(base, 15)
addOne(t, signer, kad, ab, addr)
}

waitCounter(t, &conns, 15)
kDepth(t, kad, 13)

// add one at 14 - depth should be now 14
addr = test.RandomAddressAt(base, 14)
addOne(t, signer, kad, ab, addr)
kDepth(t, kad, 14)

addr2 := test.RandomAddressAt(base, 15)
addOne(t, signer, kad, ab, addr2)
kDepth(t, kad, 14)

addr3 := test.RandomAddressAt(base, 15)
addOne(t, signer, kad, ab, addr3)
kDepth(t, kad, 15)

// now remove that peer and check that the depth is back at 14
removeOne(kad, addr3)
// remove one at 14, depth should be 14
removeOne(kad, addrs[len(addrs)-5])
kDepth(t, kad, 14)

// remove the peer at bin 1, depth should be 1
removeOne(kad, peers[1])
kDepth(t, kad, 1)
}

func TestIsWithinDepth(t *testing.T) {
var (
conns int32 // how many connect calls were made to the p2p mock
base, kad, ab, _, signer = newTestKademlia(&conns, nil, kademlia.Options{})
peers []swarm.Address
)

if err := kad.Start(context.Background()); err != nil {
t.Fatal(err)
}
defer kad.Close()

for i := 0; i < 15; i++ {
addr := test.RandomAddressAt(base, i)
peers = append(peers, addr)
// empty bin 9 and expect depth 9
for i := 0; i < 4; i++ {
removeOne(kad, addrs[i])
}
kDepth(t, kad, 9)

add(t, signer, kad, ab, peers, 0, 15)
waitCounter(t, &conns, 15)

if kad.IsWithinDepth(peers[kad.NeighborhoodDepth()-1]) {
t.Fatalf("peer should NOT be in neignborhood")
if !kad.IsWithinDepth(addrs[0]) {
t.Fatal("expected address to be within depth")
}

if !kad.IsWithinDepth(peers[kad.NeighborhoodDepth()]) {
t.Fatalf("peer should be in neignborhood")
}
}

func TestEachNeighbor(t *testing.T) {
Expand Down

0 comments on commit 78aba90

Please sign in to comment.