1// Copyright 2017 The go-ethereum Authors
2// This file is part of the go-ethereum library.
3//
4// The go-ethereum library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Lesser General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// The go-ethereum library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Lesser General Public License for more details.
13//
14// You should have received a copy of the GNU Lesser General Public License
15// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16
17package simulations
18
19import (
20	"bytes"
21	"context"
22	"encoding/json"
23	"fmt"
24	"reflect"
25	"strconv"
26	"strings"
27	"testing"
28	"time"
29
30	"github.com/ethereum/go-ethereum/log"
31	"github.com/ethereum/go-ethereum/node"
32	"github.com/ethereum/go-ethereum/p2p/enode"
33	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
34)
35
36// Tests that a created snapshot with a minimal service only contains the expected connections
37// and that a network when loaded with this snapshot only contains those same connections
38func TestSnapshot(t *testing.T) {
39
40	// PART I
41	// create snapshot from ring network
42
43	// this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting
44	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
45		"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
46			return NewNoopService(nil), nil
47		},
48	})
49
50	// create network
51	network := NewNetwork(adapter, &NetworkConfig{
52		DefaultService: "noopwoop",
53	})
54	// \todo consider making a member of network, set to true threadsafe when shutdown
55	runningOne := true
56	defer func() {
57		if runningOne {
58			network.Shutdown()
59		}
60	}()
61
62	// create and start nodes
63	nodeCount := 20
64	ids := make([]enode.ID, nodeCount)
65	for i := 0; i < nodeCount; i++ {
66		conf := adapters.RandomNodeConfig()
67		node, err := network.NewNodeWithConfig(conf)
68		if err != nil {
69			t.Fatalf("error creating node: %s", err)
70		}
71		if err := network.Start(node.ID()); err != nil {
72			t.Fatalf("error starting node: %s", err)
73		}
74		ids[i] = node.ID()
75	}
76
77	// subscribe to peer events
78	evC := make(chan *Event)
79	sub := network.Events().Subscribe(evC)
80	defer sub.Unsubscribe()
81
82	// connect nodes in a ring
83	// spawn separate thread to avoid deadlock in the event listeners
84	connectErr := make(chan error, 1)
85	go func() {
86		for i, id := range ids {
87			peerID := ids[(i+1)%len(ids)]
88			if err := network.Connect(id, peerID); err != nil {
89				connectErr <- err
90				return
91			}
92		}
93	}()
94
95	// collect connection events up to expected number
96	ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
97	defer cancel()
98	checkIds := make(map[enode.ID][]enode.ID)
99	connEventCount := nodeCount
100OUTER:
101	for {
102		select {
103		case <-ctx.Done():
104			t.Fatal(ctx.Err())
105		case err := <-connectErr:
106			t.Fatal(err)
107		case ev := <-evC:
108			if ev.Type == EventTypeConn && !ev.Control {
109				// fail on any disconnect
110				if !ev.Conn.Up {
111					t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
112				}
113				checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
114				checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
115				connEventCount--
116				log.Debug("ev", "count", connEventCount)
117				if connEventCount == 0 {
118					break OUTER
119				}
120			}
121		}
122	}
123
124	// create snapshot of current network
125	snap, err := network.Snapshot()
126	if err != nil {
127		t.Fatal(err)
128	}
129	j, err := json.Marshal(snap)
130	if err != nil {
131		t.Fatal(err)
132	}
133	log.Debug("snapshot taken", "nodes", len(snap.Nodes), "conns", len(snap.Conns), "json", string(j))
134
135	// verify that the snap element numbers check out
136	if len(checkIds) != len(snap.Conns) || len(checkIds) != len(snap.Nodes) {
137		t.Fatalf("snapshot wrong node,conn counts %d,%d != %d", len(snap.Nodes), len(snap.Conns), len(checkIds))
138	}
139
140	// shut down sim network
141	runningOne = false
142	sub.Unsubscribe()
143	network.Shutdown()
144
145	// check that we have all the expected connections in the snapshot
146	for nodid, nodConns := range checkIds {
147		for _, nodConn := range nodConns {
148			var match bool
149			for _, snapConn := range snap.Conns {
150				if snapConn.One == nodid && snapConn.Other == nodConn {
151					match = true
152					break
153				} else if snapConn.Other == nodid && snapConn.One == nodConn {
154					match = true
155					break
156				}
157			}
158			if !match {
159				t.Fatalf("snapshot missing conn %v -> %v", nodid, nodConn)
160			}
161		}
162	}
163	log.Info("snapshot checked")
164
165	// PART II
166	// load snapshot and verify that exactly same connections are formed
167
168	adapter = adapters.NewSimAdapter(adapters.LifecycleConstructors{
169		"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
170			return NewNoopService(nil), nil
171		},
172	})
173	network = NewNetwork(adapter, &NetworkConfig{
174		DefaultService: "noopwoop",
175	})
176	defer func() {
177		network.Shutdown()
178	}()
179
180	// subscribe to peer events
181	// every node up and conn up event will generate one additional control event
182	// therefore multiply the count by two
183	evC = make(chan *Event, (len(snap.Conns)*2)+(len(snap.Nodes)*2))
184	sub = network.Events().Subscribe(evC)
185	defer sub.Unsubscribe()
186
187	// load the snapshot
188	// spawn separate thread to avoid deadlock in the event listeners
189	err = network.Load(snap)
190	if err != nil {
191		t.Fatal(err)
192	}
193
194	// collect connection events up to expected number
195	ctx, cancel = context.WithTimeout(context.TODO(), time.Second*3)
196	defer cancel()
197
198	connEventCount = nodeCount
199
200OuterTwo:
201	for {
202		select {
203		case <-ctx.Done():
204			t.Fatal(ctx.Err())
205		case ev := <-evC:
206			if ev.Type == EventTypeConn && !ev.Control {
207
208				// fail on any disconnect
209				if !ev.Conn.Up {
210					t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
211				}
212				log.Debug("conn", "on", ev.Conn.One, "other", ev.Conn.Other)
213				checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
214				checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
215				connEventCount--
216				log.Debug("ev", "count", connEventCount)
217				if connEventCount == 0 {
218					break OuterTwo
219				}
220			}
221		}
222	}
223
224	// check that we have all expected connections in the network
225	for _, snapConn := range snap.Conns {
226		var match bool
227		for nodid, nodConns := range checkIds {
228			for _, nodConn := range nodConns {
229				if snapConn.One == nodid && snapConn.Other == nodConn {
230					match = true
231					break
232				} else if snapConn.Other == nodid && snapConn.One == nodConn {
233					match = true
234					break
235				}
236			}
237		}
238		if !match {
239			t.Fatalf("network missing conn %v -> %v", snapConn.One, snapConn.Other)
240		}
241	}
242
243	// verify that network didn't generate any other additional connection events after the ones we have collected within a reasonable period of time
244	ctx, cancel = context.WithTimeout(context.TODO(), time.Second)
245	defer cancel()
246	select {
247	case <-ctx.Done():
248	case ev := <-evC:
249		if ev.Type == EventTypeConn {
250			t.Fatalf("Superfluous conn found %v -> %v", ev.Conn.One, ev.Conn.Other)
251		}
252	}
253
254	// This test validates if all connections from the snapshot
255	// are created in the network.
256	t.Run("conns after load", func(t *testing.T) {
257		// Create new network.
258		n := NewNetwork(
259			adapters.NewSimAdapter(adapters.LifecycleConstructors{
260				"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
261					return NewNoopService(nil), nil
262				},
263			}),
264			&NetworkConfig{
265				DefaultService: "noopwoop",
266			},
267		)
268		defer n.Shutdown()
269
270		// Load the same snapshot.
271		err := n.Load(snap)
272		if err != nil {
273			t.Fatal(err)
274		}
275
276		// Check every connection from the snapshot
277		// if it is in the network, too.
278		for _, c := range snap.Conns {
279			if n.GetConn(c.One, c.Other) == nil {
280				t.Errorf("missing connection: %s -> %s", c.One, c.Other)
281			}
282		}
283	})
284}
285
286// TestNetworkSimulation creates a multi-node simulation network with each node
287// connected in a ring topology, checks that all nodes successfully handshake
288// with each other and that a snapshot fully represents the desired topology
289func TestNetworkSimulation(t *testing.T) {
290	// create simulation network with 20 testService nodes
291	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
292		"test": newTestService,
293	})
294	network := NewNetwork(adapter, &NetworkConfig{
295		DefaultService: "test",
296	})
297	defer network.Shutdown()
298	nodeCount := 20
299	ids := make([]enode.ID, nodeCount)
300	for i := 0; i < nodeCount; i++ {
301		conf := adapters.RandomNodeConfig()
302		node, err := network.NewNodeWithConfig(conf)
303		if err != nil {
304			t.Fatalf("error creating node: %s", err)
305		}
306		if err := network.Start(node.ID()); err != nil {
307			t.Fatalf("error starting node: %s", err)
308		}
309		ids[i] = node.ID()
310	}
311
312	// perform a check which connects the nodes in a ring (so each node is
313	// connected to exactly two peers) and then checks that all nodes
314	// performed two handshakes by checking their peerCount
315	action := func(_ context.Context) error {
316		for i, id := range ids {
317			peerID := ids[(i+1)%len(ids)]
318			if err := network.Connect(id, peerID); err != nil {
319				return err
320			}
321		}
322		return nil
323	}
324	check := func(ctx context.Context, id enode.ID) (bool, error) {
325		// check we haven't run out of time
326		select {
327		case <-ctx.Done():
328			return false, ctx.Err()
329		default:
330		}
331
332		// get the node
333		node := network.GetNode(id)
334		if node == nil {
335			return false, fmt.Errorf("unknown node: %s", id)
336		}
337
338		// check it has exactly two peers
339		client, err := node.Client()
340		if err != nil {
341			return false, err
342		}
343		var peerCount int64
344		if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil {
345			return false, err
346		}
347		switch {
348		case peerCount < 2:
349			return false, nil
350		case peerCount == 2:
351			return true, nil
352		default:
353			return false, fmt.Errorf("unexpected peerCount: %d", peerCount)
354		}
355	}
356
357	timeout := 30 * time.Second
358	ctx, cancel := context.WithTimeout(context.Background(), timeout)
359	defer cancel()
360
361	// trigger a check every 100ms
362	trigger := make(chan enode.ID)
363	go triggerChecks(ctx, ids, trigger, 100*time.Millisecond)
364
365	result := NewSimulation(network).Run(ctx, &Step{
366		Action:  action,
367		Trigger: trigger,
368		Expect: &Expectation{
369			Nodes: ids,
370			Check: check,
371		},
372	})
373	if result.Error != nil {
374		t.Fatalf("simulation failed: %s", result.Error)
375	}
376
377	// take a network snapshot and check it contains the correct topology
378	snap, err := network.Snapshot()
379	if err != nil {
380		t.Fatal(err)
381	}
382	if len(snap.Nodes) != nodeCount {
383		t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes))
384	}
385	if len(snap.Conns) != nodeCount {
386		t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns))
387	}
388	for i, id := range ids {
389		conn := snap.Conns[i]
390		if conn.One != id {
391			t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One)
392		}
393		peerID := ids[(i+1)%len(ids)]
394		if conn.Other != peerID {
395			t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other)
396		}
397	}
398}
399
400func createTestNodes(count int, network *Network) (nodes []*Node, err error) {
401	for i := 0; i < count; i++ {
402		nodeConf := adapters.RandomNodeConfig()
403		node, err := network.NewNodeWithConfig(nodeConf)
404		if err != nil {
405			return nil, err
406		}
407		if err := network.Start(node.ID()); err != nil {
408			return nil, err
409		}
410
411		nodes = append(nodes, node)
412	}
413
414	return nodes, nil
415}
416
417func createTestNodesWithProperty(property string, count int, network *Network) (propertyNodes []*Node, err error) {
418	for i := 0; i < count; i++ {
419		nodeConf := adapters.RandomNodeConfig()
420		nodeConf.Properties = append(nodeConf.Properties, property)
421
422		node, err := network.NewNodeWithConfig(nodeConf)
423		if err != nil {
424			return nil, err
425		}
426		if err := network.Start(node.ID()); err != nil {
427			return nil, err
428		}
429
430		propertyNodes = append(propertyNodes, node)
431	}
432
433	return propertyNodes, nil
434}
435
436// TestGetNodeIDs creates a set of nodes and attempts to retrieve their IDs,.
437// It then tests again whilst excluding a node ID from being returned.
438// If a node ID is not returned, or more node IDs than expected are returned, the test fails.
439func TestGetNodeIDs(t *testing.T) {
440	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
441		"test": newTestService,
442	})
443	network := NewNetwork(adapter, &NetworkConfig{
444		DefaultService: "test",
445	})
446	defer network.Shutdown()
447
448	numNodes := 5
449	nodes, err := createTestNodes(numNodes, network)
450	if err != nil {
451		t.Fatalf("Could not creat test nodes %v", err)
452	}
453
454	gotNodeIDs := network.GetNodeIDs()
455	if len(gotNodeIDs) != numNodes {
456		t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodeIDs))
457	}
458
459	for _, node1 := range nodes {
460		match := false
461		for _, node2ID := range gotNodeIDs {
462			if bytes.Equal(node1.ID().Bytes(), node2ID.Bytes()) {
463				match = true
464				break
465			}
466		}
467
468		if !match {
469			t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String())
470		}
471	}
472
473	excludeNodeID := nodes[3].ID()
474	gotNodeIDsExcl := network.GetNodeIDs(excludeNodeID)
475	if len(gotNodeIDsExcl) != numNodes-1 {
476		t.Fatalf("Expected one less node ID to be returned")
477	}
478	for _, nodeID := range gotNodeIDsExcl {
479		if bytes.Equal(excludeNodeID.Bytes(), nodeID.Bytes()) {
480			t.Fatalf("GetNodeIDs returned the node ID we excluded, ID: %s", nodeID.String())
481		}
482	}
483}
484
485// TestGetNodes creates a set of nodes and attempts to retrieve them again.
486// It then tests again whilst excluding a node from being returned.
487// If a node is not returned, or more nodes than expected are returned, the test fails.
488func TestGetNodes(t *testing.T) {
489	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
490		"test": newTestService,
491	})
492	network := NewNetwork(adapter, &NetworkConfig{
493		DefaultService: "test",
494	})
495	defer network.Shutdown()
496
497	numNodes := 5
498	nodes, err := createTestNodes(numNodes, network)
499	if err != nil {
500		t.Fatalf("Could not creat test nodes %v", err)
501	}
502
503	gotNodes := network.GetNodes()
504	if len(gotNodes) != numNodes {
505		t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodes))
506	}
507
508	for _, node1 := range nodes {
509		match := false
510		for _, node2 := range gotNodes {
511			if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) {
512				match = true
513				break
514			}
515		}
516
517		if !match {
518			t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String())
519		}
520	}
521
522	excludeNodeID := nodes[3].ID()
523	gotNodesExcl := network.GetNodes(excludeNodeID)
524	if len(gotNodesExcl) != numNodes-1 {
525		t.Fatalf("Expected one less node to be returned")
526	}
527	for _, node := range gotNodesExcl {
528		if bytes.Equal(excludeNodeID.Bytes(), node.ID().Bytes()) {
529			t.Fatalf("GetNodes returned the node we excluded, ID: %s", node.ID().String())
530		}
531	}
532}
533
534// TestGetNodesByID creates a set of nodes and attempts to retrieve a subset of them by ID
535// If a node is not returned, or more nodes than expected are returned, the test fails.
536func TestGetNodesByID(t *testing.T) {
537	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
538		"test": newTestService,
539	})
540	network := NewNetwork(adapter, &NetworkConfig{
541		DefaultService: "test",
542	})
543	defer network.Shutdown()
544
545	numNodes := 5
546	nodes, err := createTestNodes(numNodes, network)
547	if err != nil {
548		t.Fatalf("Could not create test nodes: %v", err)
549	}
550
551	numSubsetNodes := 2
552	subsetNodes := nodes[0:numSubsetNodes]
553	var subsetNodeIDs []enode.ID
554	for _, node := range subsetNodes {
555		subsetNodeIDs = append(subsetNodeIDs, node.ID())
556	}
557
558	gotNodesByID := network.GetNodesByID(subsetNodeIDs)
559	if len(gotNodesByID) != numSubsetNodes {
560		t.Fatalf("Expected %d nodes, got %d", numSubsetNodes, len(gotNodesByID))
561	}
562
563	for _, node1 := range subsetNodes {
564		match := false
565		for _, node2 := range gotNodesByID {
566			if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) {
567				match = true
568				break
569			}
570		}
571
572		if !match {
573			t.Fatalf("A created node was not returned by GetNodesByID(), ID: %s", node1.ID().String())
574		}
575	}
576}
577
578// TestGetNodesByProperty creates a subset of nodes with a property assigned.
579// GetNodesByProperty is then checked for correctness by comparing the nodes returned to those initially created.
580// If a node with a property is not found, or more nodes than expected are returned, the test fails.
581func TestGetNodesByProperty(t *testing.T) {
582	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
583		"test": newTestService,
584	})
585	network := NewNetwork(adapter, &NetworkConfig{
586		DefaultService: "test",
587	})
588	defer network.Shutdown()
589
590	numNodes := 3
591	_, err := createTestNodes(numNodes, network)
592	if err != nil {
593		t.Fatalf("Failed to create nodes: %v", err)
594	}
595
596	numPropertyNodes := 3
597	propertyTest := "test"
598	propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network)
599	if err != nil {
600		t.Fatalf("Failed to create nodes with property: %v", err)
601	}
602
603	gotNodesByProperty := network.GetNodesByProperty(propertyTest)
604	if len(gotNodesByProperty) != numPropertyNodes {
605		t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodesByProperty))
606	}
607
608	for _, node1 := range propertyNodes {
609		match := false
610		for _, node2 := range gotNodesByProperty {
611			if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) {
612				match = true
613				break
614			}
615		}
616
617		if !match {
618			t.Fatalf("A created node with property was not returned by GetNodesByProperty(), ID: %s", node1.ID().String())
619		}
620	}
621}
622
623// TestGetNodeIDsByProperty creates a subset of nodes with a property assigned.
624// GetNodeIDsByProperty is then checked for correctness by comparing the node IDs returned to those initially created.
625// If a node ID with a property is not found, or more nodes IDs than expected are returned, the test fails.
626func TestGetNodeIDsByProperty(t *testing.T) {
627	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
628		"test": newTestService,
629	})
630	network := NewNetwork(adapter, &NetworkConfig{
631		DefaultService: "test",
632	})
633	defer network.Shutdown()
634
635	numNodes := 3
636	_, err := createTestNodes(numNodes, network)
637	if err != nil {
638		t.Fatalf("Failed to create nodes: %v", err)
639	}
640
641	numPropertyNodes := 3
642	propertyTest := "test"
643	propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network)
644	if err != nil {
645		t.Fatalf("Failed to created nodes with property: %v", err)
646	}
647
648	gotNodeIDsByProperty := network.GetNodeIDsByProperty(propertyTest)
649	if len(gotNodeIDsByProperty) != numPropertyNodes {
650		t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodeIDsByProperty))
651	}
652
653	for _, node1 := range propertyNodes {
654		match := false
655		id1 := node1.ID()
656		for _, id2 := range gotNodeIDsByProperty {
657			if bytes.Equal(id1.Bytes(), id2.Bytes()) {
658				match = true
659				break
660			}
661		}
662
663		if !match {
664			t.Fatalf("Not all nodes IDs were returned by GetNodeIDsByProperty(), ID: %s", id1.String())
665		}
666	}
667}
668
669func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) {
670	tick := time.NewTicker(interval)
671	defer tick.Stop()
672	for {
673		select {
674		case <-tick.C:
675			for _, id := range ids {
676				select {
677				case trigger <- id:
678				case <-ctx.Done():
679					return
680				}
681			}
682		case <-ctx.Done():
683			return
684		}
685	}
686}
687
688// \todo: refactor to implement shapshots
689// and connect configuration methods once these are moved from
690// swarm/network/simulations/connect.go
691func BenchmarkMinimalService(b *testing.B) {
692	b.Run("ring/32", benchmarkMinimalServiceTmp)
693}
694
695func benchmarkMinimalServiceTmp(b *testing.B) {
696
697	// stop timer to discard setup time pollution
698	args := strings.Split(b.Name(), "/")
699	nodeCount, err := strconv.ParseInt(args[2], 10, 16)
700	if err != nil {
701		b.Fatal(err)
702	}
703
704	for i := 0; i < b.N; i++ {
705		// this is a minimal service, whose protocol will close a channel upon run of protocol
706		// making it possible to bench the time it takes for the service to start and protocol actually to be run
707		protoCMap := make(map[enode.ID]map[enode.ID]chan struct{})
708		adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
709			"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
710				protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{})
711				svc := NewNoopService(protoCMap[ctx.Config.ID])
712				return svc, nil
713			},
714		})
715
716		// create network
717		network := NewNetwork(adapter, &NetworkConfig{
718			DefaultService: "noopwoop",
719		})
720		defer network.Shutdown()
721
722		// create and start nodes
723		ids := make([]enode.ID, nodeCount)
724		for i := 0; i < int(nodeCount); i++ {
725			conf := adapters.RandomNodeConfig()
726			node, err := network.NewNodeWithConfig(conf)
727			if err != nil {
728				b.Fatalf("error creating node: %s", err)
729			}
730			if err := network.Start(node.ID()); err != nil {
731				b.Fatalf("error starting node: %s", err)
732			}
733			ids[i] = node.ID()
734		}
735
736		// ready, set, go
737		b.ResetTimer()
738
739		// connect nodes in a ring
740		for i, id := range ids {
741			peerID := ids[(i+1)%len(ids)]
742			if err := network.Connect(id, peerID); err != nil {
743				b.Fatal(err)
744			}
745		}
746
747		// wait for all protocols to signal to close down
748		ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
749		defer cancel()
750		for nodid, peers := range protoCMap {
751			for peerid, peerC := range peers {
752				log.Debug("getting ", "node", nodid, "peer", peerid)
753				select {
754				case <-ctx.Done():
755					b.Fatal(ctx.Err())
756				case <-peerC:
757				}
758			}
759		}
760	}
761}
762
763func TestNode_UnmarshalJSON(t *testing.T) {
764	t.Run("up_field", func(t *testing.T) {
765		runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONUpField())
766	})
767	t.Run("config_field", func(t *testing.T) {
768		runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONConfigField())
769	})
770}
771
772func runNodeUnmarshalJSON(t *testing.T, tests []nodeUnmarshalTestCase) {
773	t.Helper()
774	for _, tt := range tests {
775		t.Run(tt.name, func(t *testing.T) {
776			var got *Node
777			if err := json.Unmarshal([]byte(tt.marshaled), &got); err != nil {
778				expectErrorMessageToContain(t, err, tt.wantErr)
779				got = nil
780			}
781			expectNodeEquality(t, got, tt.want)
782		})
783	}
784}
785
786type nodeUnmarshalTestCase struct {
787	name      string
788	marshaled string
789	want      *Node
790	wantErr   string
791}
792
793func expectErrorMessageToContain(t *testing.T, got error, want string) {
794	t.Helper()
795	if got == nil && want == "" {
796		return
797	}
798
799	if got == nil && want != "" {
800		t.Errorf("error was expected, got: nil, want: %v", want)
801		return
802	}
803
804	if !strings.Contains(got.Error(), want) {
805		t.Errorf(
806			"unexpected error message, got  %v, want: %v",
807			want,
808			got,
809		)
810	}
811}
812
813func expectNodeEquality(t *testing.T, got, want *Node) {
814	t.Helper()
815	if !reflect.DeepEqual(got, want) {
816		t.Errorf("Node.UnmarshalJSON() = %v, want %v", got, want)
817	}
818}
819
820func casesNodeUnmarshalJSONUpField() []nodeUnmarshalTestCase {
821	return []nodeUnmarshalTestCase{
822		{
823			name:      "empty json",
824			marshaled: "{}",
825			want:      newNode(nil, nil, false),
826		},
827		{
828			name:      "a stopped node",
829			marshaled: "{\"up\": false}",
830			want:      newNode(nil, nil, false),
831		},
832		{
833			name:      "a running node",
834			marshaled: "{\"up\": true}",
835			want:      newNode(nil, nil, true),
836		},
837		{
838			name:      "invalid JSON value on valid key",
839			marshaled: "{\"up\": foo}",
840			wantErr:   "invalid character",
841		},
842		{
843			name:      "invalid JSON key and value",
844			marshaled: "{foo: bar}",
845			wantErr:   "invalid character",
846		},
847		{
848			name:      "bool value expected but got something else (string)",
849			marshaled: "{\"up\": \"true\"}",
850			wantErr:   "cannot unmarshal string into Go struct",
851		},
852	}
853}
854
855func casesNodeUnmarshalJSONConfigField() []nodeUnmarshalTestCase {
856	// Don't do a big fuss around testing, as adapters.NodeConfig should
857	// handle it's own serialization. Just do a sanity check.
858	return []nodeUnmarshalTestCase{
859		{
860			name:      "Config field is omitted",
861			marshaled: "{}",
862			want:      newNode(nil, nil, false),
863		},
864		{
865			name:      "Config field is nil",
866			marshaled: "{\"config\": null}",
867			want:      newNode(nil, nil, false),
868		},
869		{
870			name:      "a non default Config field",
871			marshaled: "{\"config\":{\"name\":\"node_ecdd0\",\"port\":44665}}",
872			want:      newNode(nil, &adapters.NodeConfig{Name: "node_ecdd0", Port: 44665}, false),
873		},
874	}
875}
876