Skip to content

Commit bea0b8e

Browse files
committed
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.
1 parent eac2625 commit bea0b8e

2 files changed

Lines changed: 155 additions & 0 deletions

File tree

config_tree.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package gorums
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// SubConfigOption must be implemented by subconfiguration providers.
8+
type SubConfigOption interface {
9+
subConfig(*Manager) ([]Configuration, error)
10+
}
11+
12+
// Derive subconfigurations from the manager's base configuration.
13+
func SubConfigurations(mgr *Manager, opt SubConfigOption) ([]Configuration, error) {
14+
if opt == nil {
15+
return nil, ConfigCreationError(fmt.Errorf("missing required subconfiguration option"))
16+
}
17+
return opt.subConfig(mgr)
18+
}
19+
20+
type treeConfig struct {
21+
myID uint32
22+
bf int
23+
base NodeListOption
24+
}
25+
26+
func (o treeConfig) subConfig(mgr *Manager) (configs []Configuration, err error) {
27+
base, err := o.base.newConfig(mgr)
28+
if err != nil {
29+
return nil, err
30+
}
31+
myID, found := mgr.Node(o.myID)
32+
if !found {
33+
return nil, ConfigCreationError(fmt.Errorf("node ID %d not found", o.myID))
34+
}
35+
36+
excludeCfg := Configuration{myID}
37+
for levelSize := o.bf; levelSize <= len(base); levelSize *= o.bf {
38+
levelCfg := subLevelConfiguration(base, o.myID, levelSize)
39+
// compute subconfiguration at level, excluding own configuration group
40+
subCfg, _ := levelCfg.Except(excludeCfg).newConfig(mgr)
41+
configs = append(configs, subCfg)
42+
excludeCfg, _ = excludeCfg.And(levelCfg).newConfig(mgr)
43+
}
44+
return configs, nil
45+
}
46+
47+
// subLevelConfiguration returns a configuration of size that contains myID.
48+
func subLevelConfiguration(base Configuration, myID uint32, size int) Configuration {
49+
for i := 0; i < len(base); i += size {
50+
if base[i : i+size].Contains(myID) {
51+
return base[i : i+size]
52+
}
53+
}
54+
return Configuration{}
55+
}
56+
57+
// WithTreeConfigurations returns a SubConfigOption that can be used to create
58+
// a set of subconfigurations representing a tree of nodes.
59+
func WithTreeConfigurations(branchFactor int, myID uint32, base NodeListOption) SubConfigOption {
60+
return &treeConfig{
61+
bf: branchFactor,
62+
myID: myID,
63+
base: base,
64+
}
65+
}

