code.vegaprotocol.io/vega@v0.79.0/blockexplorer/store/transactions.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 store 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "sort" 23 "strings" 24 25 "code.vegaprotocol.io/vega/blockexplorer/entities" 26 pb "code.vegaprotocol.io/vega/protos/blockexplorer/api/v1" 27 28 "github.com/georgysavva/scany/pgxscan" 29 ) 30 31 var ( 32 ErrTxNotFound = errors.New("transaction not found") 33 ErrMultipleTxFound = errors.New("multiple transactions found") 34 ) 35 36 func (s *Store) GetTransaction(ctx context.Context, txID string) (*pb.Transaction, error) { 37 txID = strings.ToUpper(txID) 38 39 query := `SELECT t.rowid, t.block_height, t.index, t.created_at, t.tx_hash, t.tx_result, t.cmd_type, t.submitter FROM tx_results t WHERE t.tx_hash=$1` 40 var rows []entities.TxResultRow 41 42 if err := pgxscan.Select(ctx, s.pool, &rows, query, txID); err != nil { 43 return nil, fmt.Errorf("querying tx_results: %w", err) 44 } 45 46 if len(rows) == 0 { 47 return nil, ErrTxNotFound 48 } 49 50 if len(rows) > 1 { 51 return nil, ErrMultipleTxFound 52 } 53 54 tx, err := rows[0].ToProto() 55 if err != nil { 56 return nil, err 57 } 58 return tx, nil 59 } 60 61 func (s *Store) ListTransactions(ctx context.Context, 62 filters map[string]string, 63 cmdTypes, exclCmdTypes, parties []string, 64 first uint32, 65 after *entities.TxCursor, 66 last uint32, 67 before *entities.TxCursor, 68 ) ([]*pb.Transaction, error) { 69 query := `SELECT t.rowid, t.block_height, t.index, t.created_at, t.tx_hash, t.tx_result, t.cmd_type, t.submitter FROM tx_results t` 70 71 args := []interface{}{} 72 predicates := []string{} 73 74 limit := uint32(0) 75 76 sortOrder := "desc" 77 78 if first > 0 { 79 // We want the N most recent transactions, descending on block height and block 80 // index: 4.1, 3.2, 3.1, 2.2... 81 // The resulting query should already sort the rows in the right order. 82 limit = first 83 sortOrder = "desc" 84 } else if last > 0 { 85 // We want the N oldest transactions, ascending on block height and block 86 // index: 1.1, 1.2, 2.1, 2.2... 87 // The resulting query should sort the rows in the chronological order. But 88 // that's necessary to apply the LIMIT clause. It will be sorted in the 89 // reverse chronological order later on. 90 limit = last 91 sortOrder = "asc" 92 } 93 94 if before != nil { 95 block := nextBindVar(&args, before.BlockNumber) 96 index := nextBindVar(&args, before.TxIndex) 97 predicate := fmt.Sprintf("(t.block_height, t.index) < (%s, %s)", block, index) 98 predicates = append(predicates, predicate) 99 // We change the sorting order because we want the transactions right before 100 // the cursor, meaning older transactions. 101 sortOrder = "desc" 102 } 103 if after != nil { 104 block := nextBindVar(&args, after.BlockNumber) 105 index := nextBindVar(&args, after.TxIndex) 106 predicate := fmt.Sprintf("(t.block_height, t.index) > (%s, %s)", block, index) 107 predicates = append(predicates, predicate) 108 // We change the sorting order because we want the transactions right after 109 // the cursor, meaning newer transaction. That's necessary to apply the 110 // LIMIT clause. 111 sortOrder = "asc" 112 } 113 114 if len(cmdTypes) > 0 { 115 predicates = append(predicates, fmt.Sprintf("t.cmd_type = ANY(%s)", nextBindVar(&args, cmdTypes))) 116 } 117 118 if len(exclCmdTypes) > 0 { 119 predicates = append(predicates, fmt.Sprintf("t.cmd_type != ALL(%s)", nextBindVar(&args, exclCmdTypes))) 120 } 121 122 if len(parties) > 0 { 123 partiesBytes := make([][]byte, len(parties)) 124 for i, p := range parties { 125 partiesBytes[i] = []byte(p) 126 } 127 predicates = append(predicates, fmt.Sprintf("t.submitter = ANY(%s)", nextBindVar(&args, partiesBytes))) 128 } 129 130 for key, value := range filters { 131 var predicate string 132 133 if key == "tx.submitter" { 134 // tx.submitter is lifted out of attributes and into tx_results by a trigger for faster access 135 predicate = fmt.Sprintf("t.submitter= %s", nextBindVar(&args, value)) 136 } else if key == "cmd.type" { 137 predicate = fmt.Sprintf("t.cmd_type= %s", nextBindVar(&args, value)) 138 } else if key == "block.height" { 139 // much quicker to filter block height by joining to the block table than looking in attributes 140 predicate = fmt.Sprintf("t.block_height = %s", nextBindVar(&args, value)) 141 } else { 142 predicate = fmt.Sprintf(` 143 EXISTS (SELECT 1 FROM events e JOIN attributes a ON e.rowid = a.event_id 144 WHERE e.tx_id = t.rowid 145 AND a.composite_key = %s 146 AND a.value = %s)`, nextBindVar(&args, key), nextBindVar(&args, value)) 147 } 148 predicates = append(predicates, predicate) 149 } 150 151 if len(predicates) > 0 { 152 query = fmt.Sprintf("%s WHERE %s", query, strings.Join(predicates, " AND ")) 153 } 154 155 query = fmt.Sprintf("%s ORDER BY t.block_height %s, t.index %s", query, sortOrder, sortOrder) 156 if limit != 0 { 157 query = fmt.Sprintf("%s LIMIT %d", query, limit) 158 } 159 160 var rows []entities.TxResultRow 161 if err := pgxscan.Select(ctx, s.pool, &rows, query, args...); err != nil { 162 return nil, fmt.Errorf("querying tx_results: %w", err) 163 } 164 165 txs := make([]*pb.Transaction, 0, len(rows)) 166 for _, row := range rows { 167 tx, err := row.ToProto() 168 if err != nil { 169 s.log.Warn(fmt.Sprintf("unable to decode transaction %s: %v", row.TxHash, err)) 170 continue 171 } 172 txs = append(txs, tx) 173 } 174 175 // Make sure the results are always order in the reverse chronological order, 176 // as required. 177 // This cannot be replaced by the `order by` in the request as it's used by the 178 // pagination system. 179 sort.Slice(txs, func(i, j int) bool { 180 if txs[i].Block == txs[j].Block { 181 return txs[i].Index > txs[j].Index 182 } 183 return txs[i].Block > txs[j].Block 184 }) 185 186 return txs, nil 187 }