diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go new file mode 100644 index 0000000000..c425e6e54b --- /dev/null +++ b/accounts/abi/reflect_test.go @@ -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 . + +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]) + } + } + } + }) + } +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index ebaeba9f46..97033c692a 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -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, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 25a702dd73..fd8ec7cd99 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -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}} diff --git a/params/config.go b/params/config.go index 2935ef1f3f..fefc161061 100644 --- a/params/config.go +++ b/params/config.go @@ -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), } diff --git a/params/version.go b/params/version.go index d3954e0bc3..33e99a0828 100644 --- a/params/version.go +++ b/params/version.go @@ -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 ) diff --git a/swarm/network/kademlia.go b/swarm/network/kademlia.go index 7d52f26f79..8042de59a0 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, @@ -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 }) @@ -643,6 +645,9 @@ func (k *Kademlia) saturation() int { if depth < prev { return depth } + if prev < 0 { + prev = 0 + } return prev } @@ -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 @@ -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 @@ -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 } @@ -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(), } } diff --git a/swarm/network/kademlia_test.go b/swarm/network/kademlia_test.go index fcb277fde7..c00ffb3253 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 @@ -31,6 +31,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) @@ -44,8 +49,8 @@ func testKadPeerAddr(s string) *BzzAddr { func newTestKademliaParams() *KadParams { params := NewKadParams() // TODO why is this 1? - params.MinBinSize = 1 - params.NeighbourhoodSize = 2 + params.MinBinSize = minBinSize + params.NeighbourhoodSize = minProxBinSize return params } @@ -162,94 +167,223 @@ 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) k := newTestKademlia("11111111") - assertHealth(t, k, false, false) + assertHealthSimple(t, k, false) // know one peer but not connected // unhealthy Register(k, "11100000") log.Trace(k.String()) - assertHealth(t, k, false, false) + assertHealthSimple(t, k, false) // know one peer and connected // healthy On(k, "11100000") - assertHealth(t, k, true, false) + assertHealthSimple(t, k, true) // know two peers, only one connected // unhealthy Register(k, "11111100") log.Trace(k.String()) - assertHealth(t, k, false, false) + assertHealthSimple(t, k, false) // know two peers and connected to both // healthy On(k, "11111100") - assertHealth(t, k, true, false) + assertHealthSimple(t, k, true) // know three peers, connected to the two deepest // healthy Register(k, "00000000") log.Trace(k.String()) - assertHealth(t, k, true, false) + assertHealthSimple(t, k, true) // know three peers, connected to all three // healthy On(k, "00000000") - assertHealth(t, k, true, false) + assertHealthSimple(t, k, true) // add fourth peer deeper than current depth // unhealthy Register(k, "11110000") log.Trace(k.String()) - assertHealth(t, k, false, false) + assertHealthSimple(t, k, false) // connected to three deepest peers // healthy On(k, "11110000") - assertHealth(t, k, true, false) + assertHealthSimple(t, k, true) // add additional peer in same bin as deepest peer // unhealthy Register(k, "11111101") log.Trace(k.String()) - assertHealth(t, k, false, false) + assertHealthSimple(t, k, false) // four deepest of five peers connected // healthy On(k, "11111101") - assertHealth(t, k, true, false) + assertHealthSimple(t, k, true) } -func assertHealth(t *testing.T, k *Kademlia, expectHealthy bool, expectSaturation bool) { - t.Helper() +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) - healthParams := k.Healthy(pp[kid]) + return k.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 +// 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 { t.Fatalf("expected kademlia health %v, is %v\n%v", expectHealthy, health, k.String()) } } +// 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) @@ -605,7 +739,7 @@ func TestKademliaHiveString(t *testing.T) { Register(k, "10000000", "10000001") k.MaxProxDisplay = 8 h := k.String() - expH := "\n=========================================================================\nMon Feb 27 12:10:28 UTC 2017 KΛÐΞMLIΛ hive: queen's address: 000000\npopulation: 2 (4), NeighbourhoodSize: 2, MinBinSize: 1, 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=========================================================================" + 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) } diff --git a/swarm/version/version.go b/swarm/version/version.go index 8301dfd2d4..fb4c531110 100644 --- a/swarm/version/version.go +++ b/swarm/version/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 0 // Major version component of the current release VersionMinor = 3 // Minor version component of the current release - VersionPatch = 9 // Patch version component of the current release + VersionPatch = 10 // Patch version component of the current release VersionMeta = "unstable" // Version metadata to append to the version string )