code.vegaprotocol.io/vega@v0.79.0/core/validators/signatures.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 "time" 25 26 "code.vegaprotocol.io/vega/core/bridges" 27 "code.vegaprotocol.io/vega/core/events" 28 "code.vegaprotocol.io/vega/core/types" 29 vgcrypto "code.vegaprotocol.io/vega/libs/crypto" 30 "code.vegaprotocol.io/vega/libs/num" 31 "code.vegaprotocol.io/vega/logging" 32 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 33 snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 34 ) 35 36 var ( 37 ErrNoPendingSignaturesForNodeID = errors.New("there are no pending signatures for the given nodeID") 38 ErrUnknownChainID = errors.New("chain id does not correspond to any bridge") 39 ) 40 41 type Signatures interface { 42 PreparePromotionsSignatures( 43 ctx context.Context, 44 currentTime time.Time, 45 epochSeq uint64, 46 previousState map[string]StatusAddress, 47 newState map[string]StatusAddress, 48 ) 49 SetNonce(currentTime time.Time) 50 PrepareValidatorSignatures(ctx context.Context, validators []NodeIDAddress, epochSeq uint64, added bool) 51 EmitValidatorAddedSignatures(ctx context.Context, submitter, nodeID, chainID string, currentTime time.Time) error 52 EmitValidatorRemovedSignatures(ctx context.Context, submitter, nodeID, chainID string, currentTime time.Time) error 53 ClearStaleSignatures() 54 SerialisePendingSignatures() *snapshot.ToplogySignatures 55 RestorePendingSignatures(*snapshot.ToplogySignatures) 56 OfferSignatures() 57 } 58 59 type signatureData struct { 60 NodeID string 61 EthAddress string 62 Nonce *num.Uint 63 EpochSeq uint64 64 Added bool 65 } 66 67 type issuedSignature struct { 68 EthAddress string 69 SubmitterAddress string 70 ChainID string 71 } 72 73 type signatureWithSubmitter struct { 74 signatureData 75 SubmitterAddress string 76 chainID string 77 } 78 79 type ERC20Signatures struct { 80 log *logging.Logger 81 notary Notary 82 primaryMultisig MultiSigTopology 83 // primaryBridge *bridges.ERC20MultiSigControl 84 secondaryMultisig MultiSigTopology 85 // secondaryBridge *bridges.ERC20MultiSigControl 86 lastNonce *num.Uint 87 broker Broker 88 isValidatorSetup bool 89 signer Signer 90 91 // stored nonce's etc. to be able to generate signatures to remove/add an ethereum address from the multisig bundle 92 pendingSignatures map[string]*signatureData 93 issuedSignatures map[string]issuedSignature 94 } 95 96 func NewSignatures( 97 log *logging.Logger, 98 primaryMultisig MultiSigTopology, 99 secondaryMultisig MultiSigTopology, 100 notary Notary, 101 nw NodeWallets, 102 broker Broker, 103 isValidatorSetup bool, 104 ) *ERC20Signatures { 105 s := &ERC20Signatures{ 106 log: log, 107 notary: notary, 108 primaryMultisig: primaryMultisig, 109 secondaryMultisig: secondaryMultisig, 110 lastNonce: num.UintZero(), 111 broker: broker, 112 isValidatorSetup: isValidatorSetup, 113 pendingSignatures: map[string]*signatureData{}, 114 issuedSignatures: map[string]issuedSignature{}, 115 } 116 if isValidatorSetup { 117 s.signer = nw.GetEthereum() 118 } 119 return s 120 } 121 122 type StatusAddress struct { 123 Status ValidatorStatus 124 EthAddress string 125 SubmitterAddress string 126 } 127 128 type NodeIDAddress struct { 129 NodeID string 130 EthAddress string 131 SubmitterAddress string 132 } 133 134 // isBridge returns whether the given chainID corresponds to one of the bridges, and returns if it is the Ethereum bridge. 135 func (s *ERC20Signatures) isBridge(chainID string) (isBridge bool) { 136 switch chainID { 137 case s.primaryMultisig.ChainID(): 138 isBridge = true 139 case s.secondaryMultisig.ChainID(): 140 isBridge = true 141 } 142 return 143 } 144 145 func (s *ERC20Signatures) OfferSignatures() { 146 s.notary.OfferSignatures(types.NodeSignatureKindERC20MultiSigSignerAdded, s.offerValidatorAddedSignatures) 147 s.notary.OfferSignatures(types.NodeSignatureKindERC20MultiSigSignerRemoved, s.offerValidatorRemovedSignatures) 148 } 149 150 func (s *ERC20Signatures) getSignatureWithSubmitterByResID(resID string) (*signatureWithSubmitter, error) { 151 is, ok := s.issuedSignatures[resID] 152 if !ok { 153 return nil, fmt.Errorf("unable to find issued signature with resource id %q", resID) 154 } 155 156 sd, ok := s.pendingSignatures[is.EthAddress] 157 if !ok { 158 return nil, fmt.Errorf("unable to find pending signature by ethereum address %q", is.EthAddress) 159 } 160 161 return &signatureWithSubmitter{ 162 signatureData: *sd, 163 SubmitterAddress: is.SubmitterAddress, 164 chainID: is.ChainID, 165 }, nil 166 } 167 168 func (s *ERC20Signatures) offerValidatorAddedSignatures(resID string) []byte { 169 if !s.isValidatorSetup { 170 return nil 171 } 172 173 sig, err := s.getSignatureWithSubmitterByResID(resID) 174 if err != nil { 175 s.log.Panic("unable to find signature", logging.Error(err)) 176 } 177 178 if !sig.Added { 179 s.log.Panic("expected added signature but got removed signature instead", logging.String("ethereumAddress", sig.EthAddress)) 180 } 181 182 isBridge := s.isBridge(sig.chainID) 183 if !isBridge { 184 s.log.Panic("unexpected bridge chainID", logging.String("chain-id", sig.chainID)) 185 } 186 187 signature, err := bridges.NewERC20MultiSigControl(s.signer, sig.chainID, false).AddSigner(sig.EthAddress, sig.SubmitterAddress, sig.Nonce.Clone()) 188 if err != nil { 189 s.log.Panic("could not sign remove signer event, wallet not configured properly", 190 logging.Error(err)) 191 } 192 193 return signature.Signature.Bytes() 194 } 195 196 func (s *ERC20Signatures) offerValidatorRemovedSignatures(resID string) []byte { 197 if !s.isValidatorSetup { 198 return nil 199 } 200 201 sig, err := s.getSignatureWithSubmitterByResID(resID) 202 if err != nil { 203 s.log.Panic("unable to find signature", logging.Error(err)) 204 } 205 206 if sig.Added { 207 s.log.Panic("expected removed signature but got added signature instead", logging.String("ethereumAddress", sig.EthAddress)) 208 } 209 210 isBridge := s.isBridge(sig.chainID) 211 if !isBridge { 212 s.log.Panic("unexpected bridge chainID", logging.String("chain-id", sig.chainID)) 213 } 214 215 signature, err := bridges.NewERC20MultiSigControl(s.signer, sig.chainID, false).RemoveSigner(sig.EthAddress, sig.SubmitterAddress, sig.Nonce.Clone()) 216 if err != nil { 217 s.log.Panic("could not sign remove signer event, wallet not configured properly", 218 logging.Error(err)) 219 } 220 221 return signature.Signature.Bytes() 222 } 223 224 func (s *ERC20Signatures) getSignatureData(nodeID string, added bool) []*signatureData { 225 r := []*signatureData{} 226 for _, p := range s.pendingSignatures { 227 if p.NodeID == nodeID && p.Added == added { 228 r = append(r, p) 229 } 230 } 231 sort.SliceStable(r, func(i, j int) bool { 232 return r[i].EthAddress < r[j].EthAddress 233 }) 234 return r 235 } 236 237 func (s *ERC20Signatures) PreparePromotionsSignatures( 238 ctx context.Context, 239 currentTime time.Time, 240 epochSeq uint64, 241 previousState map[string]StatusAddress, 242 newState map[string]StatusAddress, 243 ) { 244 toAdd := []NodeIDAddress{} 245 toRemove := []NodeIDAddress{} 246 247 // first let's cover all the previous validators 248 for k, state := range previousState { 249 if val, ok := newState[k]; !ok { 250 // in this case we were a validator before, but not even in the validator set anymore, 251 // we can remove it. 252 if state.Status == ValidatorStatusTendermint { 253 toRemove = append(toRemove, NodeIDAddress{k, state.EthAddress, state.SubmitterAddress}) 254 } 255 } else { 256 // we've been removed from the validator set then 257 if state.Status == ValidatorStatusTendermint && val.Status != ValidatorStatusTendermint { 258 toRemove = append(toRemove, NodeIDAddress{k, state.EthAddress, state.SubmitterAddress}) 259 } else if state.Status != ValidatorStatusTendermint && val.Status == ValidatorStatusTendermint { 260 // now we've become a validator 261 toAdd = append(toAdd, NodeIDAddress{k, state.EthAddress, state.SubmitterAddress}) 262 } 263 } 264 } 265 266 // now let's cover all which might have been added but might not have been in the previousStates? 267 // is that even possible? 268 for k, val := range newState { 269 if _, ok := previousState[k]; !ok { 270 // this is a new validator which didn't exist before 271 if val.Status == ValidatorStatusTendermint { 272 toAdd = append(toAdd, NodeIDAddress{k, val.EthAddress, val.SubmitterAddress}) 273 } 274 } 275 } 276 277 s.PrepareValidatorSignatures(ctx, toAdd, epochSeq, true) 278 s.PrepareValidatorSignatures(ctx, toRemove, epochSeq, false) 279 280 // check if the node being added has supplied a submitterAddress because if it has we can automatically emit 281 // signatures for the node to add itself 282 for _, v := range toAdd { 283 if v.SubmitterAddress != "" { 284 s.log.Debug("sending automatic add signatures", logging.String("submitter", v.SubmitterAddress), logging.String("nodeID", v.NodeID)) 285 s.EmitValidatorAddedSignatures(ctx, v.SubmitterAddress, v.NodeID, s.primaryMultisig.ChainID(), currentTime) 286 s.EmitValidatorAddedSignatures(ctx, v.SubmitterAddress, v.NodeID, s.secondaryMultisig.ChainID(), currentTime) 287 } 288 } 289 290 // for each node being removed check if the Tendermint nodes have a submitter address because if they do 291 // we can automatically emit remove signatures for them 292 for _, r := range toRemove { 293 for _, v := range newState { 294 if v.SubmitterAddress != "" && v.Status == ValidatorStatusTendermint { 295 s.log.Debug("sending automatic remove signatures", logging.String("submitter", v.SubmitterAddress), logging.String("nodeID", r.NodeID)) 296 s.EmitValidatorRemovedSignatures(ctx, v.SubmitterAddress, r.NodeID, s.primaryMultisig.ChainID(), currentTime) 297 s.EmitValidatorRemovedSignatures(ctx, v.SubmitterAddress, r.NodeID, s.secondaryMultisig.ChainID(), currentTime) 298 } 299 } 300 } 301 } 302 303 func (s *ERC20Signatures) SetNonce(t time.Time) { 304 s.lastNonce = num.NewUint(uint64(t.Unix()) + 1) 305 } 306 307 // PrepareValidatorSignatures make nonces and store the data needed to generate signatures to add/remove from the multisig control contract. 308 func (s *ERC20Signatures) PrepareValidatorSignatures(ctx context.Context, validators []NodeIDAddress, epochSeq uint64, added bool) { 309 sort.Slice(validators, func(i, j int) bool { 310 return validators[i].EthAddress < validators[j].EthAddress 311 }) 312 313 for _, signer := range validators { 314 d := &signatureData{ 315 NodeID: signer.NodeID, 316 EthAddress: signer.EthAddress, 317 Nonce: s.lastNonce.Clone(), 318 EpochSeq: epochSeq, 319 Added: added, 320 } 321 s.lastNonce.AddUint64(s.lastNonce, 1) 322 323 // we're ok to override whatever is in here since we can't need to both add and remove an eth-address at the same time 324 // so we'll replace it either with the correct action, or with the same action but with a later epoch which is fine 325 // because you can still get signatures to do what you need 326 s.pendingSignatures[signer.EthAddress] = d 327 s.log.Debug("prepared multisig signatures for", logging.Bool("added", added), logging.String("id", signer.NodeID), logging.String("eth-address", signer.EthAddress)) 328 } 329 } 330 331 // EmitValidatorAddedSignatures emit signatures to add nodeID's ethereum address onto that can be submitter to the contract by submitter. 332 func (s *ERC20Signatures) EmitValidatorAddedSignatures(ctx context.Context, submitter, nodeID, chainID string, currentTime time.Time) error { 333 toEmit := s.getSignatureData(nodeID, true) 334 if len(toEmit) == 0 { 335 return ErrNoPendingSignaturesForNodeID 336 } 337 338 isBridge := s.isBridge(chainID) 339 if !isBridge { 340 return ErrUnknownChainID 341 } 342 343 evts := []events.Event{} 344 for _, pending := range toEmit { 345 var sig []byte 346 nonce := pending.Nonce 347 348 resid := hex.EncodeToString(vgcrypto.Hash([]byte(submitter + nonce.String() + chainID))) 349 if _, ok := s.issuedSignatures[resid]; ok { 350 // we've already issued a signature for this submitter we don't want to do it again, it'll annoy the notary engine 351 s.log.Debug("add signatures already issued", 352 logging.String("chain-id", chainID), 353 logging.String("submitter", submitter), 354 logging.String("add-address", 355 pending.EthAddress)) 356 continue 357 } 358 359 if s.isValidatorSetup { 360 signature, err := bridges.NewERC20MultiSigControl(s.signer, chainID, false).AddSigner(pending.EthAddress, submitter, nonce) 361 if err != nil { 362 s.log.Panic("could not sign remove signer event, wallet not configured properly", 363 logging.Error(err)) 364 } 365 sig = signature.Signature 366 } 367 368 s.notary.StartAggregate(resid, types.NodeSignatureKindERC20MultiSigSignerAdded, sig) 369 evts = append(evts, events.NewERC20MultiSigSignerAdded( 370 ctx, 371 eventspb.ERC20MultiSigSignerAdded{ 372 SignatureId: resid, 373 ValidatorId: nodeID, 374 Timestamp: currentTime.UnixNano(), 375 EpochSeq: num.NewUint(pending.EpochSeq).String(), 376 NewSigner: pending.EthAddress, 377 Submitter: submitter, 378 Nonce: nonce.String(), 379 ChainId: chainID, 380 }, 381 )) 382 383 // store that we issued it for this submitter 384 s.issuedSignatures[resid] = issuedSignature{ 385 EthAddress: pending.EthAddress, 386 SubmitterAddress: submitter, 387 ChainID: chainID, 388 } 389 } 390 s.broker.SendBatch(evts) 391 return nil 392 } 393 394 // EmitValidatorRemovedSignatures emit signatures to remove nodeID's ethereum address onto that can be submitter to the contract by submitter. 395 func (s *ERC20Signatures) EmitValidatorRemovedSignatures(ctx context.Context, submitter, nodeID, chainID string, currentTime time.Time) error { 396 toEmit := s.getSignatureData(nodeID, false) 397 if len(toEmit) == 0 { 398 return ErrNoPendingSignaturesForNodeID 399 } 400 401 isBridge := s.isBridge(chainID) 402 if !isBridge { 403 return ErrUnknownChainID 404 } 405 406 evts := []events.Event{} 407 // its possible for a nodeID to need to remove 2 of their etheruem addresses from the contract. For example if they initiate a key rotation 408 // and after adding their new key but before they've removed their old key they get demoted. At that point they can have 2 signers on the 409 // contract that will need to be removed and that all vaidators could want to issue signatures for 410 for _, pending := range toEmit { 411 var sig []byte 412 nonce := pending.Nonce 413 414 resid := hex.EncodeToString(vgcrypto.Hash([]byte(pending.EthAddress + submitter + nonce.String() + chainID))) 415 if _, ok := s.issuedSignatures[resid]; ok { 416 // we've already issued a signature for this submitter we don't want to do it again, it'll annoy the notary engine 417 s.log.Debug("remove signatures already issued", logging.String("submitter", submitter), logging.String("add-address", pending.EthAddress)) 418 continue 419 } 420 421 if s.isValidatorSetup { 422 signature, err := bridges.NewERC20MultiSigControl(s.signer, chainID, false).RemoveSigner(pending.EthAddress, submitter, nonce) 423 if err != nil { 424 s.log.Panic("could not sign remove signer event, wallet not configured properly", 425 logging.Error(err)) 426 } 427 sig = signature.Signature 428 } 429 s.notary.StartAggregate( 430 resid, types.NodeSignatureKindERC20MultiSigSignerRemoved, sig) 431 432 submitters := []*eventspb.ERC20MultiSigSignerRemovedSubmitter{} 433 submitters = append(submitters, &eventspb.ERC20MultiSigSignerRemovedSubmitter{ 434 SignatureId: resid, 435 Submitter: submitter, 436 }) 437 438 evts = append(evts, events.NewERC20MultiSigSignerRemoved( 439 ctx, 440 eventspb.ERC20MultiSigSignerRemoved{ 441 SignatureSubmitters: submitters, 442 ValidatorId: nodeID, 443 Timestamp: currentTime.UnixNano(), 444 EpochSeq: num.NewUint(pending.EpochSeq).String(), 445 OldSigner: pending.EthAddress, 446 Nonce: nonce.String(), 447 ChainId: chainID, 448 }, 449 )) 450 451 // store that we issued it for this submitter 452 s.issuedSignatures[resid] = issuedSignature{ 453 EthAddress: pending.EthAddress, 454 SubmitterAddress: submitter, 455 ChainID: chainID, 456 } 457 } 458 s.broker.SendBatch(evts) 459 return nil 460 } 461 462 // ClearStaleSignatures checks core's view of who is an isn't on the multisig contract and remove any pending signatures that have 463 // been resolve e.g if a pending sig to add an address X exists but X is on the contract we can remove the pending sig. 464 func (s *ERC20Signatures) ClearStaleSignatures() { 465 toRemove := []string{} 466 for e, p := range s.pendingSignatures { 467 if p.Added == s.primaryMultisig.IsSigner(e) && p.Added == s.secondaryMultisig.IsSigner(e) { 468 toRemove = append(toRemove, e) 469 } 470 } 471 472 for _, e := range toRemove { 473 s.log.Debug("removing stale pending signature", logging.String("eth-address", e)) 474 delete(s.pendingSignatures, e) 475 } 476 } 477 478 type noopSignatures struct { 479 log *logging.Logger 480 } 481 482 func (n *noopSignatures) EmitValidatorAddedSignatures(_ context.Context, _, _, _ string, _ time.Time) error { 483 n.log.Error("noopSignatures implementation in use in production") 484 return nil 485 } 486 487 func (n *noopSignatures) EmitValidatorRemovedSignatures(_ context.Context, _, _, _ string, _ time.Time) error { 488 n.log.Error("noopSignatures implementation in use in production") 489 return nil 490 } 491 492 func (n *noopSignatures) PrepareValidatorSignatures( 493 _ context.Context, _ []NodeIDAddress, _ uint64, _ bool, 494 ) { 495 n.log.Error("noopSignatures implementation in use in production") 496 } 497 498 func (n *noopSignatures) PreparePromotionsSignatures( 499 _ context.Context, _ time.Time, _ uint64, _ map[string]StatusAddress, _ map[string]StatusAddress, 500 ) { 501 n.log.Error("noopSignatures implementation in use in production") 502 } 503 504 func (n *noopSignatures) SetNonce(_ time.Time) { 505 n.log.Error("noopSignatures implementation in use in production") 506 } 507 508 func (n *noopSignatures) ClearStaleSignatures() { 509 n.log.Error("noopSignatures implementation in use in production") 510 } 511 512 func (n *noopSignatures) SerialisePendingSignatures() *snapshot.ToplogySignatures { 513 n.log.Error("noopSignatures implementation in use in production") 514 return &snapshot.ToplogySignatures{} 515 } 516 517 func (n *noopSignatures) RestorePendingSignatures(*snapshot.ToplogySignatures) { 518 n.log.Error("noopSignatures implementation in use in production") 519 } 520 521 func (n *noopSignatures) OfferSignatures() { 522 n.log.Error("noopSignatures implementation in use in production") 523 }