code.vegaprotocol.io/vega@v0.79.0/core/validators/topology_eth_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 "encoding/hex" 21 "errors" 22 "fmt" 23 "sort" 24 25 "code.vegaprotocol.io/vega/core/events" 26 "code.vegaprotocol.io/vega/core/nodewallets/eth/clef" 27 "code.vegaprotocol.io/vega/logging" 28 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 29 30 "github.com/ethereum/go-ethereum/crypto" 31 ) 32 33 var ( 34 ErrCurrentEthAddressDoesNotMatch = errors.New("current Ethereum address does not match") 35 ErrCannotRotateToSameKey = errors.New("new Ethereum address cannot be the same as the previous Ethereum address") 36 ErrNodeHasUnresolvedRotation = errors.New("ethereum keys from a previous rotation have not been resolved on the multisig control contract") 37 ) 38 39 type PendingEthereumKeyRotation struct { 40 NodeID string 41 NewAddress string 42 OldAddress string 43 SubmitterAddress string 44 } 45 46 type pendingEthereumKeyRotationMapping map[uint64][]PendingEthereumKeyRotation 47 48 func (pm pendingEthereumKeyRotationMapping) add(height uint64, rotation PendingEthereumKeyRotation) { 49 if _, ok := pm[height]; !ok { 50 pm[height] = []PendingEthereumKeyRotation{} 51 } 52 53 pm[height] = append(pm[height], rotation) 54 } 55 56 func (pm pendingEthereumKeyRotationMapping) get(height uint64) []PendingEthereumKeyRotation { 57 rotations, ok := pm[height] 58 if !ok { 59 return []PendingEthereumKeyRotation{} 60 } 61 62 sort.Slice(rotations, func(i, j int) bool { return rotations[i].NodeID < rotations[j].NodeID }) 63 64 return rotations 65 } 66 67 func (t *Topology) hasPendingEthKeyRotation(nodeID string) bool { 68 for _, rotations := range t.pendingEthKeyRotations { 69 for _, r := range rotations { 70 if r.NodeID == nodeID { 71 return true 72 } 73 } 74 } 75 return false 76 } 77 78 func (t *Topology) ProcessEthereumKeyRotation( 79 ctx context.Context, 80 publicKey string, 81 kr *commandspb.EthereumKeyRotateSubmission, 82 verify func(message, signature []byte, hexAddress string) error, 83 ) error { 84 t.mu.Lock() 85 defer t.mu.Unlock() 86 87 t.log.Debug("Received ethereum key rotation", 88 logging.String("vega-pub-key", publicKey), 89 logging.String("newAddress", kr.NewAddress), 90 logging.Uint64("currentBlockHeight", t.currentBlockHeight), 91 logging.Uint64("targetBlock", kr.TargetBlock), 92 ) 93 94 var node *valState 95 for _, v := range t.validators { 96 if v.data.VegaPubKey == publicKey { 97 node = v 98 break 99 } 100 } 101 102 if node == nil { 103 err := fmt.Errorf("failed to rotate ethereum key for non existing validator %q", publicKey) 104 t.log.Debug("Failed to add Eth key rotation", logging.Error(err)) 105 return err 106 } 107 108 if err := t.validateRotation(kr, node.data, verify); err != nil { 109 return err 110 } 111 112 // schedule the key rotation to a future block 113 t.pendingEthKeyRotations.add(kr.TargetBlock, 114 PendingEthereumKeyRotation{ 115 NodeID: node.data.ID, 116 NewAddress: kr.NewAddress, 117 OldAddress: kr.CurrentAddress, 118 SubmitterAddress: kr.SubmitterAddress, 119 }) 120 121 t.log.Debug("Successfully added Ethereum key rotation to pending key rotations", 122 logging.String("vega-pub-key", publicKey), 123 logging.Uint64("currentBlockHeight", t.currentBlockHeight), 124 logging.Uint64("targetBlock", kr.TargetBlock), 125 logging.String("newAddress", kr.NewAddress), 126 ) 127 128 if node.status != ValidatorStatusTendermint { 129 return nil 130 } 131 132 toRemove := []NodeIDAddress{{NodeID: node.data.ID, EthAddress: node.data.EthereumAddress}} 133 t.signatures.PrepareValidatorSignatures(ctx, toRemove, t.epochSeq, false) 134 if len(kr.SubmitterAddress) != 0 { 135 // we were given an address that will be submitting the multisig changes, we can emit a remove signature for it right now 136 t.signatures.EmitValidatorRemovedSignatures(ctx, kr.SubmitterAddress, node.data.ID, t.primaryMultisig.ChainID(), t.timeService.GetTimeNow()) 137 t.signatures.EmitValidatorRemovedSignatures(ctx, kr.SubmitterAddress, node.data.ID, t.secondaryMultisig.ChainID(), t.timeService.GetTimeNow()) 138 } 139 140 return nil 141 } 142 143 func (t *Topology) GetPendingEthereumKeyRotation(blockHeight uint64, nodeID string) *PendingEthereumKeyRotation { 144 t.mu.RLock() 145 defer t.mu.RUnlock() 146 147 rotations, ok := t.pendingEthKeyRotations[blockHeight] 148 if !ok { 149 return nil 150 } 151 152 for _, r := range rotations { 153 if r.NodeID == nodeID { 154 return &PendingEthereumKeyRotation{ 155 NodeID: r.NodeID, 156 NewAddress: r.NewAddress, 157 } 158 } 159 } 160 161 return nil 162 } 163 164 func (t *Topology) ethereumKeyRotationBeginBlockLocked(ctx context.Context) { 165 // check any unfinalised key rotations 166 remove := []string{} 167 for _, r := range t.unresolvedEthKeyRotations { 168 if !t.primaryMultisig.IsSigner(r.OldAddress) && t.primaryMultisig.IsSigner(r.NewAddress) { 169 t.log.Info("ethereum key rotations have been resolved on the multisig contract", logging.String("nodeID", r.NodeID), logging.String("old-address", r.OldAddress)) 170 remove = append(remove, r.NodeID) 171 } 172 } 173 174 for _, nodeID := range remove { 175 delete(t.unresolvedEthKeyRotations, nodeID) 176 } 177 178 // key swaps should run in deterministic order 179 rotations := t.pendingEthKeyRotations.get(t.currentBlockHeight) 180 if len(rotations) == 0 { 181 return 182 } 183 184 t.log.Debug("Applying ethereum key-rotations", logging.Uint64("currentBlockHeight", t.currentBlockHeight), logging.Int("n-rotations", len(rotations))) 185 for _, r := range rotations { 186 t.log.Debug("Applying Ethereum key rotation", 187 logging.String("nodeID", r.NodeID), 188 logging.String("newAddress", r.NewAddress), 189 ) 190 191 data, ok := t.validators[r.NodeID] 192 if !ok { 193 // this should actually happen if validator was removed due to poor performance 194 t.log.Error("failed to rotate Ethereum key due to non present validator", logging.String("nodeID", r.NodeID), logging.String("EthereumAddress", r.NewAddress)) 195 continue 196 } 197 198 oldAddress := data.data.EthereumAddress 199 200 data.data.EthereumAddress = r.NewAddress 201 t.validators[r.NodeID] = data 202 203 t.broker.Send(events.NewEthereumKeyRotationEvent( 204 ctx, 205 r.NodeID, 206 oldAddress, 207 r.NewAddress, 208 t.currentBlockHeight, 209 )) 210 211 t.log.Debug("Applied Ethereum key rotation", 212 logging.String("nodeID", r.NodeID), 213 logging.String("oldAddress", oldAddress), 214 logging.String("newAddress", r.NewAddress), 215 ) 216 217 if data.status != ValidatorStatusTendermint { 218 continue 219 } 220 221 toAdd := []NodeIDAddress{{NodeID: r.NodeID, EthAddress: r.NewAddress}} 222 t.signatures.PrepareValidatorSignatures(ctx, toAdd, t.epochSeq, true) 223 224 if len(r.SubmitterAddress) != 0 { 225 // we were given an address that will be submitting the multisig changes, we can emit signatures for it right now 226 t.signatures.EmitValidatorAddedSignatures(ctx, r.SubmitterAddress, r.NodeID, t.primaryMultisig.ChainID(), t.timeService.GetTimeNow()) 227 t.signatures.EmitValidatorAddedSignatures(ctx, r.SubmitterAddress, r.NodeID, t.secondaryMultisig.ChainID(), t.timeService.GetTimeNow()) 228 } 229 230 // add to unfinalised map so we can wait to see the changes on the contract 231 t.unresolvedEthKeyRotations[data.data.ID] = r 232 } 233 234 delete(t.pendingEthKeyRotations, t.currentBlockHeight) 235 } 236 237 func (t *Topology) validateRotation(kr *commandspb.EthereumKeyRotateSubmission, data ValidatorData, verify func(message, signature []byte, hexAddress string) error) error { 238 if t.hasPendingEthKeyRotation(data.ID) { 239 return ErrNodeAlreadyHasPendingKeyRotation 240 } 241 242 if _, ok := t.unresolvedEthKeyRotations[data.ID]; ok { 243 return ErrNodeHasUnresolvedRotation 244 } 245 246 if t.currentBlockHeight >= kr.TargetBlock { 247 t.log.Debug("target block height is not above current block height", logging.Uint64("target", kr.TargetBlock), logging.Uint64("current", t.currentBlockHeight)) 248 return ErrTargetBlockHeightMustBeGreaterThanCurrentHeight 249 } 250 251 if data.EthereumAddress != kr.CurrentAddress { 252 t.log.Debug("current addresses do not match", logging.String("current", data.EthereumAddress), logging.String("submitted", kr.CurrentAddress)) 253 return ErrCurrentEthAddressDoesNotMatch 254 } 255 256 if data.EthereumAddress == kr.NewAddress { 257 t.log.Debug("trying to rotate to the same key", logging.String("current", data.EthereumAddress), logging.String("new-address", kr.NewAddress)) 258 return ErrCannotRotateToSameKey 259 } 260 261 if err := VerifyEthereumKeyRotation(kr, verify); err != nil { 262 return err 263 } 264 265 return nil 266 } 267 268 func SignEthereumKeyRotation( 269 kr *commandspb.EthereumKeyRotateSubmission, 270 ethSigner Signer, 271 ) error { 272 buf, err := makeEthKeyRotationSignableMessage(kr) 273 if err != nil { 274 return err 275 } 276 277 if ethSigner.Algo() != clef.ClefAlgoType { 278 buf = crypto.Keccak256(buf) 279 } 280 ethereumSignature, err := ethSigner.Sign(buf) 281 if err != nil { 282 return err 283 } 284 285 kr.EthereumSignature = &commandspb.Signature{ 286 Value: hex.EncodeToString(ethereumSignature), 287 Algo: ethSigner.Algo(), 288 } 289 290 return nil 291 } 292 293 func VerifyEthereumKeyRotation(kr *commandspb.EthereumKeyRotateSubmission, verify func(message, signature []byte, hexAddress string) error) error { 294 buf, err := makeEthKeyRotationSignableMessage(kr) 295 if err != nil { 296 return err 297 } 298 299 eths, err := hex.DecodeString(kr.GetEthereumSignature().Value) 300 if err != nil { 301 return err 302 } 303 304 if err := verify(buf, eths, kr.NewAddress); err != nil { 305 return err 306 } 307 308 return nil 309 } 310 311 func makeEthKeyRotationSignableMessage(kr *commandspb.EthereumKeyRotateSubmission) ([]byte, error) { 312 if len(kr.CurrentAddress) <= 0 || len(kr.NewAddress) <= 0 || kr.TargetBlock == 0 { 313 return nil, ErrMissingRequiredAnnounceNodeFields 314 } 315 316 msg := kr.CurrentAddress + kr.NewAddress + fmt.Sprintf("%d", kr.TargetBlock) 317 318 return []byte(msg), nil 319 }