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  }