From 78ac5a577a405c92260c454bc920a89e5b100d4a Mon Sep 17 00:00:00 2001 From: lash Date: Mon, 14 Jan 2019 11:48:08 +0100 Subject: [PATCH 1/5] swarm/network: Add potent method for healthy, fix saturation --- swarm/network/kademlia.go | 78 +++++++++-- swarm/network/kademlia_test.go | 243 +++++++++++++++++++++++++++++++-- 2 files changed, 298 insertions(+), 23 deletions(-) diff --git a/swarm/network/kademlia.go b/swarm/network/kademlia.go index f9b38fc48d..3d70e3ccd4 100644 --- a/swarm/network/kademlia.go +++ b/swarm/network/kademlia.go @@ -57,6 +57,7 @@ type KadParams struct { MaxProxDisplay int // number of rows the table shows NeighbourhoodSize int // nearest neighbour core minimum cardinality MinBinSize int // minimum number of peers in a row + HealthBinSize int // minimum number of peers per bin MaxBinSize int // maximum number of peers in a row before pruning RetryInterval int64 // initial interval before a peer is first redialed RetryExponent int // exponent to multiply retry intervals with @@ -71,6 +72,7 @@ func NewKadParams() *KadParams { MaxProxDisplay: 16, NeighbourhoodSize: 2, MinBinSize: 2, + HealthBinSize: 1, MaxBinSize: 4, RetryInterval: 4200000000, // 4.2 sec MaxRetries: 42, @@ -688,6 +690,9 @@ func (k *Kademlia) saturation() int { if prev < 0 { return 0 } + if prev < 0 { + prev = 0 + } return prev } @@ -715,17 +720,16 @@ func (k *Kademlia) knowNeighbours(addrs [][]byte) (got bool, n int, missing [][] // then we don't know all our neighbors // (which sadly is all too common in modern society) var gots int - var culprits [][]byte for _, p := range addrs { pk := common.Bytes2Hex(p) if pm[pk] { gots++ } else { log.Trace(fmt.Sprintf("%08x: known nearest neighbour %s not found", k.base, pk)) - culprits = append(culprits, p) + missing = append(missing, p) } } - return gots == len(addrs), gots, culprits + return gots == len(addrs), gots, missing } // connectedNeighbours tests if all neighbours in the peerpot @@ -750,18 +754,49 @@ func (k *Kademlia) connectedNeighbours(peers [][]byte) (got bool, n int, missing // iterate through nearest neighbors in the peerpot map // if we can't find the neighbor in the map we created above // then we don't know all our neighbors - var gots int - var culprits [][]byte + var connects int for _, p := range peers { pk := common.Bytes2Hex(p) if pm[pk] { - gots++ + connects++ } else { log.Trace(fmt.Sprintf("%08x: ExpNN: %s not found", k.base, pk)) - culprits = append(culprits, p) + missing = append(missing, p) + } + } + return connects == len(peers), connects, missing +} + +// connectedPotential checks whether the node is connected to a health minimum of peers it knows about in bins that are shallower than depth +// it returns an array of bin proximity orders for which this is not the case +// TODO move to separate testing tools file +func (k *Kademlia) connectedPotential() (missing []int) { + pk := make(map[int]int) + pc := make(map[int]int) + + // create a map with all bins that have known peers + // in order deepest to shallowest compared to the kademlia base address + depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) + k.eachAddr(nil, 255, func(_ *BzzAddr, po int) bool { + pk[po]++ + return true + }) + k.eachConn(nil, 255, func(_ *Peer, po int) bool { + pc[po]++ + return true + }) + + for po, v := range pk { + if pc[po] == v { + continue + } else if po >= depth && pc[po] != pk[po] { + missing = append(missing, po) + } else if pc[po] < k.HealthBinSize { + missing = append(missing, po) } + } - return gots == len(peers), gots, culprits + return missing } // Health state of the Kademlia @@ -773,7 +808,8 @@ type Health struct { ConnectNN bool // whether node is connected to all its neighbours CountConnectNN int // amount of neighbours connected to MissingConnectNN [][]byte // which neighbours we should have been connected to but we're not - Saturated bool // whether we are connected to all the peers we would have liked to + Saturation int // whether we are connected to all the peers we would have liked to + Potent bool // whether we are connected to a minimum of peers in all the bins we have known peers in Hive string } @@ -796,14 +832,28 @@ func (k *Kademlia) Healthy(pp *PeerPot) *Health { depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) saturated := k.saturation() < depth log.Trace(fmt.Sprintf("%08x: healthy: knowNNs: %v, gotNNs: %v, saturated: %v\n", k.base, knownn, gotnn, saturated)) + /* + ======= + + connectnn, countconnectnn, missingconnectnn := k.connectedNeighbours(pp.NNSet) + knownn, countknownn, missingknownn := k.knowNeighbours(pp.NNSet) + saturation := k.saturation() + impotentBins := k.connectedPotential() + potent := len(impotentBins) == 0 + + log.Trace(fmt.Sprintf("%08x: healthy: knowNNs: %v, connectNNs: %v, saturation: %v\n", k.base, knownn, connectnn, saturation)) + + >>>>>>> swarm/network: Add potent method for healthy, fix saturation + */ return &Health{ KnowNN: knownn, CountKnowNN: countknownn, - MissingKnowNN: culpritsknownn, - ConnectNN: gotnn, - CountConnectNN: countgotnn, - MissingConnectNN: culpritsgotnn, - Saturated: saturated, + MissingKnowNN: missingknownn, + ConnectNN: connectnn, + CountConnectNN: countconnectnn, + MissingConnectNN: missingconnectnn, + Saturation: saturation, + Potent: potent, Hive: k.string(), } } diff --git a/swarm/network/kademlia_test.go b/swarm/network/kademlia_test.go index 8a724756b6..9134e730e0 100644 --- a/swarm/network/kademlia_test.go +++ b/swarm/network/kademlia_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 The go-ethereum Authors +// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -30,6 +30,11 @@ import ( "github.com/ethereum/go-ethereum/swarm/pot" ) +var ( + minBinSize = 2 + minProxBinSize = 2 +) + func init() { h := log.LvlFilterHandler(log.LvlWarn, log.StreamHandler(os.Stderr, log.TerminalFormat(true))) log.Root().SetHandler(h) @@ -168,10 +173,7 @@ func TestNeighbourhoodDepth(t *testing.T) { testNum++ } -// TestHealthStrict tests the simplest definition of health -// Which means whether we are connected to all neighbors we know of -func TestHealthStrict(t *testing.T) { - +func TestHealthSimple(t *testing.T) { // base address is all zeros // no peers // unhealthy (and lonely) @@ -241,10 +243,198 @@ func (tk *testKademlia) checkHealth(expectHealthy bool, expectSaturation bool) { pp := NewPeerPotMap(tk.NeighbourhoodSize, addrs) healthParams := tk.Healthy(pp[kid]) - // definition of health, all conditions but be true: - // - we at least know one peer - // - we know all neighbors - // - we are connected to all known neighbors + /* + ======= + k := newTestKademlia("11111111") + assertHealthSimple(t, k, false) + + // know one peer but not connected + // unhealthy + Register(k, "11100000") + log.Trace(k.String()) + assertHealthSimple(t, k, false) + + // know one peer and connected + // healthy + On(k, "11100000") + assertHealthSimple(t, k, true) + + // know two peers, only one connected + // unhealthy + Register(k, "11111100") + log.Trace(k.String()) + assertHealthSimple(t, k, false) + + // know two peers and connected to both + // healthy + On(k, "11111100") + assertHealthSimple(t, k, true) + + // know three peers, connected to the two deepest + // healthy + Register(k, "00000000") + log.Trace(k.String()) + assertHealthSimple(t, k, true) + + // know three peers, connected to all three + // healthy + On(k, "00000000") + assertHealthSimple(t, k, true) + + // add fourth peer deeper than current depth + // unhealthy + Register(k, "11110000") + log.Trace(k.String()) + assertHealthSimple(t, k, false) + + // connected to three deepest peers + // healthy + On(k, "11110000") + assertHealthSimple(t, k, true) + + // add additional peer in same bin as deepest peer + // unhealthy + Register(k, "11111101") + log.Trace(k.String()) + assertHealthSimple(t, k, false) + + // four deepest of five peers connected + // healthy + On(k, "11111101") + assertHealthSimple(t, k, true) + } + + func TestHealthPotential(t *testing.T) { + k := newTestKademlia("11111111") + assertHealthPotential(t, k, true) + + // know one peer but not connected + // not potent and not healthy + Register(k, "11100000") + log.Trace(k.String()) + assertHealthPotential(t, k, false) + + // know one peer and connected + // healthy and potent + On(k, "11100000") + assertHealthPotential(t, k, true) + + // know two peers, only one connected + // not healthy, not potent + Register(k, "11111100") + log.Trace(k.String()) + assertHealthPotential(t, k, false) + + // know two peers and connected to both + // healthy and potent + On(k, "11111100") + assertHealthPotential(t, k, true) + + // know three peers, connected to the two deepest + // healthy but not potent + Register(k, "00000000") + log.Trace(k.String()) + assertHealthPotential(t, k, false) + + // know three peers, connected to all three + // healthy and potent + On(k, "00000000") + assertHealthPotential(t, k, true) + + // add another peer in the zero-bin + // still healthy and potent + Register(k, "00000000") + log.Trace(k.String()) + assertHealthPotential(t, k, true) + + // add peers until depth + // healthy but not potent + Register(k, "10000000") + Register(k, "11000000") + log.Trace(k.String()) + assertHealthPotential(t, k, false) + + // add fourth peer deeper than current depth + // still healthy, still not potent + On(k, "10000000") + log.Trace(k.String()) + assertHealthPotential(t, k, false) + + // add fourth peer deeper than current depth + // healthy and potent + On(k, "11000000") + log.Trace(k.String()) + assertHealthPotential(t, k, true) + } + + func TestHealthSaturation(t *testing.T) { + baseAddressBytes := RandomAddr().OAddr + k := NewKademlia(baseAddressBytes, NewKadParams()) + + baseAddress := pot.NewAddressFromBytes(baseAddressBytes) + + // add first connected neighbor + // saturation 0 + addr := pot.RandomAddressAt(baseAddress, 3) + peer := newTestDiscoveryPeer(addr, k) + k.On(peer) + assertHealthSaturation(t, k, 0) + + // add second connected neighbor + // saturation 0 + addr = pot.RandomAddressAt(baseAddress, 4) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + assertHealthSaturation(t, k, 0) + + // connect peer in zero-bin + // saturation 0 + addr = pot.RandomAddressAt(baseAddress, 0) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + assertHealthSaturation(t, k, 0) + + // connect another peer in zero-bin + // saturation 1 + addr = pot.RandomAddressAt(baseAddress, 0) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + assertHealthSaturation(t, k, 1) + + // one connection in zero-bin, two in one-bin + // saturation 0 + k.Off(peer) + addr = pot.RandomAddressAt(baseAddress, 1) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + addr = pot.RandomAddressAt(baseAddress, 1) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + assertHealthSaturation(t, k, 0) + } + + // retrieves the health object based on the current connectivity of the given kademlia + func getHealth(k *Kademlia) *Health { + kid := common.Bytes2Hex(k.BaseAddr()) + addrs := [][]byte{k.BaseAddr()} + k.EachAddr(nil, 255, func(addr *BzzAddr, po int) bool { + addrs = append(addrs, addr.Address()) + return true + }) + pp := NewPeerPotMap(k.NeighbourhoodSize, addrs) + return k.Healthy(pp[kid]) + } + >>>>>>> swarm/network: Add potent method for healthy, fix saturation + + // evaluates the simplest definition of health: + // all conditions must be true: + // - we at least know one peer + // - we know all neighbors + // - we are connected to all known neighbors + func assertHealthSimple(t *testing.T, k *Kademlia, expectHealthy bool) { + t.Helper() + healthParams := getHealth(k) + */ health := healthParams.KnowNN && healthParams.ConnectNN && healthParams.CountKnowNN > 0 if expectHealthy != health { tk.t.Fatalf("expected kademlia health %v, is %v\n%v", expectHealthy, health, tk.String()) @@ -255,6 +445,32 @@ func (tk *testKademlia) checkSuggestPeer(expAddr string, expDepth int, expChange tk.t.Helper() addr, depth, changed := tk.SuggestPeer() log.Trace("suggestPeer return", "addr", addr, "depth", depth, "changed", changed) + /* + ======= + // evaluates healthiness by taking into account potential connections + // additional conditions for healthiness + // - IF we know of peers in bins shallower than depth, connected to at least HealthBinSize of them + func assertHealthPotential(t *testing.T, k *Kademlia, expectPotent bool) { + t.Helper() + healthParams := getHealth(k) + if expectPotent != healthParams.Potent { + t.Fatalf("expected kademlia potency %v, is %v\n%v", expectPotent, healthParams.Potent, k.String()) + } + } + + func assertHealthSaturation(t *testing.T, k *Kademlia, expectSaturation int) { + t.Helper() + healthParams := getHealth(k) + if expectSaturation != healthParams.Saturation { + t.Fatalf("expected kademlia saturation %v, is %v\n%v", expectSaturation, healthParams.Saturation, k.String()) + } + } + + func testSuggestPeer(k *Kademlia, expAddr string, expPo int, expWant bool) error { + addr, o, want := k.SuggestPeer() + log.Trace("suggestpeer return", "a", addr, "o", o, "want", want) + >>>>>>> swarm/network: Add potent method for healthy, fix saturation + */ if binStr(addr) != expAddr { tk.t.Fatalf("incorrect peer address suggested. expected %v, got %v", expAddr, binStr(addr)) } @@ -445,6 +661,15 @@ func TestKademliaHiveString(t *testing.T) { tk.Register("10000000", "10000001") tk.MaxProxDisplay = 8 h := tk.String() + /* + ======= + k := newTestKademlia("00000000") + On(k, "01000000", "00100000") + Register(k, "10000000", "10000001") + k.MaxProxDisplay = 8 + h := k.String() + >>>>>>> swarm/network: Add potent method for healthy, fix saturation + */ expH := "\n=========================================================================\nMon Feb 27 12:10:28 UTC 2017 KΛÐΞMLIΛ hive: queen's address: 000000\npopulation: 2 (4), NeighbourhoodSize: 2, MinBinSize: 2, MaxBinSize: 4\n============ DEPTH: 0 ==========================================\n000 0 | 2 8100 (0) 8000 (0)\n001 1 4000 | 1 4000 (0)\n002 1 2000 | 1 2000 (0)\n003 0 | 0\n004 0 | 0\n005 0 | 0\n006 0 | 0\n007 0 | 0\n=========================================================================" if expH[104:] != h[104:] { t.Fatalf("incorrect hive output. expected %v, got %v", expH, h) From 4341a07e46bce9d3524c804baa21eb5b8c273b1b Mon Sep 17 00:00:00 2001 From: Fabio Barone Date: Tue, 22 Jan 2019 16:58:37 -0500 Subject: [PATCH 2/5] swarm/network: added as new Healthy parameter --- swarm/network/kademlia.go | 44 ++-- swarm/network/kademlia_test.go | 376 +++++++++++++-------------------- 2 files changed, 160 insertions(+), 260 deletions(-) diff --git a/swarm/network/kademlia.go b/swarm/network/kademlia.go index 3d70e3ccd4..26d6e6db0d 100644 --- a/swarm/network/kademlia.go +++ b/swarm/network/kademlia.go @@ -767,10 +767,10 @@ func (k *Kademlia) connectedNeighbours(peers [][]byte) (got bool, n int, missing return connects == len(peers), connects, missing } -// connectedPotential checks whether the node is connected to a health minimum of peers it knows about in bins that are shallower than depth +// getWeakBins checks whether the node is connected to a health minimum of peers it knows about in bins that are shallower than depth // it returns an array of bin proximity orders for which this is not the case // TODO move to separate testing tools file -func (k *Kademlia) connectedPotential() (missing []int) { +func (k *Kademlia) getWeakBins() (missing []int) { pk := make(map[int]int) pc := make(map[int]int) @@ -808,8 +808,8 @@ type Health struct { ConnectNN bool // whether node is connected to all its neighbours CountConnectNN int // amount of neighbours connected to MissingConnectNN [][]byte // which neighbours we should have been connected to but we're not - Saturation int // whether we are connected to all the peers we would have liked to - Potent bool // whether we are connected to a minimum of peers in all the bins we have known peers in + Saturated bool // whether we are connected to all the peers we would have liked to + Robust bool // whether we are connected to a minimum of peers in all the bins we have known peers in Hive string } @@ -827,33 +827,25 @@ func (k *Kademlia) Healthy(pp *PeerPot) *Health { if len(pp.NNSet) < k.NeighbourhoodSize { log.Warn("peerpot NNSet < NeighbourhoodSize") } - gotnn, countgotnn, culpritsgotnn := k.connectedNeighbours(pp.NNSet) - knownn, countknownn, culpritsknownn := k.knowNeighbours(pp.NNSet) + connectNN, countConnectNN, missingConnectNN := k.connectedNeighbours(pp.NNSet) + knownNN, countKnownNN, missingKnownNN := k.knowNeighbours(pp.NNSet) depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) - saturated := k.saturation() < depth - log.Trace(fmt.Sprintf("%08x: healthy: knowNNs: %v, gotNNs: %v, saturated: %v\n", k.base, knownn, gotnn, saturated)) - /* - ======= + isSaturated := k.saturation() < depth - connectnn, countconnectnn, missingconnectnn := k.connectedNeighbours(pp.NNSet) - knownn, countknownn, missingknownn := k.knowNeighbours(pp.NNSet) - saturation := k.saturation() - impotentBins := k.connectedPotential() - potent := len(impotentBins) == 0 + weakBins := k.getWeakBins() + isRobust := len(weakBins) == 0 - log.Trace(fmt.Sprintf("%08x: healthy: knowNNs: %v, connectNNs: %v, saturation: %v\n", k.base, knownn, connectnn, saturation)) + log.Trace(fmt.Sprintf("%08x: healthy: knowNNs: %v, gotNNs: %v, isSaturated: %v, isRobust:%v\n", k.base, knownNN, connectNN, isSaturated, isRobust)) - >>>>>>> swarm/network: Add potent method for healthy, fix saturation - */ return &Health{ - KnowNN: knownn, - CountKnowNN: countknownn, - MissingKnowNN: missingknownn, - ConnectNN: connectnn, - CountConnectNN: countconnectnn, - MissingConnectNN: missingconnectnn, - Saturation: saturation, - Potent: potent, + KnowNN: knownNN, + CountKnowNN: countKnownNN, + MissingKnowNN: missingKnownNN, + ConnectNN: connectNN, + CountConnectNN: countConnectNN, + MissingConnectNN: missingConnectNN, + Saturated: isSaturated, + Robust: isRobust, Hive: k.string(), } } diff --git a/swarm/network/kademlia_test.go b/swarm/network/kademlia_test.go index 9134e730e0..0beda100fe 100644 --- a/swarm/network/kademlia_test.go +++ b/swarm/network/kademlia_test.go @@ -30,11 +30,6 @@ import ( "github.com/ethereum/go-ethereum/swarm/pot" ) -var ( - minBinSize = 2 - minProxBinSize = 2 -) - func init() { h := log.LvlFilterHandler(log.LvlWarn, log.StreamHandler(os.Stderr, log.TerminalFormat(true))) log.Root().SetHandler(h) @@ -173,7 +168,7 @@ func TestNeighbourhoodDepth(t *testing.T) { testNum++ } -func TestHealthSimple(t *testing.T) { +func TestHealthStrict(t *testing.T) { // base address is all zeros // no peers // unhealthy (and lonely) @@ -231,7 +226,7 @@ func TestHealthSimple(t *testing.T) { tk.checkHealth(true, false) } -func (tk *testKademlia) checkHealth(expectHealthy bool, expectSaturation bool) { +func (tk *testKademlia) checkHealth(expectHealthy bool, expectSaturated bool) { tk.t.Helper() kid := common.Bytes2Hex(tk.BaseAddr()) addrs := [][]byte{tk.BaseAddr()} @@ -243,234 +238,156 @@ func (tk *testKademlia) checkHealth(expectHealthy bool, expectSaturation bool) { pp := NewPeerPotMap(tk.NeighbourhoodSize, addrs) healthParams := tk.Healthy(pp[kid]) - /* - ======= - k := newTestKademlia("11111111") - assertHealthSimple(t, k, false) - - // know one peer but not connected - // unhealthy - Register(k, "11100000") - log.Trace(k.String()) - assertHealthSimple(t, k, false) - - // know one peer and connected - // healthy - On(k, "11100000") - assertHealthSimple(t, k, true) - - // know two peers, only one connected - // unhealthy - Register(k, "11111100") - log.Trace(k.String()) - assertHealthSimple(t, k, false) - - // know two peers and connected to both - // healthy - On(k, "11111100") - assertHealthSimple(t, k, true) - - // know three peers, connected to the two deepest - // healthy - Register(k, "00000000") - log.Trace(k.String()) - assertHealthSimple(t, k, true) - - // know three peers, connected to all three - // healthy - On(k, "00000000") - assertHealthSimple(t, k, true) - - // add fourth peer deeper than current depth - // unhealthy - Register(k, "11110000") - log.Trace(k.String()) - assertHealthSimple(t, k, false) - - // connected to three deepest peers - // healthy - On(k, "11110000") - assertHealthSimple(t, k, true) - - // add additional peer in same bin as deepest peer - // unhealthy - Register(k, "11111101") - log.Trace(k.String()) - assertHealthSimple(t, k, false) - - // four deepest of five peers connected - // healthy - On(k, "11111101") - assertHealthSimple(t, k, true) - } - - func TestHealthPotential(t *testing.T) { - k := newTestKademlia("11111111") - assertHealthPotential(t, k, true) - - // know one peer but not connected - // not potent and not healthy - Register(k, "11100000") - log.Trace(k.String()) - assertHealthPotential(t, k, false) - - // know one peer and connected - // healthy and potent - On(k, "11100000") - assertHealthPotential(t, k, true) - - // know two peers, only one connected - // not healthy, not potent - Register(k, "11111100") - log.Trace(k.String()) - assertHealthPotential(t, k, false) - - // know two peers and connected to both - // healthy and potent - On(k, "11111100") - assertHealthPotential(t, k, true) - - // know three peers, connected to the two deepest - // healthy but not potent - Register(k, "00000000") - log.Trace(k.String()) - assertHealthPotential(t, k, false) - - // know three peers, connected to all three - // healthy and potent - On(k, "00000000") - assertHealthPotential(t, k, true) - - // add another peer in the zero-bin - // still healthy and potent - Register(k, "00000000") - log.Trace(k.String()) - assertHealthPotential(t, k, true) - - // add peers until depth - // healthy but not potent - Register(k, "10000000") - Register(k, "11000000") - log.Trace(k.String()) - assertHealthPotential(t, k, false) - - // add fourth peer deeper than current depth - // still healthy, still not potent - On(k, "10000000") - log.Trace(k.String()) - assertHealthPotential(t, k, false) - - // add fourth peer deeper than current depth - // healthy and potent - On(k, "11000000") - log.Trace(k.String()) - assertHealthPotential(t, k, true) - } - - func TestHealthSaturation(t *testing.T) { - baseAddressBytes := RandomAddr().OAddr - k := NewKademlia(baseAddressBytes, NewKadParams()) - - baseAddress := pot.NewAddressFromBytes(baseAddressBytes) - - // add first connected neighbor - // saturation 0 - addr := pot.RandomAddressAt(baseAddress, 3) - peer := newTestDiscoveryPeer(addr, k) - k.On(peer) - assertHealthSaturation(t, k, 0) - - // add second connected neighbor - // saturation 0 - addr = pot.RandomAddressAt(baseAddress, 4) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - assertHealthSaturation(t, k, 0) - - // connect peer in zero-bin - // saturation 0 - addr = pot.RandomAddressAt(baseAddress, 0) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - assertHealthSaturation(t, k, 0) - - // connect another peer in zero-bin - // saturation 1 - addr = pot.RandomAddressAt(baseAddress, 0) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - assertHealthSaturation(t, k, 1) - - // one connection in zero-bin, two in one-bin - // saturation 0 - k.Off(peer) - addr = pot.RandomAddressAt(baseAddress, 1) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - addr = pot.RandomAddressAt(baseAddress, 1) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - assertHealthSaturation(t, k, 0) - } - - // retrieves the health object based on the current connectivity of the given kademlia - func getHealth(k *Kademlia) *Health { - kid := common.Bytes2Hex(k.BaseAddr()) - addrs := [][]byte{k.BaseAddr()} - k.EachAddr(nil, 255, func(addr *BzzAddr, po int) bool { - addrs = append(addrs, addr.Address()) - return true - }) - pp := NewPeerPotMap(k.NeighbourhoodSize, addrs) - return k.Healthy(pp[kid]) - } - >>>>>>> swarm/network: Add potent method for healthy, fix saturation - - // evaluates the simplest definition of health: - // all conditions must be true: - // - we at least know one peer - // - we know all neighbors - // - we are connected to all known neighbors - func assertHealthSimple(t *testing.T, k *Kademlia, expectHealthy bool) { - t.Helper() - healthParams := getHealth(k) - */ health := healthParams.KnowNN && healthParams.ConnectNN && healthParams.CountKnowNN > 0 if expectHealthy != health { tk.t.Fatalf("expected kademlia health %v, is %v\n%v", expectHealthy, health, tk.String()) } } +func TestIsRobust(t *testing.T) { + tk := newTestKademlia(t, "11111111") + isRobust(t, tk, true) + + // know one peer but not connected + // not robust and not healthy + tk.Register("11100000") + log.Trace(tk.String()) + isRobust(t, tk, false) + + // know one peer and connected + // healthy and robust + tk.On("11100000") + isRobust(t, tk, true) + + // know two peers, only one connected + // not healthy, not robust + tk.Register("11111100") + log.Trace(tk.String()) + isRobust(t, tk, false) + + // know two peers and connected to both + // healthy and robust + tk.On("11111100") + isRobust(t, tk, true) + + // know three peers, connected to the two deepest + // healthy but not robust + tk.Register("00000000") + log.Trace(tk.String()) + isRobust(t, tk, false) + + // know three peers, connected to all three + // healthy and robust + tk.On("00000000") + isRobust(t, tk, true) + + // add another peer in the zero-bin + // still healthy and robust + tk.Register("00000000") + log.Trace(tk.String()) + isRobust(t, tk, true) + + // add peers until depth + // healthy but not robust + tk.Register("10000000") + tk.Register("11000000") + log.Trace(tk.String()) + isRobust(t, tk, false) + + // add fourth peer deeper than current depth + // still healthy, still not robust + tk.On("10000000") + log.Trace(tk.String()) + isRobust(t, tk, false) + + // add fourth peer deeper than current depth + // healthy and robust + tk.On("11000000") + log.Trace(tk.String()) + isRobust(t, tk, true) +} + +func TestIsSaturated(t *testing.T) { + baseAddressBytes := RandomAddr().OAddr + k := NewKademlia(baseAddressBytes, NewKadParams()) + + baseAddress := pot.NewAddressFromBytes(baseAddressBytes) + + // add first connected neighbor + // saturated false + addr := pot.RandomAddressAt(baseAddress, 3) + peer := newTestDiscoveryPeer(addr, k) + k.On(peer) + isSaturated(t, k, false) + + // add second connected neighbor + // saturated false + addr = pot.RandomAddressAt(baseAddress, 4) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + isSaturated(t, k, false) + + // connect peer in zero-bin + // saturated true + addr = pot.RandomAddressAt(baseAddress, 0) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + isSaturated(t, k, true) + + // connect another peer in zero-bin + // saturated false + addr = pot.RandomAddressAt(baseAddress, 0) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + isSaturated(t, k, false) + + // one connection in zero-bin, two in one-bin + // saturated true + k.Off(peer) + addr = pot.RandomAddressAt(baseAddress, 1) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + addr = pot.RandomAddressAt(baseAddress, 1) + peer = newTestDiscoveryPeer(addr, k) + k.On(peer) + isSaturated(t, k, true) +} + +// retrieves the health object based on the current connectivity of the given kademlia +func getHealth(k *Kademlia) *Health { + kid := common.Bytes2Hex(k.BaseAddr()) + addrs := [][]byte{k.BaseAddr()} + k.EachAddr(nil, 255, func(addr *BzzAddr, po int) bool { + addrs = append(addrs, addr.Address()) + return true + }) + pp := NewPeerPotMap(k.NeighbourhoodSize, addrs) + return k.Healthy(pp[kid]) +} + +// evaluates healthiness by taking into account robustial connections +// additional conditions for healthiness +// - IF we know of peers in bins shallower than depth, connected to at least HealthBinSize of them +func isRobust(t *testing.T, k *testKademlia, expectIsRobust bool) { + t.Helper() + healthParams := getHealth(k.Kademlia) + if expectIsRobust != healthParams.Robust { + t.Fatalf("expected kademlia potency %v, is %v\n%v", expectIsRobust, healthParams.Robust, k.String()) + } +} + +func isSaturated(t *testing.T, k *Kademlia, expectSaturated bool) { + t.Helper() + healthParams := getHealth(k) + if expectSaturated != healthParams.Saturated { + t.Fatalf("expected kademlia saturation %v, is %v\n%v", expectSaturated, healthParams.Saturated, k.String()) + } +} + func (tk *testKademlia) checkSuggestPeer(expAddr string, expDepth int, expChanged bool) { tk.t.Helper() addr, depth, changed := tk.SuggestPeer() log.Trace("suggestPeer return", "addr", addr, "depth", depth, "changed", changed) - /* - ======= - // evaluates healthiness by taking into account potential connections - // additional conditions for healthiness - // - IF we know of peers in bins shallower than depth, connected to at least HealthBinSize of them - func assertHealthPotential(t *testing.T, k *Kademlia, expectPotent bool) { - t.Helper() - healthParams := getHealth(k) - if expectPotent != healthParams.Potent { - t.Fatalf("expected kademlia potency %v, is %v\n%v", expectPotent, healthParams.Potent, k.String()) - } - } - - func assertHealthSaturation(t *testing.T, k *Kademlia, expectSaturation int) { - t.Helper() - healthParams := getHealth(k) - if expectSaturation != healthParams.Saturation { - t.Fatalf("expected kademlia saturation %v, is %v\n%v", expectSaturation, healthParams.Saturation, k.String()) - } - } - - func testSuggestPeer(k *Kademlia, expAddr string, expPo int, expWant bool) error { - addr, o, want := k.SuggestPeer() - log.Trace("suggestpeer return", "a", addr, "o", o, "want", want) - >>>>>>> swarm/network: Add potent method for healthy, fix saturation - */ if binStr(addr) != expAddr { tk.t.Fatalf("incorrect peer address suggested. expected %v, got %v", expAddr, binStr(addr)) } @@ -661,15 +578,6 @@ func TestKademliaHiveString(t *testing.T) { tk.Register("10000000", "10000001") tk.MaxProxDisplay = 8 h := tk.String() - /* - ======= - k := newTestKademlia("00000000") - On(k, "01000000", "00100000") - Register(k, "10000000", "10000001") - k.MaxProxDisplay = 8 - h := k.String() - >>>>>>> swarm/network: Add potent method for healthy, fix saturation - */ expH := "\n=========================================================================\nMon Feb 27 12:10:28 UTC 2017 KΛÐΞMLIΛ hive: queen's address: 000000\npopulation: 2 (4), NeighbourhoodSize: 2, MinBinSize: 2, MaxBinSize: 4\n============ DEPTH: 0 ==========================================\n000 0 | 2 8100 (0) 8000 (0)\n001 1 4000 | 1 4000 (0)\n002 1 2000 | 1 2000 (0)\n003 0 | 0\n004 0 | 0\n005 0 | 0\n006 0 | 0\n007 0 | 0\n=========================================================================" if expH[104:] != h[104:] { t.Fatalf("incorrect hive output. expected %v, got %v", expH, h) From fabaa963177c066dbb3808925fbcf568405f5ef3 Mon Sep 17 00:00:00 2001 From: Fabio Barone Date: Thu, 24 Jan 2019 13:18:27 -0500 Subject: [PATCH 3/5] swarm/network: new IsHealthyStrict function; removed saturation tests --- swarm/network/kademlia.go | 12 ++-- swarm/network/kademlia_test.go | 64 ++----------------- swarm/network/simulation/kademlia.go | 4 +- .../simulations/discovery/discovery_test.go | 12 ++-- 4 files changed, 20 insertions(+), 72 deletions(-) diff --git a/swarm/network/kademlia.go b/swarm/network/kademlia.go index 26d6e6db0d..e1a905ede9 100644 --- a/swarm/network/kademlia.go +++ b/swarm/network/kademlia.go @@ -690,9 +690,6 @@ func (k *Kademlia) saturation() int { if prev < 0 { return 0 } - if prev < 0 { - prev = 0 - } return prev } @@ -813,7 +810,12 @@ type Health struct { Hive string } -// Healthy reports the health state of the kademlia connectivity +// IsHealthyStrict return the strict interpretation of `Healthy` given a `Health` struct +func (h *Health) IsHealthyStrict() bool { + return h.KnowNN && h.ConnectNN && h.CountKnowNN > 0 && h.Robust +} + +// GetHealthInfo reports the health state of the kademlia connectivity // // The PeerPot argument provides an all-knowing view of the network // The resulting Health object is a result of comparisons between @@ -821,7 +823,7 @@ type Health struct { // what SHOULD it have been when we take all we know about the network into consideration. // // used for testing only -func (k *Kademlia) Healthy(pp *PeerPot) *Health { +func (k *Kademlia) GetHealthInfo(pp *PeerPot) *Health { k.lock.RLock() defer k.lock.RUnlock() if len(pp.NNSet) < k.NeighbourhoodSize { diff --git a/swarm/network/kademlia_test.go b/swarm/network/kademlia_test.go index 0beda100fe..d59d687cb6 100644 --- a/swarm/network/kademlia_test.go +++ b/swarm/network/kademlia_test.go @@ -196,9 +196,9 @@ func TestHealthStrict(t *testing.T) { tk.checkHealth(true, false) // know three peers, connected to the two deepest - // healthy + // unhealthy (not robust) tk.Register("00000000") - tk.checkHealth(true, false) + tk.checkHealth(false, false) // know three peers, connected to all three // healthy @@ -236,9 +236,9 @@ func (tk *testKademlia) checkHealth(expectHealthy bool, expectSaturated bool) { }) pp := NewPeerPotMap(tk.NeighbourhoodSize, addrs) - healthParams := tk.Healthy(pp[kid]) + healthParams := tk.GetHealthInfo(pp[kid]) - health := healthParams.KnowNN && healthParams.ConnectNN && healthParams.CountKnowNN > 0 + health := healthParams.IsHealthyStrict() if expectHealthy != health { tk.t.Fatalf("expected kademlia health %v, is %v\n%v", expectHealthy, health, tk.String()) } @@ -307,52 +307,6 @@ func TestIsRobust(t *testing.T) { isRobust(t, tk, true) } -func TestIsSaturated(t *testing.T) { - baseAddressBytes := RandomAddr().OAddr - k := NewKademlia(baseAddressBytes, NewKadParams()) - - baseAddress := pot.NewAddressFromBytes(baseAddressBytes) - - // add first connected neighbor - // saturated false - addr := pot.RandomAddressAt(baseAddress, 3) - peer := newTestDiscoveryPeer(addr, k) - k.On(peer) - isSaturated(t, k, false) - - // add second connected neighbor - // saturated false - addr = pot.RandomAddressAt(baseAddress, 4) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - isSaturated(t, k, false) - - // connect peer in zero-bin - // saturated true - addr = pot.RandomAddressAt(baseAddress, 0) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - isSaturated(t, k, true) - - // connect another peer in zero-bin - // saturated false - addr = pot.RandomAddressAt(baseAddress, 0) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - isSaturated(t, k, false) - - // one connection in zero-bin, two in one-bin - // saturated true - k.Off(peer) - addr = pot.RandomAddressAt(baseAddress, 1) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - addr = pot.RandomAddressAt(baseAddress, 1) - peer = newTestDiscoveryPeer(addr, k) - k.On(peer) - isSaturated(t, k, true) -} - // retrieves the health object based on the current connectivity of the given kademlia func getHealth(k *Kademlia) *Health { kid := common.Bytes2Hex(k.BaseAddr()) @@ -362,7 +316,7 @@ func getHealth(k *Kademlia) *Health { return true }) pp := NewPeerPotMap(k.NeighbourhoodSize, addrs) - return k.Healthy(pp[kid]) + return k.GetHealthInfo(pp[kid]) } // evaluates healthiness by taking into account robustial connections @@ -376,14 +330,6 @@ func isRobust(t *testing.T, k *testKademlia, expectIsRobust bool) { } } -func isSaturated(t *testing.T, k *Kademlia, expectSaturated bool) { - t.Helper() - healthParams := getHealth(k) - if expectSaturated != healthParams.Saturated { - t.Fatalf("expected kademlia saturation %v, is %v\n%v", expectSaturated, healthParams.Saturated, k.String()) - } -} - func (tk *testKademlia) checkSuggestPeer(expAddr string, expDepth int, expChanged bool) { tk.t.Helper() addr, depth, changed := tk.SuggestPeer() diff --git a/swarm/network/simulation/kademlia.go b/swarm/network/simulation/kademlia.go index 6d8d0e0a2c..b7ef024a68 100644 --- a/swarm/network/simulation/kademlia.go +++ b/swarm/network/simulation/kademlia.go @@ -64,13 +64,13 @@ func (s *Simulation) WaitTillHealthy(ctx context.Context) (ill map[enode.ID]*net addr := common.Bytes2Hex(k.BaseAddr()) pp := ppmap[addr] //call Healthy RPC - h := k.Healthy(pp) + h := k.GetHealthInfo(pp) //print info log.Debug(k.String()) log.Debug("kademlia", "connectNN", h.ConnectNN, "knowNN", h.KnowNN) log.Debug("kademlia", "health", h.ConnectNN && h.KnowNN, "addr", hex.EncodeToString(k.BaseAddr()), "node", id) log.Debug("kademlia", "ill condition", !h.ConnectNN, "addr", hex.EncodeToString(k.BaseAddr()), "node", id) - if !h.ConnectNN { + if !h.IsHealthyStrict() { ill[id] = k } } diff --git a/swarm/network/simulations/discovery/discovery_test.go b/swarm/network/simulations/discovery/discovery_test.go index e695bc4ac0..897dba18a5 100644 --- a/swarm/network/simulations/discovery/discovery_test.go +++ b/swarm/network/simulations/discovery/discovery_test.go @@ -267,11 +267,11 @@ func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simul } healthy := &network.Health{} - if err := client.Call(&healthy, "hive_healthy", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { + if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { return false, fmt.Errorf("error getting node health: %s", err) } - log.Debug(fmt.Sprintf("node %4s healthy: connected nearest neighbours: %v, know nearest neighbours: %v,\n\n%v", id, healthy.ConnectNN, healthy.KnowNN, healthy.Hive)) - return healthy.KnowNN && healthy.ConnectNN, nil + log.Debug(fmt.Sprintf("node %4s healthy: connected nearest neighbours: %v, know nearest neighbours: %v, is robuts: %v, \n\n%v", id, healthy.ConnectNN, healthy.KnowNN, healthy.Robust, healthy.Hive)) + return healthy.IsHealthyStrict(), nil } // 64 nodes ~ 1min @@ -352,7 +352,7 @@ func discoveryPersistenceSimulation(nodes, conns int, adapter adapters.NodeAdapt healthy := &network.Health{} addr := id.String() ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs) - if err := client.Call(&healthy, "hive_healthy", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { + if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { return fmt.Errorf("error getting node health: %s", err) } @@ -425,12 +425,12 @@ func discoveryPersistenceSimulation(nodes, conns int, adapter adapters.NodeAdapt healthy := &network.Health{} ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs) - if err := client.Call(&healthy, "hive_healthy", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { + if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { return false, fmt.Errorf("error getting node health: %s", err) } log.Info(fmt.Sprintf("node %4s healthy: got nearest neighbours: %v, know nearest neighbours: %v", id, healthy.ConnectNN, healthy.KnowNN)) - return healthy.KnowNN && healthy.ConnectNN, nil + return healthy.IsHealthyStrict(), nil } // 64 nodes ~ 1min From 13010ff9b5825c91a5881e1f63dd5675a6843c2c Mon Sep 17 00:00:00 2001 From: Fabio Barone Date: Tue, 29 Jan 2019 14:39:31 -0500 Subject: [PATCH 4/5] swarm/network: documentation of strict --- swarm/network/kademlia.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/swarm/network/kademlia.go b/swarm/network/kademlia.go index e1a905ede9..d8c53468e3 100644 --- a/swarm/network/kademlia.go +++ b/swarm/network/kademlia.go @@ -811,6 +811,11 @@ type Health struct { } // IsHealthyStrict return the strict interpretation of `Healthy` given a `Health` struct +// definition of strict health: all conditions must be true: +// - we at least know one peer +// - we know all neighbors +// - we are connected to all known neighbors +// - it is robust (we are connected to a minimum of peers in all the bins we have known peers in) func (h *Health) IsHealthyStrict() bool { return h.KnowNN && h.ConnectNN && h.CountKnowNN > 0 && h.Robust } From 24c2c8bb4dd21f3b0ee1aa4f1f57eabf80e5f28e Mon Sep 17 00:00:00 2001 From: Fabio Barone Date: Thu, 31 Jan 2019 11:18:34 -0500 Subject: [PATCH 5/5] swarm/network: addressed PR comments --- swarm/network/kademlia.go | 2 +- swarm/network/simulations/discovery/discovery_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/swarm/network/kademlia.go b/swarm/network/kademlia.go index d8c53468e3..b59735e6dc 100644 --- a/swarm/network/kademlia.go +++ b/swarm/network/kademlia.go @@ -786,7 +786,7 @@ func (k *Kademlia) getWeakBins() (missing []int) { for po, v := range pk { if pc[po] == v { continue - } else if po >= depth && pc[po] != pk[po] { + } else if po >= depth { missing = append(missing, po) } else if pc[po] < k.HealthBinSize { missing = append(missing, po) diff --git a/swarm/network/simulations/discovery/discovery_test.go b/swarm/network/simulations/discovery/discovery_test.go index 897dba18a5..737ffcf3e3 100644 --- a/swarm/network/simulations/discovery/discovery_test.go +++ b/swarm/network/simulations/discovery/discovery_test.go @@ -270,7 +270,7 @@ func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simul if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { return false, fmt.Errorf("error getting node health: %s", err) } - log.Debug(fmt.Sprintf("node %4s healthy: connected nearest neighbours: %v, know nearest neighbours: %v, is robuts: %v, \n\n%v", id, healthy.ConnectNN, healthy.KnowNN, healthy.Robust, healthy.Hive)) + log.Debug(fmt.Sprintf("node %4s healthy: connected nearest neighbours: %v, know nearest neighbours: %v, is robust: %v, \n\n%v", id, healthy.ConnectNN, healthy.KnowNN, healthy.Robust, healthy.Hive)) return healthy.IsHealthyStrict(), nil }