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 }