From 15a2140e008471780154799c4ce10379d151ba47 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Thu, 30 Dec 2021 21:22:03 +0100 Subject: [PATCH 1/6] Added Configuration.Contains(nodeID) --- config.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config.go b/config.go index 5ad6e27a..cbcc9d54 100644 --- a/config.go +++ b/config.go @@ -50,6 +50,16 @@ func (c Configuration) Equal(b Configuration) bool { return true } +// Contains returns true if nodeID is in configuration c. +func (c Configuration) Contains(nodeID uint32) bool { + for i := range c { + if c[i].ID() == nodeID { + return true + } + } + return false +} + func (c Configuration) getMsgID() uint64 { return c[0].mgr.getMsgID() } From eac2625797f47d3c7ab27160784d70f13c67a184 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Thu, 30 Dec 2021 21:23:28 +0100 Subject: [PATCH 2/6] Changed WithNodeIDs(ids []uint32) to ids ...uint32 This makes it easier to use with single entry node IDs. --- config_opts.go | 2 +- config_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config_opts.go b/config_opts.go index 91474e2c..509e2fb5 100644 --- a/config_opts.go +++ b/config_opts.go @@ -107,7 +107,7 @@ func (o nodeIDs) newConfig(mgr *Manager) (nodes Configuration, err error) { // WithNodeIDs returns a NodeListOption containing a list of node IDs. // This assumes that the provided node IDs have already been registered with the manager. -func WithNodeIDs(ids []uint32) NodeListOption { +func WithNodeIDs(ids ...uint32) NodeListOption { return &nodeIDs{nodeIDs: ids} } diff --git a/config_test.go b/config_test.go index 7b73a0c0..664b0c30 100644 --- a/config_test.go +++ b/config_test.go @@ -79,7 +79,7 @@ func TestNewConfigurationNodeIDs(t *testing.T) { // Identical configurations c1 == c2 nodeIDs := c1.NodeIDs() - c2, err := gorums.NewConfiguration(mgr, gorums.WithNodeIDs(nodeIDs)) + c2, err := gorums.NewConfiguration(mgr, gorums.WithNodeIDs(nodeIDs...)) if err != nil { t.Fatal(err) } @@ -91,7 +91,7 @@ func TestNewConfigurationNodeIDs(t *testing.T) { } // Configuration with one less node |c3| == |c1| - 1 - c3, err := gorums.NewConfiguration(mgr, gorums.WithNodeIDs(nodeIDs[:len(nodeIDs)-1])) + c3, err := gorums.NewConfiguration(mgr, gorums.WithNodeIDs(nodeIDs[:len(nodeIDs)-1]...)) if err != nil { t.Fatal(err) } From bea0b8e6ce17ba8e1fa4ee876d2fce30071a65d0 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Fri, 31 Dec 2021 18:37:02 +0100 Subject: [PATCH 3/6] Custom subconfigurations for a tree This implements methods for constructing subconfigurations. The example implementation provided herein is for a binary tree that can be used for signature aggregation such as Handle. --- config_tree.go | 65 ++++++++++++++++++++++++++++++++ config_tree_test.go | 90 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 config_tree.go create mode 100644 config_tree_test.go diff --git a/config_tree.go b/config_tree.go new file mode 100644 index 00000000..d9a05134 --- /dev/null +++ b/config_tree.go @@ -0,0 +1,65 @@ +package gorums + +import ( + "fmt" +) + +// SubConfigOption must be implemented by subconfiguration providers. +type SubConfigOption interface { + subConfig(*Manager) ([]Configuration, error) +} + +// Derive subconfigurations from the manager's base configuration. +func SubConfigurations(mgr *Manager, opt SubConfigOption) ([]Configuration, error) { + if opt == nil { + return nil, ConfigCreationError(fmt.Errorf("missing required subconfiguration option")) + } + return opt.subConfig(mgr) +} + +type treeConfig struct { + myID uint32 + bf int + base NodeListOption +} + +func (o treeConfig) subConfig(mgr *Manager) (configs []Configuration, err error) { + base, err := o.base.newConfig(mgr) + if err != nil { + return nil, err + } + myID, found := mgr.Node(o.myID) + if !found { + return nil, ConfigCreationError(fmt.Errorf("node ID %d not found", o.myID)) + } + + excludeCfg := Configuration{myID} + for levelSize := o.bf; levelSize <= len(base); levelSize *= o.bf { + levelCfg := subLevelConfiguration(base, o.myID, levelSize) + // compute subconfiguration at level, excluding own configuration group + subCfg, _ := levelCfg.Except(excludeCfg).newConfig(mgr) + configs = append(configs, subCfg) + excludeCfg, _ = excludeCfg.And(levelCfg).newConfig(mgr) + } + return configs, nil +} + +// subLevelConfiguration returns a configuration of size that contains myID. +func subLevelConfiguration(base Configuration, myID uint32, size int) Configuration { + for i := 0; i < len(base); i += size { + if base[i : i+size].Contains(myID) { + return base[i : i+size] + } + } + return Configuration{} +} + +// WithTreeConfigurations returns a SubConfigOption that can be used to create +// a set of subconfigurations representing a tree of nodes. +func WithTreeConfigurations(branchFactor int, myID uint32, base NodeListOption) SubConfigOption { + return &treeConfig{ + bf: branchFactor, + myID: myID, + base: base, + } +} diff --git a/config_tree_test.go b/config_tree_test.go new file mode 100644 index 00000000..73311ea3 --- /dev/null +++ b/config_tree_test.go @@ -0,0 +1,90 @@ +package gorums_test + +import ( + "fmt" + "testing" + + "github.com/relab/gorums" +) + +// TODO check if okay to use node id 0? the Node.ID() function returns 0 if node == nil. + +func TestTreeConfiguration(t *testing.T) { + const branchFactor = 2 + tests := []struct { + nodeIDs map[string]uint32 // all nodes in the configuration + id uint32 // my ID to create subconfigurations from + wantConfig [][]uint32 // index: tree level, value: list of node IDs in the configuration + }{ + {nodeIDs: nodeMapIDs(0, 8), id: 0, wantConfig: [][]uint32{{1}, {2, 3}, {4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 1, wantConfig: [][]uint32{{0}, {2, 3}, {4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 2, wantConfig: [][]uint32{{3}, {0, 1}, {4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 3, wantConfig: [][]uint32{{2}, {0, 1}, {4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 4, wantConfig: [][]uint32{{5}, {6, 7}, {0, 1, 2, 3}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 5, wantConfig: [][]uint32{{4}, {6, 7}, {0, 1, 2, 3}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 6, wantConfig: [][]uint32{{7}, {4, 5}, {0, 1, 2, 3}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 7, wantConfig: [][]uint32{{6}, {4, 5}, {0, 1, 2, 3}}}, + // + {nodeIDs: nodeMapIDs(0, 16), id: 0, wantConfig: [][]uint32{{1}, {2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 1, wantConfig: [][]uint32{{0}, {2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 2, wantConfig: [][]uint32{{3}, {0, 1}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 3, wantConfig: [][]uint32{{2}, {0, 1}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 4, wantConfig: [][]uint32{{5}, {6, 7}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 5, wantConfig: [][]uint32{{4}, {6, 7}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 6, wantConfig: [][]uint32{{7}, {4, 5}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 7, wantConfig: [][]uint32{{6}, {4, 5}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + // + {nodeIDs: nodeMapIDs(0, 16), id: 8, wantConfig: [][]uint32{{9}, {10, 11}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 9, wantConfig: [][]uint32{{8}, {10, 11}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 10, wantConfig: [][]uint32{{11}, {8, 9}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 11, wantConfig: [][]uint32{{10}, {8, 9}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 12, wantConfig: [][]uint32{{13}, {14, 15}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 13, wantConfig: [][]uint32{{12}, {14, 15}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 14, wantConfig: [][]uint32{{15}, {12, 13}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 15, wantConfig: [][]uint32{{14}, {12, 13}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + } + for _, test := range tests { + t.Run(fmt.Sprintf("%d", len(test.nodeIDs)), func(t *testing.T) { + mgr := gorums.NewManager(gorums.WithNoConnect()) + cfgs, err := gorums.SubConfigurations(mgr, + gorums.WithTreeConfigurations(branchFactor, test.id, gorums.WithNodeMap(test.nodeIDs))) + if err != nil { + t.Fatal(err) + } + if len(cfgs) != log2(len(test.nodeIDs)) { + t.Errorf("len(cfgs) = %d, expected %d", len(cfgs), 2) + } + for i, cfg := range cfgs { + if len(cfg) != len(test.wantConfig[i]) { + t.Errorf("len(cfg) = %d, expected %d", len(cfg), len(test.wantConfig[i])) + } + for _, id := range test.wantConfig[i] { + if !cfg.Contains(id) { + t.Errorf("{%v}.Contains(%d) = false, expected true", cfg.NodeIDs(), id) + } + } + // fmt.Printf("c%d (%d): %v\n", i, cfg.Size(), cfg.NodeIDs()) + } + }) + } +} + +// nodeMapIDs returns a map of localhost node IDs for the given node count. +func nodeMapIDs(s, n int) map[string]uint32 { + basePort := 9080 + nodeMapIDs := make(map[string]uint32) + for i := s; i < s+n; i++ { + nodeMapIDs[fmt.Sprintf("127.0.0.1:%d", basePort+i)] = uint32(i) + } + return nodeMapIDs +} + +// log2 returns the base 2 logarithm of x. +func log2(x int) int { + y := 0 + for x > 1 { + x >>= 1 + y++ + } + return y +} From 731fbbb209cf9acdacf35f235d0b36ecb9b4fada Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Thu, 30 Dec 2021 21:22:03 +0100 Subject: [PATCH 4/6] Added Configuration.Contains(nodeID) --- config.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config.go b/config.go index 15a569ef..dab3ae03 100644 --- a/config.go +++ b/config.go @@ -57,6 +57,16 @@ func (c RawConfiguration) Equal(b RawConfiguration) bool { return true } +// Contains returns true if nodeID is in configuration c. +func (c RawConfiguration) Contains(nodeID uint32) bool { + for i := range c { + if c[i].ID() == nodeID { + return true + } + } + return false +} + func (c RawConfiguration) getMsgID() uint64 { return c[0].mgr.getMsgID() } From 5e1e990303b5b6ea794e66834c0710458635343e Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Thu, 30 Dec 2021 21:23:28 +0100 Subject: [PATCH 5/6] Changed WithNodeIDs(ids []uint32) to ids ...uint32 This makes it easier to use with single entry node IDs. --- config_opts.go | 2 +- config_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config_opts.go b/config_opts.go index 155e586a..4b5266c1 100644 --- a/config_opts.go +++ b/config_opts.go @@ -107,7 +107,7 @@ func (o nodeIDs) newConfig(mgr *RawManager) (nodes RawConfiguration, err error) // WithNodeIDs returns a NodeListOption containing a list of node IDs. // This assumes that the provided node IDs have already been registered with the manager. -func WithNodeIDs(ids []uint32) NodeListOption { +func WithNodeIDs(ids ...uint32) NodeListOption { return &nodeIDs{nodeIDs: ids} } diff --git a/config_test.go b/config_test.go index dd1cff3a..10cf6661 100644 --- a/config_test.go +++ b/config_test.go @@ -79,7 +79,7 @@ func TestNewConfigurationNodeIDs(t *testing.T) { // Identical configurations c1 == c2 nodeIDs := c1.NodeIDs() - c2, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeIDs(nodeIDs)) + c2, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeIDs(nodeIDs...)) if err != nil { t.Fatal(err) } @@ -91,7 +91,7 @@ func TestNewConfigurationNodeIDs(t *testing.T) { } // Configuration with one less node |c3| == |c1| - 1 - c3, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeIDs(nodeIDs[:len(nodeIDs)-1])) + c3, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeIDs(nodeIDs[:len(nodeIDs)-1]...)) if err != nil { t.Fatal(err) } From bf7cc7f1b9b493a75aad99fea2dc66fadadf4bb7 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Fri, 31 Dec 2021 18:37:02 +0100 Subject: [PATCH 6/6] Custom subconfigurations for a tree This implements methods for constructing subconfigurations. The example implementation provided herein is for a binary tree that can be used for signature aggregation such as Handle. --- config_tree.go | 65 ++++++++++++++++++++++++++++++++ config_tree_test.go | 90 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 config_tree.go create mode 100644 config_tree_test.go diff --git a/config_tree.go b/config_tree.go new file mode 100644 index 00000000..672e13b1 --- /dev/null +++ b/config_tree.go @@ -0,0 +1,65 @@ +package gorums + +import ( + "fmt" +) + +// SubConfigOption must be implemented by subconfiguration providers. +type SubConfigOption interface { + subConfig(*RawManager) ([]RawConfiguration, error) +} + +// Derive subconfigurations from the manager's base configuration. +func SubConfigurations(mgr *RawManager, opt SubConfigOption) ([]RawConfiguration, error) { + if opt == nil { + return nil, ConfigCreationError(fmt.Errorf("missing required subconfiguration option")) + } + return opt.subConfig(mgr) +} + +type treeConfig struct { + myID uint32 + bf int + base NodeListOption +} + +func (o treeConfig) subConfig(mgr *RawManager) (configs []RawConfiguration, err error) { + base, err := o.base.newConfig(mgr) + if err != nil { + return nil, err + } + myID, found := mgr.Node(o.myID) + if !found { + return nil, ConfigCreationError(fmt.Errorf("node ID %d not found", o.myID)) + } + + excludeCfg := RawConfiguration{myID} + for levelSize := o.bf; levelSize <= len(base); levelSize *= o.bf { + levelCfg := subLevelConfiguration(base, o.myID, levelSize) + // compute subconfiguration at level, excluding own configuration group + subCfg, _ := levelCfg.Except(excludeCfg).newConfig(mgr) + configs = append(configs, subCfg) + excludeCfg, _ = excludeCfg.And(levelCfg).newConfig(mgr) + } + return configs, nil +} + +// subLevelConfiguration returns a configuration of size that contains myID. +func subLevelConfiguration(base RawConfiguration, myID uint32, size int) RawConfiguration { + for i := 0; i < len(base); i += size { + if base[i : i+size].Contains(myID) { + return base[i : i+size] + } + } + return RawConfiguration{} +} + +// WithTreeConfigurations returns a SubConfigOption that can be used to create +// a set of subconfigurations representing a tree of nodes. +func WithTreeConfigurations(branchFactor int, myID uint32, base NodeListOption) SubConfigOption { + return &treeConfig{ + bf: branchFactor, + myID: myID, + base: base, + } +} diff --git a/config_tree_test.go b/config_tree_test.go new file mode 100644 index 00000000..d2821bab --- /dev/null +++ b/config_tree_test.go @@ -0,0 +1,90 @@ +package gorums_test + +import ( + "fmt" + "testing" + + "github.com/relab/gorums" +) + +// TODO check if okay to use node id 0? the Node.ID() function returns 0 if node == nil. + +func TestTreeConfiguration(t *testing.T) { + const branchFactor = 2 + tests := []struct { + nodeIDs map[string]uint32 // all nodes in the configuration + id uint32 // my ID to create subconfigurations from + wantConfig [][]uint32 // index: tree level, value: list of node IDs in the configuration + }{ + {nodeIDs: nodeMapIDs(0, 8), id: 0, wantConfig: [][]uint32{{1}, {2, 3}, {4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 1, wantConfig: [][]uint32{{0}, {2, 3}, {4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 2, wantConfig: [][]uint32{{3}, {0, 1}, {4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 3, wantConfig: [][]uint32{{2}, {0, 1}, {4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 4, wantConfig: [][]uint32{{5}, {6, 7}, {0, 1, 2, 3}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 5, wantConfig: [][]uint32{{4}, {6, 7}, {0, 1, 2, 3}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 6, wantConfig: [][]uint32{{7}, {4, 5}, {0, 1, 2, 3}}}, + {nodeIDs: nodeMapIDs(0, 8), id: 7, wantConfig: [][]uint32{{6}, {4, 5}, {0, 1, 2, 3}}}, + // + {nodeIDs: nodeMapIDs(0, 16), id: 0, wantConfig: [][]uint32{{1}, {2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 1, wantConfig: [][]uint32{{0}, {2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 2, wantConfig: [][]uint32{{3}, {0, 1}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 3, wantConfig: [][]uint32{{2}, {0, 1}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 4, wantConfig: [][]uint32{{5}, {6, 7}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 5, wantConfig: [][]uint32{{4}, {6, 7}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 6, wantConfig: [][]uint32{{7}, {4, 5}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 7, wantConfig: [][]uint32{{6}, {4, 5}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}}, + // + {nodeIDs: nodeMapIDs(0, 16), id: 8, wantConfig: [][]uint32{{9}, {10, 11}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 9, wantConfig: [][]uint32{{8}, {10, 11}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 10, wantConfig: [][]uint32{{11}, {8, 9}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 11, wantConfig: [][]uint32{{10}, {8, 9}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 12, wantConfig: [][]uint32{{13}, {14, 15}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 13, wantConfig: [][]uint32{{12}, {14, 15}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 14, wantConfig: [][]uint32{{15}, {12, 13}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + {nodeIDs: nodeMapIDs(0, 16), id: 15, wantConfig: [][]uint32{{14}, {12, 13}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}}, + } + for _, test := range tests { + t.Run(fmt.Sprintf("%d", len(test.nodeIDs)), func(t *testing.T) { + mgr := gorums.NewRawManager(gorums.WithNoConnect()) + cfgs, err := gorums.SubConfigurations(mgr, + gorums.WithTreeConfigurations(branchFactor, test.id, gorums.WithNodeMap(test.nodeIDs))) + if err != nil { + t.Fatal(err) + } + if len(cfgs) != log2(len(test.nodeIDs)) { + t.Errorf("len(cfgs) = %d, expected %d", len(cfgs), 2) + } + for i, cfg := range cfgs { + if len(cfg) != len(test.wantConfig[i]) { + t.Errorf("len(cfg) = %d, expected %d", len(cfg), len(test.wantConfig[i])) + } + for _, id := range test.wantConfig[i] { + if !cfg.Contains(id) { + t.Errorf("{%v}.Contains(%d) = false, expected true", cfg.NodeIDs(), id) + } + } + // fmt.Printf("c%d (%d): %v\n", i, cfg.Size(), cfg.NodeIDs()) + } + }) + } +} + +// nodeMapIDs returns a map of localhost node IDs for the given node count. +func nodeMapIDs(s, n int) map[string]uint32 { + basePort := 9080 + nodeMapIDs := make(map[string]uint32) + for i := s; i < s+n; i++ { + nodeMapIDs[fmt.Sprintf("127.0.0.1:%d", basePort+i)] = uint32(i) + } + return nodeMapIDs +} + +// log2 returns the base 2 logarithm of x. +func log2(x int) int { + y := 0 + for x > 1 { + x >>= 1 + y++ + } + return y +}