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  }