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 }