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  }