code.vegaprotocol.io/vega@v0.79.0/core/coreapi/services/parties_stake.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 services 17 18 import ( 19 "context" 20 "sort" 21 "sync" 22 23 "code.vegaprotocol.io/vega/core/events" 24 "code.vegaprotocol.io/vega/core/subscribers" 25 "code.vegaprotocol.io/vega/libs/num" 26 "code.vegaprotocol.io/vega/logging" 27 coreapipb "code.vegaprotocol.io/vega/protos/vega/api/v1" 28 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 29 ) 30 31 type StakeLinkingEvent interface { 32 events.Event 33 StakeLinking() eventspb.StakeLinking 34 } 35 36 type stakingAccount struct { 37 currentStakeAvailable *num.Uint 38 links []eventspb.StakeLinking 39 } 40 41 type PartiesStake struct { 42 *subscribers.Base 43 44 log *logging.Logger 45 ch chan eventspb.StakeLinking 46 47 mu sync.RWMutex 48 // party id -> staking account 49 stakingPerParty map[string]*stakingAccount 50 } 51 52 func NewPartiesStake(ctx context.Context, log *logging.Logger) (svc *PartiesStake) { 53 defer func() { 54 go svc.consume() 55 }() 56 57 return &PartiesStake{ 58 Base: subscribers.NewBase(ctx, 10, true), 59 log: log, 60 ch: make(chan eventspb.StakeLinking, 100), 61 stakingPerParty: map[string]*stakingAccount{}, 62 } 63 } 64 65 func (p *PartiesStake) List(party string) []*coreapipb.PartyStake { 66 p.mu.RLock() 67 defer p.mu.RUnlock() 68 if len(party) > 0 { 69 return p.getParty(party) 70 } 71 return p.getAll() 72 } 73 74 func (p *PartiesStake) getParty(party string) []*coreapipb.PartyStake { 75 partyAccount, ok := p.stakingPerParty[party] 76 if !ok { 77 return nil 78 } 79 80 return []*coreapipb.PartyStake{ 81 { 82 Party: party, 83 CurrentStakeAvailable: partyAccount.currentStakeAvailable.String(), 84 StakeLinkings: Links(partyAccount.links).IntoPointers(), 85 }, 86 } 87 } 88 89 func (p *PartiesStake) getAll() []*coreapipb.PartyStake { 90 out := make([]*coreapipb.PartyStake, 0, len(p.stakingPerParty)) 91 92 for k, v := range p.stakingPerParty { 93 out = append(out, &coreapipb.PartyStake{ 94 Party: k, 95 CurrentStakeAvailable: v.currentStakeAvailable.String(), 96 StakeLinkings: Links(v.links).IntoPointers(), 97 }) 98 } 99 100 return out 101 } 102 103 func (p *PartiesStake) Push(evts ...events.Event) { 104 for _, e := range evts { 105 select { 106 case <-p.Closed(): 107 close(p.ch) 108 return 109 default: 110 if evt, ok := e.(StakeLinkingEvent); ok { 111 p.ch <- evt.StakeLinking() 112 } 113 } 114 } 115 } 116 117 func (p *PartiesStake) Types() []events.Type { 118 return []events.Type{ 119 events.StakeLinkingEvent, 120 } 121 } 122 123 func (p *PartiesStake) consume() { 124 for { 125 select { 126 case <-p.Closed(): 127 return 128 case evt, ok := <-p.ch: 129 if !ok { 130 // cleanup base 131 p.Halt() 132 // channel is closed 133 return 134 } 135 p.mu.Lock() 136 partyAccount, ok := p.stakingPerParty[evt.Party] 137 if !ok { 138 partyAccount = &stakingAccount{ 139 currentStakeAvailable: num.UintZero(), 140 links: []eventspb.StakeLinking{}, 141 } 142 p.stakingPerParty[evt.Party] = partyAccount 143 } 144 p.addLink(partyAccount, evt) 145 p.computeCurrentBalance(partyAccount) 146 p.mu.Unlock() 147 } 148 } 149 } 150 151 func (p *PartiesStake) addLink(partyAccount *stakingAccount, evt eventspb.StakeLinking) { 152 for i, v := range partyAccount.links { 153 if v.Id == evt.Id { 154 partyAccount.links[i] = evt 155 return 156 } 157 } 158 partyAccount.links = append(partyAccount.links, evt) 159 } 160 161 func (p *PartiesStake) computeCurrentBalance(pacc *stakingAccount) { 162 // just sort so we are sure our stake linking are in order 163 sort.SliceStable(pacc.links, func(i, j int) bool { 164 return pacc.links[i].Ts < pacc.links[j].Ts 165 }) 166 balance := num.UintZero() 167 for _, link := range pacc.links { 168 if link.Status == eventspb.StakeLinking_STATUS_PENDING || link.Status == eventspb.StakeLinking_STATUS_REJECTED { 169 // ignore 170 continue 171 } 172 amount, overflowed := num.UintFromString(link.Amount, 10) 173 if overflowed { 174 p.log.Error("received non base 10 amount to link", logging.String("amount", link.Amount)) 175 // not much to do, just ignore this one. 176 continue 177 } 178 switch link.Type { 179 case eventspb.StakeLinking_TYPE_LINK: 180 balance.Add(balance, amount) 181 continue 182 case eventspb.StakeLinking_TYPE_UNLINK: 183 if amount.GT(balance) { 184 p.log.Error("could not apply unlink", 185 logging.String("amount", link.Amount), 186 logging.String("party", link.Party), 187 ) 188 // that's an error, we are missing, events, return now. 189 return 190 } 191 balance.Sub(balance, amount) 192 } 193 } 194 pacc.currentStakeAvailable.Set(balance) 195 } 196 197 type Links []eventspb.StakeLinking 198 199 func (l Links) IntoPointers() []*eventspb.StakeLinking { 200 out := make([]*eventspb.StakeLinking, 0, len(l)) 201 for _, v := range l { 202 v := v 203 out = append(out, &v) 204 } 205 return out 206 }