Skip to content
This repository has been archived by the owner on Aug 2, 2021. It is now read-only.

swarm/network: Add tests for complex connectivity evaluations (2) #1108

Closed
wants to merge 7 commits into from
191 changes: 191 additions & 0 deletions accounts/abi/reflect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright 2019 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
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package abi

import (
"reflect"
"testing"
)

type reflectTest struct {
name string
args []string
struc interface{}
want map[string]string
err string
}

var reflectTests = []reflectTest{
{
name: "OneToOneCorrespondance",
args: []string{"fieldA"},
struc: struct {
FieldA int `abi:"fieldA"`
}{},
want: map[string]string{
"fieldA": "FieldA",
},
},
{
name: "MissingFieldsInStruct",
args: []string{"fieldA", "fieldB"},
struc: struct {
FieldA int `abi:"fieldA"`
}{},
want: map[string]string{
"fieldA": "FieldA",
},
},
{
name: "MoreFieldsInStructThanArgs",
args: []string{"fieldA"},
struc: struct {
FieldA int `abi:"fieldA"`
FieldB int
}{},
want: map[string]string{
"fieldA": "FieldA",
},
},
{
name: "MissingFieldInArgs",
args: []string{"fieldA"},
struc: struct {
FieldA int `abi:"fieldA"`
FieldB int `abi:"fieldB"`
}{},
err: "struct: abi tag 'fieldB' defined but not found in abi",
},
{
name: "NoAbiDescriptor",
args: []string{"fieldA"},
struc: struct {
FieldA int
}{},
want: map[string]string{
"fieldA": "FieldA",
},
},
{
name: "NoArgs",
args: []string{},
struc: struct {
FieldA int `abi:"fieldA"`
}{},
err: "struct: abi tag 'fieldA' defined but not found in abi",
},
{
name: "DifferentName",
args: []string{"fieldB"},
struc: struct {
FieldA int `abi:"fieldB"`
}{},
want: map[string]string{
"fieldB": "FieldA",
},
},
{
name: "DifferentName",
args: []string{"fieldB"},
struc: struct {
FieldA int `abi:"fieldB"`
}{},
want: map[string]string{
"fieldB": "FieldA",
},
},
{
name: "MultipleFields",
args: []string{"fieldA", "fieldB"},
struc: struct {
FieldA int `abi:"fieldA"`
FieldB int `abi:"fieldB"`
}{},
want: map[string]string{
"fieldA": "FieldA",
"fieldB": "FieldB",
},
},
{
name: "MultipleFieldsABIMissing",
args: []string{"fieldA", "fieldB"},
struc: struct {
FieldA int `abi:"fieldA"`
FieldB int
}{},
want: map[string]string{
"fieldA": "FieldA",
"fieldB": "FieldB",
},
},
{
name: "NameConflict",
args: []string{"fieldB"},
struc: struct {
FieldA int `abi:"fieldB"`
FieldB int
}{},
err: "abi: multiple variables maps to the same abi field 'fieldB'",
},
{
name: "Underscored",
args: []string{"_"},
struc: struct {
FieldA int
}{},
err: "abi: purely underscored output cannot unpack to struct",
},
{
name: "DoubleMapping",
args: []string{"fieldB", "fieldC", "fieldA"},
struc: struct {
FieldA int `abi:"fieldC"`
FieldB int
}{},
err: "abi: multiple outputs mapping to the same struct field 'FieldA'",
},
{
name: "AlreadyMapped",
args: []string{"fieldB", "fieldB"},
struc: struct {
FieldB int `abi:"fieldB"`
}{},
err: "struct: abi tag in 'FieldB' already mapped",
},
}

func TestReflectNameToStruct(t *testing.T) {
for _, test := range reflectTests {
t.Run(test.name, func(t *testing.T) {
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
if len(test.err) > 0 {
if err == nil || err.Error() != test.err {
t.Fatalf("Invalid error: expected %v, got %v", test.err, err)
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for fname := range test.want {
if m[fname] != test.want[fname] {
t.Fatalf("Incorrect value for field %s: expected %v, got %v", fname, test.want[fname], m[fname])
}
}
}
})
}
}
2 changes: 1 addition & 1 deletion cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func init() {
// Initialize the CLI app and start Geth
app.Action = geth
app.HideVersion = true // we have a command to print the version
app.Copyright = "Copyright 2013-2018 The go-ethereum Authors"
app.Copyright = "Copyright 2013-2019 The go-ethereum Authors"
app.Commands = []cli.Command{
// See chaincmd.go:
initCommand,
Expand Down
2 changes: 1 addition & 1 deletion cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
var AppHelpTemplate = `NAME:
{{.App.Name}} - {{.App.Usage}}

Copyright 2013-2018 The go-ethereum Authors
Copyright 2013-2019 The go-ethereum Authors

USAGE:
{{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}}
Expand Down
2 changes: 1 addition & 1 deletion params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ var (
EIP155Block: big.NewInt(2675000),
EIP158Block: big.NewInt(2675000),
ByzantiumBlock: big.NewInt(4370000),
ConstantinopleBlock: big.NewInt(7080000),
ConstantinopleBlock: nil,
Ethash: new(EthashConfig),
}

Expand Down
4 changes: 2 additions & 2 deletions params/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import (

const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 9 // Minor version component of the current release
VersionPatch = 0 // Patch version component of the current release
VersionMinor = 8 // Minor version component of the current release
VersionPatch = 22 // Patch version component of the current release
VersionMeta = "unstable" // Version metadata to append to the version string
)

Expand Down
81 changes: 61 additions & 20 deletions swarm/network/kademlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -71,6 +72,7 @@ func NewKadParams() *KadParams {
MaxProxDisplay: 16,
NeighbourhoodSize: 2,
MinBinSize: 2,
HealthBinSize: 1,
MaxBinSize: 4,
RetryInterval: 4200000000, // 4.2 sec
MaxRetries: 42,
Expand Down Expand Up @@ -634,7 +636,7 @@ func NewPeerPotMap(neighbourhoodSize int, addrs [][]byte) map[string]*PeerPot {
// TODO this function will stop at the first bin with less than MinBinSize peers, even if there are empty bins between that bin and the depth. This may not be correct behavior
func (k *Kademlia) saturation() int {
prev := -1
k.addrs.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val, i int) bool) bool) bool {
prev++
return prev == po && size >= k.MinBinSize
})
Expand All @@ -643,6 +645,9 @@ func (k *Kademlia) saturation() int {
if depth < prev {
return depth
}
if prev < 0 {
prev = 0
}
return prev
}

Expand Down Expand Up @@ -670,17 +675,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
Expand All @@ -705,18 +709,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
Expand All @@ -728,7 +763,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
}

Expand All @@ -743,19 +779,24 @@ type Health struct {
func (k *Kademlia) Healthy(pp *PeerPot) *Health {
k.lock.RLock()
defer k.lock.RUnlock()
gotnn, countgotnn, culpritsgotnn := k.connectedNeighbours(pp.NNSet)
knownn, countknownn, culpritsknownn := 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))

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))

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(),
}
}
Loading