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  }