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 }