config_tree_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package gorums_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/relab/gorums"
8+
)
9+
10+
// TODO check if okay to use node id 0? the Node.ID() function returns 0 if node == nil.
11+
12+
func TestTreeConfiguration(t *testing.T) {
13+
const branchFactor = 2
14+
tests := []struct {
15+
nodeIDs map[string]uint32 // all nodes in the configuration
16+
id uint32 // my ID to create subconfigurations from
17+
wantConfig [][]uint32 // index: tree level, value: list of node IDs in the configuration
18+
}{
19+
{nodeIDs: nodeMapIDs(0, 8), id: 0, wantConfig: [][]uint32{{1}, {2, 3}, {4, 5, 6, 7}}},
20+
{nodeIDs: nodeMapIDs(0, 8), id: 1, wantConfig: [][]uint32{{0}, {2, 3}, {4, 5, 6, 7}}},
21+
{nodeIDs: nodeMapIDs(0, 8), id: 2, wantConfig: [][]uint32{{3}, {0, 1}, {4, 5, 6, 7}}},
22+
{nodeIDs: nodeMapIDs(0, 8), id: 3, wantConfig: [][]uint32{{2}, {0, 1}, {4, 5, 6, 7}}},
23+
{nodeIDs: nodeMapIDs(0, 8), id: 4, wantConfig: [][]uint32{{5}, {6, 7}, {0, 1, 2, 3}}},
24+
{nodeIDs: nodeMapIDs(0, 8), id: 5, wantConfig: [][]uint32{{4}, {6, 7}, {0, 1, 2, 3}}},
25+
{nodeIDs: nodeMapIDs(0, 8), id: 6, wantConfig: [][]uint32{{7}, {4, 5}, {0, 1, 2, 3}}},
26+
{nodeIDs: nodeMapIDs(0, 8), id: 7, wantConfig: [][]uint32{{6}, {4, 5}, {0, 1, 2, 3}}},
27+
//
28+
{nodeIDs: nodeMapIDs(0, 16), id: 0, wantConfig: [][]uint32{{1}, {2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}},
29+
{nodeIDs: nodeMapIDs(0, 16), id: 1, wantConfig: [][]uint32{{0}, {2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}},
30+
{nodeIDs: nodeMapIDs(0, 16), id: 2, wantConfig: [][]uint32{{3}, {0, 1}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}},
31+
{nodeIDs: nodeMapIDs(0, 16), id: 3, wantConfig: [][]uint32{{2}, {0, 1}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}},
32+
{nodeIDs: nodeMapIDs(0, 16), id: 4, wantConfig: [][]uint32{{5}, {6, 7}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}},
33+
{nodeIDs: nodeMapIDs(0, 16), id: 5, wantConfig: [][]uint32{{4}, {6, 7}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}},
34+
{nodeIDs: nodeMapIDs(0, 16), id: 6, wantConfig: [][]uint32{{7}, {4, 5}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}},
35+
{nodeIDs: nodeMapIDs(0, 16), id: 7, wantConfig: [][]uint32{{6}, {4, 5}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}},
36+
//
37+
{nodeIDs: nodeMapIDs(0, 16), id: 8, wantConfig: [][]uint32{{9}, {10, 11}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}},
38+
{nodeIDs: nodeMapIDs(0, 16), id: 9, wantConfig: [][]uint32{{8}, {10, 11}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}},
39+
{nodeIDs: nodeMapIDs(0, 16), id: 10, wantConfig: [][]uint32{{11}, {8, 9}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}},
40+
{nodeIDs: nodeMapIDs(0, 16), id: 11, wantConfig: [][]uint32{{10}, {8, 9}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}},
41+
{nodeIDs: nodeMapIDs(0, 16), id: 12, wantConfig: [][]uint32{{13}, {14, 15}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}},
42+
{nodeIDs: nodeMapIDs(0, 16), id: 13, wantConfig: [][]uint32{{12}, {14, 15}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}},
43+
{nodeIDs: nodeMapIDs(0, 16), id: 14, wantConfig: [][]uint32{{15}, {12, 13}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}},
44+
{nodeIDs: nodeMapIDs(0, 16), id: 15, wantConfig: [][]uint32{{14}, {12, 13}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}},
45+
}
46+
for _, test := range tests {
47+
t.Run(fmt.Sprintf("%d", len(test.nodeIDs)), func(t *testing.T) {
48+
mgr := gorums.NewManager(gorums.WithNoConnect())
49+
cfgs, err := gorums.SubConfigurations(mgr,
50+
gorums.WithTreeConfigurations(branchFactor, test.id, gorums.WithNodeMap(test.nodeIDs)))
51+
if err != nil {
52+
t.Fatal(err)
53+
}
54+
if len(cfgs) != log2(len(test.nodeIDs)) {
55+
t.Errorf("len(cfgs) = %d, expected %d", len(cfgs), 2)
56+
}
57+
for i, cfg := range cfgs {
58+
if len(cfg) != len(test.wantConfig[i]) {
59+
t.Errorf("len(cfg) = %d, expected %d", len(cfg), len(test.wantConfig[i]))
60+
}
61+
for _, id := range test.wantConfig[i] {
62+
if !cfg.Contains(id) {
63+
t.Errorf("{%v}.Contains(%d) = false, expected true", cfg.NodeIDs(), id)
64+
}
65+
}
66+
// fmt.Printf("c%d (%d): %v\n", i, cfg.Size(), cfg.NodeIDs())
67+
}
68+
})
69+
}
70+
}
71+
72+
// nodeMapIDs returns a map of localhost node IDs for the given node count.
73+
func nodeMapIDs(s, n int) map[string]uint32 {
74+
basePort := 9080
75+
nodeMapIDs := make(map[string]uint32)
76+
for i := s; i < s+n; i++ {
77+
nodeMapIDs[fmt.Sprintf("127.0.0.1:%d", basePort+i)] = uint32(i)
78+
}
79+
return nodeMapIDs
80+
}
81+
82+
// log2 returns the base 2 logarithm of x.
83+
func log2(x int) int {
84+
y := 0
85+
for x > 1 {
86+
x >>= 1
87+
y++
88+
}
89+
return y
90+
}

0 commit comments

Comments
 (0)