code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/markets.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  	"errors"
    21  	"fmt"
    22  	"strings"
    23  	"sync"
    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  
    29  	"github.com/georgysavva/scany/pgxscan"
    30  	"golang.org/x/exp/maps"
    31  )
    32  
    33  type cacheKey struct {
    34  	forwardOffset  int32
    35  	backwardOffset int32
    36  	forwardCursor  string
    37  	backwardCursor string
    38  	newestFirst    bool
    39  	marketID       string
    40  	includeSettled bool
    41  }
    42  
    43  type cacheValue struct {
    44  	markets  []entities.Market
    45  	pageInfo entities.PageInfo
    46  }
    47  
    48  func newCacheKey(marketID string, pagination entities.CursorPagination, includeSettled bool) cacheKey {
    49  	k := cacheKey{
    50  		marketID:       marketID,
    51  		newestFirst:    pagination.NewestFirst,
    52  		includeSettled: includeSettled,
    53  	}
    54  
    55  	if pagination.Forward != nil {
    56  		if pagination.Forward.Limit != nil {
    57  			k.forwardOffset = *pagination.Forward.Limit
    58  		}
    59  		if pagination.Forward.Cursor != nil {
    60  			k.forwardCursor = pagination.Forward.Cursor.Value()
    61  		}
    62  	}
    63  
    64  	if pagination.Backward != nil {
    65  		if pagination.Backward.Limit != nil {
    66  			k.backwardOffset = *pagination.Backward.Limit
    67  		}
    68  		if pagination.Backward.Cursor != nil {
    69  			k.backwardCursor = pagination.Backward.Cursor.Value()
    70  		}
    71  	}
    72  
    73  	return k
    74  }
    75  
    76  type Markets struct {
    77  	*ConnectionSource
    78  	cache        map[string]entities.Market
    79  	cacheLock    sync.Mutex
    80  	allCache     map[cacheKey]cacheValue
    81  	allCacheLock sync.Mutex
    82  }
    83  
    84  var marketOrdering = TableOrdering{
    85  	ColumnOrdering{Name: "vega_time", Sorting: ASC},
    86  }
    87  
    88  var lineageOrdering = TableOrdering{
    89  	ColumnOrdering{Name: "vega_time", Sorting: ASC, Prefix: "m"},
    90  	// ColumnOrdering{Name: "id", Sorting: ASC, Prefix: "m"},
    91  	// ColumnOrdering{Name: "id", Sorting: ASC, Prefix: "pc"},
    92  }
    93  
    94  const (
    95  	sqlMarketsColumns = `id, tx_hash, vega_time, instrument_id, tradable_instrument, decimal_places,
    96  		fees, opening_auction, price_monitoring_settings, liquidity_monitoring_parameters,
    97  		trading_mode, state, market_timestamps, position_decimal_places, lp_price_range, linear_slippage_factor, quadratic_slippage_factor,
    98  		parent_market_id, insurance_pool_fraction, liquidity_sla_parameters, liquidation_strategy, mark_price_configuration, tick_size, enable_tx_reordering, allowed_empty_amm_levels, allowed_sellers`
    99  )
   100  
   101  func NewMarkets(connectionSource *ConnectionSource) *Markets {
   102  	return &Markets{
   103  		ConnectionSource: connectionSource,
   104  		cache:            make(map[string]entities.Market),
   105  		allCache:         make(map[cacheKey]cacheValue),
   106  	}
   107  }
   108  
   109  func (m *Markets) Upsert(ctx context.Context, market *entities.Market) error {
   110  	query := fmt.Sprintf(`insert into markets(%s)
   111  values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26)
   112  on conflict (id, vega_time) do update
   113  set
   114  	instrument_id=EXCLUDED.instrument_id,
   115  	tradable_instrument=EXCLUDED.tradable_instrument,
   116  	decimal_places=EXCLUDED.decimal_places,
   117  	fees=EXCLUDED.fees,
   118  	opening_auction=EXCLUDED.opening_auction,
   119  	price_monitoring_settings=EXCLUDED.price_monitoring_settings,
   120  	liquidity_monitoring_parameters=EXCLUDED.liquidity_monitoring_parameters,
   121  	trading_mode=EXCLUDED.trading_mode,
   122  	state=EXCLUDED.state,
   123  	market_timestamps=EXCLUDED.market_timestamps,
   124  	position_decimal_places=EXCLUDED.position_decimal_places,
   125  	lp_price_range=EXCLUDED.lp_price_range,
   126  	linear_slippage_factor=EXCLUDED.linear_slippage_factor,
   127      quadratic_slippage_factor=EXCLUDED.quadratic_slippage_factor,
   128      parent_market_id=EXCLUDED.parent_market_id,
   129      insurance_pool_fraction=EXCLUDED.insurance_pool_fraction,
   130  	tx_hash=EXCLUDED.tx_hash,
   131      liquidity_sla_parameters=EXCLUDED.liquidity_sla_parameters,
   132  	liquidation_strategy=EXCLUDED.liquidation_strategy,
   133  	mark_price_configuration=EXCLUDED.mark_price_configuration,
   134  	tick_size=EXCLUDED.tick_size,
   135  	enable_tx_reordering=EXCLUDED.enable_tx_reordering,
   136  	allowed_empty_amm_levels=EXCLUDED.allowed_empty_amm_levels,
   137  	allowed_sellers=EXCLUDED.allowed_sellers;`, sqlMarketsColumns)
   138  
   139  	defer metrics.StartSQLQuery("Markets", "Upsert")()
   140  
   141  	if market.AllowedSellers == nil {
   142  		market.AllowedSellers = []string{}
   143  	}
   144  
   145  	if _, err := m.Exec(ctx, query, market.ID, market.TxHash, market.VegaTime, market.InstrumentID, market.TradableInstrument, market.DecimalPlaces,
   146  		market.Fees, market.OpeningAuction, market.PriceMonitoringSettings, market.LiquidityMonitoringParameters,
   147  		market.TradingMode, market.State, market.MarketTimestamps, market.PositionDecimalPlaces, market.LpPriceRange,
   148  		market.LinearSlippageFactor, market.QuadraticSlippageFactor, market.ParentMarketID, market.InsurancePoolFraction,
   149  		market.LiquiditySLAParameters, market.LiquidationStrategy,
   150  		market.MarkPriceConfiguration, market.TickSize, market.EnableTXReordering, market.AllowedEmptyAMMLevels, market.AllowedSellers); err != nil {
   151  		err = fmt.Errorf("could not insert market into database: %w", err)
   152  		return err
   153  	}
   154  
   155  	m.AfterCommit(ctx, func() {
   156  		// delete cache
   157  		m.cacheLock.Lock()
   158  		defer m.cacheLock.Unlock()
   159  		delete(m.cache, market.ID.String())
   160  
   161  		m.allCacheLock.Lock()
   162  		defer m.allCacheLock.Unlock()
   163  		maps.Clear(m.allCache)
   164  	})
   165  
   166  	return nil
   167  }
   168  
   169  func getSelect() string {
   170  	return `with lineage(market_id, parent_market_id) as (
   171  	select market_id, parent_market_id
   172      from market_lineage
   173  )
   174  select mc.id,  mc.tx_hash,  mc.vega_time,  mc.instrument_id,  mc.tradable_instrument,  mc.decimal_places,
   175  		mc.fees, mc.opening_auction, mc.price_monitoring_settings, mc.liquidity_monitoring_parameters,
   176  		mc.trading_mode, mc.state, mc.market_timestamps, mc.position_decimal_places, mc.lp_price_range, mc.linear_slippage_factor, mc.quadratic_slippage_factor,
   177  		mc.parent_market_id, mc.insurance_pool_fraction, ml.market_id as successor_market_id, mc.liquidity_sla_parameters, mc.liquidation_strategy, mc.mark_price_configuration, mc.tick_size, mc.enable_tx_reordering, mc.allowed_empty_amm_levels, mc.allowed_sellers
   178  from markets_current mc
   179  left join lineage ml on mc.id = ml.parent_market_id
   180  `
   181  }
   182  
   183  func (m *Markets) GetByID(ctx context.Context, marketID string) (entities.Market, error) {
   184  	m.cacheLock.Lock()
   185  	defer m.cacheLock.Unlock()
   186  
   187  	if market, ok := m.cache[marketID]; ok {
   188  		return market, nil
   189  	}
   190  
   191  	defer metrics.StartSQLQuery("Markets", "GetByID")()
   192  	market, err := m.queryByID(ctx, marketID)
   193  
   194  	if err == nil {
   195  		m.cache[marketID] = market
   196  	}
   197  
   198  	return market, m.wrapE(err)
   199  }
   200  
   201  // queryByID assumes the cache lock has been acquired, and the cache doesn't yet contain the requested market.
   202  // This function does not access the cache, so technically it can be called without a cache lock, but it's best not to go down that route.
   203  func (m *Markets) queryByID(ctx context.Context, mktID string) (entities.Market, error) {
   204  	var market entities.Market
   205  	query := fmt.Sprintf(`%s
   206  where id = $1
   207  order by id, vega_time desc
   208  `, getSelect())
   209  
   210  	err := pgxscan.Get(ctx, m.ConnectionSource, &market, query, entities.MarketID(mktID))
   211  
   212  	return market, m.wrapE(err)
   213  }
   214  
   215  func (m *Markets) GetByIDs(ctx context.Context, markets []string) ([]entities.Market, error) {
   216  	m.cacheLock.Lock()
   217  	defer m.cacheLock.Unlock()
   218  	ret := make([]entities.Market, 0, len(markets))
   219  	missing := make([]string, 0, len(markets))
   220  	for _, mid := range markets {
   221  		if mkt, ok := m.cache[mid]; ok {
   222  			ret = append(ret, mkt)
   223  		} else {
   224  			missing = append(missing, mid)
   225  		}
   226  	}
   227  	if len(missing) == 0 {
   228  		return ret, nil
   229  	}
   230  	for _, mid := range missing {
   231  		mkt, err := m.queryByID(ctx, mid)
   232  		// if a requested market couldn't be found, just return an error.
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  		m.cache[mid] = mkt
   237  		ret = append(ret, mkt)
   238  	}
   239  	return ret, nil
   240  }
   241  
   242  func (m *Markets) GetByTxHash(ctx context.Context, txHash entities.TxHash) ([]entities.Market, error) {
   243  	defer metrics.StartSQLQuery("Markets", "GetByTxHash")()
   244  
   245  	var markets []entities.Market
   246  	query := fmt.Sprintf(`%s where tx_hash = $1`, getSelect())
   247  	err := pgxscan.Select(ctx, m.ConnectionSource, &markets, query, txHash)
   248  
   249  	if err == nil {
   250  		m.cacheLock.Lock()
   251  		for _, market := range markets {
   252  			m.cache[market.ID.String()] = market
   253  		}
   254  		m.cacheLock.Unlock()
   255  	}
   256  
   257  	return markets, m.wrapE(err)
   258  }
   259  
   260  // GetAllFees returns fee information for all markets.
   261  // it returns a market entity with only id and fee.
   262  // NB: it's not cached nor paged.
   263  func (m *Markets) GetAllFees(ctx context.Context) ([]entities.Market, error) {
   264  	markets := make([]entities.Market, 0)
   265  	args := make([]interface{}, 0)
   266  	query := `select mc.id,  mc.fees from markets_current mc where state != 'STATE_REJECTED' AND state != 'STATE_SETTLED' AND state != 'STATE_CLOSED' order by mc.id`
   267  	err := pgxscan.Select(ctx, m.ConnectionSource, &markets, query, args...)
   268  	return markets, err
   269  }
   270  
   271  func (m *Markets) GetAllPaged(ctx context.Context, marketID string, pagination entities.CursorPagination, includeSettled bool) ([]entities.Market, entities.PageInfo, error) {
   272  	key := newCacheKey(marketID, pagination, includeSettled)
   273  	m.allCacheLock.Lock()
   274  	defer m.allCacheLock.Unlock()
   275  	if value, ok := m.allCache[key]; ok {
   276  		return value.markets, value.pageInfo, nil
   277  	}
   278  
   279  	if marketID != "" {
   280  		market, err := m.GetByID(ctx, marketID)
   281  		if err != nil {
   282  			return nil, entities.PageInfo{}, err
   283  		}
   284  
   285  		return []entities.Market{market}, entities.PageInfo{
   286  			HasNextPage:     false,
   287  			HasPreviousPage: false,
   288  			StartCursor:     market.Cursor().Encode(),
   289  			EndCursor:       market.Cursor().Encode(),
   290  		}, nil
   291  	}
   292  
   293  	markets := make([]entities.Market, 0)
   294  	args := make([]interface{}, 0)
   295  
   296  	settledClause := ""
   297  	if !includeSettled {
   298  		settledClause = " AND state != 'STATE_SETTLED' AND state != 'STATE_CLOSED'"
   299  	}
   300  
   301  	query := fmt.Sprintf(`%s
   302  		where state != 'STATE_REJECTED' %s`, getSelect(), settledClause)
   303  
   304  	var (
   305  		pageInfo entities.PageInfo
   306  		err      error
   307  	)
   308  
   309  	query, args, err = PaginateQuery[entities.MarketCursor](query, args, marketOrdering, pagination)
   310  	if err != nil {
   311  		return markets, pageInfo, err
   312  	}
   313  
   314  	if err = pgxscan.Select(ctx, m.ConnectionSource, &markets, query, args...); err != nil {
   315  		return markets, pageInfo, err
   316  	}
   317  
   318  	markets, pageInfo = entities.PageEntities[*v2.MarketEdge](markets, pagination)
   319  
   320  	m.allCache[key] = cacheValue{markets: markets, pageInfo: pageInfo}
   321  	return markets, pageInfo, nil
   322  }
   323  
   324  func (m *Markets) ListSuccessorMarkets(ctx context.Context, marketID string, fullHistory bool, pagination entities.CursorPagination) ([]entities.SuccessorMarket, entities.PageInfo, error) {
   325  	if marketID == "" {
   326  		return nil, entities.PageInfo{}, errors.New("invalid market ID. Market ID cannot be empty")
   327  	}
   328  
   329  	// We paginate by market, so first we have to get all the markets and apply pagination to those first
   330  
   331  	args := make([]interface{}, 0)
   332  
   333  	lineageFilter := ""
   334  
   335  	if !fullHistory {
   336  		lineageFilter = "and vega_time >= (select vega_time from lineage_root)"
   337  	}
   338  
   339  	preQuery := fmt.Sprintf(`
   340  with lineage_root(root_id, vega_time) as (
   341  	select root_id, vega_time
   342  	from market_lineage
   343  	where market_id = %s
   344  ), lineage(successor_market_id, parent_id, root_id) as (
   345  	select market_id, parent_market_id, root_id
   346  	from market_lineage
   347  	where root_id = (select root_id from lineage_root)
   348    %s
   349  ) `, nextBindVar(&args, entities.MarketID(marketID)), lineageFilter)
   350  
   351  	query := `select m.*, s.successor_market_id
   352  from markets_current m
   353  join lineage l on l.successor_market_id = m.id
   354  left join lineage s on l.successor_market_id = s.parent_id
   355  `
   356  	var markets []entities.Market
   357  	var pageInfo entities.PageInfo
   358  	var err error
   359  	query, args, err = PaginateQuery[entities.SuccessorMarketCursor](query, args, lineageOrdering, pagination)
   360  	if err != nil {
   361  		return nil, pageInfo, err
   362  	}
   363  
   364  	query = fmt.Sprintf("%s %s", preQuery, query)
   365  
   366  	if err = pgxscan.Select(ctx, m.ConnectionSource, &markets, query, args...); err != nil {
   367  		return nil, entities.PageInfo{}, m.wrapE(err)
   368  	}
   369  
   370  	markets, pageInfo = entities.PageEntities[*v2.MarketEdge](markets, pagination)
   371  
   372  	// Now that we have the markets we are going to return, we need to get all the related proposals where the parent market
   373  	// is one of the markets we are returning. We will do this in one query and process the results in memory
   374  	// rather than making a separate database query for each market in case the succession line becomes very long.
   375  
   376  	parentMarketList := make([]string, 0)
   377  	for _, m := range markets {
   378  		parentMarketList = append(parentMarketList, m.ID.String())
   379  	}
   380  
   381  	var proposals []entities.Proposal
   382  
   383  	proposalsQuery := fmt.Sprintf(`select * from proposals_current where terms->'newMarket'->'changes'->'successor'->>'parentMarketId' in ('%s') order by vega_time, id`, strings.Join(parentMarketList, "', '"))
   384  
   385  	if err = pgxscan.Select(ctx, m.ConnectionSource, &proposals, proposalsQuery); err != nil {
   386  		return nil, entities.PageInfo{}, m.wrapE(err)
   387  	}
   388  
   389  	edges := []entities.SuccessorMarket{}
   390  
   391  	// Now we have the proposals, we need to create the successor market edges and add them to the market
   392  	for _, m := range markets {
   393  		edge := entities.SuccessorMarket{
   394  			Market: m,
   395  		}
   396  
   397  		for i, p := range proposals {
   398  			if p.Terms.ProposalTerms.GetNewMarket().Changes.Successor.ParentMarketId == m.ID.String() {
   399  				edge.Proposals = append(edge.Proposals, &proposals[i])
   400  			}
   401  		}
   402  
   403  		edges = append(edges, edge)
   404  	}
   405  
   406  	if len(markets) == 0 {
   407  		// We do not have any markets in the given succession line, so we need to return the market
   408  		// associated with the given market ID, which should be the parent market.
   409  		market, err := m.GetByID(ctx, marketID)
   410  		if err != nil {
   411  			return nil, entities.PageInfo{}, err
   412  		}
   413  
   414  		edge := entities.SuccessorMarket{
   415  			Market: market,
   416  		}
   417  
   418  		edges = append(edges, edge)
   419  	}
   420  
   421  	return edges, pageInfo, nil
   422  }