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  }