diff --git a/cmd/stellar-rpc/internal/db/ledger.go b/cmd/stellar-rpc/internal/db/ledger.go index 762309ca..b9b157d8 100644 --- a/cmd/stellar-rpc/internal/db/ledger.go +++ b/cmd/stellar-rpc/internal/db/ledger.go @@ -28,6 +28,7 @@ type LedgerReader interface { } type LedgerReaderTx interface { + GetLedger(ctx context.Context, sequence uint32) (xdr.LedgerCloseMeta, bool, error) GetLedgerRange(ctx context.Context) (ledgerbucketwindow.LedgerRange, error) BatchGetLedgers(ctx context.Context, sequence uint32, batchSize uint) ([]xdr.LedgerCloseMeta, error) Done() error @@ -77,6 +78,24 @@ func (l ledgerReaderTx) BatchGetLedgers(ctx context.Context, sequence uint32, return results, nil } +// GetLedger fetches a single ledger from the db. +func (l ledgerReaderTx) GetLedger(ctx context.Context, sequence uint32) (xdr.LedgerCloseMeta, bool, error) { + sql := sq.Select("meta").From(ledgerCloseMetaTableName).Where(sq.Eq{"sequence": sequence}) + var results []xdr.LedgerCloseMeta + if err := l.tx.Select(ctx, &results, sql); err != nil { + return xdr.LedgerCloseMeta{}, false, err + } + switch len(results) { + case 0: + return xdr.LedgerCloseMeta{}, false, nil + case 1: + return results[0], true, nil + default: + return xdr.LedgerCloseMeta{}, false, fmt.Errorf("multiple lcm entries (%d) for sequence %d in table %q", + len(results), sequence, ledgerCloseMetaTableName) + } +} + func (l ledgerReaderTx) Done() error { return l.tx.Rollback() } diff --git a/cmd/stellar-rpc/internal/methods/get_transactions.go b/cmd/stellar-rpc/internal/methods/get_transactions.go index 1f3d4b33..20d4c6a2 100644 --- a/cmd/stellar-rpc/internal/methods/get_transactions.go +++ b/cmd/stellar-rpc/internal/methods/get_transactions.go @@ -136,8 +136,10 @@ func (h transactionsRPCHandler) initializePagination(request GetTransactionsRequ } // fetchLedgerData calls the meta table to fetch the corresponding ledger data. -func (h transactionsRPCHandler) fetchLedgerData(ctx context.Context, ledgerSeq uint32) (xdr.LedgerCloseMeta, error) { - ledger, found, err := h.ledgerReader.GetLedger(ctx, ledgerSeq) +func (h transactionsRPCHandler) fetchLedgerData(ctx context.Context, ledgerSeq uint32, + readTx db.LedgerReaderTx, +) (xdr.LedgerCloseMeta, error) { + ledger, found, err := readTx.GetLedger(ctx, ledgerSeq) if err != nil { return ledger, &jrpc2.Error{ Code: jrpc2.InternalError, @@ -262,7 +264,18 @@ func (h transactionsRPCHandler) processTransactionsInLedger( func (h transactionsRPCHandler) getTransactionsByLedgerSequence(ctx context.Context, request GetTransactionsRequest, ) (GetTransactionsResponse, error) { - ledgerRange, err := h.ledgerReader.GetLedgerRange(ctx) + readTx, err := h.ledgerReader.NewTx(ctx) + if err != nil { + return GetTransactionsResponse{}, &jrpc2.Error{ + Code: jrpc2.InternalError, + Message: err.Error(), + } + } + defer func() { + _ = readTx.Done() + }() + + ledgerRange, err := readTx.GetLedgerRange(ctx) if err != nil { return GetTransactionsResponse{}, &jrpc2.Error{ Code: jrpc2.InternalError, @@ -289,7 +302,7 @@ func (h transactionsRPCHandler) getTransactionsByLedgerSequence(ctx context.Cont var done bool cursor := toid.New(0, 0, 0) for ledgerSeq := start.LedgerSequence; ledgerSeq <= int32(ledgerRange.LastLedger.Sequence); ledgerSeq++ { - ledger, err := h.fetchLedgerData(ctx, uint32(ledgerSeq)) + ledger, err := h.fetchLedgerData(ctx, uint32(ledgerSeq), readTx) if err != nil { return GetTransactionsResponse{}, err } diff --git a/cmd/stellar-rpc/internal/methods/get_transactions_test.go b/cmd/stellar-rpc/internal/methods/get_transactions_test.go index 3ad026ab..a240168e 100644 --- a/cmd/stellar-rpc/internal/methods/get_transactions_test.go +++ b/cmd/stellar-rpc/internal/methods/get_transactions_test.go @@ -7,12 +7,14 @@ import ( "testing" "github.com/creachadair/jrpc2" + "github.com/stellar/go/support/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stellar/go/toid" "github.com/stellar/go/xdr" + "github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/daemon/interfaces" "github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/db" ) @@ -53,17 +55,26 @@ func createTestLedger(sequence uint32) xdr.LedgerCloseMeta { return meta } -func TestGetTransactions_DefaultLimit(t *testing.T) { - mockDBReader := db.NewMockTransactionStore(NetworkPassphrase) - mockLedgerReader := db.NewMockLedgerReader(mockDBReader) - for i := 1; i <= 10; i++ { - meta := createTestLedger(uint32(i)) - err := mockDBReader.InsertTransactions(meta) +func setupDB(t *testing.T, numLedgers int, skipLedger int) *db.DB { + testDB := NewTestDB(t) + daemon := interfaces.MakeNoOpDeamon() + for sequence := 1; sequence <= numLedgers; sequence++ { + if sequence == skipLedger { + continue + } + ledgerCloseMeta := createTestLedger(uint32(sequence)) + tx, err := db.NewReadWriter(log.DefaultLogger, testDB, daemon, 150, 100, passphrase).NewTx(context.Background()) require.NoError(t, err) + require.NoError(t, tx.LedgerWriter().InsertLedger(ledgerCloseMeta)) + require.NoError(t, tx.Commit(ledgerCloseMeta)) } + return testDB +} +func TestGetTransactions_DefaultLimit(t *testing.T) { + testDB := setupDB(t, 10, 0) handler := transactionsRPCHandler{ - ledgerReader: mockLedgerReader, + ledgerReader: db.NewLedgerReader(testDB), maxLimit: 100, defaultLimit: 10, networkPassphrase: NetworkPassphrase, @@ -91,16 +102,9 @@ func TestGetTransactions_DefaultLimit(t *testing.T) { } func TestGetTransactions_DefaultLimitExceedsLatestLedger(t *testing.T) { - mockDBReader := db.NewMockTransactionStore(NetworkPassphrase) - mockLedgerReader := db.NewMockLedgerReader(mockDBReader) - for i := 1; i <= 3; i++ { - meta := createTestLedger(uint32(i)) - err := mockDBReader.InsertTransactions(meta) - require.NoError(t, err) - } - + testDB := setupDB(t, 3, 0) handler := transactionsRPCHandler{ - ledgerReader: mockLedgerReader, + ledgerReader: db.NewLedgerReader(testDB), maxLimit: 100, defaultLimit: 10, networkPassphrase: NetworkPassphrase, @@ -128,16 +132,9 @@ func TestGetTransactions_DefaultLimitExceedsLatestLedger(t *testing.T) { } func TestGetTransactions_CustomLimit(t *testing.T) { - mockDBReader := db.NewMockTransactionStore(NetworkPassphrase) - mockLedgerReader := db.NewMockLedgerReader(mockDBReader) - for i := 1; i <= 10; i++ { - meta := createTestLedger(uint32(i)) - err := mockDBReader.InsertTransactions(meta) - require.NoError(t, err) - } - + testDB := setupDB(t, 10, 0) handler := transactionsRPCHandler{ - ledgerReader: mockLedgerReader, + ledgerReader: db.NewLedgerReader(testDB), maxLimit: 100, defaultLimit: 10, networkPassphrase: NetworkPassphrase, @@ -170,16 +167,9 @@ func TestGetTransactions_CustomLimit(t *testing.T) { } func TestGetTransactions_CustomLimitAndCursor(t *testing.T) { - mockDBReader := db.NewMockTransactionStore(NetworkPassphrase) - mockLedgerReader := db.NewMockLedgerReader(mockDBReader) - for i := 1; i <= 10; i++ { - meta := createTestLedger(uint32(i)) - err := mockDBReader.InsertTransactions(meta) - require.NoError(t, err) - } - + testDB := setupDB(t, 10, 0) handler := transactionsRPCHandler{ - ledgerReader: mockLedgerReader, + ledgerReader: db.NewLedgerReader(testDB), maxLimit: 100, defaultLimit: 10, networkPassphrase: NetworkPassphrase, @@ -210,16 +200,9 @@ func TestGetTransactions_CustomLimitAndCursor(t *testing.T) { } func TestGetTransactions_InvalidStartLedger(t *testing.T) { - mockDBReader := db.NewMockTransactionStore(NetworkPassphrase) - mockLedgerReader := db.NewMockLedgerReader(mockDBReader) - for i := 1; i <= 3; i++ { - meta := createTestLedger(uint32(i)) - err := mockDBReader.InsertTransactions(meta) - require.NoError(t, err) - } - + testDB := setupDB(t, 3, 0) handler := transactionsRPCHandler{ - ledgerReader: mockLedgerReader, + ledgerReader: db.NewLedgerReader(testDB), maxLimit: 100, defaultLimit: 10, networkPassphrase: NetworkPassphrase, @@ -239,20 +222,9 @@ func TestGetTransactions_InvalidStartLedger(t *testing.T) { } func TestGetTransactions_LedgerNotFound(t *testing.T) { - mockDBReader := db.NewMockTransactionStore(NetworkPassphrase) - mockLedgerReader := db.NewMockLedgerReader(mockDBReader) - for i := 1; i <= 3; i++ { - // Skip creation of ledger 2 - if i == 2 { - continue - } - meta := createTestLedger(uint32(i)) - err := mockDBReader.InsertTransactions(meta) - require.NoError(t, err) - } - + testDB := setupDB(t, 3, 2) handler := transactionsRPCHandler{ - ledgerReader: mockLedgerReader, + ledgerReader: db.NewLedgerReader(testDB), maxLimit: 100, defaultLimit: 10, networkPassphrase: NetworkPassphrase, @@ -269,16 +241,9 @@ func TestGetTransactions_LedgerNotFound(t *testing.T) { } func TestGetTransactions_LimitGreaterThanMaxLimit(t *testing.T) { - mockDBReader := db.NewMockTransactionStore(NetworkPassphrase) - mockLedgerReader := db.NewMockLedgerReader(mockDBReader) - for i := 1; i <= 3; i++ { - meta := createTestLedger(uint32(i)) - err := mockDBReader.InsertTransactions(meta) - require.NoError(t, err) - } - + testDB := setupDB(t, 3, 0) handler := transactionsRPCHandler{ - ledgerReader: mockLedgerReader, + ledgerReader: db.NewLedgerReader(testDB), maxLimit: 100, defaultLimit: 10, networkPassphrase: NetworkPassphrase, @@ -297,16 +262,9 @@ func TestGetTransactions_LimitGreaterThanMaxLimit(t *testing.T) { } func TestGetTransactions_InvalidCursorString(t *testing.T) { - mockDBReader := db.NewMockTransactionStore(NetworkPassphrase) - mockLedgerReader := db.NewMockLedgerReader(mockDBReader) - for i := 1; i <= 3; i++ { - meta := createTestLedger(uint32(i)) - err := mockDBReader.InsertTransactions(meta) - require.NoError(t, err) - } - + testDB := setupDB(t, 3, 0) handler := transactionsRPCHandler{ - ledgerReader: mockLedgerReader, + ledgerReader: db.NewLedgerReader(testDB), maxLimit: 100, defaultLimit: 10, networkPassphrase: NetworkPassphrase, @@ -324,16 +282,9 @@ func TestGetTransactions_InvalidCursorString(t *testing.T) { } func TestGetTransactions_JSONFormat(t *testing.T) { - mockDBReader := db.NewMockTransactionStore(NetworkPassphrase) - mockLedgerReader := db.NewMockLedgerReader(mockDBReader) - for i := 1; i <= 3; i++ { - meta := createTestLedger(uint32(i)) - err := mockDBReader.InsertTransactions(meta) - require.NoError(t, err) - } - + testDB := setupDB(t, 3, 0) handler := transactionsRPCHandler{ - ledgerReader: mockLedgerReader, + ledgerReader: db.NewLedgerReader(testDB), maxLimit: 100, defaultLimit: 10, networkPassphrase: NetworkPassphrase,