diff options
Diffstat (limited to 'swarm/network/simulation')
-rw-r--r-- | swarm/network/simulation/bucket.go | 79 | ||||
-rw-r--r-- | swarm/network/simulation/bucket_test.go | 155 | ||||
-rw-r--r-- | swarm/network/simulation/events.go | 217 | ||||
-rw-r--r-- | swarm/network/simulation/events_test.go | 107 | ||||
-rw-r--r-- | swarm/network/simulation/example_test.go | 141 | ||||
-rw-r--r-- | swarm/network/simulation/http.go | 68 | ||||
-rw-r--r-- | swarm/network/simulation/http_test.go | 110 | ||||
-rw-r--r-- | swarm/network/simulation/kademlia.go | 203 | ||||
-rw-r--r-- | swarm/network/simulation/kademlia_test.go | 310 | ||||
-rw-r--r-- | swarm/network/simulation/node.go | 341 | ||||
-rw-r--r-- | swarm/network/simulation/node_test.go | 446 | ||||
-rw-r--r-- | swarm/network/simulation/service.go | 65 | ||||
-rw-r--r-- | swarm/network/simulation/service_test.go | 46 | ||||
-rw-r--r-- | swarm/network/simulation/simulation.go | 218 | ||||
-rw-r--r-- | swarm/network/simulation/simulation_test.go | 203 |
15 files changed, 0 insertions, 2709 deletions
diff --git a/swarm/network/simulation/bucket.go b/swarm/network/simulation/bucket.go deleted file mode 100644 index 49a1f4309..000000000 --- a/swarm/network/simulation/bucket.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 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 simulation - -import "github.com/ethereum/go-ethereum/p2p/enode" - -// BucketKey is the type that should be used for keys in simulation buckets. -type BucketKey string - -// NodeItem returns an item set in ServiceFunc function for a particular node. -func (s *Simulation) NodeItem(id enode.ID, key interface{}) (value interface{}, ok bool) { - s.mu.Lock() - defer s.mu.Unlock() - - if _, ok := s.buckets[id]; !ok { - return nil, false - } - return s.buckets[id].Load(key) -} - -// SetNodeItem sets a new item associated with the node with provided NodeID. -// Buckets should be used to avoid managing separate simulation global state. -func (s *Simulation) SetNodeItem(id enode.ID, key interface{}, value interface{}) { - s.mu.Lock() - defer s.mu.Unlock() - - s.buckets[id].Store(key, value) -} - -// NodesItems returns a map of items from all nodes that are all set under the -// same BucketKey. -func (s *Simulation) NodesItems(key interface{}) (values map[enode.ID]interface{}) { - s.mu.RLock() - defer s.mu.RUnlock() - - ids := s.NodeIDs() - values = make(map[enode.ID]interface{}, len(ids)) - for _, id := range ids { - if _, ok := s.buckets[id]; !ok { - continue - } - if v, ok := s.buckets[id].Load(key); ok { - values[id] = v - } - } - return values -} - -// UpNodesItems returns a map of items with the same BucketKey from all nodes that are up. -func (s *Simulation) UpNodesItems(key interface{}) (values map[enode.ID]interface{}) { - s.mu.RLock() - defer s.mu.RUnlock() - - ids := s.UpNodeIDs() - values = make(map[enode.ID]interface{}) - for _, id := range ids { - if _, ok := s.buckets[id]; !ok { - continue - } - if v, ok := s.buckets[id].Load(key); ok { - values[id] = v - } - } - return values -} diff --git a/swarm/network/simulation/bucket_test.go b/swarm/network/simulation/bucket_test.go deleted file mode 100644 index 2273d35a2..000000000 --- a/swarm/network/simulation/bucket_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "sync" - "testing" - - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" -) - -// TestServiceBucket tests all bucket functionality using subtests. -// It constructs a simulation of two nodes by adding items to their buckets -// in ServiceFunc constructor, then by SetNodeItem. Testing UpNodesItems -// is done by stopping one node and validating availability of its items. -func TestServiceBucket(t *testing.T) { - testKey := "Key" - testValue := "Value" - - sim := New(map[string]ServiceFunc{ - "noop": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - b.Store(testKey, testValue+ctx.Config.ID.String()) - return newNoopService(), nil, nil - }, - }) - defer sim.Close() - - id1, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - id2, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - t.Run("ServiceFunc bucket Store", func(t *testing.T) { - v, ok := sim.NodeItem(id1, testKey) - if !ok { - t.Fatal("bucket item not found") - } - s, ok := v.(string) - if !ok { - t.Fatal("bucket item value is not string") - } - if s != testValue+id1.String() { - t.Fatalf("expected %q, got %q", testValue+id1.String(), s) - } - - v, ok = sim.NodeItem(id2, testKey) - if !ok { - t.Fatal("bucket item not found") - } - s, ok = v.(string) - if !ok { - t.Fatal("bucket item value is not string") - } - if s != testValue+id2.String() { - t.Fatalf("expected %q, got %q", testValue+id2.String(), s) - } - }) - - customKey := "anotherKey" - customValue := "anotherValue" - - t.Run("SetNodeItem", func(t *testing.T) { - sim.SetNodeItem(id1, customKey, customValue) - - v, ok := sim.NodeItem(id1, customKey) - if !ok { - t.Fatal("bucket item not found") - } - s, ok := v.(string) - if !ok { - t.Fatal("bucket item value is not string") - } - if s != customValue { - t.Fatalf("expected %q, got %q", customValue, s) - } - - _, ok = sim.NodeItem(id2, customKey) - if ok { - t.Fatal("bucket item should not be found") - } - }) - - if err := sim.StopNode(id2); err != nil { - t.Fatal(err) - } - - t.Run("UpNodesItems", func(t *testing.T) { - items := sim.UpNodesItems(testKey) - - v, ok := items[id1] - if !ok { - t.Errorf("node 1 item not found") - } - s, ok := v.(string) - if !ok { - t.Fatal("node 1 item value is not string") - } - if s != testValue+id1.String() { - t.Fatalf("expected %q, got %q", testValue+id1.String(), s) - } - - _, ok = items[id2] - if ok { - t.Errorf("node 2 item should not be found") - } - }) - - t.Run("NodeItems", func(t *testing.T) { - items := sim.NodesItems(testKey) - - v, ok := items[id1] - if !ok { - t.Errorf("node 1 item not found") - } - s, ok := v.(string) - if !ok { - t.Fatal("node 1 item value is not string") - } - if s != testValue+id1.String() { - t.Fatalf("expected %q, got %q", testValue+id1.String(), s) - } - - v, ok = items[id2] - if !ok { - t.Errorf("node 2 item not found") - } - s, ok = v.(string) - if !ok { - t.Fatal("node 1 item value is not string") - } - if s != testValue+id2.String() { - t.Fatalf("expected %q, got %q", testValue+id2.String(), s) - } - }) -} diff --git a/swarm/network/simulation/events.go b/swarm/network/simulation/events.go deleted file mode 100644 index d73c3af4e..000000000 --- a/swarm/network/simulation/events.go +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "context" - "sync" - - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/simulations" -) - -// PeerEvent is the type of the channel returned by Simulation.PeerEvents. -type PeerEvent struct { - // NodeID is the ID of node that the event is caught on. - NodeID enode.ID - // PeerID is the ID of the peer node that the event is caught on. - PeerID enode.ID - // Event is the event that is caught. - Event *simulations.Event - // Error is the error that may have happened during event watching. - Error error -} - -// PeerEventsFilter defines a filter on PeerEvents to exclude messages with -// defined properties. Use PeerEventsFilter methods to set required options. -type PeerEventsFilter struct { - eventType simulations.EventType - - connUp *bool - - msgReceive *bool - protocol *string - msgCode *uint64 -} - -// NewPeerEventsFilter returns a new PeerEventsFilter instance. -func NewPeerEventsFilter() *PeerEventsFilter { - return &PeerEventsFilter{} -} - -// Connect sets the filter to events when two nodes connect. -func (f *PeerEventsFilter) Connect() *PeerEventsFilter { - f.eventType = simulations.EventTypeConn - b := true - f.connUp = &b - return f -} - -// Drop sets the filter to events when two nodes disconnect. -func (f *PeerEventsFilter) Drop() *PeerEventsFilter { - f.eventType = simulations.EventTypeConn - b := false - f.connUp = &b - return f -} - -// ReceivedMessages sets the filter to only messages that are received. -func (f *PeerEventsFilter) ReceivedMessages() *PeerEventsFilter { - f.eventType = simulations.EventTypeMsg - b := true - f.msgReceive = &b - return f -} - -// SentMessages sets the filter to only messages that are sent. -func (f *PeerEventsFilter) SentMessages() *PeerEventsFilter { - f.eventType = simulations.EventTypeMsg - b := false - f.msgReceive = &b - return f -} - -// Protocol sets the filter to only one message protocol. -func (f *PeerEventsFilter) Protocol(p string) *PeerEventsFilter { - f.eventType = simulations.EventTypeMsg - f.protocol = &p - return f -} - -// MsgCode sets the filter to only one msg code. -func (f *PeerEventsFilter) MsgCode(c uint64) *PeerEventsFilter { - f.eventType = simulations.EventTypeMsg - f.msgCode = &c - return f -} - -// PeerEvents returns a channel of events that are captured by admin peerEvents -// subscription nodes with provided NodeIDs. Additional filters can be set to ignore -// events that are not relevant. -func (s *Simulation) PeerEvents(ctx context.Context, ids []enode.ID, filters ...*PeerEventsFilter) <-chan PeerEvent { - eventC := make(chan PeerEvent) - - // wait group to make sure all subscriptions to admin peerEvents are established - // before this function returns. - var subsWG sync.WaitGroup - for _, id := range ids { - s.shutdownWG.Add(1) - subsWG.Add(1) - go func(id enode.ID) { - defer s.shutdownWG.Done() - - events := make(chan *simulations.Event) - sub := s.Net.Events().Subscribe(events) - defer sub.Unsubscribe() - - subsWG.Done() - - for { - select { - case <-ctx.Done(): - if err := ctx.Err(); err != nil { - select { - case eventC <- PeerEvent{NodeID: id, Error: err}: - case <-s.Done(): - } - } - return - case <-s.Done(): - return - case e := <-events: - // ignore control events - if e.Control { - continue - } - match := len(filters) == 0 // if there are no filters match all events - for _, f := range filters { - if f.eventType == simulations.EventTypeConn && e.Conn != nil { - if *f.connUp != e.Conn.Up { - continue - } - // all connection filter parameters matched, break the loop - match = true - break - } - if f.eventType == simulations.EventTypeMsg && e.Msg != nil { - if f.msgReceive != nil && *f.msgReceive != e.Msg.Received { - continue - } - if f.protocol != nil && *f.protocol != e.Msg.Protocol { - continue - } - if f.msgCode != nil && *f.msgCode != e.Msg.Code { - continue - } - // all message filter parameters matched, break the loop - match = true - break - } - } - var peerID enode.ID - switch e.Type { - case simulations.EventTypeConn: - peerID = e.Conn.One - if peerID == id { - peerID = e.Conn.Other - } - case simulations.EventTypeMsg: - peerID = e.Msg.One - if peerID == id { - peerID = e.Msg.Other - } - } - if match { - select { - case eventC <- PeerEvent{NodeID: id, PeerID: peerID, Event: e}: - case <-ctx.Done(): - if err := ctx.Err(); err != nil { - select { - case eventC <- PeerEvent{NodeID: id, PeerID: peerID, Error: err}: - case <-s.Done(): - } - } - return - case <-s.Done(): - return - } - } - case err := <-sub.Err(): - if err != nil { - select { - case eventC <- PeerEvent{NodeID: id, Error: err}: - case <-ctx.Done(): - if err := ctx.Err(); err != nil { - select { - case eventC <- PeerEvent{NodeID: id, Error: err}: - case <-s.Done(): - } - } - return - case <-s.Done(): - return - } - } - } - } - }(id) - } - - // wait all subscriptions - subsWG.Wait() - return eventC -} diff --git a/swarm/network/simulation/events_test.go b/swarm/network/simulation/events_test.go deleted file mode 100644 index 529844816..000000000 --- a/swarm/network/simulation/events_test.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "context" - "sync" - "testing" - "time" -) - -// TestPeerEvents creates simulation, adds two nodes, -// register for peer events, connects nodes in a chain -// and waits for the number of connection events to -// be received. -func TestPeerEvents(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodes(2) - if err != nil { - t.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - events := sim.PeerEvents(ctx, sim.NodeIDs()) - - // two nodes -> two connection events - expectedEventCount := 2 - - var wg sync.WaitGroup - wg.Add(expectedEventCount) - - go func() { - for e := range events { - if e.Error != nil { - if e.Error == context.Canceled { - return - } - t.Error(e.Error) - continue - } - wg.Done() - } - }() - - err = sim.Net.ConnectNodesChain(sim.NodeIDs()) - if err != nil { - t.Fatal(err) - } - - wg.Wait() -} - -func TestPeerEventsTimeout(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodes(2) - if err != nil { - t.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) - defer cancel() - events := sim.PeerEvents(ctx, sim.NodeIDs()) - - done := make(chan struct{}) - errC := make(chan error) - go func() { - for e := range events { - if e.Error == context.Canceled { - return - } - if e.Error == context.DeadlineExceeded { - close(done) - return - } else { - errC <- e.Error - } - } - }() - - select { - case <-time.After(time.Second): - t.Fatal("no context deadline received") - case err := <-errC: - t.Fatal(err) - case <-done: - // all good, context deadline detected - } -} diff --git a/swarm/network/simulation/example_test.go b/swarm/network/simulation/example_test.go deleted file mode 100644 index 9d1492979..000000000 --- a/swarm/network/simulation/example_test.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2018 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 simulation_test - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/swarm/network" - "github.com/ethereum/go-ethereum/swarm/network/simulation" -) - -// Every node can have a Kademlia associated using the node bucket under -// BucketKeyKademlia key. This allows to use WaitTillHealthy to block until -// all nodes have the their Kademlias healthy. -func ExampleSimulation_WaitTillHealthy() { - - sim := simulation.New(map[string]simulation.ServiceFunc{ - "bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - addr := network.NewAddr(ctx.Config.Node()) - hp := network.NewHiveParams() - hp.Discovery = false - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - kad := network.NewKademlia(addr.Over(), network.NewKadParams()) - // store kademlia in node's bucket under BucketKeyKademlia - // so that it can be found by WaitTillHealthy method. - b.Store(simulation.BucketKeyKademlia, kad) - return network.NewBzz(config, kad, nil, nil, nil), nil, nil - }, - }) - defer sim.Close() - - _, err := sim.AddNodesAndConnectRing(10) - if err != nil { - // handle error properly... - panic(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) - defer cancel() - ill, err := sim.WaitTillHealthy(ctx) - if err != nil { - // inspect the latest detected not healthy kademlias - for id, kad := range ill { - fmt.Println("Node", id) - fmt.Println(kad.String()) - } - // handle error... - } - - // continue with the test - -} - -// Watch all peer events in the simulation network, buy receiving from a channel. -func ExampleSimulation_PeerEvents() { - sim := simulation.New(nil) - defer sim.Close() - - events := sim.PeerEvents(context.Background(), sim.NodeIDs()) - - go func() { - for e := range events { - if e.Error != nil { - log.Error("peer event", "err", e.Error) - continue - } - log.Info("peer event", "node", e.NodeID, "peer", e.PeerID, "type", e.Event.Type) - } - }() -} - -// Detect when a nodes drop a peer. -func ExampleSimulation_PeerEvents_disconnections() { - sim := simulation.New(nil) - defer sim.Close() - - disconnections := sim.PeerEvents( - context.Background(), - sim.NodeIDs(), - simulation.NewPeerEventsFilter().Drop(), - ) - - go func() { - for d := range disconnections { - if d.Error != nil { - log.Error("peer drop", "err", d.Error) - continue - } - log.Warn("peer drop", "node", d.NodeID, "peer", d.PeerID) - } - }() -} - -// Watch multiple types of events or messages. In this case, they differ only -// by MsgCode, but filters can be set for different types or protocols, too. -func ExampleSimulation_PeerEvents_multipleFilters() { - sim := simulation.New(nil) - defer sim.Close() - - msgs := sim.PeerEvents( - context.Background(), - sim.NodeIDs(), - // Watch when bzz messages 1 and 4 are received. - simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("bzz").MsgCode(1), - simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("bzz").MsgCode(4), - ) - - go func() { - for m := range msgs { - if m.Error != nil { - log.Error("bzz message", "err", m.Error) - continue - } - log.Info("bzz message", "node", m.NodeID, "peer", m.PeerID) - } - }() -} diff --git a/swarm/network/simulation/http.go b/swarm/network/simulation/http.go deleted file mode 100644 index 69ae3baec..000000000 --- a/swarm/network/simulation/http.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "fmt" - "net/http" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/simulations" -) - -// Package defaults. -var ( - DefaultHTTPSimAddr = ":8888" -) - -//WithServer implements the builder pattern constructor for Simulation to -//start with a HTTP server -func (s *Simulation) WithServer(addr string) *Simulation { - //assign default addr if nothing provided - if addr == "" { - addr = DefaultHTTPSimAddr - } - log.Info(fmt.Sprintf("Initializing simulation server on %s...", addr)) - //initialize the HTTP server - s.handler = simulations.NewServer(s.Net) - s.runC = make(chan struct{}) - //add swarm specific routes to the HTTP server - s.addSimulationRoutes() - s.httpSrv = &http.Server{ - Addr: addr, - Handler: s.handler, - } - go func() { - err := s.httpSrv.ListenAndServe() - if err != nil { - log.Error("Error starting the HTTP server", "error", err) - } - }() - return s -} - -//register additional HTTP routes -func (s *Simulation) addSimulationRoutes() { - s.handler.POST("/runsim", s.RunSimulation) -} - -// RunSimulation is the actual POST endpoint runner -func (s *Simulation) RunSimulation(w http.ResponseWriter, req *http.Request) { - log.Debug("RunSimulation endpoint running") - s.runC <- struct{}{} - w.WriteHeader(http.StatusOK) -} diff --git a/swarm/network/simulation/http_test.go b/swarm/network/simulation/http_test.go deleted file mode 100644 index dffd03a03..000000000 --- a/swarm/network/simulation/http_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "context" - "fmt" - "net/http" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" -) - -func TestSimulationWithHTTPServer(t *testing.T) { - log.Debug("Init simulation") - - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - sim := New( - map[string]ServiceFunc{ - "noop": func(_ *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - return newNoopService(), nil, nil - }, - }).WithServer(DefaultHTTPSimAddr) - defer sim.Close() - log.Debug("Done.") - - _, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - log.Debug("Starting sim round and let it time out...") - //first test that running without sending to the channel will actually - //block the simulation, so let it time out - result := sim.Run(ctx, func(ctx context.Context, sim *Simulation) error { - log.Debug("Just start the sim without any action and wait for the timeout") - //ensure with a Sleep that simulation doesn't terminate before the timeout - time.Sleep(2 * time.Second) - return nil - }) - - if result.Error != nil { - if result.Error.Error() == "context deadline exceeded" { - log.Debug("Expected timeout error received") - } else { - t.Fatal(result.Error) - } - } - - //now run it again and send the expected signal on the waiting channel, - //then close the simulation - log.Debug("Starting sim round and wait for frontend signal...") - //this time the timeout should be long enough so that it doesn't kick in too early - ctx, cancel2 := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel2() - errC := make(chan error, 1) - go triggerSimulationRun(t, errC) - result = sim.Run(ctx, func(ctx context.Context, sim *Simulation) error { - log.Debug("This run waits for the run signal from `frontend`...") - //ensure with a Sleep that simulation doesn't terminate before the signal is received - time.Sleep(2 * time.Second) - return nil - }) - if result.Error != nil { - t.Fatal(result.Error) - } - if err := <-errC; err != nil { - t.Fatal(err) - } - log.Debug("Test terminated successfully") -} - -func triggerSimulationRun(t *testing.T, errC chan error) { - //We need to first wait for the sim HTTP server to start running... - time.Sleep(2 * time.Second) - //then we can send the signal - - log.Debug("Sending run signal to simulation: POST /runsim...") - resp, err := http.Post(fmt.Sprintf("http://localhost%s/runsim", DefaultHTTPSimAddr), "application/json", nil) - if err != nil { - errC <- fmt.Errorf("Request failed: %v", err) - return - } - log.Debug("Signal sent") - if resp.StatusCode != http.StatusOK { - errC <- fmt.Errorf("err %s", resp.Status) - return - } - errC <- resp.Body.Close() -} diff --git a/swarm/network/simulation/kademlia.go b/swarm/network/simulation/kademlia.go deleted file mode 100644 index 00e870a07..000000000 --- a/swarm/network/simulation/kademlia.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "context" - "encoding/binary" - "encoding/hex" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/simulations" - "github.com/ethereum/go-ethereum/swarm/network" -) - -// BucketKeyKademlia is the key to be used for storing the kademlia -// instance for particular node, usually inside the ServiceFunc function. -var BucketKeyKademlia BucketKey = "kademlia" - -// WaitTillHealthy is blocking until the health of all kademlias is true. -// If error is not nil, a map of kademlia that was found not healthy is returned. -// TODO: Check correctness since change in kademlia depth calculation logic -func (s *Simulation) WaitTillHealthy(ctx context.Context) (ill map[enode.ID]*network.Kademlia, err error) { - // Prepare PeerPot map for checking Kademlia health - var ppmap map[string]*network.PeerPot - kademlias := s.kademlias() - addrs := make([][]byte, 0, len(kademlias)) - // TODO verify that all kademlias have same params - for _, k := range kademlias { - addrs = append(addrs, k.BaseAddr()) - } - ppmap = network.NewPeerPotMap(s.neighbourhoodSize, addrs) - - // Wait for healthy Kademlia on every node before checking files - ticker := time.NewTicker(200 * time.Millisecond) - defer ticker.Stop() - - ill = make(map[enode.ID]*network.Kademlia) - for { - select { - case <-ctx.Done(): - return ill, ctx.Err() - case <-ticker.C: - for k := range ill { - delete(ill, k) - } - log.Debug("kademlia health check", "addr count", len(addrs), "kad len", len(kademlias)) - for id, k := range kademlias { - //PeerPot for this node - addr := common.Bytes2Hex(k.BaseAddr()) - pp := ppmap[addr] - //call Healthy RPC - 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.Healthy() { - ill[id] = k - } - } - if len(ill) == 0 { - return nil, nil - } - } - } -} - -// kademlias returns all Kademlia instances that are set -// in simulation bucket. -func (s *Simulation) kademlias() (ks map[enode.ID]*network.Kademlia) { - items := s.UpNodesItems(BucketKeyKademlia) - log.Debug("kademlia len items", "len", len(items)) - ks = make(map[enode.ID]*network.Kademlia, len(items)) - for id, v := range items { - k, ok := v.(*network.Kademlia) - if !ok { - continue - } - ks[id] = k - } - return ks -} - -// WaitTillSnapshotRecreated is blocking until all the connections specified -// in the snapshot are registered in the kademlia. -// It differs from WaitTillHealthy, which waits only until all the kademlias are -// healthy (it might happen even before all the connections are established). -func (s *Simulation) WaitTillSnapshotRecreated(ctx context.Context, snap *simulations.Snapshot) error { - expected := getSnapshotConnections(snap.Conns) - ticker := time.NewTicker(150 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - actual := s.getActualConnections() - if isAllDeployed(expected, actual) { - return nil - } - } - } -} - -func (s *Simulation) getActualConnections() (res []uint64) { - kademlias := s.kademlias() - for base, k := range kademlias { - k.EachConn(base[:], 256, func(p *network.Peer, _ int) bool { - res = append(res, getConnectionHash(base, p.ID())) - return true - }) - } - - // only list those connections that appear twice (both peers should recognize connection as active) - res = removeDuplicatesAndSingletons(res) - return res -} - -func getSnapshotConnections(conns []simulations.Conn) (res []uint64) { - for _, c := range conns { - res = append(res, getConnectionHash(c.One, c.Other)) - } - return res -} - -// returns an integer connection identifier (similar to 8-byte hash) -func getConnectionHash(a, b enode.ID) uint64 { - var h [8]byte - for i := 0; i < 8; i++ { - h[i] = a[i] ^ b[i] - } - res := binary.LittleEndian.Uint64(h[:]) - return res -} - -// returns true if all connections in expected are listed in actual -func isAllDeployed(expected []uint64, actual []uint64) bool { - if len(expected) == 0 { - return true - } - - exp := make([]uint64, len(expected)) - copy(exp, expected) - for _, c := range actual { - // remove value c from exp - for i := 0; i < len(exp); i++ { - if exp[i] == c { - exp = removeListElement(exp, i) - if len(exp) == 0 { - return true - } - } - } - } - return len(exp) == 0 -} - -func removeListElement(arr []uint64, i int) []uint64 { - last := len(arr) - 1 - arr[i] = arr[last] - arr = arr[:last] - return arr -} - -func removeDuplicatesAndSingletons(arr []uint64) []uint64 { - for i := 0; i < len(arr); { - found := false - for j := i + 1; j < len(arr); j++ { - if arr[i] == arr[j] { - arr = removeListElement(arr, j) // remove duplicate - found = true - break - } - } - - if found { - i++ - } else { - arr = removeListElement(arr, i) // remove singleton - } - } - - return arr -} diff --git a/swarm/network/simulation/kademlia_test.go b/swarm/network/simulation/kademlia_test.go deleted file mode 100644 index af50aacfd..000000000 --- a/swarm/network/simulation/kademlia_test.go +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/swarm/network" -) - -/* - TestWaitTillHealthy tests that we indeed get a healthy network after we wait for it. - For this to be tested, a bit of a snake tail bite needs to happen: - * First we create a first simulation - * Run it as nodes connected in a ring - * Wait until the network is healthy - * Then we create a snapshot - * With this snapshot we create a new simulation - * This simulation is expected to have a healthy configuration, as it uses the snapshot - * Thus we just iterate all nodes and check that their kademlias are healthy - * If all kademlias are healthy, the test succeeded, otherwise it failed -*/ -func TestWaitTillHealthy(t *testing.T) { - t.Skip("this test is flaky; disabling till underlying problem is solved") - testNodesNum := 10 - - // create the first simulation - sim := New(createSimServiceMap(true)) - - // connect and... - nodeIDs, err := sim.AddNodesAndConnectRing(testNodesNum) - if err != nil { - t.Fatal(err) - } - - // array of all overlay addresses - var addrs [][]byte - // iterate once to be able to build the peer map - for _, node := range nodeIDs { - //get the kademlia overlay address from this ID - a := node.Bytes() - //append it to the array of all overlay addresses - addrs = append(addrs, a) - } - // build a PeerPot only once - pp := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs) - - ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) - defer cancel() - - // ...wait until healthy - ill, err := sim.WaitTillHealthy(ctx) - if err != nil { - for id, kad := range ill { - t.Log("Node", id) - t.Log(kad.String()) - } - t.Fatal(err) - } - - // now create a snapshot of this network - snap, err := sim.Net.Snapshot() - if err != nil { - t.Fatal(err) - } - - // close the initial simulation - sim.Close() - // create a control simulation - controlSim := New(createSimServiceMap(false)) - defer controlSim.Close() - - // load the snapshot into this control simulation - err = controlSim.Net.Load(snap) - if err != nil { - t.Fatal(err) - } - _, err = controlSim.WaitTillHealthy(ctx) - if err != nil { - t.Fatal(err) - } - - for _, node := range nodeIDs { - // ...get its kademlia - item, ok := controlSim.NodeItem(node, BucketKeyKademlia) - if !ok { - t.Fatal("No kademlia bucket item") - } - kad := item.(*network.Kademlia) - // get its base address - kid := common.Bytes2Hex(kad.BaseAddr()) - - //get the health info - info := kad.GetHealthInfo(pp[kid]) - log.Trace("Health info", "info", info) - // check that it is healthy - healthy := info.Healthy() - if !healthy { - t.Fatalf("Expected node %v of control simulation to be healthy, but it is not, unhealthy kademlias: %v", node, kad.String()) - } - } -} - -// createSimServiceMap returns the services map -// this function will create the sim services with or without discovery enabled -// based on the flag passed -func createSimServiceMap(discovery bool) map[string]ServiceFunc { - return map[string]ServiceFunc{ - "bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - addr := network.NewAddr(ctx.Config.Node()) - hp := network.NewHiveParams() - hp.Discovery = discovery - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - kad := network.NewKademlia(addr.Over(), network.NewKadParams()) - // store kademlia in node's bucket under BucketKeyKademlia - // so that it can be found by WaitTillHealthy method. - b.Store(BucketKeyKademlia, kad) - return network.NewBzz(config, kad, nil, nil, nil), nil, nil - }, - } -} - -// TestWaitTillSnapshotRecreated tests that we indeed have a network -// configuration specified in the snapshot file, after we wait for it. -// -// First we create a first simulation -// Run it as nodes connected in a ring -// Wait until the network is healthy -// Then we create a snapshot -// With this snapshot we create a new simulation -// Call WaitTillSnapshotRecreated() function and wait until it returns -// Iterate the nodes and check if all the connections are successfully recreated -func TestWaitTillSnapshotRecreated(t *testing.T) { - t.Skip("test is flaky. disabling until underlying problem is addressed") - var err error - sim := New(createSimServiceMap(true)) - _, err = sim.AddNodesAndConnectRing(16) - if err != nil { - t.Fatal(err) - } - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) - defer cancel() - _, err = sim.WaitTillHealthy(ctx) - if err != nil { - t.Fatal(err) - } - - originalConnections := sim.getActualConnections() - snap, err := sim.Net.Snapshot() - sim.Close() - if err != nil { - t.Fatal(err) - } - - controlSim := New(createSimServiceMap(false)) - defer controlSim.Close() - err = controlSim.Net.Load(snap) - if err != nil { - t.Fatal(err) - } - err = controlSim.WaitTillSnapshotRecreated(ctx, snap) - if err != nil { - t.Fatal(err) - } - controlConnections := controlSim.getActualConnections() - - for _, c := range originalConnections { - if !exist(controlConnections, c) { - t.Fatal("connection was not recreated") - } - } -} - -// exist returns true if val is found in arr -func exist(arr []uint64, val uint64) bool { - for _, c := range arr { - if c == val { - return true - } - } - return false -} - -func TestRemoveDuplicatesAndSingletons(t *testing.T) { - singletons := []uint64{ - 0x3c127c6f6cb026b0, - 0x0f45190d72e71fc5, - 0xb0184c02449e0bb6, - 0xa85c7b84239c54d3, - 0xe3b0c44298fc1c14, - 0x9afbf4c8996fb924, - 0x27ae41e4649b934c, - 0xa495991b7852b855, - } - - doubles := []uint64{ - 0x1b879f878de7fc7a, - 0xc6791470521bdab4, - 0xdd34b0ee39bbccc6, - 0x4d904fbf0f31da10, - 0x6403c2560432c8f8, - 0x18954e33cf3ad847, - 0x90db00e98dc7a8a6, - 0x92886b0dfcc1809b, - } - - var arr []uint64 - arr = append(arr, doubles...) - arr = append(arr, singletons...) - arr = append(arr, doubles...) - arr = removeDuplicatesAndSingletons(arr) - - for _, i := range singletons { - if exist(arr, i) { - t.Fatalf("singleton not removed: %d", i) - } - } - - for _, i := range doubles { - if !exist(arr, i) { - t.Fatalf("wrong value removed: %d", i) - } - } - - for j := 0; j < len(doubles); j++ { - v := doubles[j] + singletons[j] - if exist(arr, v) { - t.Fatalf("non-existing value found, index: %d", j) - } - } -} - -func TestIsAllDeployed(t *testing.T) { - a := []uint64{ - 0x3c127c6f6cb026b0, - 0x0f45190d72e71fc5, - 0xb0184c02449e0bb6, - 0xa85c7b84239c54d3, - 0xe3b0c44298fc1c14, - 0x9afbf4c8996fb924, - 0x27ae41e4649b934c, - 0xa495991b7852b855, - } - - b := []uint64{ - 0x1b879f878de7fc7a, - 0xc6791470521bdab4, - 0xdd34b0ee39bbccc6, - 0x4d904fbf0f31da10, - 0x6403c2560432c8f8, - 0x18954e33cf3ad847, - 0x90db00e98dc7a8a6, - 0x92886b0dfcc1809b, - } - - var c []uint64 - c = append(c, a...) - c = append(c, b...) - - if !isAllDeployed(a, c) { - t.Fatal("isAllDeployed failed") - } - - if !isAllDeployed(b, c) { - t.Fatal("isAllDeployed failed") - } - - if isAllDeployed(c, a) { - t.Fatal("isAllDeployed failed: false positive") - } - - if isAllDeployed(c, b) { - t.Fatal("isAllDeployed failed: false positive") - } - - c = c[2:] - - if isAllDeployed(a, c) { - t.Fatal("isAllDeployed failed: false positive") - } - - if !isAllDeployed(b, c) { - t.Fatal("isAllDeployed failed") - } -} diff --git a/swarm/network/simulation/node.go b/swarm/network/simulation/node.go deleted file mode 100644 index f66b0afd0..000000000 --- a/swarm/network/simulation/node.go +++ /dev/null @@ -1,341 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "bytes" - "context" - "crypto/ecdsa" - "encoding/json" - "errors" - "io/ioutil" - "math/rand" - "os" - "sync" - "time" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/simulations" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/swarm/network" -) - -var ( - BucketKeyBzzPrivateKey BucketKey = "bzzprivkey" -) - -// NodeIDs returns NodeIDs for all nodes in the network. -func (s *Simulation) NodeIDs() (ids []enode.ID) { - nodes := s.Net.GetNodes() - ids = make([]enode.ID, len(nodes)) - for i, node := range nodes { - ids[i] = node.ID() - } - return ids -} - -// UpNodeIDs returns NodeIDs for nodes that are up in the network. -func (s *Simulation) UpNodeIDs() (ids []enode.ID) { - nodes := s.Net.GetNodes() - for _, node := range nodes { - if node.Up() { - ids = append(ids, node.ID()) - } - } - return ids -} - -// DownNodeIDs returns NodeIDs for nodes that are stopped in the network. -func (s *Simulation) DownNodeIDs() (ids []enode.ID) { - nodes := s.Net.GetNodes() - for _, node := range nodes { - if !node.Up() { - ids = append(ids, node.ID()) - } - } - return ids -} - -// AddNodeOption defines the option that can be passed -// to Simulation.AddNode method. -type AddNodeOption func(*adapters.NodeConfig) - -// AddNodeWithMsgEvents sets the EnableMsgEvents option -// to NodeConfig. -func AddNodeWithMsgEvents(enable bool) AddNodeOption { - return func(o *adapters.NodeConfig) { - o.EnableMsgEvents = enable - } -} - -// AddNodeWithService specifies a service that should be -// started on a node. This option can be repeated as variadic -// argument toe AddNode and other add node related methods. -// If AddNodeWithService is not specified, all services will be started. -func AddNodeWithService(serviceName string) AddNodeOption { - return func(o *adapters.NodeConfig) { - o.Services = append(o.Services, serviceName) - } -} - -// AddNode creates a new node with random configuration, -// applies provided options to the config and adds the node to network. -// By default all services will be started on a node. If one or more -// AddNodeWithService option are provided, only specified services will be started. -func (s *Simulation) AddNode(opts ...AddNodeOption) (id enode.ID, err error) { - conf := adapters.RandomNodeConfig() - for _, o := range opts { - o(conf) - } - if len(conf.Services) == 0 { - conf.Services = s.serviceNames - } - - // add ENR records to the underlying node - // most importantly the bzz overlay address - // - // for now we have no way of setting bootnodes or lightnodes in sims - // so we just let them be set to false - // they should perhaps be possible to override them with AddNodeOption - bzzPrivateKey, err := BzzPrivateKeyFromConfig(conf) - if err != nil { - return enode.ID{}, err - } - - enodeParams := &network.EnodeParams{ - PrivateKey: bzzPrivateKey, - } - record, err := network.NewEnodeRecord(enodeParams) - conf.Record = *record - - // Add the bzz address to the node config - node, err := s.Net.NewNodeWithConfig(conf) - if err != nil { - return id, err - } - s.buckets[node.ID()] = new(sync.Map) - s.SetNodeItem(node.ID(), BucketKeyBzzPrivateKey, bzzPrivateKey) - - return node.ID(), s.Net.Start(node.ID()) -} - -// AddNodes creates new nodes with random configurations, -// applies provided options to the config and adds nodes to network. -func (s *Simulation) AddNodes(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - ids = make([]enode.ID, 0, count) - for i := 0; i < count; i++ { - id, err := s.AddNode(opts...) - if err != nil { - return nil, err - } - ids = append(ids, id) - } - return ids, nil -} - -// AddNodesAndConnectFull is a helpper method that combines -// AddNodes and ConnectNodesFull. Only new nodes will be connected. -func (s *Simulation) AddNodesAndConnectFull(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - if count < 2 { - return nil, errors.New("count of nodes must be at least 2") - } - ids, err = s.AddNodes(count, opts...) - if err != nil { - return nil, err - } - err = s.Net.ConnectNodesFull(ids) - if err != nil { - return nil, err - } - return ids, nil -} - -// AddNodesAndConnectChain is a helpper method that combines -// AddNodes and ConnectNodesChain. The chain will be continued from the last -// added node, if there is one in simulation using ConnectToLastNode method. -func (s *Simulation) AddNodesAndConnectChain(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - if count < 2 { - return nil, errors.New("count of nodes must be at least 2") - } - id, err := s.AddNode(opts...) - if err != nil { - return nil, err - } - err = s.Net.ConnectToLastNode(id) - if err != nil { - return nil, err - } - ids, err = s.AddNodes(count-1, opts...) - if err != nil { - return nil, err - } - ids = append([]enode.ID{id}, ids...) - err = s.Net.ConnectNodesChain(ids) - if err != nil { - return nil, err - } - return ids, nil -} - -// AddNodesAndConnectRing is a helpper method that combines -// AddNodes and ConnectNodesRing. -func (s *Simulation) AddNodesAndConnectRing(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - if count < 2 { - return nil, errors.New("count of nodes must be at least 2") - } - ids, err = s.AddNodes(count, opts...) - if err != nil { - return nil, err - } - err = s.Net.ConnectNodesRing(ids) - if err != nil { - return nil, err - } - return ids, nil -} - -// AddNodesAndConnectStar is a helpper method that combines -// AddNodes and ConnectNodesStar. -func (s *Simulation) AddNodesAndConnectStar(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - if count < 2 { - return nil, errors.New("count of nodes must be at least 2") - } - ids, err = s.AddNodes(count, opts...) - if err != nil { - return nil, err - } - err = s.Net.ConnectNodesStar(ids[1:], ids[0]) - if err != nil { - return nil, err - } - return ids, nil -} - -// UploadSnapshot uploads a snapshot to the simulation -// This method tries to open the json file provided, applies the config to all nodes -// and then loads the snapshot into the Simulation network -func (s *Simulation) UploadSnapshot(ctx context.Context, snapshotFile string, opts ...AddNodeOption) error { - f, err := os.Open(snapshotFile) - if err != nil { - return err - } - - jsonbyte, err := ioutil.ReadAll(f) - f.Close() - if err != nil { - return err - } - var snap simulations.Snapshot - if err := json.Unmarshal(jsonbyte, &snap); err != nil { - return err - } - - //the snapshot probably has the property EnableMsgEvents not set - //set it to true (we need this to wait for messages before uploading) - for i := range snap.Nodes { - snap.Nodes[i].Node.Config.EnableMsgEvents = true - snap.Nodes[i].Node.Config.Services = s.serviceNames - for _, o := range opts { - o(snap.Nodes[i].Node.Config) - } - } - - if err := s.Net.Load(&snap); err != nil { - return err - } - return s.WaitTillSnapshotRecreated(ctx, &snap) -} - -// StartNode starts a node by NodeID. -func (s *Simulation) StartNode(id enode.ID) (err error) { - return s.Net.Start(id) -} - -// StartRandomNode starts a random node. -func (s *Simulation) StartRandomNode() (id enode.ID, err error) { - n := s.Net.GetRandomDownNode() - if n == nil { - return id, ErrNodeNotFound - } - return n.ID(), s.Net.Start(n.ID()) -} - -// StartRandomNodes starts random nodes. -func (s *Simulation) StartRandomNodes(count int) (ids []enode.ID, err error) { - ids = make([]enode.ID, 0, count) - for i := 0; i < count; i++ { - n := s.Net.GetRandomDownNode() - if n == nil { - return nil, ErrNodeNotFound - } - err = s.Net.Start(n.ID()) - if err != nil { - return nil, err - } - ids = append(ids, n.ID()) - } - return ids, nil -} - -// StopNode stops a node by NodeID. -func (s *Simulation) StopNode(id enode.ID) (err error) { - return s.Net.Stop(id) -} - -// StopRandomNode stops a random node. -func (s *Simulation) StopRandomNode() (id enode.ID, err error) { - n := s.Net.GetRandomUpNode() - if n == nil { - return id, ErrNodeNotFound - } - return n.ID(), s.Net.Stop(n.ID()) -} - -// StopRandomNodes stops random nodes. -func (s *Simulation) StopRandomNodes(count int) (ids []enode.ID, err error) { - ids = make([]enode.ID, 0, count) - for i := 0; i < count; i++ { - n := s.Net.GetRandomUpNode() - if n == nil { - return nil, ErrNodeNotFound - } - err = s.Net.Stop(n.ID()) - if err != nil { - return nil, err - } - ids = append(ids, n.ID()) - } - return ids, nil -} - -// seed the random generator for Simulation.randomNode. -func init() { - rand.Seed(time.Now().UnixNano()) -} - -// derive a private key for swarm for the node key -// returns the private key used to generate the bzz key -func BzzPrivateKeyFromConfig(conf *adapters.NodeConfig) (*ecdsa.PrivateKey, error) { - // pad the seed key some arbitrary data as ecdsa.GenerateKey takes 40 bytes seed data - privKeyBuf := append(crypto.FromECDSA(conf.PrivateKey), []byte{0x62, 0x7a, 0x7a, 0x62, 0x7a, 0x7a, 0x62, 0x7a}...) - bzzPrivateKey, err := ecdsa.GenerateKey(crypto.S256(), bytes.NewReader(privKeyBuf)) - if err != nil { - return nil, err - } - return bzzPrivateKey, nil -} diff --git a/swarm/network/simulation/node_test.go b/swarm/network/simulation/node_test.go deleted file mode 100644 index e1e20a0f1..000000000 --- a/swarm/network/simulation/node_test.go +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "context" - "fmt" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/simulations" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/swarm/network" -) - -func TestUpDownNodeIDs(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - ids, err := sim.AddNodes(10) - if err != nil { - t.Fatal(err) - } - - gotIDs := sim.NodeIDs() - - if !equalNodeIDs(ids, gotIDs) { - t.Error("returned nodes are not equal to added ones") - } - - stoppedIDs, err := sim.StopRandomNodes(3) - if err != nil { - t.Fatal(err) - } - - gotIDs = sim.UpNodeIDs() - - for _, id := range gotIDs { - if !sim.Net.GetNode(id).Up() { - t.Errorf("node %s should not be down", id) - } - } - - if !equalNodeIDs(ids, append(gotIDs, stoppedIDs...)) { - t.Error("returned nodes are not equal to added ones") - } - - gotIDs = sim.DownNodeIDs() - - for _, id := range gotIDs { - if sim.Net.GetNode(id).Up() { - t.Errorf("node %s should not be up", id) - } - } - - if !equalNodeIDs(stoppedIDs, gotIDs) { - t.Error("returned nodes are not equal to the stopped ones") - } -} - -func equalNodeIDs(one, other []enode.ID) bool { - if len(one) != len(other) { - return false - } - var count int - for _, a := range one { - var found bool - for _, b := range other { - if a == b { - found = true - break - } - } - if found { - count++ - } else { - return false - } - } - return count == len(one) -} - -func TestAddNode(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - id, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - - if !n.Up() { - t.Error("node not started") - } -} - -func TestAddNodeWithMsgEvents(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - id, err := sim.AddNode(AddNodeWithMsgEvents(true)) - if err != nil { - t.Fatal(err) - } - - if !sim.Net.GetNode(id).Config.EnableMsgEvents { - t.Error("EnableMsgEvents is false") - } - - id, err = sim.AddNode(AddNodeWithMsgEvents(false)) - if err != nil { - t.Fatal(err) - } - - if sim.Net.GetNode(id).Config.EnableMsgEvents { - t.Error("EnableMsgEvents is true") - } -} - -func TestAddNodeWithService(t *testing.T) { - sim := New(map[string]ServiceFunc{ - "noop1": noopServiceFunc, - "noop2": noopServiceFunc, - }) - defer sim.Close() - - id, err := sim.AddNode(AddNodeWithService("noop1")) - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id).Node.(*adapters.SimNode) - if n.Service("noop1") == nil { - t.Error("service noop1 not found on node") - } - if n.Service("noop2") != nil { - t.Error("service noop2 should not be found on node") - } -} - -func TestAddNodeMultipleServices(t *testing.T) { - sim := New(map[string]ServiceFunc{ - "noop1": noopServiceFunc, - "noop2": noopService2Func, - }) - defer sim.Close() - - id, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id).Node.(*adapters.SimNode) - if n.Service("noop1") == nil { - t.Error("service noop1 not found on node") - } - if n.Service("noop2") == nil { - t.Error("service noop2 not found on node") - } -} - -func TestAddNodeDuplicateServiceError(t *testing.T) { - sim := New(map[string]ServiceFunc{ - "noop1": noopServiceFunc, - "noop2": noopServiceFunc, - }) - defer sim.Close() - - wantErr := "duplicate service: *simulation.noopService" - _, err := sim.AddNode() - if err.Error() != wantErr { - t.Errorf("got error %q, want %q", err, wantErr) - } -} - -func TestAddNodes(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - nodesCount := 12 - - ids, err := sim.AddNodes(nodesCount) - if err != nil { - t.Fatal(err) - } - - count := len(ids) - if count != nodesCount { - t.Errorf("expected %v nodes, got %v", nodesCount, count) - } - - count = len(sim.Net.GetNodes()) - if count != nodesCount { - t.Errorf("expected %v nodes, got %v", nodesCount, count) - } -} - -func TestAddNodesAndConnectFull(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - n := 12 - - ids, err := sim.AddNodesAndConnectFull(n) - if err != nil { - t.Fatal(err) - } - - simulations.VerifyFull(t, sim.Net, ids) -} - -func TestAddNodesAndConnectChain(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodesAndConnectChain(12) - if err != nil { - t.Fatal(err) - } - - // add another set of nodes to test - // if two chains are connected - _, err = sim.AddNodesAndConnectChain(7) - if err != nil { - t.Fatal(err) - } - - simulations.VerifyChain(t, sim.Net, sim.UpNodeIDs()) -} - -func TestAddNodesAndConnectRing(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - ids, err := sim.AddNodesAndConnectRing(12) - if err != nil { - t.Fatal(err) - } - - simulations.VerifyRing(t, sim.Net, ids) -} - -func TestAddNodesAndConnectStar(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - ids, err := sim.AddNodesAndConnectStar(12) - if err != nil { - t.Fatal(err) - } - - simulations.VerifyStar(t, sim.Net, ids, 0) -} - -//To test that uploading a snapshot works -func TestUploadSnapshot(t *testing.T) { - log.Debug("Creating simulation") - s := New(map[string]ServiceFunc{ - "bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - addr := network.NewAddr(ctx.Config.Node()) - hp := network.NewHiveParams() - hp.Discovery = false - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - kad := network.NewKademlia(addr.Over(), network.NewKadParams()) - b.Store(BucketKeyKademlia, kad) - return network.NewBzz(config, kad, nil, nil, nil), nil, nil - }, - }) - defer s.Close() - - nodeCount := 16 - log.Debug("Uploading snapshot") - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - err := s.UploadSnapshot(ctx, fmt.Sprintf("../stream/testing/snapshot_%d.json", nodeCount)) - if err != nil { - t.Fatalf("Error uploading snapshot to simulation network: %v", err) - } - - log.Debug("Starting simulation...") - s.Run(ctx, func(ctx context.Context, sim *Simulation) error { - log.Debug("Checking") - nodes := sim.UpNodeIDs() - if len(nodes) != nodeCount { - t.Fatal("Simulation network node number doesn't match snapshot node number") - } - return nil - }) - log.Debug("Done.") -} - -func TestStartStopNode(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - id, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - if !n.Up() { - t.Error("node not started") - } - - err = sim.StopNode(id) - if err != nil { - t.Fatal(err) - } - if n.Up() { - t.Error("node not stopped") - } - - waitForPeerEventPropagation() - - err = sim.StartNode(id) - if err != nil { - t.Fatal(err) - } - if !n.Up() { - t.Error("node not started") - } -} - -func TestStartStopRandomNode(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodes(3) - if err != nil { - t.Fatal(err) - } - - id, err := sim.StopRandomNode() - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - if n.Up() { - t.Error("node not stopped") - } - - id2, err := sim.StopRandomNode() - if err != nil { - t.Fatal(err) - } - - waitForPeerEventPropagation() - - idStarted, err := sim.StartRandomNode() - if err != nil { - t.Fatal(err) - } - - if idStarted != id && idStarted != id2 { - t.Error("unexpected started node ID") - } -} - -func TestStartStopRandomNodes(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodes(10) - if err != nil { - t.Fatal(err) - } - - ids, err := sim.StopRandomNodes(3) - if err != nil { - t.Fatal(err) - } - - for _, id := range ids { - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - if n.Up() { - t.Error("node not stopped") - } - } - - waitForPeerEventPropagation() - - ids, err = sim.StartRandomNodes(2) - if err != nil { - t.Fatal(err) - } - - for _, id := range ids { - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - if !n.Up() { - t.Error("node not started") - } - } -} - -func waitForPeerEventPropagation() { - // Sleep here to ensure that Network.watchPeerEvents defer function - // has set the `node.Up() = false` before we start the node again. - // - // The same node is stopped and started again, and upon start - // watchPeerEvents is started in a goroutine. If the node is stopped - // and then very quickly started, that goroutine may be scheduled later - // then start and force `node.Up() = false` in its defer function. - // This will make this test unreliable. - time.Sleep(1 * time.Second) -} diff --git a/swarm/network/simulation/service.go b/swarm/network/simulation/service.go deleted file mode 100644 index 0ac8149a9..000000000 --- a/swarm/network/simulation/service.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" -) - -// Service returns a single Service by name on a particular node -// with provided id. -func (s *Simulation) Service(name string, id enode.ID) node.Service { - simNode, ok := s.Net.GetNode(id).Node.(*adapters.SimNode) - if !ok { - return nil - } - services := simNode.ServiceMap() - if len(services) == 0 { - return nil - } - return services[name] -} - -// RandomService returns a single Service by name on a -// randomly chosen node that is up. -func (s *Simulation) RandomService(name string) node.Service { - n := s.Net.GetRandomUpNode().Node.(*adapters.SimNode) - if n == nil { - return nil - } - return n.Service(name) -} - -// Services returns all services with a provided name -// from nodes that are up. -func (s *Simulation) Services(name string) (services map[enode.ID]node.Service) { - nodes := s.Net.GetNodes() - services = make(map[enode.ID]node.Service) - for _, node := range nodes { - if !node.Up() { - continue - } - simNode, ok := node.Node.(*adapters.SimNode) - if !ok { - continue - } - services[node.ID()] = simNode.Service(name) - } - return services -} diff --git a/swarm/network/simulation/service_test.go b/swarm/network/simulation/service_test.go deleted file mode 100644 index 23b0d86f2..000000000 --- a/swarm/network/simulation/service_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "testing" -) - -func TestService(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - id, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - _, ok := sim.Service("noop", id).(*noopService) - if !ok { - t.Fatalf("service is not of %T type", &noopService{}) - } - - _, ok = sim.RandomService("noop").(*noopService) - if !ok { - t.Fatalf("service is not of %T type", &noopService{}) - } - - _, ok = sim.Services("noop")[id].(*noopService) - if !ok { - t.Fatalf("service is not of %T type", &noopService{}) - } -} diff --git a/swarm/network/simulation/simulation.go b/swarm/network/simulation/simulation.go deleted file mode 100644 index 5787cafda..000000000 --- a/swarm/network/simulation/simulation.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "context" - "errors" - "net/http" - "sync" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/simulations" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/swarm/network" -) - -// Common errors that are returned by functions in this package. -var ( - ErrNodeNotFound = errors.New("node not found") -) - -// Simulation provides methods on network, nodes and services -// to manage them. -type Simulation struct { - // Net is exposed as a way to access lower level functionalities - // of p2p/simulations.Network. - Net *simulations.Network - - serviceNames []string - cleanupFuncs []func() - buckets map[enode.ID]*sync.Map - shutdownWG sync.WaitGroup - done chan struct{} - mu sync.RWMutex - neighbourhoodSize int - - httpSrv *http.Server //attach a HTTP server via SimulationOptions - handler *simulations.Server //HTTP handler for the server - runC chan struct{} //channel where frontend signals it is ready -} - -// ServiceFunc is used in New to declare new service constructor. -// The first argument provides ServiceContext from the adapters package -// giving for example the access to NodeID. Second argument is the sync.Map -// where all "global" state related to the service should be kept. -// All cleanups needed for constructed service and any other constructed -// objects should ne provided in a single returned cleanup function. -// Returned cleanup function will be called by Close function -// after network shutdown. -type ServiceFunc func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) - -// New creates a new simulation instance -// Services map must have unique keys as service names and -// every ServiceFunc must return a node.Service of the unique type. -// This restriction is required by node.Node.Start() function -// which is used to start node.Service returned by ServiceFunc. -func New(services map[string]ServiceFunc) (s *Simulation) { - s = &Simulation{ - buckets: make(map[enode.ID]*sync.Map), - done: make(chan struct{}), - neighbourhoodSize: network.NewKadParams().NeighbourhoodSize, - } - - adapterServices := make(map[string]adapters.ServiceFunc, len(services)) - for name, serviceFunc := range services { - // Scope this variables correctly - // as they will be in the adapterServices[name] function accessed later. - name, serviceFunc := name, serviceFunc - s.serviceNames = append(s.serviceNames, name) - adapterServices[name] = func(ctx *adapters.ServiceContext) (node.Service, error) { - s.mu.Lock() - defer s.mu.Unlock() - b, ok := s.buckets[ctx.Config.ID] - if !ok { - b = new(sync.Map) - } - service, cleanup, err := serviceFunc(ctx, b) - if err != nil { - return nil, err - } - if cleanup != nil { - s.cleanupFuncs = append(s.cleanupFuncs, cleanup) - } - s.buckets[ctx.Config.ID] = b - return service, nil - } - } - - s.Net = simulations.NewNetwork( - adapters.NewTCPAdapter(adapterServices), - &simulations.NetworkConfig{ID: "0"}, - ) - - return s -} - -// RunFunc is the function that will be called -// on Simulation.Run method call. -type RunFunc func(context.Context, *Simulation) error - -// Result is the returned value of Simulation.Run method. -type Result struct { - Duration time.Duration - Error error -} - -// Run calls the RunFunc function while taking care of -// cancellation provided through the Context. -func (s *Simulation) Run(ctx context.Context, f RunFunc) (r Result) { - //if the option is set to run a HTTP server with the simulation, - //init the server and start it - start := time.Now() - if s.httpSrv != nil { - log.Info("Waiting for frontend to be ready...(send POST /runsim to HTTP server)") - //wait for the frontend to connect - select { - case <-s.runC: - case <-ctx.Done(): - return Result{ - Duration: time.Since(start), - Error: ctx.Err(), - } - } - log.Info("Received signal from frontend - starting simulation run.") - } - errc := make(chan error) - quit := make(chan struct{}) - defer close(quit) - go func() { - select { - case errc <- f(ctx, s): - case <-quit: - } - }() - var err error - select { - case <-ctx.Done(): - err = ctx.Err() - case err = <-errc: - } - return Result{ - Duration: time.Since(start), - Error: err, - } -} - -// Maximal number of parallel calls to cleanup functions on -// Simulation.Close. -var maxParallelCleanups = 10 - -// Close calls all cleanup functions that are returned by -// ServiceFunc, waits for all of them to finish and other -// functions that explicitly block shutdownWG -// (like Simulation.PeerEvents) and shuts down the network -// at the end. It is used to clean all resources from the -// simulation. -func (s *Simulation) Close() { - close(s.done) - - sem := make(chan struct{}, maxParallelCleanups) - s.mu.RLock() - cleanupFuncs := make([]func(), len(s.cleanupFuncs)) - for i, f := range s.cleanupFuncs { - if f != nil { - cleanupFuncs[i] = f - } - } - s.mu.RUnlock() - var cleanupWG sync.WaitGroup - for _, cleanup := range cleanupFuncs { - cleanupWG.Add(1) - sem <- struct{}{} - go func(cleanup func()) { - defer cleanupWG.Done() - defer func() { <-sem }() - - cleanup() - }(cleanup) - } - cleanupWG.Wait() - - if s.httpSrv != nil { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - err := s.httpSrv.Shutdown(ctx) - if err != nil { - log.Error("Error shutting down HTTP server!", "err", err) - } - close(s.runC) - } - - s.shutdownWG.Wait() - s.Net.Shutdown() -} - -// Done returns a channel that is closed when the simulation -// is closed by Close method. It is useful for signaling termination -// of all possible goroutines that are created within the test. -func (s *Simulation) Done() <-chan struct{} { - return s.done -} diff --git a/swarm/network/simulation/simulation_test.go b/swarm/network/simulation/simulation_test.go deleted file mode 100644 index 1d0338f59..000000000 --- a/swarm/network/simulation/simulation_test.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2018 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 simulation - -import ( - "context" - "errors" - "flag" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/simulations" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/mattn/go-colorable" -) - -var ( - loglevel = flag.Int("loglevel", 2, "verbosity of logs") -) - -func init() { - flag.Parse() - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) -} - -// TestRun tests if Run method calls RunFunc and if it handles context properly. -func TestRun(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - t.Run("call", func(t *testing.T) { - expect := "something" - var got string - r := sim.Run(context.Background(), func(ctx context.Context, sim *Simulation) error { - got = expect - return nil - }) - - if r.Error != nil { - t.Errorf("unexpected error: %v", r.Error) - } - if got != expect { - t.Errorf("expected %q, got %q", expect, got) - } - }) - - t.Run("cancellation", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) - defer cancel() - - r := sim.Run(ctx, func(ctx context.Context, sim *Simulation) error { - time.Sleep(time.Second) - return nil - }) - - if r.Error != context.DeadlineExceeded { - t.Errorf("unexpected error: %v", r.Error) - } - }) - - t.Run("context value and duration", func(t *testing.T) { - ctx := context.WithValue(context.Background(), "hey", "there") - sleep := 50 * time.Millisecond - - r := sim.Run(ctx, func(ctx context.Context, sim *Simulation) error { - if ctx.Value("hey") != "there" { - return errors.New("expected context value not passed") - } - time.Sleep(sleep) - return nil - }) - - if r.Error != nil { - t.Errorf("unexpected error: %v", r.Error) - } - if r.Duration < sleep { - t.Errorf("reported run duration less then expected: %s", r.Duration) - } - }) -} - -// TestClose tests are Close method triggers all close functions and are all nodes not up anymore. -func TestClose(t *testing.T) { - var mu sync.Mutex - var cleanupCount int - - sleep := 50 * time.Millisecond - - sim := New(map[string]ServiceFunc{ - "noop": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - return newNoopService(), func() { - time.Sleep(sleep) - mu.Lock() - defer mu.Unlock() - cleanupCount++ - }, nil - }, - }) - - nodeCount := 30 - - _, err := sim.AddNodes(nodeCount) - if err != nil { - t.Fatal(err) - } - - var upNodeCount int - for _, n := range sim.Net.GetNodes() { - if n.Up() { - upNodeCount++ - } - } - if upNodeCount != nodeCount { - t.Errorf("all nodes should be up, insted only %v are up", upNodeCount) - } - - sim.Close() - - if cleanupCount != nodeCount { - t.Errorf("number of cleanups expected %v, got %v", nodeCount, cleanupCount) - } - - upNodeCount = 0 - for _, n := range sim.Net.GetNodes() { - if n.Up() { - upNodeCount++ - } - } - if upNodeCount != 0 { - t.Errorf("all nodes should be down, insted %v are up", upNodeCount) - } -} - -// TestDone checks if Close method triggers the closing of done channel. -func TestDone(t *testing.T) { - sim := New(noopServiceFuncMap) - sleep := 50 * time.Millisecond - timeout := 2 * time.Second - - start := time.Now() - go func() { - time.Sleep(sleep) - sim.Close() - }() - - select { - case <-time.After(timeout): - t.Error("done channel closing timed out") - case <-sim.Done(): - if d := time.Since(start); d < sleep { - t.Errorf("done channel closed sooner then expected: %s", d) - } - } -} - -// a helper map for usual services that do not do anything -var noopServiceFuncMap = map[string]ServiceFunc{ - "noop": noopServiceFunc, -} - -// a helper function for most basic noop service -func noopServiceFunc(_ *adapters.ServiceContext, _ *sync.Map) (node.Service, func(), error) { - return newNoopService(), nil, nil -} - -func newNoopService() node.Service { - return &noopService{} -} - -// a helper function for most basic Noop service -// of a different type then NoopService to test -// multiple services on one node. -func noopService2Func(_ *adapters.ServiceContext, _ *sync.Map) (node.Service, func(), error) { - return new(noopService2), nil, nil -} - -// NoopService2 is the service that does not do anything -// but implements node.Service interface. -type noopService2 struct { - simulations.NoopService -} - -type noopService struct { - simulations.NoopService -} |