code.vegaprotocol.io/vega@v0.79.0/core/delegation/engine.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 delegation 17 18 import ( 19 "context" 20 "encoding/hex" 21 "errors" 22 "sort" 23 "time" 24 25 "code.vegaprotocol.io/vega/core/events" 26 "code.vegaprotocol.io/vega/core/types" 27 "code.vegaprotocol.io/vega/core/validators" 28 "code.vegaprotocol.io/vega/libs/crypto" 29 "code.vegaprotocol.io/vega/libs/num" 30 "code.vegaprotocol.io/vega/logging" 31 ) 32 33 var minRatioForAutoDelegation, _ = num.DecimalFromString("0.95") 34 35 const reconciliationInterval = 30 * time.Second 36 37 var ( 38 activeKey = (&types.PayloadDelegationActive{}).Key() 39 pendingKey = (&types.PayloadDelegationPending{}).Key() 40 autoKey = (&types.PayloadDelegationAuto{}).Key() 41 lastReconKey = (&types.PayloadDelegationLastReconTime{}).Key() 42 ) 43 44 var ( 45 // ErrPartyHasNoStakingAccount is returned when the staking account for the party cannot be found. 46 ErrPartyHasNoStakingAccount = errors.New("cannot find staking account for the party") 47 // ErrInvalidNodeID is returned when the node id passed for delegation/undelegation is not a validator node identifier. 48 ErrInvalidNodeID = errors.New("invalid node ID") 49 // ErrInsufficientBalanceForDelegation is returned when the balance in the staking account is insufficient to cover all committed and pending delegations. 50 ErrInsufficientBalanceForDelegation = errors.New("insufficient balance for delegation") 51 // ErrIncorrectTokenAmountForUndelegation is returned when the amount to undelegation doesn't match the delegation balance (pending + committed) for the party and validator. 52 ErrIncorrectTokenAmountForUndelegation = errors.New("incorrect token amount for undelegation") 53 // ErrAmountLTMinAmountForDelegation is returned when the amount to delegate to a node is lower than the minimum allowed amount from network params. 54 ErrAmountLTMinAmountForDelegation = errors.New("delegation amount is lower than the minimum amount for delegation for a validator") 55 ) 56 57 // TimeService notifies the reward engine on time updates 58 // 59 //go:generate go run github.com/golang/mock/mockgen -destination mocks/time_service_mock.go -package mocks code.vegaprotocol.io/vega/core/rewards TimeService 60 type TimeService interface { 61 GetTimeNow() time.Time 62 } 63 64 // ValidatorTopology represents the topology of validators and can check if a given node is a validator. 65 type ValidatorTopology interface { 66 IsValidatorNodeID(nodeID string) bool 67 AllNodeIDs() []string 68 Get(key string) *validators.ValidatorData 69 } 70 71 // Broker send events 72 // we no longer need to generate this mock here, we can use the broker/mocks package instead. 73 type Broker interface { 74 Send(event events.Event) 75 SendBatch(events []events.Event) 76 } 77 78 // StakingAccounts provides access to the staking balance of a given party now and within a duration of an epoch. 79 type StakingAccounts interface { 80 GetAvailableBalance(party string) (*num.Uint, error) 81 GetAvailableBalanceInRange(party string, from, to time.Time) (*num.Uint, error) 82 } 83 84 type EpochEngine interface { 85 NotifyOnEpoch(f func(context.Context, types.Epoch), r func(context.Context, types.Epoch)) 86 } 87 88 // party delegation state - how much is delegated by the party to each validator and in total. 89 type partyDelegation struct { 90 party string // party ID 91 nodeToAmount map[string]*num.Uint // nodeID -> delegated amount 92 totalDelegated *num.Uint // total amount delegated by party 93 } 94 95 // Engine is handling the delegations balances from parties to validators 96 // The delegation engine is designed in the following way with the following assumptions: 97 // 1. during epoch it is called with delegation requests that update the delegation balance of the party for the next epoch 98 // 2. At the end of the epoch: 99 // 2.1 updates the delegated balances to reconcile the epoch's staking account balance for each party such that if a party withdrew from their 100 // 101 // staking account during the epoch it will not count for them for rewarding 102 // 103 // 2.2 capture the state after 2.1 to be returned to the rewarding engine 104 // 2.3 process all pending delegations. 105 type Engine struct { 106 log *logging.Logger 107 config Config 108 broker Broker 109 topology ValidatorTopology // an interface to the topoology to interact with validator nodes if needed 110 stakingAccounts StakingAccounts // an interface to the staking account for getting party balances 111 partyDelegationState map[string]*partyDelegation // party to active delegation balances 112 nextPartyDelegationState map[string]*partyDelegation // party to next epoch delegation balances 113 minDelegationAmount *num.Uint // min delegation amount per delegation request 114 currentEpoch types.Epoch // the current epoch for pending delegations 115 autoDelegationMode map[string]struct{} // parties entered auto-delegation mode 116 dss *delegationSnapshotState // snapshot state 117 lastReconciliation time.Time // last time staking balance has been reconciled against delegation balance 118 } 119 120 // New instantiates a new delegation engine. 121 func New(log *logging.Logger, config Config, broker Broker, topology ValidatorTopology, stakingAccounts StakingAccounts, epochEngine EpochEngine, ts TimeService) *Engine { 122 log = log.Named(namedLogger) 123 log.SetLevel(config.Level.Get()) 124 e := &Engine{ 125 config: config, 126 log: log, 127 broker: broker, 128 topology: topology, 129 stakingAccounts: stakingAccounts, 130 partyDelegationState: map[string]*partyDelegation{}, 131 nextPartyDelegationState: map[string]*partyDelegation{}, 132 autoDelegationMode: map[string]struct{}{}, 133 dss: &delegationSnapshotState{}, 134 lastReconciliation: time.Time{}, 135 } 136 // register for epoch notifications 137 epochEngine.NotifyOnEpoch(e.onEpochEvent, e.onEpochRestore) 138 139 return e 140 } 141 142 func (e *Engine) Hash() []byte { 143 buf, err := e.Checkpoint() 144 if err != nil { 145 e.log.Panic("could not create checkpoint", logging.Error(err)) 146 } 147 h := crypto.Hash(buf) 148 e.log.Debug("delegations state hash", logging.String("hash", hex.EncodeToString(h))) 149 return h 150 } 151 152 // OnMinAmountChanged updates the network parameter for minDelegationAmount. 153 func (e *Engine) OnMinAmountChanged(ctx context.Context, minAmount num.Decimal) error { 154 e.minDelegationAmount, _ = num.UintFromDecimal(minAmount) 155 return nil 156 } 157 158 // every few blocks try to reconcile the association and nomination for the current and next epoch. 159 func (e *Engine) OnTick(ctx context.Context, t time.Time) { 160 // if we've already done reconciliation (i.e. not first epoch) and it's been over <reconciliationIntervalSeconds> since, then reconcile. 161 if (e.lastReconciliation != time.Time{}) && t.Sub(e.lastReconciliation) >= reconciliationInterval { 162 // always reconcile the balance from the start of the epoch to the current time for simplicity 163 e.reconcileAssociationWithNomination(ctx, e.currentEpoch.StartTime, t, e.currentEpoch.Seq) 164 } 165 } 166 167 // update the current epoch at which current pending delegations are recorded 168 // regardless if the event is start or stop of the epoch. the sequence is what identifies the epoch. 169 func (e *Engine) onEpochEvent(ctx context.Context, epoch types.Epoch) { 170 if (e.lastReconciliation == time.Time{}) { 171 e.lastReconciliation = epoch.StartTime 172 } 173 if epoch.Seq != e.currentEpoch.Seq { 174 // emit an event for the next epoch's delegations 175 for _, p := range e.sortParties(e.nextPartyDelegationState) { 176 for _, n := range e.sortNodes(e.nextPartyDelegationState[p].nodeToAmount) { 177 e.sendDelegatedBalanceEvent(ctx, p, n, epoch.Seq+1, e.nextPartyDelegationState[p].nodeToAmount[n]) 178 } 179 } 180 } 181 e.currentEpoch = epoch 182 } 183 184 // reconcileAssociationWithNomination adjusts if necessary the nomination balance with the association balance for the current and next epoch. 185 func (e *Engine) reconcileAssociationWithNomination(ctx context.Context, from, to time.Time, epochSeq uint64) { 186 // for current epoch we reconcile against the minimum balance for the epoch as given by the partial function 187 e.reconcile(ctx, e.partyDelegationState, e.stakeInRangeFunc(from, to), epochSeq) 188 // for the next epoch we reconcile against the current balance 189 e.reconcile(ctx, e.nextPartyDelegationState, e.stakingAccounts.GetAvailableBalance, epochSeq+1) 190 e.lastReconciliation = to 191 } 192 193 // reconcile checks if there is a mismatch between the amount associated with VEGA by a party and the amount nominated by this party. If a mismatch is found it is auto-adjusted. 194 func (e *Engine) reconcile(ctx context.Context, delegationState map[string]*partyDelegation, stakeFunc func(string) (*num.Uint, error), epochSeq uint64) { 195 parties := e.sortParties(delegationState) 196 for _, party := range parties { 197 stakeBalance, err := stakeFunc(party) 198 if err != nil { 199 e.log.Error("Failed to get available balance", logging.Error(err)) 200 continue 201 } 202 203 // if the stake covers the total delegated balance nothing to do further for the party 204 if stakeBalance.GTE(delegationState[party].totalDelegated) { 205 continue 206 } 207 208 partyDelegation := delegationState[party] 209 // if the stake account balance for the epoch is less than the delegated balance - we need to undelegate the difference 210 // this will be done evenly as much as possible between all validators with delegation from the party 211 remainingBalanceToUndelegate := num.UintZero().Sub(partyDelegation.totalDelegated, stakeBalance) 212 totalTaken := num.UintZero() 213 nodeIDs := e.sortNodes(partyDelegation.nodeToAmount) 214 215 // undelegate proportionally across delegated validator nodes 216 totalDeletation := partyDelegation.totalDelegated.Clone() 217 for _, nodeID := range nodeIDs { 218 balance := partyDelegation.nodeToAmount[nodeID] 219 balanceToTake := num.UintZero().Mul(balance, remainingBalanceToUndelegate) 220 balanceToTake = num.UintZero().Div(balanceToTake, totalDeletation) 221 222 if balanceToTake.IsZero() { 223 continue 224 } 225 226 e.decreaseBalanceAndFireEvent(ctx, party, nodeID, balanceToTake, epochSeq, delegationState, false, false) 227 totalTaken = num.Sum(totalTaken, balanceToTake) 228 } 229 230 // if there was a remainder, the maximum that we need to take more from each node is 1, 231 if totalTaken.LT(remainingBalanceToUndelegate) { 232 for _, nodeID := range nodeIDs { 233 balance, ok := partyDelegation.nodeToAmount[nodeID] 234 if !ok { 235 continue 236 } 237 if totalTaken.EQ(remainingBalanceToUndelegate) { 238 break 239 } 240 if !balance.IsZero() { 241 e.decreaseBalanceAndFireEvent(ctx, party, nodeID, num.NewUint(1), epochSeq, delegationState, false, false) 242 totalTaken = num.Sum(totalTaken, num.NewUint(1)) 243 } 244 } 245 } 246 247 currentNodeIDs := e.sortNodes(delegationState[party].nodeToAmount) 248 for _, nodeID := range currentNodeIDs { 249 e.sendDelegatedBalanceEvent(ctx, party, nodeID, epochSeq, delegationState[party].nodeToAmount[nodeID]) 250 if amt, ok := delegationState[party].nodeToAmount[nodeID]; ok { 251 if amt.IsZero() { 252 delete(delegationState[party].nodeToAmount, nodeID) 253 } 254 } 255 } 256 257 if state, ok := delegationState[party]; ok { 258 if state.totalDelegated.IsZero() { 259 delete(delegationState, party) 260 } 261 } 262 263 // get out of auto delegation mode 264 delete(e.autoDelegationMode, party) 265 } 266 } 267 268 // Delegate updates the delegation balance for the next epoch. 269 func (e *Engine) Delegate(ctx context.Context, party string, nodeID string, amount *num.Uint) error { 270 amt := amount.Clone() 271 272 // check if the node is a validator node 273 if !e.topology.IsValidatorNodeID(nodeID) { 274 e.log.Error("Trying to delegate to an invalid node", logging.Uint64("epoch", e.currentEpoch.Seq), logging.String("party", party), logging.String("validator", nodeID)) 275 return ErrInvalidNodeID 276 } 277 278 // check if the delegator has a staking account 279 partyBalance, err := e.stakingAccounts.GetAvailableBalance(party) 280 if err != nil { 281 e.log.Error("Party has no staking account balance", logging.Uint64("epoch", e.currentEpoch.Seq), logging.String("party", party), logging.String("validator", nodeID)) 282 return ErrPartyHasNoStakingAccount 283 } 284 285 // check if the amount for delegation is valid 286 if amt.LT(e.minDelegationAmount) { 287 e.log.Error("Amount for delegation is lower than minimum required amount", logging.Uint64("epoch", e.currentEpoch.Seq), logging.String("party", party), logging.String("validator", nodeID), logging.String("amount", num.UintToString(amount)), logging.String("minAmount", num.UintToString(e.minDelegationAmount))) 288 return ErrAmountLTMinAmountForDelegation 289 } 290 291 // get the pending balance for the next epoch 292 nextEpochBalance := num.UintZero() 293 if nextEpoch, ok := e.nextPartyDelegationState[party]; ok { 294 nextEpochBalance = nextEpoch.totalDelegated 295 } 296 297 // if the projected balance for next epoch is greater than the current staking account balance reject the transaction 298 if num.Sum(nextEpochBalance, amt).GT(partyBalance) { 299 e.log.Error("Party has insufficient account balance", logging.Uint64("epoch", e.currentEpoch.Seq), logging.String("party", party), logging.String("validator", nodeID), logging.String("associatedBalance", num.UintToString(partyBalance)), logging.String("delegationBalance", num.UintToString(nextEpochBalance)), logging.String("amount", num.UintToString(amount))) 300 return ErrInsufficientBalanceForDelegation 301 } 302 303 // update the balance for next epoch 304 if _, ok := e.nextPartyDelegationState[party]; !ok { 305 e.nextPartyDelegationState[party] = &partyDelegation{ 306 party: party, 307 totalDelegated: num.UintZero(), 308 nodeToAmount: map[string]*num.Uint{nodeID: num.UintZero()}, 309 } 310 } 311 312 // update next epoch's balance and send an event 313 nextEpochState := e.nextPartyDelegationState[party] 314 nextEpochState.totalDelegated.AddSum(amt) 315 if _, ok := nextEpochState.nodeToAmount[nodeID]; !ok { 316 nextEpochState.nodeToAmount[nodeID] = num.UintZero() 317 } 318 nextEpochState.nodeToAmount[nodeID].AddSum(amt) 319 e.sendDelegatedBalanceEvent(ctx, party, nodeID, e.currentEpoch.Seq+1, e.nextPartyDelegationState[party].nodeToAmount[nodeID]) 320 return nil 321 } 322 323 // UndelegateAtEndOfEpoch increases the pending undelegation balance and potentially decreases the pending delegation balance for a given validator node and party. 324 func (e *Engine) UndelegateAtEndOfEpoch(ctx context.Context, party string, nodeID string, amount *num.Uint) error { 325 // check if the node is a validator node 326 if e.topology == nil || !e.topology.IsValidatorNodeID(nodeID) { 327 e.log.Error("Trying to delegate to an invalid node", logging.Uint64("epoch", e.currentEpoch.Seq), logging.String("party", party), logging.String("validator", nodeID)) 328 return ErrInvalidNodeID 329 } 330 331 // get the balance for next epoch 332 nextEpochBalanceOnNode := num.UintZero() 333 if nextEpoch, ok := e.nextPartyDelegationState[party]; ok { 334 if nodeAmount, ok := nextEpoch.nodeToAmount[nodeID]; ok { 335 nextEpochBalanceOnNode = nodeAmount 336 } 337 } 338 339 // if the request is for undelegating the whole balance set the amount to the total balance 340 amt := amount.Clone() 341 if amt.IsZero() { 342 amt = nextEpochBalanceOnNode.Clone() 343 } 344 345 // if the amount is greater than the available balance to undelegate return error 346 if amt.GT(nextEpochBalanceOnNode) { 347 e.log.Error("Invalid undelegation - trying to undelegate more than delegated", logging.Uint64("epoch", e.currentEpoch.Seq), logging.String("party", party), logging.String("validator", nodeID), logging.String("undelegationAmount", num.UintToString(amt)), logging.String("totalDelegationBalance", num.UintToString(nextEpochBalanceOnNode))) 348 return ErrIncorrectTokenAmountForUndelegation 349 } 350 351 // update next epoch's balance and send an event 352 e.decreaseBalanceAndFireEvent(ctx, party, nodeID, amt, e.currentEpoch.Seq+1, e.nextPartyDelegationState, true, true) 353 354 // get out of auto delegation mode as the party made explicit undelegations 355 delete(e.autoDelegationMode, party) 356 return nil 357 } 358 359 // UndelegateNow changes the balance of delegation immediately without waiting for the end of the epoch 360 // if possible it removed balance from pending delegated, if not enough it removes balance from the current epoch delegated amount. 361 func (e *Engine) UndelegateNow(ctx context.Context, party string, nodeID string, amount *num.Uint) error { 362 // check if the node is a validator node 363 if e.topology == nil || !e.topology.IsValidatorNodeID(nodeID) { 364 e.log.Error("Trying to delegate to an invalid node", logging.Uint64("epoch", e.currentEpoch.Seq), logging.String("party", party), logging.String("validator", nodeID)) 365 return ErrInvalidNodeID 366 } 367 368 // the purpose of this is that if a party has x delegated in the current epoch and x + a delegated for the next epoch, undelegateNow will start with undelegating 369 // the current epoch but if there's any left it will undelegate from the next epoch. This is unlikely to happen but still 370 currentEpochBalanceOnNode := num.UintZero() 371 if epoch, ok := e.partyDelegationState[party]; ok { 372 if nodeAmount, ok := epoch.nodeToAmount[nodeID]; ok { 373 currentEpochBalanceOnNode = nodeAmount 374 } 375 } 376 nextEpochBalanceOnNode := num.UintZero() 377 if epoch, ok := e.nextPartyDelegationState[party]; ok { 378 if nodeAmount, ok := epoch.nodeToAmount[nodeID]; ok { 379 nextEpochBalanceOnNode = nodeAmount 380 } 381 } 382 383 epochBalanceOnNode := num.Max(currentEpochBalanceOnNode, nextEpochBalanceOnNode) 384 385 // if the request is for undelegating the whole balance set the amount to the total balance 386 amt := amount.Clone() 387 if amt.IsZero() { 388 amt = epochBalanceOnNode.Clone() 389 } 390 391 // if the amount is greater than the available balance to undelegate return error 392 if amt.GT(epochBalanceOnNode) { 393 e.log.Error("Invalid undelegation - trying to undelegate more than delegated", logging.Uint64("epoch", e.currentEpoch.Seq), logging.String("party", party), logging.String("validator", nodeID), logging.String("undelegationAmount", num.UintToString(amt)), logging.String("totalDelegationBalance", num.UintToString(epochBalanceOnNode))) 394 return ErrIncorrectTokenAmountForUndelegation 395 } 396 397 undelegateFromCurrentEpoch := num.Min(currentEpochBalanceOnNode, amt) 398 if !undelegateFromCurrentEpoch.IsZero() { 399 e.decreaseBalanceAndFireEvent(ctx, party, nodeID, undelegateFromCurrentEpoch, e.currentEpoch.Seq, e.partyDelegationState, true, true) 400 } 401 402 undelegateFromNextEpoch := num.Min(nextEpochBalanceOnNode, amt) 403 if !undelegateFromNextEpoch.IsZero() { 404 e.decreaseBalanceAndFireEvent(ctx, party, nodeID, undelegateFromNextEpoch, e.currentEpoch.Seq+1, e.nextPartyDelegationState, true, true) 405 } 406 407 // get out of auto delegation mode 408 delete(e.autoDelegationMode, party) 409 return nil 410 } 411 412 // ProcessEpochDelegations updates the delegation engine state at the end of a given epoch and returns the validation-delegation data for rewarding for that epoch 413 // step 1: process delegation data for the epoch - undelegate if the balance of the staking account doesn't cover all delegations 414 // step 2: capture validator delegation data to be returned 415 // step 3: apply pending undelegations 416 // step 4: apply pending delegations 417 // step 5: apply auto delegations 418 // epoch here is the epoch that ended. 419 func (e *Engine) ProcessEpochDelegations(ctx context.Context, epoch types.Epoch) []*types.ValidatorData { 420 if e.log.IsDebug() { 421 e.log.Debug("ProcessEpochDelegations:", logging.Time("start", epoch.StartTime), logging.Time("end", epoch.EndTime)) 422 } 423 424 // check balance for the epoch duration and undelegate if delegations don't have sufficient cover 425 // the state of the engine by the end of this method reflects the state to be used for reward engine. 426 e.reconcileAssociationWithNomination(ctx, epoch.StartTime, epoch.EndTime, epoch.Seq) 427 stateForRewards := e.getValidatorData() 428 429 // promote pending delegations 430 431 excludeFromAutoDelegation := map[string]struct{}{} 432 for p, state := range e.nextPartyDelegationState { 433 for n, nAmt := range state.nodeToAmount { 434 if currState, ok := e.partyDelegationState[p]; ok { 435 if currAmt, ok := currState.nodeToAmount[n]; ok { 436 if currAmt.NEQ(nAmt) { 437 excludeFromAutoDelegation[p] = struct{}{} 438 } 439 } else { 440 excludeFromAutoDelegation[p] = struct{}{} 441 } 442 } else { 443 excludeFromAutoDelegation[p] = struct{}{} 444 } 445 } 446 } 447 448 next := e.prepareNextEpochDelegationState() 449 e.partyDelegationState = e.nextPartyDelegationState 450 e.nextPartyDelegationState = next 451 452 // process auto delegations 453 // this is updating the state for the epoch that's about to begin therefore it needs to have incremented sequence 454 e.processAutoDelegation(ctx, e.eligiblePartiesForAutoDelegtion(excludeFromAutoDelegation), epoch.Seq+1) 455 456 for p, state := range e.partyDelegationState { 457 if _, ok := e.autoDelegationMode[p]; !ok { 458 if balance, err := e.stakingAccounts.GetAvailableBalance(p); err == nil { 459 if state.totalDelegated.ToDecimal().Div(balance.ToDecimal()).GreaterThanOrEqual(minRatioForAutoDelegation) { 460 e.autoDelegationMode[p] = struct{}{} 461 } 462 } 463 } 464 } 465 return stateForRewards 466 } 467 468 // sendDelegatedBalanceEvent emits an event with the delegation balance for the given epoch. 469 func (e *Engine) sendDelegatedBalanceEvent(ctx context.Context, party, nodeID string, seq uint64, amt *num.Uint) { 470 if amt == nil { 471 e.broker.Send(events.NewDelegationBalance(ctx, party, nodeID, num.UintZero(), num.NewUint(seq).String())) 472 } else { 473 e.broker.Send(events.NewDelegationBalance(ctx, party, nodeID, amt.Clone(), num.NewUint(seq).String())) 474 } 475 } 476 477 // decrease the delegation balance fire an event and cleanup if requested. 478 func (e *Engine) decreaseBalanceAndFireEvent(ctx context.Context, party, nodeID string, amt *num.Uint, epoch uint64, delegationState map[string]*partyDelegation, cleanup, fireEvent bool) { 479 if _, ok := delegationState[party]; !ok { 480 return 481 } 482 partyState := delegationState[party] 483 if partyState.totalDelegated.GT(amt) { 484 partyState.totalDelegated.Sub(partyState.totalDelegated, amt) 485 } else { 486 partyState.totalDelegated = num.UintZero() 487 } 488 489 if nodeAmt, ok := partyState.nodeToAmount[nodeID]; ok { 490 if nodeAmt.GT(amt) { 491 partyState.nodeToAmount[nodeID].Sub(nodeAmt, amt) 492 } else { 493 partyState.nodeToAmount[nodeID] = num.UintZero() 494 } 495 if fireEvent { 496 e.sendDelegatedBalanceEvent(ctx, party, nodeID, epoch, partyState.nodeToAmount[nodeID]) 497 } 498 if cleanup && partyState.nodeToAmount[nodeID].IsZero() { 499 delete(partyState.nodeToAmount, nodeID) 500 } 501 } 502 503 if cleanup && partyState.totalDelegated.IsZero() { 504 delete(delegationState, party) 505 } 506 } 507 508 // sort node IDs for deterministic processing. 509 func (e *Engine) sortNodes(nodes map[string]*num.Uint) []string { 510 nodeIDs := make([]string, 0, len(nodes)) 511 for nodeID := range nodes { 512 nodeIDs = append(nodeIDs, nodeID) 513 } 514 515 // sort the parties for deterministic handling 516 sort.Strings(nodeIDs) 517 return nodeIDs 518 } 519 520 func (e *Engine) sortParties(delegation map[string]*partyDelegation) []string { 521 parties := make([]string, 0, len(delegation)) 522 for party := range delegation { 523 parties = append(parties, party) 524 } 525 526 // sort the parties for deterministic handling 527 sort.Strings(parties) 528 return parties 529 } 530 531 func (e *Engine) stakeInRangeFunc(from, to time.Time) func(string) (*num.Uint, error) { 532 return func(party string) (*num.Uint, error) { 533 return e.stakingAccounts.GetAvailableBalanceInRange(party, from, to) 534 } 535 } 536 537 // take a copy of the next epoch delegation ignoring delegations that have been zero for the currend and next epoch. 538 func (e *Engine) prepareNextEpochDelegationState() map[string]*partyDelegation { 539 nextEpoch := make(map[string]*partyDelegation, len(e.nextPartyDelegationState)) 540 for party, partyDS := range e.nextPartyDelegationState { 541 nextEpoch[party] = &partyDelegation{ 542 totalDelegated: partyDS.totalDelegated.Clone(), 543 nodeToAmount: make(map[string]*num.Uint, len(partyDS.nodeToAmount)), 544 } 545 for n, amt := range partyDS.nodeToAmount { 546 if amt.IsZero() { 547 // check the balance in the previous epoch - if it was there and was non zero keep it, otherwise it means it hasn't changed so we can drop 548 if pds, ok := e.partyDelegationState[party]; ok { 549 if prevAmt, ok := pds.nodeToAmount[n]; ok && !prevAmt.IsZero() { 550 nextEpoch[party].nodeToAmount[n] = amt.Clone() 551 } 552 } 553 } else { 554 nextEpoch[party].nodeToAmount[n] = amt.Clone() 555 } 556 } 557 } 558 return nextEpoch 559 } 560 561 // eligiblePartiesForAutoDelegtion calculates how much is available for auto delegation in parties that have qualifies for auto delegation 562 // and have not done any manual actions during the past epoch and have any active delegations and have available balance. 563 func (e *Engine) eligiblePartiesForAutoDelegtion(exclude map[string]struct{}) map[string]*num.Uint { 564 partyToAvailableBalance := map[string]*num.Uint{} 565 for party := range e.autoDelegationMode { 566 // if the party has no delegation we can't auto delegate 567 if _, ok := e.partyDelegationState[party]; !ok { 568 continue 569 } 570 571 if _, ok := exclude[party]; ok { 572 continue 573 } 574 575 // check if they have balance 576 balance, err := e.stakingAccounts.GetAvailableBalance(party) 577 if err != nil { 578 continue 579 } 580 581 // check how much they already have delegated off the staking account balance 582 delegated := e.partyDelegationState[party].totalDelegated 583 if delegated.GTE(balance) { 584 continue 585 } 586 587 // calculate the available balance 588 available := num.UintZero().Sub(balance, delegated) 589 if !available.IsZero() { 590 partyToAvailableBalance[party] = available 591 } 592 } 593 return partyToAvailableBalance 594 } 595 596 // processAutoDelegation takes a slice of parties which are known to be eligible for auto delegation and attempts to distribute their available 597 // undelegated stake proportionally across the nodes to which it already delegated to. 598 // It respects the max delegation per validator, and if the node does not accept any more stake it will not try to delegate it to other nodes. 599 func (e *Engine) processAutoDelegation(ctx context.Context, partyToAvailableBalance map[string]*num.Uint, seq uint64) { 600 parties := make([]string, 0, len(partyToAvailableBalance)) 601 for p := range partyToAvailableBalance { 602 parties = append(parties, p) 603 } 604 sort.Strings(parties) 605 606 for _, p := range parties { 607 totalDelegation := e.partyDelegationState[p].totalDelegated.ToDecimal() 608 balanceDec := partyToAvailableBalance[p].ToDecimal() 609 nodes := e.sortNodes(e.partyDelegationState[p].nodeToAmount) 610 611 for _, n := range nodes { 612 nodeBalance := e.partyDelegationState[p].nodeToAmount[n] 613 ratio := nodeBalance.ToDecimal().Div(totalDelegation) 614 delegationToNodeN, _ := num.UintFromDecimal(ratio.Mul(balanceDec)) 615 616 if !delegationToNodeN.IsZero() { 617 e.partyDelegationState[p].totalDelegated.AddSum(delegationToNodeN) 618 e.partyDelegationState[p].nodeToAmount[n].AddSum(delegationToNodeN) 619 e.sendDelegatedBalanceEvent(ctx, p, n, seq, e.partyDelegationState[p].nodeToAmount[n]) 620 e.nextPartyDelegationState[p].totalDelegated.AddSum(delegationToNodeN) 621 e.nextPartyDelegationState[p].nodeToAmount[n].AddSum(delegationToNodeN) 622 } 623 } 624 } 625 } 626 627 // GetValidatorData returns the current state of the delegation per node. 628 func (e *Engine) GetValidatorData() []*types.ValidatorData { 629 return e.getValidatorData() 630 } 631 632 // returns the current state of the delegation per node. 633 func (e *Engine) getValidatorData() []*types.ValidatorData { 634 validatorNodes := e.topology.AllNodeIDs() 635 validatorData := make(map[string]*types.ValidatorData, len(validatorNodes)) 636 637 for _, vn := range validatorNodes { 638 validatorData[vn] = &types.ValidatorData{ 639 NodeID: vn, 640 PubKey: e.topology.Get(vn).VegaPubKey, 641 Delegators: map[string]*num.Uint{}, 642 SelfStake: num.UintZero(), 643 StakeByDelegators: num.UintZero(), 644 TmPubKey: e.topology.Get(vn).TmPubKey, 645 } 646 } 647 648 for party, partyDS := range e.partyDelegationState { 649 for node, amt := range partyDS.nodeToAmount { 650 vn := validatorData[node] 651 if party == vn.PubKey { 652 vn.SelfStake = amt.Clone() 653 } else { 654 vn.Delegators[party] = amt.Clone() 655 vn.StakeByDelegators.AddSum(amt) 656 } 657 } 658 } 659 660 validators := make([]*types.ValidatorData, 0, len(validatorNodes)) 661 sort.Strings(validatorNodes) 662 for _, v := range validatorNodes { 663 validators = append(validators, validatorData[v]) 664 } 665 666 return validators 667 }