code.vegaprotocol.io/vega@v0.79.0/core/delegation/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 delegation
    17  
    18  import (
    19  	"context"
    20  	"sort"
    21  	"strings"
    22  
    23  	"code.vegaprotocol.io/vega/core/events"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	"code.vegaprotocol.io/vega/libs/proto"
    27  	checkpoint "code.vegaprotocol.io/vega/protos/vega/checkpoint/v1"
    28  )
    29  
    30  func (e *Engine) Name() types.CheckpointName {
    31  	return types.DelegationCheckpoint
    32  }
    33  
    34  func (e *Engine) Checkpoint() ([]byte, error) {
    35  	data := &types.DelegateCP{
    36  		Active:  e.getActive(),
    37  		Pending: e.getPendingBackwardCompatible(),
    38  		Auto:    e.getAuto(),
    39  	}
    40  	return proto.Marshal(data.IntoProto())
    41  }
    42  
    43  func (e *Engine) Load(ctx context.Context, data []byte) error {
    44  	cp := &checkpoint.Delegate{}
    45  	if err := proto.Unmarshal(data, cp); err != nil {
    46  		return err
    47  	}
    48  	cpData := types.NewDelegationCPFromProto(cp)
    49  	// reset state
    50  	e.partyDelegationState = map[string]*partyDelegation{}
    51  	e.nextPartyDelegationState = map[string]*partyDelegation{}
    52  	e.setActive(ctx, cpData.Active)
    53  	e.setPendingBackwardCompatible(ctx, cpData.Pending)
    54  
    55  	e.autoDelegationMode = map[string]struct{}{}
    56  	e.setAuto(cpData.Auto)
    57  
    58  	return nil
    59  }
    60  
    61  func (e *Engine) delegationStateFromDelegationEntry(ctx context.Context, delegationState map[string]*partyDelegation, entries []*types.DelegationEntry, epochSeq string) {
    62  	// each entry results in a delegation event
    63  	evts := make([]events.Event, 0, len(entries))
    64  	for _, de := range entries {
    65  		// add to party state
    66  		ps, ok := delegationState[de.Party]
    67  		if !ok {
    68  			ps = &partyDelegation{
    69  				party:          de.Party,
    70  				nodeToAmount:   map[string]*num.Uint{},
    71  				totalDelegated: num.UintZero(),
    72  			}
    73  			delegationState[de.Party] = ps
    74  		}
    75  		ps.totalDelegated.AddSum(de.Amount)
    76  		ps.nodeToAmount[de.Node] = de.Amount.Clone()
    77  
    78  		evts = append(evts, events.NewDelegationBalance(ctx, de.Party, de.Node, de.Amount, epochSeq))
    79  	}
    80  	if len(evts) > 0 {
    81  		e.broker.SendBatch(evts)
    82  	}
    83  }
    84  
    85  func (e *Engine) setActive(ctx context.Context, entries []*types.DelegationEntry) {
    86  	e.delegationStateFromDelegationEntry(ctx, e.partyDelegationState, entries, num.NewUint(e.currentEpoch.Seq).String())
    87  }
    88  
    89  func (e *Engine) delegationStateToDelegationEntry(delegationState map[string]*partyDelegation, epochSeq uint64) []*types.DelegationEntry {
    90  	slice := []*types.DelegationEntry{}
    91  	// iterate over parties
    92  	for p, ds := range delegationState {
    93  		for n, amt := range ds.nodeToAmount {
    94  			slice = append(slice, &types.DelegationEntry{
    95  				Party:    p,
    96  				Node:     n,
    97  				Amount:   amt.Clone(),
    98  				EpochSeq: epochSeq,
    99  			})
   100  		}
   101  	}
   102  
   103  	// sort the slice
   104  	e.sortActive(slice)
   105  
   106  	return slice
   107  }
   108  
   109  func (e *Engine) getActive() []*types.DelegationEntry {
   110  	return e.delegationStateToDelegationEntry(e.partyDelegationState, e.currentEpoch.Seq)
   111  }
   112  
   113  func (e *Engine) sortActive(active []*types.DelegationEntry) {
   114  	sort.SliceStable(active, func(i, j int) bool {
   115  		switch strings.Compare(active[i].Party, active[j].Party) {
   116  		case -1:
   117  			return true
   118  		case 1:
   119  			return false
   120  		}
   121  
   122  		return active[i].Node < active[j].Node
   123  	})
   124  }
   125  
   126  func (e *Engine) getAuto() []string {
   127  	auto := make([]string, 0, len(e.autoDelegationMode))
   128  	for p := range e.autoDelegationMode {
   129  		auto = append(auto, p)
   130  	}
   131  	sort.Strings(auto)
   132  	return auto
   133  }
   134  
   135  func (e *Engine) getPendingNew() []*types.DelegationEntry {
   136  	return e.delegationStateToDelegationEntry(e.nextPartyDelegationState, e.currentEpoch.Seq+1)
   137  }
   138  
   139  // getPendingBackwardCompatible is calculating deltas based on next epoch balances to be saved in the checkpoint.
   140  // this is because for backward compatibility we continue to save deltas rather than balances in the checkpoint for pending (i.e. next epoch's delegations).
   141  func (e *Engine) getPendingBackwardCompatible() []*types.DelegationEntry {
   142  	des := []*types.DelegationEntry{}
   143  	for party, state := range e.nextPartyDelegationState {
   144  		currState, ok := e.partyDelegationState[party]
   145  		if !ok {
   146  			for node, amt := range state.nodeToAmount {
   147  				des = append(des, &types.DelegationEntry{Party: party, Node: node, Amount: amt.Clone(), Undelegate: false, EpochSeq: e.currentEpoch.Seq + 1})
   148  			}
   149  			continue
   150  		}
   151  		for node, amt := range state.nodeToAmount {
   152  			currNodeAmt, ok := currState.nodeToAmount[node]
   153  			if !ok {
   154  				des = append(des, &types.DelegationEntry{Party: party, Node: node, Amount: amt.Clone(), Undelegate: false, EpochSeq: e.currentEpoch.Seq + 1})
   155  			} else {
   156  				if amt.GT(currNodeAmt) {
   157  					des = append(des, &types.DelegationEntry{Party: party, Node: node, Amount: num.UintZero().Sub(amt, currNodeAmt), Undelegate: false, EpochSeq: e.currentEpoch.Seq + 1})
   158  				} else if amt.LT(currNodeAmt) {
   159  					des = append(des, &types.DelegationEntry{Party: party, Node: node, Amount: num.UintZero().Sub(currNodeAmt, amt), Undelegate: true, EpochSeq: e.currentEpoch.Seq + 1})
   160  				}
   161  			}
   162  		}
   163  		// handle nominations that were removed completely
   164  		for currNode, currAmt := range currState.nodeToAmount {
   165  			if _, ok := state.nodeToAmount[currNode]; !ok {
   166  				des = append(des, &types.DelegationEntry{Party: party, Node: currNode, Amount: currAmt.Clone(), Undelegate: true, EpochSeq: e.currentEpoch.Seq + 1})
   167  			}
   168  		}
   169  	}
   170  
   171  	// handle nominations from parties that were completed cleared
   172  	for party, state := range e.partyDelegationState {
   173  		if _, ok := e.nextPartyDelegationState[party]; !ok {
   174  			for node, amt := range state.nodeToAmount {
   175  				des = append(des, &types.DelegationEntry{Party: party, Node: node, Amount: amt.Clone(), Undelegate: true, EpochSeq: e.currentEpoch.Seq + 1})
   176  			}
   177  		}
   178  	}
   179  
   180  	e.sortPending(des)
   181  	return des
   182  }
   183  
   184  func (e *Engine) sortPending(pending []*types.DelegationEntry) {
   185  	sort.SliceStable(pending, func(i, j int) bool {
   186  		pi, pj := pending[i], pending[j]
   187  
   188  		switch strings.Compare(pi.Party, pj.Party) {
   189  		case -1:
   190  			return true
   191  		case 1:
   192  			return false
   193  		}
   194  
   195  		switch strings.Compare(pi.Node, pj.Node) {
   196  		case -1:
   197  			return true
   198  		case 1:
   199  			return false
   200  		}
   201  
   202  		return pi.EpochSeq < pj.EpochSeq
   203  	})
   204  }
   205  
   206  func (e *Engine) setAuto(parties []string) {
   207  	for _, p := range parties {
   208  		e.autoDelegationMode[p] = struct{}{}
   209  	}
   210  }
   211  
   212  func (e *Engine) setPendingNew(ctx context.Context, entries []*types.DelegationEntry) {
   213  	e.delegationStateFromDelegationEntry(ctx, e.nextPartyDelegationState, entries, num.NewUint(e.currentEpoch.Seq+1).String())
   214  }
   215  
   216  // setPendingBackwardCompatible is taking deltas from the checkpoint and calculate from them the associated balance for the next epoch
   217  // populating nextPartyDelegationState.
   218  // NB: the event emitted are based on the *current* epoch in play rather than on the meaningless epoch from the DelegationEntry.
   219  func (e *Engine) setPendingBackwardCompatible(ctx context.Context, entries []*types.DelegationEntry) {
   220  	// first initialise the state with the current state
   221  	for party, pds := range e.partyDelegationState {
   222  		e.nextPartyDelegationState[party] = &partyDelegation{
   223  			party:          party,
   224  			nodeToAmount:   map[string]*num.Uint{},
   225  			totalDelegated: pds.totalDelegated.Clone(),
   226  		}
   227  		for node, amt := range pds.nodeToAmount {
   228  			e.nextPartyDelegationState[party].nodeToAmount[node] = amt.Clone()
   229  		}
   230  	}
   231  
   232  	for _, de := range entries {
   233  		// add to party state
   234  		ps, ok := e.nextPartyDelegationState[de.Party]
   235  		if !ok {
   236  			ps = &partyDelegation{
   237  				party:          de.Party,
   238  				nodeToAmount:   map[string]*num.Uint{},
   239  				totalDelegated: num.UintZero(),
   240  			}
   241  			e.nextPartyDelegationState[de.Party] = ps
   242  		}
   243  
   244  		if !de.Undelegate {
   245  			ps.totalDelegated.AddSum(de.Amount)
   246  			if _, ok := ps.nodeToAmount[de.Node]; !ok {
   247  				ps.nodeToAmount[de.Node] = de.Amount.Clone()
   248  			} else {
   249  				ps.nodeToAmount[de.Node].AddSum(de.Amount)
   250  			}
   251  		} else {
   252  			if _, ok := ps.nodeToAmount[de.Node]; ok {
   253  				amt := num.Min(ps.nodeToAmount[de.Node], de.Amount)
   254  				ps.nodeToAmount[de.Node].Sub(ps.nodeToAmount[de.Node], amt)
   255  				ps.totalDelegated.Sub(ps.totalDelegated, amt)
   256  			}
   257  		}
   258  	}
   259  
   260  	epoch := num.NewUint(e.currentEpoch.Seq + 1).String()
   261  	evts := []events.Event{}
   262  	parties := e.sortParties(e.nextPartyDelegationState)
   263  	for _, party := range parties {
   264  		nodes := e.sortNodes(e.nextPartyDelegationState[party].nodeToAmount)
   265  		for _, node := range nodes {
   266  			balance := e.nextPartyDelegationState[party].nodeToAmount[node]
   267  			evts = append(evts, events.NewDelegationBalance(ctx, party, node, balance, epoch))
   268  			if balance.IsZero() {
   269  				delete(e.nextPartyDelegationState[party].nodeToAmount, node)
   270  			}
   271  		}
   272  		if e.nextPartyDelegationState[party].totalDelegated.IsZero() {
   273  			delete(e.nextPartyDelegationState, party)
   274  		}
   275  	}
   276  
   277  	if len(evts) > 0 {
   278  		e.broker.SendBatch(evts)
   279  	}
   280  }