code.vegaprotocol.io/vega@v0.79.0/core/validators/topology_checkpoint.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/base64"
    21  	"sort"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/proto"
    26  	checkpoint "code.vegaprotocol.io/vega/protos/vega/checkpoint/v1"
    27  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    28  
    29  	tmtypes "github.com/cometbft/cometbft/abci/types"
    30  )
    31  
    32  func (t *Topology) Name() types.CheckpointName {
    33  	return types.ValidatorsCheckpoint
    34  }
    35  
    36  func (t *Topology) Checkpoint() ([]byte, error) {
    37  	snap := &checkpoint.Validators{
    38  		ValidatorState:              t.getValidatorStateCheckpoint(),
    39  		PendingKeyRotations:         t.getCheckpointPendingKeyRotations(),
    40  		PendingEthereumKeyRotations: t.getCheckpointPendingEthereumKeyRotations(),
    41  	}
    42  	return proto.Marshal(snap)
    43  }
    44  
    45  func (t *Topology) Load(ctx context.Context, data []byte) error {
    46  	ckp := &checkpoint.Validators{}
    47  	if err := proto.Unmarshal(data, ckp); err != nil {
    48  		return err
    49  	}
    50  
    51  	toRemove := t.validators
    52  	t.validators = make(map[string]*valState, len(ckp.ValidatorState))
    53  	nextValidators := []string{}
    54  	for _, node := range ckp.ValidatorState {
    55  		vs := &valState{
    56  			data: ValidatorData{
    57  				ID:              node.ValidatorUpdate.NodeId,
    58  				VegaPubKey:      node.ValidatorUpdate.VegaPubKey,
    59  				VegaPubKeyIndex: node.ValidatorUpdate.VegaPubKeyIndex,
    60  				EthereumAddress: node.ValidatorUpdate.EthereumAddress,
    61  				TmPubKey:        node.ValidatorUpdate.TmPubKey,
    62  				InfoURL:         node.ValidatorUpdate.InfoUrl,
    63  				Country:         node.ValidatorUpdate.Country,
    64  				Name:            node.ValidatorUpdate.Name,
    65  				AvatarURL:       node.ValidatorUpdate.AvatarUrl,
    66  				FromEpoch:       node.ValidatorUpdate.FromEpoch,
    67  			},
    68  			blockAdded:                      int64(t.currentBlockHeight),
    69  			status:                          ValidatorStatus(node.Status),
    70  			statusChangeBlock:               int64(t.currentBlockHeight),
    71  			lastBlockWithPositiveRanking:    int64(t.currentBlockHeight - 1),
    72  			numberOfEthereumEventsForwarded: node.EthEventsForwarded,
    73  			heartbeatTracker: &validatorHeartbeatTracker{
    74  				blockIndex:            int(node.HeartbeatBlockIndex),
    75  				expectedNextHash:      "",
    76  				expectedNexthashSince: time.Time{},
    77  			},
    78  			validatorPower: node.ValidatorPower,
    79  			rankingScore:   node.RankingScore,
    80  		}
    81  
    82  		// we check if its populate so that we remain compatible with old checkpoints
    83  		if len(node.HeartbeatBlockSigs) == 10 {
    84  			for i := 0; i < 10; i++ {
    85  				vs.heartbeatTracker.blockSigs[i] = node.HeartbeatBlockSigs[i]
    86  			}
    87  		}
    88  
    89  		// this node is started and expect to be a validator
    90  		// but so far we haven't seen ourselves as validators for
    91  		// this network.
    92  		if t.isValidatorSetup && !t.isValidator {
    93  			t.checkValidatorDataWithSelfWallets(vs.data)
    94  		}
    95  
    96  		t.validators[node.ValidatorUpdate.NodeId] = vs
    97  		if t.validators[node.ValidatorUpdate.NodeId].validatorPower > 0 {
    98  			nextValidators = append(nextValidators, node.ValidatorUpdate.NodeId)
    99  		}
   100  		t.sendValidatorUpdateEvent(ctx, t.validators[node.ValidatorUpdate.NodeId].data, true)
   101  		t.checkpointLoaded = true
   102  
   103  		delete(toRemove, node.ValidatorUpdate.NodeId)
   104  	}
   105  
   106  	// send an update event to remove any validators that were in the genesis file, but not in the checkpoint
   107  	for _, v := range toRemove {
   108  		t.sendValidatorUpdateEvent(ctx, v.data, false)
   109  	}
   110  
   111  	t.restoreCheckpointPendingKeyRotations(ckp.PendingKeyRotations)
   112  	t.restoreCheckpointPendingEthereumKeyRotations(ckp.PendingEthereumKeyRotations)
   113  
   114  	sort.Strings(nextValidators)
   115  
   116  	// generate the tendermint updates from the voting power so that in end of the block the validator powers are pushed to tentermint
   117  	vUpdates := make([]tmtypes.ValidatorUpdate, 0, len(nextValidators))
   118  	for _, v := range nextValidators {
   119  		// NB: if the validator set in the checkpoint doesn't match genesis, vd may be nil
   120  		vd := t.validators[v]
   121  		pubkey, err := base64.StdEncoding.DecodeString(vd.data.TmPubKey)
   122  		if err != nil {
   123  			continue
   124  		}
   125  
   126  		update := tmtypes.UpdateValidator(pubkey, vd.validatorPower, "")
   127  		vUpdates = append(vUpdates, update)
   128  	}
   129  
   130  	// setting this to true so we can pass the powers back to tendermint after initChain
   131  	t.validatorPowerUpdates = vUpdates
   132  	t.newEpochStarted = true
   133  	return nil
   134  }
   135  
   136  func (t *Topology) restoreCheckpointPendingKeyRotations(rotations []*checkpoint.PendingKeyRotation) {
   137  	for _, pr := range rotations {
   138  		// skip this key rotation as the node is not parcitipating in the new network
   139  		if _, ok := t.validators[pr.NodeId]; !ok {
   140  			continue
   141  		}
   142  
   143  		targetBlockHeight := t.currentBlockHeight + pr.RelativeTargetBlockHeight
   144  
   145  		if _, ok := t.pendingPubKeyRotations[targetBlockHeight]; !ok {
   146  			t.pendingPubKeyRotations[targetBlockHeight] = map[string]pendingKeyRotation{}
   147  		}
   148  
   149  		t.pendingPubKeyRotations[targetBlockHeight][pr.NodeId] = pendingKeyRotation{
   150  			newPubKey:   pr.NewPubKey,
   151  			newKeyIndex: pr.NewPubKeyIndex,
   152  		}
   153  	}
   154  }
   155  
   156  func (t *Topology) restoreCheckpointPendingEthereumKeyRotations(rotations []*checkpoint.PendingEthereumKeyRotation) {
   157  	for _, pr := range rotations {
   158  		// skip this key rotation as the node is not parcitipating in the new network
   159  		if _, ok := t.validators[pr.NodeId]; !ok {
   160  			continue
   161  		}
   162  
   163  		targetBlockHeight := t.currentBlockHeight + pr.RelativeTargetBlockHeight
   164  
   165  		t.pendingEthKeyRotations.add(targetBlockHeight, PendingEthereumKeyRotation{
   166  			NodeID:     pr.NodeId,
   167  			NewAddress: pr.NewAddress,
   168  		})
   169  	}
   170  }
   171  
   172  func (t *Topology) getRelativeBlockHeight(blockHeight, currentBlockHeight uint64) uint64 {
   173  	// this should never happen but (just in case) we want to make sure the key rotation will happen in future
   174  	// so adding it's shifted artificially 2 blocks ahead
   175  	if blockHeight <= currentBlockHeight {
   176  		return 2
   177  	}
   178  	return blockHeight - currentBlockHeight
   179  }
   180  
   181  func (t *Topology) getCheckpointPendingKeyRotations() []*checkpoint.PendingKeyRotation {
   182  	rotations := make([]*checkpoint.PendingKeyRotation, 0, len(t.pendingPubKeyRotations)*2)
   183  
   184  	blockHeights := make([]uint64, 0, len(t.pendingPubKeyRotations))
   185  	for blockHeight := range t.pendingPubKeyRotations {
   186  		blockHeights = append(blockHeights, blockHeight)
   187  	}
   188  	sort.Slice(blockHeights, func(i, j int) bool { return blockHeights[i] < blockHeights[j] })
   189  
   190  	for _, blockHeight := range blockHeights {
   191  		rs := t.pendingPubKeyRotations[blockHeight]
   192  		nodeIDs := make([]string, 0, len(rs))
   193  		for nodeID := range rs {
   194  			nodeIDs = append(nodeIDs, nodeID)
   195  		}
   196  		sort.Strings(nodeIDs)
   197  
   198  		for _, nodeID := range nodeIDs {
   199  			r := rs[nodeID]
   200  			rotations = append(rotations, &checkpoint.PendingKeyRotation{
   201  				RelativeTargetBlockHeight: t.getRelativeBlockHeight(blockHeight, t.currentBlockHeight),
   202  				NodeId:                    nodeID,
   203  				NewPubKey:                 r.newPubKey,
   204  				NewPubKeyIndex:            r.newKeyIndex,
   205  			})
   206  		}
   207  	}
   208  	return rotations
   209  }
   210  
   211  func (t *Topology) getCheckpointPendingEthereumKeyRotations() []*checkpoint.PendingEthereumKeyRotation {
   212  	outRotations := make([]*checkpoint.PendingEthereumKeyRotation, 0, len(t.pendingEthKeyRotations)*2)
   213  
   214  	for blockHeight, rotations := range t.pendingEthKeyRotations {
   215  		for _, r := range rotations {
   216  			outRotations = append(outRotations, &checkpoint.PendingEthereumKeyRotation{
   217  				RelativeTargetBlockHeight: t.getRelativeBlockHeight(blockHeight, t.currentBlockHeight),
   218  				NodeId:                    r.NodeID,
   219  				NewAddress:                r.NewAddress,
   220  			})
   221  		}
   222  	}
   223  
   224  	sort.SliceStable(outRotations, func(i, j int) bool {
   225  		if outRotations[i].GetRelativeTargetBlockHeight() == outRotations[j].GetRelativeTargetBlockHeight() {
   226  			return outRotations[i].GetNodeId() < outRotations[j].GetNodeId()
   227  		}
   228  		return outRotations[i].GetRelativeTargetBlockHeight() < outRotations[j].GetRelativeTargetBlockHeight()
   229  	})
   230  
   231  	return outRotations
   232  }
   233  
   234  func (t *Topology) getValidatorStateCheckpoint() []*checkpoint.ValidatorState {
   235  	vsSlice := make([]*checkpoint.ValidatorState, 0, len(t.validators))
   236  
   237  	keys := make([]string, 0, len(t.validators))
   238  	for k := range t.validators {
   239  		keys = append(keys, k)
   240  	}
   241  	sort.Strings(keys)
   242  	for _, v := range keys {
   243  		node := t.validators[v]
   244  		vsSlice = append(vsSlice, &checkpoint.ValidatorState{
   245  			ValidatorUpdate: &eventspb.ValidatorUpdate{
   246  				NodeId:          node.data.ID,
   247  				VegaPubKey:      node.data.VegaPubKey,
   248  				VegaPubKeyIndex: node.data.VegaPubKeyIndex,
   249  				EthereumAddress: node.data.EthereumAddress,
   250  				TmPubKey:        node.data.TmPubKey,
   251  				InfoUrl:         node.data.InfoURL,
   252  				Country:         node.data.Country,
   253  				Name:            node.data.Name,
   254  				AvatarUrl:       node.data.AvatarURL,
   255  				FromEpoch:       node.data.FromEpoch,
   256  			},
   257  			Status:              int32(node.status),
   258  			EthEventsForwarded:  node.numberOfEthereumEventsForwarded,
   259  			ValidatorPower:      node.validatorPower,
   260  			RankingScore:        node.rankingScore,
   261  			HeartbeatBlockIndex: int32(node.heartbeatTracker.blockIndex),
   262  			HeartbeatBlockSigs:  node.heartbeatTracker.blockSigs[:],
   263  		})
   264  	}
   265  	return vsSlice
   266  }