code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/referral_sets.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 sqlstore 17 18 import ( 19 "context" 20 "encoding/json" 21 "fmt" 22 "strings" 23 "time" 24 25 "code.vegaprotocol.io/vega/datanode/entities" 26 "code.vegaprotocol.io/vega/datanode/metrics" 27 v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2" 28 "code.vegaprotocol.io/vega/protos/vega" 29 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 30 31 "github.com/georgysavva/scany/pgxscan" 32 ) 33 34 type ReferralSets struct { 35 *ConnectionSource 36 } 37 38 var ( 39 referralSetOrdering = TableOrdering{ 40 ColumnOrdering{Name: "created_at", Sorting: ASC}, 41 } 42 43 referralSetRefereeOrdering = TableOrdering{ 44 ColumnOrdering{Name: "joined_at", Sorting: ASC}, 45 ColumnOrdering{Name: "referee", Sorting: ASC}, 46 } 47 48 referralSetStatsOrdering = TableOrdering{ 49 ColumnOrdering{Name: "at_epoch", Sorting: DESC}, 50 ColumnOrdering{Name: "set_id", Sorting: ASC}, 51 ColumnOrdering{Name: "party_id", Sorting: ASC, Ref: "referee_stats->>'party_id'"}, 52 } 53 54 paidLiquidityFeesStatsCursorOrdering = TableOrdering{ 55 ColumnOrdering{Name: "market_id", Sorting: ASC}, 56 ColumnOrdering{Name: "asset_id", Sorting: ASC}, 57 ColumnOrdering{Name: "epoch_seq", Sorting: DESC}, 58 } 59 ) 60 61 func NewReferralSets(connectionSource *ConnectionSource) *ReferralSets { 62 return &ReferralSets{ 63 ConnectionSource: connectionSource, 64 } 65 } 66 67 func (rs *ReferralSets) AddReferralSet(ctx context.Context, referralSet *entities.ReferralSet) error { 68 defer metrics.StartSQLQuery("ReferralSets", "AddReferralSet")() 69 _, err := rs.Exec( 70 ctx, 71 "INSERT INTO referral_sets(id, referrer, created_at, updated_at, vega_time) VALUES ($1, $2, $3, $4, $5)", 72 referralSet.ID, 73 referralSet.Referrer, 74 referralSet.CreatedAt, 75 referralSet.UpdatedAt, 76 referralSet.VegaTime, 77 ) 78 79 return err 80 } 81 82 func (rs *ReferralSets) RefereeJoinedReferralSet(ctx context.Context, referee *entities.ReferralSetReferee) error { 83 defer metrics.StartSQLQuery("ReferralSets", "AddReferralSetReferee")() 84 _, err := rs.Exec( 85 ctx, 86 "INSERT INTO referral_set_referees(referral_set_id, referee, joined_at, at_epoch, vega_time) VALUES ($1, $2, $3, $4, $5)", 87 referee.ReferralSetID, 88 referee.Referee, 89 referee.JoinedAt, 90 referee.AtEpoch, 91 referee.VegaTime, 92 ) 93 94 return err 95 } 96 97 func (rs *ReferralSets) ListReferralSets(ctx context.Context, referralSetID *entities.ReferralSetID, referrer, referee *entities.PartyID, pagination entities.CursorPagination) ( 98 []entities.ReferralSet, entities.PageInfo, error, 99 ) { 100 defer metrics.StartSQLQuery("ReferralSets", "ListReferralSets")() 101 var ( 102 sets []entities.ReferralSet 103 args []interface{} 104 err error 105 pageInfo entities.PageInfo 106 ) 107 108 query := `WITH 109 referees_stats AS ( 110 SELECT referral_set_id, COUNT(DISTINCT referee) AS total_referees 111 FROM current_referral_set_referees 112 GROUP BY 113 referral_set_id 114 ) 115 SELECT referral_sets.*, COALESCE(referees_stats.total_referees, 0) + 1 AS total_members -- plus the referrer 116 FROM referral_sets 117 LEFT JOIN referees_stats ON referral_sets.id = referees_stats.referral_set_id` 118 119 // we only allow one of the following to be used as the filter 120 if referralSetID != nil { 121 query = fmt.Sprintf("%s WHERE referral_sets.id = %s", query, nextBindVar(&args, referralSetID)) 122 } else if referrer != nil { 123 query = fmt.Sprintf("%s WHERE referral_sets.referrer = %s", query, nextBindVar(&args, referrer)) 124 } else if referee != nil { 125 query = fmt.Sprintf("%s INNER JOIN current_referral_set_referees ON current_referral_set_referees.referee = %s AND referral_sets.id = current_referral_set_referees.referral_set_id", query, nextBindVar(&args, referee)) 126 } 127 128 query, args, err = PaginateQuery[entities.ReferralSetCursor](query, args, referralSetOrdering, pagination) 129 if err != nil { 130 return nil, pageInfo, err 131 } 132 133 if err := pgxscan.Select(ctx, rs.ConnectionSource, &sets, query, args...); err != nil { 134 return nil, pageInfo, err 135 } 136 137 sets, pageInfo = entities.PageEntities[*v2.ReferralSetEdge](sets, pagination) 138 return sets, pageInfo, nil 139 } 140 141 func (rs *ReferralSets) AddReferralSetStats(ctx context.Context, stats *entities.ReferralSetStats) error { 142 defer metrics.StartSQLQuery("ReferralSets", "AddReferralSetStats")() 143 144 // Just to ensure "nil" doesn't get inserted, in place of an empty array. 145 refereesStats := stats.RefereesStats 146 if refereesStats == nil { 147 refereesStats = []*eventspb.RefereeStats{} 148 } 149 150 _, err := rs.Exec( 151 ctx, 152 `INSERT INTO referral_set_stats( 153 set_id, 154 at_epoch, 155 was_eligible, 156 referral_set_running_notional_taker_volume, 157 referrer_taker_volume, 158 referees_stats, 159 vega_time, 160 reward_factors, 161 rewards_multiplier, 162 rewards_factors_multiplier) 163 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, 164 stats.SetID, 165 stats.AtEpoch, 166 stats.WasEligible, 167 stats.ReferralSetRunningNotionalTakerVolume, 168 stats.ReferrerTakerVolume, 169 refereesStats, 170 stats.VegaTime, 171 stats.RewardFactors, 172 stats.RewardsMultiplier, 173 stats.RewardsFactorsMultiplier, 174 ) 175 176 return err 177 } 178 179 func (rs *ReferralSets) GetReferralSetStats(ctx context.Context, setID *entities.ReferralSetID, atEpoch *uint64, referee *entities.PartyID, pagination entities.CursorPagination) ([]entities.FlattenReferralSetStats, entities.PageInfo, error) { 180 defer metrics.StartSQLQuery("ReferralSets", "GetReferralSetStats")() 181 var ( 182 query string 183 args []interface{} 184 pageInfo entities.PageInfo 185 ) 186 187 stats := []struct { 188 SetID entities.ReferralSetID 189 AtEpoch uint64 190 WasEligible bool 191 ReferralSetRunningNotionalTakerVolume string 192 ReferrerTakerVolume string 193 VegaTime time.Time 194 PartyID string 195 DiscountFactors string 196 EpochNotionalTakerVolume string 197 RewardFactors *vega.RewardFactors 198 RewardsMultiplier string 199 RewardsFactorsMultiplier *vega.RewardFactors 200 }{} 201 202 query = `SELECT set_id, 203 at_epoch, 204 was_eligible, 205 vega_time, 206 referral_set_running_notional_taker_volume, 207 referrer_taker_volume, 208 reward_factors, 209 referee_stats->>'party_id' AS party_id, 210 referee_stats->>'discount_factors' AS discount_factors, 211 referee_stats->>'epoch_notional_taker_volume' AS epoch_notional_taker_volume, 212 rewards_multiplier, 213 rewards_factors_multiplier 214 FROM referral_set_stats, JSONB_ARRAY_ELEMENTS(referees_stats) AS referee_stats` 215 216 whereClauses := []string{} 217 218 if (setID == nil || referee == nil) && atEpoch == nil { 219 whereClauses = append(whereClauses, "at_epoch = (SELECT MAX(at_epoch) FROM referral_set_stats)") 220 } 221 222 if atEpoch != nil { 223 whereClauses = append(whereClauses, fmt.Sprintf("at_epoch = %s", nextBindVar(&args, *atEpoch))) 224 } 225 226 if referee != nil { 227 whereClauses = append(whereClauses, fmt.Sprintf("referee_stats->>'party_id' = %s", nextBindVar(&args, referee.String()))) 228 } 229 230 if setID != nil { 231 whereClauses = append(whereClauses, fmt.Sprintf("set_id = %s", nextBindVar(&args, setID))) 232 } 233 234 var whereStr string 235 if len(whereClauses) > 0 { 236 whereStr = " where " + strings.Join(whereClauses, " AND ") 237 } 238 239 query = fmt.Sprintf("%s %s", query, whereStr) 240 241 query, args, err := PaginateQuery[entities.ReferralSetStatsCursor](query, args, referralSetStatsOrdering, pagination) 242 if err != nil { 243 return nil, pageInfo, err 244 } 245 246 if err := pgxscan.Select(ctx, rs.ConnectionSource, &stats, query, args...); err != nil { 247 return nil, pageInfo, err 248 } 249 250 flattenStats := []entities.FlattenReferralSetStats{} 251 for _, stat := range stats { 252 discountFactors := &vega.DiscountFactors{} 253 if err := json.Unmarshal([]byte(stat.DiscountFactors), discountFactors); err != nil { 254 return nil, pageInfo, err 255 } 256 257 flattenStats = append(flattenStats, entities.FlattenReferralSetStats{ 258 SetID: stat.SetID, 259 AtEpoch: stat.AtEpoch, 260 WasEligible: stat.WasEligible, 261 ReferralSetRunningNotionalTakerVolume: stat.ReferralSetRunningNotionalTakerVolume, 262 ReferrerTakerVolume: stat.ReferrerTakerVolume, 263 VegaTime: stat.VegaTime, 264 PartyID: stat.PartyID, 265 DiscountFactors: discountFactors, 266 EpochNotionalTakerVolume: stat.EpochNotionalTakerVolume, 267 RewardFactors: stat.RewardFactors, 268 RewardsMultiplier: stat.RewardsMultiplier, 269 RewardsFactorsMultiplier: stat.RewardsFactorsMultiplier, 270 }) 271 } 272 273 flattenStats, pageInfo = entities.PageEntities[*v2.ReferralSetStatsEdge](flattenStats, pagination) 274 275 return flattenStats, pageInfo, nil 276 } 277 278 func (rs *ReferralSets) ListReferralSetReferees(ctx context.Context, referralSetID *entities.ReferralSetID, referrer, referee *entities.PartyID, 279 pagination entities.CursorPagination, aggregationEpochs uint32) ( 280 []entities.ReferralSetRefereeStats, entities.PageInfo, error, 281 ) { 282 defer metrics.StartSQLQuery("ReferralSets", "ListReferralSetReferees")() 283 var ( 284 referees []entities.ReferralSetRefereeStats 285 args []interface{} 286 err error 287 pageInfo entities.PageInfo 288 ) 289 290 query := getSelectQuery(aggregationEpochs) 291 292 var hasWhere bool 293 // we only allow one of the following to be used as the filter 294 if referralSetID != nil { 295 query = fmt.Sprintf("%s where rf.referral_set_id = %s", query, nextBindVar(&args, referralSetID)) 296 hasWhere = true 297 } else if referrer != nil { 298 query = fmt.Sprintf("%s where rs.referrer = %s", query, nextBindVar(&args, referrer)) 299 hasWhere = true 300 } else if referee != nil { 301 query = fmt.Sprintf("%s where rf.referee = %s", query, nextBindVar(&args, referee)) 302 hasWhere = true 303 } 304 305 paginate := PaginateQueryWithWhere[entities.ReferralSetRefereeCursor] 306 if hasWhere { 307 paginate = PaginateQuery[entities.ReferralSetRefereeCursor] 308 } 309 310 query, args, err = paginate(query, args, referralSetRefereeOrdering, pagination) 311 if err != nil { 312 return nil, pageInfo, err 313 } 314 315 if err := pgxscan.Select(ctx, rs.ConnectionSource, &referees, query, args...); err != nil { 316 return nil, pageInfo, err 317 } 318 319 referees, pageInfo = entities.PageEntities[*v2.ReferralSetRefereeEdge](referees, pagination) 320 321 return referees, pageInfo, nil 322 } 323 324 func getSelectQuery(aggregationEpochs uint32) string { 325 return fmt.Sprintf(` 326 with epoch_range as (select GREATEST(max(id) - %d, 0) as start_epoch, GREATEST(max(id), 0) as end_epoch 327 from epochs 328 where end_time is not null 329 ), ref_period_volume (party, period_volume) as ( 330 select decode(ref_stats->>'party_id', 'hex'), sum((ref_stats->>'epoch_notional_taker_volume')::numeric) as period_volume 331 from referral_set_stats, jsonb_array_elements(referees_stats) as ref_stats, epoch_range 332 where at_epoch > epoch_range.start_epoch and at_epoch <= epoch_range.end_epoch 333 and jsonb_typeof(referees_stats) != 'null' 334 group by ref_stats->>'party_id' 335 ), ref_period_rewards (party, period_rewards) as ( 336 select decode(gen_rewards->>'party', 'hex'), sum((gen_rewards ->> 'quantum_amount')::numeric) as period_rewards 337 from fees_stats, 338 jsonb_array_elements(referrer_rewards_generated) as ref_rewards, 339 jsonb_array_elements(ref_rewards->'generated_reward') as gen_rewards, 340 epoch_range 341 where epoch_seq > epoch_range.start_epoch and epoch_seq <= epoch_range.end_epoch 342 and jsonb_typeof(referrer_rewards_generated) != 'null' 343 group by gen_rewards->>'party' 344 ) 345 SELECT rf.referral_set_id, rf.referee, rf.joined_at, rf.at_epoch, rf.vega_time, coalesce(pv.period_volume, 0) period_volume, coalesce(pr.period_rewards, 0) period_rewards_paid 346 from current_referral_set_referees rf 347 join referral_sets rs on rf.referral_set_id = rs.id 348 left join ref_period_volume pv on rf.referee = pv.party 349 left join ref_period_rewards pr on rf.referee = pr.party 350 `, aggregationEpochs) 351 }