code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/oracle_data.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 "fmt" 21 22 "code.vegaprotocol.io/vega/datanode/entities" 23 "code.vegaprotocol.io/vega/datanode/metrics" 24 v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2" 25 26 "github.com/georgysavva/scany/pgxscan" 27 ) 28 29 type OracleData struct { 30 *ConnectionSource 31 } 32 33 const ( 34 sqlOracleDataColumns = `signers, data, meta_data, broadcast_at, error, tx_hash, vega_time, seq_num` 35 oracleDataQuery = `SELECT od.*, aggregated.spec_ids as matched_spec_ids 36 FROM 37 oracle_data od 38 LEFT JOIN LATERAL ( 39 SELECT ARRAY_AGG(spec_id) AS spec_ids 40 FROM oracle_data_oracle_specs ods 41 WHERE od.vega_time = ods.vega_time 42 AND od.seq_num = ods.seq_num 43 ) aggregated ON true 44 ` 45 ) 46 47 var oracleDataOrdering = TableOrdering{ 48 ColumnOrdering{Name: "vega_time", Sorting: ASC}, 49 ColumnOrdering{Name: "signers", Sorting: ASC}, 50 } 51 52 func NewOracleData(connectionSource *ConnectionSource) *OracleData { 53 return &OracleData{ 54 ConnectionSource: connectionSource, 55 } 56 } 57 58 func (od *OracleData) Add(ctx context.Context, oracleData *entities.OracleData) error { 59 defer metrics.StartSQLQuery("OracleData", "Add")() 60 query := fmt.Sprintf("insert into oracle_data(%s) values ($1, $2, $3, $4, $5, $6, $7, $8)", sqlOracleDataColumns) 61 62 if _, err := od.Exec( 63 ctx, query, 64 oracleData.ExternalData.Data.Signers, oracleData.ExternalData.Data.Data, oracleData.ExternalData.Data.MetaData, 65 oracleData.ExternalData.Data.BroadcastAt, 66 oracleData.ExternalData.Data.Error, oracleData.ExternalData.Data.TxHash, 67 oracleData.ExternalData.Data.VegaTime, oracleData.ExternalData.Data.SeqNum, 68 ); err != nil { 69 err = fmt.Errorf("could not insert oracle data into database: %w", err) 70 return err 71 } 72 73 query2 := "insert into oracle_data_oracle_specs(vega_time, seq_num, spec_id) values ($1, $2, unnest($3::bytea[]))" 74 if _, err := od.Exec( 75 ctx, query2, 76 oracleData.ExternalData.Data.VegaTime, oracleData.ExternalData.Data.SeqNum, oracleData.ExternalData.Data.MatchedSpecIds, 77 ); err != nil { 78 err = fmt.Errorf("could not insert oracle data join into database: %w", err) 79 return err 80 } 81 82 return nil 83 } 84 85 func (od *OracleData) ListOracleData(ctx context.Context, id string, pagination entities.Pagination) ([]entities.OracleData, entities.PageInfo, error) { 86 switch p := pagination.(type) { 87 case entities.CursorPagination: 88 return listOracleDataBySpecIDCursorPagination(ctx, od.ConnectionSource, id, p) 89 default: 90 panic("unsupported pagination") 91 } 92 } 93 94 func (od *OracleData) GetByTxHash(ctx context.Context, txHash entities.TxHash) ([]entities.OracleData, error) { 95 defer metrics.StartSQLQuery("OracleData", "GetByTxHash")() 96 97 var data []entities.Data 98 query := fmt.Sprintf(`%s WHERE tx_hash = $1`, oracleDataQuery) 99 err := pgxscan.Select(ctx, od.ConnectionSource, &data, query, txHash) 100 if err != nil { 101 return nil, err 102 } 103 104 return scannedDataToOracleData(data), nil 105 } 106 107 func scannedDataToOracleData(scanned []entities.Data) []entities.OracleData { 108 oracleData := []entities.OracleData{} 109 if len(scanned) > 0 { 110 for _, s := range scanned { 111 oracleData = append(oracleData, entities.OracleData{ 112 ExternalData: &entities.ExternalData{ 113 Data: &entities.Data{ 114 Signers: s.Signers, 115 Data: s.Data, 116 MetaData: s.MetaData, 117 MatchedSpecIds: s.MatchedSpecIds, 118 BroadcastAt: s.BroadcastAt, 119 Error: s.Error, 120 TxHash: s.TxHash, 121 VegaTime: s.VegaTime, 122 SeqNum: s.SeqNum, 123 }, 124 }, 125 }) 126 } 127 } 128 129 return oracleData 130 } 131 132 func listOracleDataBySpecIDCursorPagination(ctx context.Context, conn Connection, id string, pagination entities.CursorPagination) ( 133 []entities.OracleData, entities.PageInfo, error, 134 ) { 135 var ( 136 oracleData []entities.OracleData 137 data = []entities.Data{} 138 139 pageInfo entities.PageInfo 140 bindVars []interface{} 141 err error 142 ) 143 144 query := oracleDataQuery 145 andOrWhere := "WHERE" 146 147 if len(id) > 0 { 148 specID := entities.SpecID(id) 149 150 query = fmt.Sprintf(`%s 151 WHERE EXISTS (SELECT 1 from oracle_data_oracle_specs ods 152 WHERE od.vega_time = ods.vega_time 153 AND od.seq_num = ods.seq_num 154 AND ods.spec_id=%s)`, query, nextBindVar(&bindVars, specID)) 155 156 andOrWhere = "AND" 157 } 158 159 // if the cursor is empty, we should restrict the query to the last day of data as otherwise, the query will scan the full hypertable 160 // we only do this if we are returning the newest first data because that should be kept in memory by TimescaleDB anyway. 161 // If we have a first N cursor traversing newest first data, without an after cursor, we should also restrict by date. 162 // Traversing from the oldest data to the newest data will result in table scans and take time as we don't know what the oldest data is due to retention policies. 163 // Anything after the first page will have a vega time in the cursor so this will not be needed. 164 if pagination.HasForward() && !pagination.Forward.HasCursor() && pagination.NewestFirst { 165 query = fmt.Sprintf("%s %s vega_time > now() - interval '1 day'", query, andOrWhere) 166 } 167 168 query, bindVars, err = PaginateQuery[entities.OracleDataCursor](query, bindVars, oracleDataOrdering, pagination) 169 if err != nil { 170 return oracleData, pageInfo, err 171 } 172 173 defer metrics.StartSQLQuery("OracleData", "ListOracleData")() 174 // NOTE: If any error during the scan occurred, we return empty oracle data object. 175 if err = pgxscan.Select(ctx, conn, &data, query, bindVars...); err != nil { 176 return oracleData, pageInfo, err 177 } 178 179 oracleData = scannedDataToOracleData(data) 180 181 oracleData, pageInfo = entities.PageEntities[*v2.OracleDataEdge](oracleData, pagination) 182 return oracleData, pageInfo, nil 183 }