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

network/simulation: Basic bootnode support + expanded enode.ID exclusion #1799

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 70 additions & 27 deletions network/simulation/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ import (
"github.com/ethersphere/swarm/network"
)

const (
// PropertyBootnode is a property string for NodeConfig, representing that a node is a bootnode
PropertyBootnode = "bootnode"
)

var (
BucketKeyBzzPrivateKey BucketKey = "bzzprivkey"
)
Expand Down Expand Up @@ -83,6 +88,14 @@ func AddNodeWithMsgEvents(enable bool) AddNodeOption {
}
}

// AddNodeWithProperty specifies a property that this node should hold
// in the running services. (e.g. "bootnode", etc)
func AddNodeWithProperty(propertyName string) AddNodeOption {
return func(o *adapters.NodeConfig) {
o.Properties = append(o.Properties, propertyName)
}
}

// 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.
Expand All @@ -108,10 +121,6 @@ func (s *Simulation) AddNode(opts ...AddNodeOption) (id enode.ID, err error) {

// 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
Expand All @@ -120,6 +129,14 @@ func (s *Simulation) AddNode(opts ...AddNodeOption) (id enode.ID, err error) {
enodeParams := &network.EnodeParams{
PrivateKey: bzzPrivateKey,
}

// Check for any properties relevant to the creation of the Enode Record
for _, property := range conf.Properties {
if property == PropertyBootnode {
enodeParams.Bootnode = true
}
}

record, err := network.NewEnodeRecord(enodeParams)
conf.Record = *record

Expand Down Expand Up @@ -148,7 +165,18 @@ func (s *Simulation) AddNodes(count int, opts ...AddNodeOption) (ids []enode.ID,
return ids, nil
}

// AddNodesAndConnectFull is a helpper method that combines
// AddBootnode creates a bootnode using AddNode(opts) and appends it to Simulation.bootNodes
func (s *Simulation) AddBootnode(opts ...AddNodeOption) (id enode.ID, err error) {
opts = append(opts, AddNodeWithProperty(PropertyBootnode))
id, err = s.AddNode(opts...)
if err != nil {
return id, err
}

return id, nil
}

// AddNodesAndConnectFull is a helper 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 {
Expand Down Expand Up @@ -209,7 +237,7 @@ func (s *Simulation) AddNodesAndConnectRing(count int, opts ...AddNodeOption) (i
return ids, nil
}

// AddNodesAndConnectStar is a helpper method that combines
// AddNodesAndConnectStar is a helper method that combines
// AddNodes and ConnectNodesStar.
func (s *Simulation) AddNodesAndConnectStar(count int, opts ...AddNodeOption) (ids []enode.ID, err error) {
if count < 2 {
Expand All @@ -226,6 +254,28 @@ func (s *Simulation) AddNodesAndConnectStar(count int, opts ...AddNodeOption) (i
return ids, nil
}

// AddNodesAndConnectToBootnode is a helper method that combines
// AddNodes, AddBootnode and ConnectNodesStar, where the center node is a new bootnode.
// The count parameter excludes the bootnode.
func (s *Simulation) AddNodesAndConnectToBootnode(count int, opts ...AddNodeOption) (ids []enode.ID, bootNodeID enode.ID, err error) {
bootNodeID, err = s.AddBootnode(opts...)
if err != nil {
return nil, bootNodeID, err
}

ids, err = s.AddNodes(count, opts...)
if err != nil {
return nil, bootNodeID, err
}

err = s.Net.ConnectNodesStar(ids, bootNodeID)
if err != nil {
return nil, bootNodeID, err
}

return ids, bootNodeID, 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
Expand Down Expand Up @@ -267,19 +317,21 @@ func (s *Simulation) StartNode(id enode.ID) (err error) {
}

// StartRandomNode starts a random node.
func (s *Simulation) StartRandomNode() (id enode.ID, err error) {
n := s.Net.GetRandomDownNode()
// Nodes can be excluded by providing their enode.ID.
func (s *Simulation) StartRandomNode(excludeIDs ...enode.ID) (id enode.ID, err error) {
n := s.Net.GetRandomDownNode(excludeIDs...)
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) {
// StartRandomNodes starts random nodes. Nodes
// Nodes can be excluded by providing their enode.ID.
func (s *Simulation) StartRandomNodes(count int, excludeIDs ...enode.ID) (ids []enode.ID, err error) {
ids = make([]enode.ID, 0, count)
for i := 0; i < count; i++ {
n := s.Net.GetRandomDownNode()
n := s.Net.GetRandomDownNode(excludeIDs...)
if n == nil {
return nil, ErrNodeNotFound
}
Expand All @@ -298,30 +350,21 @@ func (s *Simulation) StopNode(id enode.ID) (err error) {
}

// StopRandomNode stops a random node.
func (s *Simulation) StopRandomNode(protect ...enode.ID) (id enode.ID, err error) {
found := false
var n *simulations.Node
outer:
for !found {
n = s.Net.GetRandomUpNode()
for _, v := range protect {
if bytes.Equal(n.ID().Bytes(), v.Bytes()) {
continue outer
}
}
found = true
}
// Nodes can be excluded by providing their enode.ID.
func (s *Simulation) StopRandomNode(excludeIDs ...enode.ID) (id enode.ID, err error) {
n := s.Net.GetRandomUpNode(excludeIDs...)
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) {
// Nodes can be excluded by providing their enode.ID.
func (s *Simulation) StopRandomNodes(count int, excludeIDs ...enode.ID) (ids []enode.ID, err error) {
ids = make([]enode.ID, 0, count)
for i := 0; i < count; i++ {
n := s.Net.GetRandomUpNode()
n := s.Net.GetRandomUpNode(excludeIDs...)
if n == nil {
return nil, ErrNodeNotFound
}
Expand All @@ -339,7 +382,7 @@ func init() {
rand.Seed(time.Now().UnixNano())
}

// derive a private key for swarm for the node key
// BzzPrivateKeyFromConfig derives 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
Expand Down
44 changes: 44 additions & 0 deletions network/simulation/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package simulation

import (
"bytes"
"context"
"fmt"
"sync"
Expand Down Expand Up @@ -218,6 +219,49 @@ func TestAddNodes(t *testing.T) {
}
}

// TestAddBootnode adds a bootnode to a simulation network
// and checks that the bootnode is returned by GetNodeIDsByProperty with PropertyBootnode
// If other nodes are returned, or the bootnode ID is incorrect, the test fails
func TestAddBootnode(t *testing.T) {
sim := NewInProc(noopServiceFuncMap)
defer sim.Close()

// Add some normal nodes for the sake of the bootnode not being the only node
_, err := sim.AddNodes(4)

bootnodeID, err := sim.AddBootnode()
if err != nil {
t.Fatalf("Failed to add bootnode: %s", err)
}

gotBootnodeIDs := sim.Net.GetNodeIDsByProperty(PropertyBootnode)
if len(gotBootnodeIDs) != 1 {
t.Fatalf("Expected 1 bootnode, got %d", len(gotBootnodeIDs))
}

if !bytes.Equal(gotBootnodeIDs[0].Bytes(), bootnodeID.Bytes()) {
t.Fatalf("Found an unexpected bootnode with ID: %s", gotBootnodeIDs[0].String())
}
}

// TestAddNodesAndConnectToBootnode adds nodeCount nodes, and a bootnode in a star topology
// VerifyStar is then used to confirm that the nodes connected to the bootnode as expected
func TestAddNodesAndConnectToBootnode(t *testing.T) {
nodeCount := 5

sim := NewInProc(noopServiceFuncMap)
defer sim.Close()

ids, bootNodeID, err := sim.AddNodesAndConnectToBootnode(nodeCount)
if err != nil {
t.Fatalf("AddNodesAndConnectToBootnode Failed: %s", err)
}

// VerifyStar takes a map and an index to the bootnode, so append it and use the index
ids = append(ids, bootNodeID)
simulations.VerifyStar(t, sim.Net, ids, len(ids)-1)
acud marked this conversation as resolved.
Show resolved Hide resolved
}

func TestAddNodesAndConnectFull(t *testing.T) {
sim := NewInProc(noopServiceFuncMap)
defer sim.Close()
Expand Down
9 changes: 9 additions & 0 deletions network/simulation/simulation.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ func NewBzzInProc(services map[string]ServiceFunc, disableAutoConnect bool) (s *
UnderlayAddr: addr.Under(),
HiveParams: hp,
}

// Check for relevant properties
for _, property := range ctx.Config.Properties {
switch property {
case PropertyBootnode:
config.BootnodeMode = true
}
}

return network.NewBzz(config, kad, nil, nil, nil, nil, nil), nil, nil
}

Expand Down