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  }