code.vegaprotocol.io/vega@v0.79.0/core/validators/topology_key_rotate.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package validators 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "sort" 23 24 "code.vegaprotocol.io/vega/core/events" 25 "code.vegaprotocol.io/vega/logging" 26 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 27 ) 28 29 var ( 30 ErrTargetBlockHeightMustBeGreaterThanCurrentHeight = errors.New("target block height must be greater then current block") 31 ErrNewVegaPubKeyIndexMustBeGreaterThenCurrentPubKeyIndex = errors.New("a new vega public key index must be greather then current public key index") 32 ErrInvalidVegaPubKeyForNode = errors.New("current vega public key is invalid for node") 33 ErrNodeAlreadyHasPendingKeyRotation = errors.New("node already has a pending key rotation") 34 ErrCurrentPubKeyHashDoesNotMatch = errors.New("current public key hash does not match") 35 ) 36 37 type PendingKeyRotation struct { 38 BlockHeight uint64 39 NodeID string 40 NewPubKey string 41 NewKeyIndex uint32 42 } 43 44 type pendingKeyRotation struct { 45 newPubKey string 46 newKeyIndex uint32 47 } 48 49 // pendingKeyRotationMapping maps a block height => node id => new pending key rotation. 50 type pendingKeyRotationMapping map[uint64]map[string]pendingKeyRotation 51 52 func (pr pendingKeyRotationMapping) getSortedNodeIDsPerHeight(height uint64) []string { 53 rotationsPerHeight := pr[height] 54 if len(rotationsPerHeight) == 0 { 55 return nil 56 } 57 58 nodeIDs := make([]string, 0, len(rotationsPerHeight)) 59 for nodeID := range rotationsPerHeight { 60 nodeIDs = append(nodeIDs, nodeID) 61 } 62 63 sort.Strings(nodeIDs) 64 65 return nodeIDs 66 } 67 68 func (t *Topology) hasPendingKeyRotation(nodeID string) bool { 69 for _, rotationsPerNodeID := range t.pendingPubKeyRotations { 70 if _, ok := rotationsPerNodeID[nodeID]; ok { 71 return true 72 } 73 } 74 return false 75 } 76 77 func (t *Topology) AddKeyRotate(ctx context.Context, nodeID string, currentBlockHeight uint64, kr *commandspb.KeyRotateSubmission) error { 78 t.mu.Lock() 79 defer t.mu.Unlock() 80 81 t.log.Debug("Adding key rotation", 82 logging.String("nodeID", nodeID), 83 logging.Uint64("currentBlockHeight", currentBlockHeight), 84 logging.Uint64("targetBlock", kr.TargetBlock), 85 logging.String("currentPubKeyHash", kr.CurrentPubKeyHash), 86 ) 87 88 node, ok := t.validators[nodeID] 89 if !ok { 90 return fmt.Errorf("failed to add key rotate for non existing node %q", nodeID) 91 } 92 93 if t.hasPendingKeyRotation(nodeID) { 94 return ErrNodeAlreadyHasPendingKeyRotation 95 } 96 97 if currentBlockHeight > kr.TargetBlock { 98 return ErrTargetBlockHeightMustBeGreaterThanCurrentHeight 99 } 100 101 if node.data.VegaPubKeyIndex >= kr.NewPubKeyIndex { 102 return ErrNewVegaPubKeyIndexMustBeGreaterThenCurrentPubKeyIndex 103 } 104 105 hashedVegaPubKey, err := node.data.HashVegaPubKey() 106 if err != nil { 107 return err 108 } 109 110 if hashedVegaPubKey != kr.CurrentPubKeyHash { 111 return ErrCurrentPubKeyHashDoesNotMatch 112 } 113 114 if _, ok = t.pendingPubKeyRotations[kr.TargetBlock]; !ok { 115 t.pendingPubKeyRotations[kr.TargetBlock] = map[string]pendingKeyRotation{} 116 } 117 t.pendingPubKeyRotations[kr.TargetBlock][nodeID] = pendingKeyRotation{ 118 newPubKey: kr.NewPubKey, 119 newKeyIndex: kr.NewPubKeyIndex, 120 } 121 t.log.Debug("Successfully added key rotation to pending key rotations", 122 logging.String("nodeID", nodeID), 123 logging.Uint64("currentBlockHeight", currentBlockHeight), 124 logging.Uint64("targetBlock", kr.TargetBlock), 125 ) 126 127 return nil 128 } 129 130 func (t *Topology) GetPendingKeyRotation(blockHeight uint64, nodeID string) *PendingKeyRotation { 131 t.mu.RLock() 132 defer t.mu.RUnlock() 133 134 if _, ok := t.pendingPubKeyRotations[blockHeight]; !ok { 135 return nil 136 } 137 138 if pkr, ok := t.pendingPubKeyRotations[blockHeight][nodeID]; ok { 139 return &PendingKeyRotation{ 140 BlockHeight: blockHeight, 141 NodeID: nodeID, 142 NewPubKey: pkr.newPubKey, 143 NewKeyIndex: pkr.newKeyIndex, 144 } 145 } 146 147 return nil 148 } 149 150 func (t *Topology) GetAllPendingKeyRotations() []*PendingKeyRotation { 151 t.mu.RLock() 152 defer t.mu.RUnlock() 153 154 pkrs := make([]*PendingKeyRotation, 0, len(t.pendingPubKeyRotations)*2) 155 156 blockHeights := make([]uint64, 0, len(t.pendingPubKeyRotations)) 157 for blockHeight := range t.pendingPubKeyRotations { 158 blockHeights = append(blockHeights, blockHeight) 159 } 160 sort.Slice(blockHeights, func(i, j int) bool { return blockHeights[i] < blockHeights[j] }) 161 162 for _, blockHeight := range blockHeights { 163 rotations := t.pendingPubKeyRotations[blockHeight] 164 nodeIDs := make([]string, 0, len(rotations)) 165 for nodeID := range rotations { 166 nodeIDs = append(nodeIDs, nodeID) 167 } 168 sort.Strings(nodeIDs) 169 for _, nodeID := range nodeIDs { 170 r := rotations[nodeID] 171 pkrs = append(pkrs, &PendingKeyRotation{ 172 BlockHeight: blockHeight, 173 NodeID: nodeID, 174 NewPubKey: r.newPubKey, 175 NewKeyIndex: r.newKeyIndex, 176 }) 177 } 178 } 179 180 return pkrs 181 } 182 183 func (t *Topology) keyRotationBeginBlockLocked(ctx context.Context) { 184 t.log.Debug("Trying to apply pending key rotations", logging.Uint64("currentBlockHeight", t.currentBlockHeight)) 185 186 // key swaps should run in deterministic order 187 nodeIDs := t.pendingPubKeyRotations.getSortedNodeIDsPerHeight(t.currentBlockHeight) 188 if len(nodeIDs) == 0 { 189 return 190 } 191 192 t.log.Debug("Applying pending key rotations", logging.Strings("nodeIDs", nodeIDs)) 193 194 for _, nodeID := range nodeIDs { 195 data, ok := t.validators[nodeID] 196 if !ok { 197 // this should actually happen if validator was removed due to poor performance 198 t.log.Error("failed to rotate Vega key due to non present validator", logging.String("nodeID", nodeID)) 199 continue 200 } 201 202 oldPubKey := data.data.VegaPubKey 203 rotation := t.pendingPubKeyRotations[t.currentBlockHeight][nodeID] 204 205 data.data.VegaPubKey = rotation.newPubKey 206 data.data.VegaPubKeyIndex = rotation.newKeyIndex 207 t.validators[nodeID] = data 208 209 t.notifyKeyChange(ctx, oldPubKey, rotation.newPubKey) 210 t.broker.Send(events.NewVegaKeyRotationEvent(ctx, nodeID, oldPubKey, rotation.newPubKey, t.currentBlockHeight)) 211 212 t.log.Debug("Applied key rotation", 213 logging.String("nodeID", nodeID), 214 logging.String("oldPubKey", oldPubKey), 215 logging.String("newPubKey", rotation.newPubKey), 216 ) 217 } 218 219 delete(t.pendingPubKeyRotations, t.currentBlockHeight) 220 } 221 222 func (t *Topology) NotifyOnKeyChange(fns ...func(ctx context.Context, oldPubKey, newPubKey string)) { 223 t.pubKeyChangeListeners = append(t.pubKeyChangeListeners, fns...) 224 } 225 226 func (t *Topology) notifyKeyChange(ctx context.Context, oldPubKey, newPubKey string) { 227 for _, f := range t.pubKeyChangeListeners { 228 f(ctx, oldPubKey, newPubKey) 229 } 230 }