code.vegaprotocol.io/vega@v0.79.0/blockexplorer/store/transactions_test.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_test 17 18 import ( 19 "bytes" 20 "context" 21 "fmt" 22 "io/fs" 23 "os" 24 "path/filepath" 25 "testing" 26 "time" 27 28 "code.vegaprotocol.io/vega/blockexplorer/entities" 29 "code.vegaprotocol.io/vega/blockexplorer/store" 30 "code.vegaprotocol.io/vega/datanode/sqlstore" 31 "code.vegaprotocol.io/vega/datanode/utils/databasetest" 32 "code.vegaprotocol.io/vega/libs/config" 33 "code.vegaprotocol.io/vega/logging" 34 pb "code.vegaprotocol.io/vega/protos/blockexplorer/api/v1" 35 36 "github.com/cenkalti/backoff" 37 tmTypes "github.com/cometbft/cometbft/abci/types" 38 "github.com/jackc/pgx/v4" 39 "github.com/stretchr/testify/assert" 40 "github.com/stretchr/testify/require" 41 ) 42 43 var ( 44 postgresServerTimeout = time.Minute * 10 45 postgresRuntimePath string 46 connectionSource *sqlstore.ConnectionSource 47 ) 48 49 func TestMain(m *testing.M) { 50 log := logging.NewTestLogger() 51 52 tempDir, err := os.MkdirTemp("", "block_explorer") 53 if err != nil { 54 panic(fmt.Errorf("could not create temporary root directory for block_explorer tests: %w", err)) 55 } 56 postgresRuntimePath = filepath.Join(tempDir, "sqlstore") 57 err = os.Mkdir(postgresRuntimePath, fs.ModePerm) 58 if err != nil { 59 panic(fmt.Errorf("could not create temporary directory for postgres runtime: %w", err)) 60 } 61 defer os.RemoveAll(postgresRuntimePath) 62 63 testDBPort := 5432 64 testDBHost := "" 65 sqlConfig := databasetest.NewTestConfig(testDBPort, testDBHost, postgresRuntimePath) 66 postgresLog := &bytes.Buffer{} 67 embeddedPostgres, err := sqlstore.StartEmbeddedPostgres(log, sqlConfig, postgresRuntimePath, postgresLog) 68 if err != nil { 69 log.Errorf("failed to start postgres: %s", postgresLog.String()) 70 panic(err) 71 } 72 73 log.Infof("Test DB Socket Directory: %s", postgresRuntimePath) 74 75 // Make sure the database has started before we run the tests. 76 ctx, cancel := context.WithTimeout(context.Background(), postgresServerTimeout) 77 78 op := func() error { 79 connStr := sqlConfig.ConnectionConfig.GetConnectionString() 80 conn, err := pgx.Connect(ctx, connStr) 81 if err != nil { 82 return err 83 } 84 85 return conn.Ping(ctx) 86 } 87 88 if err := backoff.Retry(op, backoff.NewExponentialBackOff()); err != nil { 89 cancel() 90 panic(err) 91 } 92 93 cancel() 94 ctx, cancel = context.WithCancel(context.Background()) 95 connectionSource, err = sqlstore.NewTransactionalConnectionSource(ctx, log, sqlConfig.ConnectionConfig) 96 if err != nil { 97 cancel() 98 panic(err) 99 } 100 defer embeddedPostgres.Stop() 101 102 if err = sqlstore.WipeDatabaseAndMigrateSchemaToLatestVersion(log, sqlConfig.ConnectionConfig, store.EmbedMigrations, false); err != nil { 103 log.Errorf("failed to wipe database and migrate schema, dumping postgres log:\n %s", postgresLog.String()) 104 cancel() 105 panic(err) 106 } 107 108 code := m.Run() 109 cancel() 110 os.Exit(code) 111 } 112 113 type txResult struct { 114 height int64 115 index int64 116 createdAt time.Time 117 txHash string 118 txResult []byte 119 submitter string 120 cmdType string 121 } 122 123 func addTestTxResults(ctx context.Context, t *testing.T, txResultTable string, txResults ...txResult) []*pb.Transaction { 124 t.Helper() 125 126 rows := make([]*pb.Transaction, 0, len(txResults)) 127 blockIDs := make(map[int64]int64) 128 129 // just in case 130 if txResultTable == "" { 131 txResultTable = "tx_results" 132 } 133 134 blockSQL := `INSERT INTO blocks (height, chain_id, created_at) VALUES ($1, $2, $3) ON CONFLICT (height, chain_id) DO UPDATE SET created_at = EXCLUDED.created_at RETURNING rowid` 135 resultSQL := fmt.Sprintf(`INSERT INTO %s (block_id, index, created_at, tx_hash, tx_result, submitter, cmd_type) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING rowid`, txResultTable) 136 137 for _, txr := range txResults { 138 var blockID int64 139 var ok bool 140 141 if blockID, ok = blockIDs[txr.height]; !ok { 142 require.NoError(t, connectionSource.QueryRow(ctx, blockSQL, txr.height, "test-chain", txr.createdAt).Scan(&blockID)) 143 blockIDs[txr.height] = blockID 144 } 145 146 index := txr.index 147 148 var rowID int64 149 require.NoError(t, connectionSource.QueryRow(ctx, resultSQL, blockID, index, txr.createdAt, txr.txHash, txr.txResult, txr.submitter, txr.cmdType).Scan(&rowID)) 150 151 row := entities.TxResultRow{ 152 RowID: rowID, 153 BlockHeight: txr.height, 154 Index: index, 155 CreatedAt: txr.createdAt, 156 TxHash: txr.txHash, 157 TxResult: txr.txResult, 158 Submitter: txr.submitter, 159 CmdType: txr.cmdType, 160 } 161 162 proto, err := row.ToProto() 163 require.NoError(t, err) 164 165 rows = append(rows, proto) 166 } 167 168 return rows 169 } 170 171 func cleanupTransactionsTest(ctx context.Context, t *testing.T) { 172 t.Helper() 173 174 _, err := connectionSource.Exec(ctx, `DELETE FROM tx_results`) 175 require.NoError(t, err) 176 _, err = connectionSource.Exec(ctx, `DELETE FROM blocks`) 177 require.NoError(t, err) 178 } 179 180 func setupTestTransactions(ctx context.Context, t *testing.T) []*pb.Transaction { 181 t.Helper() 182 183 txr, err := (&tmTypes.TxResult{}).Marshal() 184 require.NoError(t, err) 185 186 now := time.Now() 187 txResults := []txResult{ 188 { 189 height: 0, 190 index: 1, 191 createdAt: now, 192 txHash: "deadbeef01", 193 txResult: txr, 194 submitter: "TEST", 195 cmdType: "TEST", 196 }, 197 { 198 height: 0, 199 index: 2, 200 createdAt: now.Add(100), 201 txHash: "deadbeef02", 202 txResult: txr, 203 submitter: "TEST", 204 cmdType: "TEST", 205 }, 206 { 207 height: 1, 208 index: 1, 209 createdAt: now.Add(1 * time.Second), 210 txHash: "deadbeef11", 211 txResult: txr, 212 submitter: "TEST", 213 cmdType: "TEST", 214 }, 215 { 216 height: 2, 217 index: 1, 218 createdAt: now.Add(2 * time.Second), 219 txHash: "deadbeef21", 220 txResult: txr, 221 submitter: "TEST", 222 cmdType: "TEST", 223 }, 224 { 225 height: 2, 226 index: 2, 227 createdAt: now.Add(2*time.Second + 50), 228 txHash: "deadbeef22", 229 txResult: txr, 230 submitter: "TEST", 231 cmdType: "TEST", 232 }, 233 { 234 height: 2, 235 index: 4, 236 createdAt: now.Add(2*time.Second + 700), 237 txHash: "deadbeef24", 238 txResult: txr, 239 submitter: "TEST", 240 cmdType: "TEST", 241 }, 242 { 243 height: 3, 244 index: 1, 245 createdAt: now.Add(3 * time.Second), 246 txHash: "deadbeef31", 247 txResult: txr, 248 submitter: "TEST", 249 cmdType: "TEST", 250 }, 251 { 252 height: 4, 253 index: 1, 254 createdAt: now.Add(4 * time.Second), 255 txHash: "deadbeef41", 256 txResult: txr, 257 submitter: "TEST", 258 cmdType: "TEST", 259 }, 260 { 261 height: 5, 262 index: 1, 263 createdAt: now.Add(5 * time.Second), 264 txHash: "deadbeef51", 265 txResult: txr, 266 submitter: "TEST", 267 cmdType: "TEST", 268 }, 269 { 270 height: 6, 271 index: 1, 272 createdAt: now.Add(6 * time.Second), 273 txHash: "deadbeef61", 274 txResult: txr, 275 submitter: "TEST", 276 cmdType: "TEST", 277 }, 278 } 279 280 return addTestTxResults(ctx, t, "tx_results", txResults...) 281 } 282 283 func TestStore_ListTransactions(t *testing.T) { 284 ctx, cancel := context.WithTimeout(context.Background(), postgresServerTimeout) 285 t.Cleanup(func() { 286 cleanupTransactionsTest(ctx, t) 287 cancel() 288 }) 289 290 inserted := setupTestTransactions(ctx, t) 291 292 s := store.MustNewStore(store.Config{ 293 Postgres: config.PostgresConnection{ 294 Host: "", 295 Port: 5432, 296 Username: "vega", 297 Password: "vega", 298 Database: "vega", 299 SocketDir: postgresRuntimePath, 300 }, 301 }, logging.NewTestLogger()) 302 303 t.Run("should return the most recent transactions when first is set without cursor", func(t *testing.T) { 304 got, err := s.ListTransactions(ctx, nil, nil, nil, nil, 2, nil, 0, nil) 305 require.NoError(t, err) 306 want := []*pb.Transaction{inserted[9], inserted[8]} 307 assert.Equal(t, want, got) 308 }) 309 310 t.Run("should return the oldest transactions when last is set without cursor", func(t *testing.T) { 311 got, err := s.ListTransactions(ctx, nil, nil, nil, nil, 0, nil, 2, nil) 312 require.NoError(t, err) 313 want := []*pb.Transaction{inserted[1], inserted[0]} 314 assert.Equal(t, want, got) 315 }) 316 317 t.Run("should return the transactions after the cursor when first is set", func(t *testing.T) { 318 after := entities.TxCursor{ 319 BlockNumber: 2, 320 TxIndex: 1, 321 } 322 got, err := s.ListTransactions(ctx, nil, nil, nil, nil, 2, &after, 0, nil) 323 require.NoError(t, err) 324 want := []*pb.Transaction{inserted[5], inserted[4]} 325 assert.Equal(t, want, got) 326 }) 327 328 t.Run("should return the transactions before the cursor when last is set", func(t *testing.T) { 329 before := entities.TxCursor{ 330 BlockNumber: 2, 331 TxIndex: 1, 332 } 333 got, err := s.ListTransactions(ctx, nil, nil, nil, nil, 0, nil, 2, &before) 334 require.NoError(t, err) 335 want := []*pb.Transaction{inserted[2], inserted[1]} 336 assert.Equal(t, want, got) 337 }) 338 339 t.Run("should return the transactions before the cursor when last is set", func(t *testing.T) { 340 before := entities.TxCursor{ 341 BlockNumber: 5, 342 TxIndex: 1, 343 } 344 after := entities.TxCursor{ 345 BlockNumber: 2, 346 TxIndex: 2, 347 } 348 got, err := s.ListTransactions(ctx, nil, nil, nil, nil, 0, &after, 0, &before) 349 require.NoError(t, err) 350 want := []*pb.Transaction{inserted[7], inserted[6], inserted[5]} 351 assert.Equal(t, want, got) 352 }) 353 }