Skip to content

Commit 1d6adac

Browse files
authored
dkg: fixed edit remove-operators (#4147)
Fixed bug found in `edit remove-operators` command logic. category: bug ticket: none
1 parent 99d5f26 commit 1d6adac

File tree

3 files changed

+94
-26
lines changed

3 files changed

+94
-26
lines changed

dkg/pedersen/config.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ type Config struct {
2929
type ReshareConfig struct {
3030
TotalShares int
3131
NewThreshold int
32-
NewPeers []peer.ID
33-
OldPeers []peer.ID
32+
AddedPeers []peer.ID // Nodes being added to the cluster
33+
RemovedPeers []peer.ID // Nodes being removed from the cluster
3434
}
3535

3636
// NewReshareConfig creates a new ReshareConfig instance.
37-
func NewReshareConfig(totalShares, newThreshold int, newPeers, oldPeers []peer.ID) *ReshareConfig {
37+
func NewReshareConfig(totalShares, newThreshold int, addedPeers, removedPeers []peer.ID) *ReshareConfig {
3838
return &ReshareConfig{
3939
TotalShares: totalShares,
4040
NewThreshold: newThreshold,
41-
NewPeers: newPeers,
42-
OldPeers: oldPeers,
41+
AddedPeers: addedPeers,
42+
RemovedPeers: removedPeers,
4343
}
4444
}
4545

dkg/pedersen/config_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ func TestNewConfig(t *testing.T) {
3636
reshareConfig := pedersen.NewReshareConfig(2, 3, newPeers, oldPeers)
3737
require.Equal(t, 2, reshareConfig.TotalShares)
3838
require.Equal(t, 3, reshareConfig.NewThreshold)
39-
require.Equal(t, newPeers, reshareConfig.NewPeers)
40-
require.Equal(t, oldPeers, reshareConfig.OldPeers)
39+
require.Equal(t, newPeers, reshareConfig.AddedPeers)
40+
require.Equal(t, oldPeers, reshareConfig.RemovedPeers)
4141

4242
config2 := pedersen.NewConfig("peer21", peerMap, 2, []byte("session2"), phaseDuration, reshareConfig)
4343
require.Equal(t, reshareConfig, config2.Reshare)

dkg/pedersen/reshare.go

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
2727
return nil, errors.New("reshare config is nil")
2828
}
2929

30+
// Validate that AddedPeers and RemovedPeers are disjoint sets
31+
for _, addedPeer := range config.Reshare.AddedPeers {
32+
for _, removedPeer := range config.Reshare.RemovedPeers {
33+
if addedPeer == removedPeer {
34+
return nil, errors.New("peer cannot be both added and removed", z.Any("peer_id", addedPeer))
35+
}
36+
}
37+
}
38+
3039
thisNodeIndex, err := config.ThisNodeIndex()
3140
if err != nil {
3241
return nil, err
@@ -91,24 +100,77 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
91100
distKeyShares = append(distKeyShares, dks)
92101
}
93102

94-
// The new nodes are always appended to the old nodes slice
95-
// The old nodes can be indices within the existing nodes slice
96-
oldN := len(nodes) - len(config.Reshare.NewPeers)
97-
oldNodes := nodes[:oldN]
98-
newNodes := make([]kdkg.Node, len(nodes))
99-
copy(newNodes, nodes)
103+
// Classify nodes for reshare operation.
104+
// In KDKG terminology:
105+
// - oldNodes: nodes with existing shares (participating in the old cluster)
106+
// - newNodes: nodes that will have shares in the new cluster
107+
//
108+
// Supported scenarios:
109+
// 1. Pure reshare (no adds/removes): oldNodes = newNodes = all participating nodes
110+
// 2. Add-only: oldNodes = existing nodes; newNodes = existing + added nodes
111+
// 3. Remove-only: oldNodes = all participating nodes; newNodes = participating - removed nodes
112+
113+
// Determine if this node has existing shares to contribute
114+
thisIsOldNode := len(distKeyShares) > 0
115+
116+
oldNodes := make([]kdkg.Node, 0, len(nodes))
117+
newNodes := make([]kdkg.Node, 0, len(nodes))
118+
thisIsRemovedNode := false
119+
thisIsAddedNode := false
120+
121+
for _, node := range nodes {
122+
isRemoving := false
123+
isNewlyAdded := false
124+
125+
// Check if this node is being removed
126+
for _, removedPeerID := range config.Reshare.RemovedPeers {
127+
if idx, ok := config.PeerMap[removedPeerID]; ok && idx.PeerIdx == int(node.Index) {
128+
isRemoving = true
129+
if idx.PeerIdx == thisNodeIndex {
130+
thisIsRemovedNode = true
131+
}
132+
133+
break
134+
}
135+
}
136+
137+
// Check if this node is newly added
138+
for _, addedPeerID := range config.Reshare.AddedPeers {
139+
if idx, ok := config.PeerMap[addedPeerID]; ok && idx.PeerIdx == int(node.Index) {
140+
isNewlyAdded = true
141+
if idx.PeerIdx == thisNodeIndex {
142+
thisIsAddedNode = true
143+
}
144+
145+
break
146+
}
147+
}
100148

101-
thisIsOldNode := false
149+
// Classify nodes:
150+
// - oldNodes: nodes that are not newly added (have existing shares)
151+
// - newNodes: nodes that are not being removed (will be in new cluster)
152+
if !isNewlyAdded {
153+
oldNodes = append(oldNodes, node)
154+
}
102155

103-
for _, oid := range config.Reshare.OldPeers {
104-
idx := config.PeerMap[oid]
105-
if idx.PeerIdx == thisNodeIndex {
106-
thisIsOldNode = true
156+
if !isRemoving {
157+
newNodes = append(newNodes, node)
107158
}
159+
}
160+
161+
// Validate node classification
162+
if len(config.Reshare.RemovedPeers) > 0 && len(oldNodes) == 0 {
163+
return nil, errors.New("remove operation requires at least one node with existing shares to participate")
164+
}
108165

109-
newNodes = slices.DeleteFunc(newNodes, func(n kdkg.Node) bool {
110-
return n.Index == kdkg.Index(idx.PeerIdx)
111-
})
166+
if len(config.Reshare.AddedPeers) > 0 && len(newNodes) <= len(oldNodes) {
167+
return nil, errors.New("add operation requires new nodes to join, but all nodes already exist in the cluster")
168+
}
169+
170+
// In add operations, old nodes must have shares to contribute
171+
// (new nodes being added won't have shares, which is expected)
172+
if len(config.Reshare.AddedPeers) > 0 && !thisIsAddedNode && len(distKeyShares) == 0 {
173+
return nil, errors.New("existing node in add operation must have shares to contribute")
112174
}
113175

114176
nonce, err := generateNonce(nodes)
@@ -132,19 +194,23 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
132194
log.Info(ctx, "Starting pedersen reshare...",
133195
z.Int("oldNodes", len(oldNodes)), z.Int("newNodes", len(newNodes)),
134196
z.Int("oldThreshold", config.Threshold), z.Int("newThreshold", config.Reshare.NewThreshold),
135-
z.Bool("removed", thisIsOldNode))
197+
z.Bool("thisIsOldNode", thisIsOldNode), z.Bool("thisIsRemovedNode", thisIsRemovedNode))
136198

137199
newShares := make([]share.Share, 0, config.Reshare.TotalShares)
138200

139201
for shareNum := range config.Reshare.TotalShares {
140202
phaser := kdkg.NewTimePhaser(config.PhaseDuration)
141203

142-
if len(distKeyShares) > 0 {
143-
// Share is to be set by old nodes only
204+
// Nodes with existing shares provide their share to the reshare protocol.
205+
// New nodes without shares provide public coefficients instead.
206+
isNodeWithExistingShares := len(distKeyShares) > 0
207+
208+
if isNodeWithExistingShares {
209+
// This node has existing shares to contribute to the reshare
144210
reshareConfig.Share = distKeyShares[shareNum]
145211
reshareConfig.PublicCoeffs = nil
146212
} else {
147-
// PublicCoeffs is to be set by new nodes only, but not the Share
213+
// This is a new node - restore public coefficients from exchanged public key shares
148214
commits, err := restoreCommits(pubKeyShares, shareNum, config.Threshold)
149215
if err != nil {
150216
return nil, errors.Wrap(err, "restore commits")
@@ -170,11 +236,13 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
170236
case <-ctx.Done():
171237
return nil, errors.New("pedersen reshare context done, protocol aborted")
172238
case kdkgResult := <-protocol.WaitEnd():
173-
if thisIsOldNode {
239+
if thisIsRemovedNode {
240+
// This node is being removed and will not receive new shares
174241
if err := broadcastNoneKey(ctx, config, board); err != nil {
175242
return nil, err
176243
}
177244
} else {
245+
// This node will be part of the new cluster and receives new shares
178246
if kdkgResult.Error != nil {
179247
return nil, errors.Wrap(kdkgResult.Error, "pedersen reshare protocol failed", z.Bool("thisIsOldNode", thisIsOldNode))
180248
}

0 commit comments

Comments
 (0)