code.vegaprotocol.io/vega@v0.79.0/datanode/sqlsubscribers/funding_payments.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 sqlsubscribers 17 18 import ( 19 "context" 20 "fmt" 21 "sync" 22 "time" 23 24 "code.vegaprotocol.io/vega/core/events" 25 "code.vegaprotocol.io/vega/datanode/entities" 26 "code.vegaprotocol.io/vega/libs/num" 27 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 28 29 "github.com/pkg/errors" 30 ) 31 32 type FundingPaymentsEvent interface { 33 events.Event 34 FundingPayments() *eventspb.FundingPayments 35 } 36 37 type LossSocEvt interface { 38 events.Event 39 IsFunding() bool 40 Negative() bool 41 PartyID() string 42 MarketID() string 43 Amount() *num.Int 44 } 45 46 type FundingPaymentsStore interface { 47 Add(ctx context.Context, data []*entities.FundingPayment) error 48 GetByPartyAndMarket(ctx context.Context, partyID, marketID string) (entities.FundingPayment, error) 49 } 50 51 type FundingPaymentSubscriber struct { 52 subscriber 53 store FundingPaymentsStore 54 mu *sync.Mutex 55 cache map[string]entities.FundingPayment 56 } 57 58 func NewFundingPaymentsSubscriber(store FundingPaymentsStore) *FundingPaymentSubscriber { 59 return &FundingPaymentSubscriber{ 60 store: store, 61 mu: &sync.Mutex{}, 62 cache: map[string]entities.FundingPayment{}, 63 } 64 } 65 66 func (ts *FundingPaymentSubscriber) Types() []events.Type { 67 return []events.Type{ 68 events.FundingPaymentsEvent, 69 events.LossSocializationEvent, 70 } 71 } 72 73 func (ts *FundingPaymentSubscriber) Flush(ctx context.Context) error { 74 ts.mu.Lock() 75 defer ts.mu.Unlock() 76 ts.cache = make(map[string]entities.FundingPayment, len(ts.cache)) 77 return nil 78 } 79 80 func (ts *FundingPaymentSubscriber) Push(ctx context.Context, evt events.Event) error { 81 switch et := evt.(type) { 82 case FundingPaymentsEvent: 83 return ts.consume(ctx, et) 84 case LossSocEvt: 85 return ts.handleLossSoc(ctx, et) 86 default: 87 return fmt.Errorf("received unknown event %T (%#v) in FundingPaymentSubscriber", evt, evt) 88 } 89 } 90 91 func (ts *FundingPaymentSubscriber) handleLossSoc(ctx context.Context, e LossSocEvt) error { 92 if !e.IsFunding() { 93 return nil 94 } 95 var err error 96 partyID, marketID := e.PartyID(), e.MarketID() 97 k := fmt.Sprintf("%s%s", partyID, marketID) 98 ts.mu.Lock() 99 defer ts.mu.Unlock() 100 fp, ok := ts.cache[k] 101 // loss socialisation for a party that wasn't included in the funding payment event somehow, 102 // or the funding payment even in question has not been received yet. 103 if !ok { 104 fp, err = ts.store.GetByPartyAndMarket(ctx, partyID, marketID) 105 if err != nil { 106 return err 107 } 108 // update the tx hash and time 109 fp.TxHash = entities.TxHash(e.TxHash()) 110 fp.VegaTime = ts.vegaTime 111 } 112 amtD := num.DecimalFromUint(e.Amount().U) 113 if e.Negative() { 114 fp.LossSocialisationAmount = fp.LossSocialisationAmount.Sub(amtD) 115 } else { 116 diff := fp.LossSocialisationAmount.Add(amtD) 117 if diff.IsNegative() || diff.IsZero() { 118 // reduce or zero out the accrued loss? 119 fp.LossSocialisationAmount = diff 120 } else { 121 // loss "overflow", zero out the loss, add the remainder to the amount paid out. 122 fp.LossSocialisationAmount = num.DecimalZero() 123 fp.Amount = fp.Amount.Add(diff) 124 } 125 } 126 // let's insert this as a new row 127 ts.cache[k] = fp 128 // first figure out which keys we are missing 129 return errors.Wrap(ts.store.Add(ctx, []*entities.FundingPayment{&fp}), "adding funding payment to store with loss socialisation") 130 } 131 132 func (ts *FundingPaymentSubscriber) consume(ctx context.Context, te FundingPaymentsEvent) error { 133 fps := te.FundingPayments() 134 135 return errors.Wrap(ts.addFundingPayments(ctx, fps, entities.TxHash(te.TxHash()), ts.vegaTime, te.Sequence()), "failed to consume funding payment") 136 } 137 138 func (ts *FundingPaymentSubscriber) addFundingPayments( 139 ctx context.Context, 140 fps *eventspb.FundingPayments, 141 txHash entities.TxHash, 142 vegaTime time.Time, 143 _ uint64, 144 ) error { 145 ts.mu.Lock() 146 defer ts.mu.Unlock() 147 payments, err := entities.NewFundingPaymentsFromProto(fps, txHash, vegaTime) 148 if err != nil { 149 return errors.Wrap(err, "converting event to funding payments") 150 } 151 for _, fp := range payments { 152 k := fmt.Sprintf("%s%s", fp.PartyID, fp.MarketID) 153 ts.cache[k] = *fp 154 } 155 156 return errors.Wrap(ts.store.Add(ctx, payments), "adding funding payment to store") 157 } 158 159 func (ts *FundingPaymentSubscriber) Name() string { 160 return "FundingPaymentSubscriber" 161 }