diff --git a/services/horizon/internal/actions/effects.go b/services/horizon/internal/actions/effects.go index cc7406d7bc..e04997aca5 100644 --- a/services/horizon/internal/actions/effects.go +++ b/services/horizon/internal/actions/effects.go @@ -51,7 +51,7 @@ type GetEffectsHandler struct { } func (handler GetEffectsHandler) GetResourcePage(w HeaderWriter, r *http.Request) ([]hal.Pageable, error) { - pq, err := GetPageQuery(handler.LedgerState, r) + pq, err := GetPageQuery(handler.LedgerState, r, DefaultTOID) if err != nil { return nil, err } diff --git a/services/horizon/internal/actions/helpers.go b/services/horizon/internal/actions/helpers.go index 579e1056ff..2575b13251 100644 --- a/services/horizon/internal/actions/helpers.go +++ b/services/horizon/internal/actions/helpers.go @@ -21,6 +21,7 @@ import ( "github.com/stellar/go/services/horizon/internal/ledger" hProblem "github.com/stellar/go/services/horizon/internal/render/problem" "github.com/stellar/go/support/errors" + "github.com/stellar/go/support/ordered" "github.com/stellar/go/support/render/problem" "github.com/stellar/go/toid" "github.com/stellar/go/xdr" @@ -44,6 +45,8 @@ type Opt int const ( // DisableCursorValidation disables cursor validation in GetPageQuery DisableCursorValidation Opt = iota + // DefaultTOID sets a default cursor value in GetPageQuery based on the ledger state + DefaultTOID Opt = iota ) // HeaderWriter is an interface for setting HTTP response headers @@ -182,10 +185,14 @@ func getLimit(r *http.Request, name string, def uint64, max uint64) (uint64, err // using the results from a call to GetPagingParams() func GetPageQuery(ledgerState *ledger.State, r *http.Request, opts ...Opt) (db2.PageQuery, error) { disableCursorValidation := false + defaultTOID := false for _, opt := range opts { if opt == DisableCursorValidation { disableCursorValidation = true } + if opt == DefaultTOID { + defaultTOID = true + } } cursor, err := getCursor(ledgerState, r, ParamCursor) @@ -214,6 +221,13 @@ func GetPageQuery(ledgerState *ledger.State, r *http.Request, opts ...Opt) (db2. return db2.PageQuery{}, err } + if cursor == "" && defaultTOID { + if pageQuery.Order == db2.OrderAscending { + pageQuery.Cursor = toid.AfterLedger( + ordered.Max(0, ledgerState.CurrentStatus().HistoryElder-1), + ).String() + } + } return pageQuery, nil } diff --git a/services/horizon/internal/actions/helpers_test.go b/services/horizon/internal/actions/helpers_test.go index 445862c25e..05e840577e 100644 --- a/services/horizon/internal/actions/helpers_test.go +++ b/services/horizon/internal/actions/helpers_test.go @@ -8,6 +8,7 @@ import ( "net/url" "strings" "testing" + "time" "github.com/go-chi/chi" "github.com/stretchr/testify/assert" @@ -290,6 +291,63 @@ func TestGetPageQuery(t *testing.T) { tt.Assert.Error(err) } +func TestGetPageQueryCursorDefaultTOID(t *testing.T) { + ascReq := makeTestActionRequest("/foo-bar/blah?limit=2", testURLParams()) + descReq := makeTestActionRequest("/foo-bar/blah?limit=2&order=desc", testURLParams()) + + ledgerState := &ledger.State{} + ledgerState.SetHorizonStatus(ledger.HorizonStatus{ + HistoryLatest: 7000, + HistoryLatestClosedAt: time.Now(), + HistoryElder: 300, + ExpHistoryLatest: 7000, + }) + + pq, err := GetPageQuery(ledgerState, ascReq, DefaultTOID) + assert.NoError(t, err) + assert.Equal(t, toid.AfterLedger(299).String(), pq.Cursor) + assert.Equal(t, uint64(2), pq.Limit) + assert.Equal(t, "asc", pq.Order) + + pq, err = GetPageQuery(ledgerState, descReq, DefaultTOID) + assert.NoError(t, err) + assert.Equal(t, "", pq.Cursor) + assert.Equal(t, uint64(2), pq.Limit) + assert.Equal(t, "desc", pq.Order) + + pq, err = GetPageQuery(ledgerState, ascReq) + assert.NoError(t, err) + assert.Empty(t, pq.Cursor) + assert.Equal(t, uint64(2), pq.Limit) + assert.Equal(t, "asc", pq.Order) + + pq, err = GetPageQuery(ledgerState, descReq) + assert.NoError(t, err) + assert.Empty(t, pq.Cursor) + assert.Equal(t, "", pq.Cursor) + assert.Equal(t, "desc", pq.Order) + + ledgerState.SetHorizonStatus(ledger.HorizonStatus{ + HistoryLatest: 7000, + HistoryLatestClosedAt: time.Now(), + HistoryElder: 0, + ExpHistoryLatest: 7000, + }) + + pq, err = GetPageQuery(ledgerState, ascReq, DefaultTOID) + assert.NoError(t, err) + assert.Equal(t, toid.AfterLedger(0).String(), pq.Cursor) + assert.Equal(t, uint64(2), pq.Limit) + assert.Equal(t, "asc", pq.Order) + + pq, err = GetPageQuery(ledgerState, descReq, DefaultTOID) + assert.NoError(t, err) + assert.Equal(t, "", pq.Cursor) + assert.Equal(t, uint64(2), pq.Limit) + assert.Equal(t, "desc", pq.Order) + +} + func TestGetString(t *testing.T) { tt := test.Start(t) defer tt.Finish() diff --git a/services/horizon/internal/actions/ledger.go b/services/horizon/internal/actions/ledger.go index 37fddb5bd9..0b3d51b8e1 100644 --- a/services/horizon/internal/actions/ledger.go +++ b/services/horizon/internal/actions/ledger.go @@ -17,7 +17,7 @@ type GetLedgersHandler struct { } func (handler GetLedgersHandler) GetResourcePage(w HeaderWriter, r *http.Request) ([]hal.Pageable, error) { - pq, err := GetPageQuery(handler.LedgerState, r) + pq, err := GetPageQuery(handler.LedgerState, r, DefaultTOID) if err != nil { return nil, err } diff --git a/services/horizon/internal/actions/operation.go b/services/horizon/internal/actions/operation.go index f59191aee0..9ccadb272e 100644 --- a/services/horizon/internal/actions/operation.go +++ b/services/horizon/internal/actions/operation.go @@ -72,7 +72,7 @@ type GetOperationsHandler struct { func (handler GetOperationsHandler) GetResourcePage(w HeaderWriter, r *http.Request) ([]hal.Pageable, error) { ctx := r.Context() - pq, err := GetPageQuery(handler.LedgerState, r) + pq, err := GetPageQuery(handler.LedgerState, r, DefaultTOID) if err != nil { return nil, err } diff --git a/services/horizon/internal/actions/operation_test.go b/services/horizon/internal/actions/operation_test.go index 9aa033fd24..aaa21ef2a3 100644 --- a/services/horizon/internal/actions/operation_test.go +++ b/services/horizon/internal/actions/operation_test.go @@ -10,6 +10,8 @@ import ( "time" "github.com/guregu/null" + "github.com/stretchr/testify/assert" + "github.com/stellar/go/ingest" "github.com/stellar/go/protocols/horizon/operations" "github.com/stellar/go/services/horizon/internal/db2/history" @@ -19,7 +21,6 @@ import ( supportProblem "github.com/stellar/go/support/render/problem" "github.com/stellar/go/toid" "github.com/stellar/go/xdr" - "github.com/stretchr/testify/assert" ) func TestInvokeHostFnDetailsInPaymentOperations(t *testing.T) { @@ -28,8 +29,14 @@ func TestInvokeHostFnDetailsInPaymentOperations(t *testing.T) { test.ResetHorizonDB(t, tt.HorizonDB) q := &history.Q{tt.HorizonSession()} - handler := GetOperationsHandler{OnlyPayments: true} - + handler := GetOperationsHandler{OnlyPayments: true, + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetHorizonStatus(ledger.HorizonStatus{ + HistoryLatest: 56, + HistoryElder: 56, + ExpHistoryLatest: 56, + }) txIndex := int32(1) sequence := int32(56) txID := toid.New(sequence, txIndex, 0).ToInt64() @@ -178,10 +185,12 @@ func TestInvokeHostFnDetailsInPaymentOperations(t *testing.T) { func TestGetOperationsWithoutFilter(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("base") q := &history.Q{tt.HorizonSession()} - handler := GetOperationsHandler{} + handler := GetOperationsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetStatus(tt.Scenario("base")) records, err := handler.GetResourcePage( httptest.NewRecorder(), @@ -196,10 +205,12 @@ func TestGetOperationsWithoutFilter(t *testing.T) { func TestGetOperationsExclusiveFilters(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("base") q := &history.Q{tt.HorizonSession()} - handler := GetOperationsHandler{} + handler := GetOperationsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetStatus(tt.Scenario("base")) testCases := []struct { desc string @@ -255,10 +266,12 @@ func TestGetOperationsByLiquidityPool(t *testing.T) { func TestGetOperationsFilterByAccountID(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("base") q := &history.Q{tt.HorizonSession()} - handler := GetOperationsHandler{} + handler := GetOperationsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetStatus(tt.Scenario("base")) testCases := []struct { accountID string @@ -296,10 +309,12 @@ func TestGetOperationsFilterByAccountID(t *testing.T) { func TestGetOperationsFilterByTxID(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("base") q := &history.Q{tt.HorizonSession()} - handler := GetOperationsHandler{} + handler := GetOperationsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetStatus(tt.Scenario("base")) testCases := []struct { desc string @@ -370,10 +385,12 @@ func TestGetOperationsFilterByTxID(t *testing.T) { func TestGetOperationsIncludeFailed(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("failed_transactions") q := &history.Q{tt.HorizonSession()} - handler := GetOperationsHandler{} + handler := GetOperationsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetStatus(tt.Scenario("failed_transactions")) records, err := handler.GetResourcePage( httptest.NewRecorder(), @@ -500,10 +517,12 @@ func TestGetOperationsIncludeFailed(t *testing.T) { func TestGetOperationsFilterByLedgerID(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("base") q := &history.Q{tt.HorizonSession()} - handler := GetOperationsHandler{} + handler := GetOperationsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetStatus(tt.Scenario("base")) testCases := []struct { ledgerID string @@ -571,12 +590,13 @@ func TestGetOperationsFilterByLedgerID(t *testing.T) { func TestGetOperationsOnlyPayments(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("base") q := &history.Q{tt.HorizonSession()} handler := GetOperationsHandler{ + LedgerState: &ledger.State{}, OnlyPayments: true, } + handler.LedgerState.SetStatus(tt.Scenario("base")) records, err := handler.GetResourcePage( httptest.NewRecorder(), @@ -620,7 +640,7 @@ func TestGetOperationsOnlyPayments(t *testing.T) { tt.Assert.NoError(err) tt.Assert.Len(records, 1) - tt.Scenario("pathed_payment") + handler.LedgerState.SetStatus(tt.Scenario("pathed_payment")) records, err = handler.GetResourcePage( httptest.NewRecorder(), @@ -651,10 +671,12 @@ func TestGetOperationsOnlyPayments(t *testing.T) { func TestOperation_CreatedAt(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("base") q := &history.Q{tt.HorizonSession()} - handler := GetOperationsHandler{} + handler := GetOperationsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetStatus(tt.Scenario("base")) records, err := handler.GetResourcePage( httptest.NewRecorder(), @@ -676,12 +698,12 @@ func TestOperation_CreatedAt(t *testing.T) { func TestGetOperationsPagination(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("base") q := &history.Q{tt.HorizonSession()} handler := GetOperationsHandler{ LedgerState: &ledger.State{}, } + handler.LedgerState.SetStatus(tt.Scenario("base")) records, err := handler.GetResourcePage( httptest.NewRecorder(), @@ -735,10 +757,12 @@ func TestGetOperationsPagination(t *testing.T) { func TestGetOperations_IncludeTransactions(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("failed_transactions") q := &history.Q{tt.HorizonSession()} - handler := GetOperationsHandler{} + handler := GetOperationsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetStatus(tt.Scenario("failed_transactions")) _, err := handler.GetResourcePage( httptest.NewRecorder(), @@ -828,11 +852,12 @@ func TestGetOperation(t *testing.T) { func TestOperation_IncludeTransaction(t *testing.T) { tt := test.Start(t) defer tt.Finish() - tt.Scenario("kahuna") handler := GetOperationByIDHandler{ LedgerState: &ledger.State{}, } + handler.LedgerState.SetStatus(tt.Scenario("kahuna")) + record, err := handler.GetResource( httptest.NewRecorder(), makeRequest( diff --git a/services/horizon/internal/actions/trade.go b/services/horizon/internal/actions/trade.go index d6bf415424..b29ad64715 100644 --- a/services/horizon/internal/actions/trade.go +++ b/services/horizon/internal/actions/trade.go @@ -159,7 +159,7 @@ type GetTradesHandler struct { func (handler GetTradesHandler) GetResourcePage(w HeaderWriter, r *http.Request) ([]hal.Pageable, error) { ctx := r.Context() - pq, err := GetPageQuery(handler.LedgerState, r) + pq, err := GetPageQuery(handler.LedgerState, r, DefaultTOID) if err != nil { return nil, err } diff --git a/services/horizon/internal/actions/transaction.go b/services/horizon/internal/actions/transaction.go index 6903d5db2f..6a2fd1c6e2 100644 --- a/services/horizon/internal/actions/transaction.go +++ b/services/horizon/internal/actions/transaction.go @@ -98,7 +98,7 @@ type GetTransactionsHandler struct { func (handler GetTransactionsHandler) GetResourcePage(w HeaderWriter, r *http.Request) ([]hal.Pageable, error) { ctx := r.Context() - pq, err := GetPageQuery(handler.LedgerState, r) + pq, err := GetPageQuery(handler.LedgerState, r, DefaultTOID) if err != nil { return nil, err } diff --git a/services/horizon/internal/actions/transaction_test.go b/services/horizon/internal/actions/transaction_test.go index b76cf1b0bf..d501e38213 100644 --- a/services/horizon/internal/actions/transaction_test.go +++ b/services/horizon/internal/actions/transaction_test.go @@ -6,6 +6,7 @@ import ( "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/services/horizon/internal/db2/history" + "github.com/stellar/go/services/horizon/internal/ledger" "github.com/stellar/go/services/horizon/internal/test" supportProblem "github.com/stellar/go/support/render/problem" ) @@ -16,7 +17,10 @@ func TestGetTransactionsHandler(t *testing.T) { defer tt.Finish() q := &history.Q{tt.HorizonSession()} - handler := GetTransactionsHandler{} + handler := GetTransactionsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetStatus(tt.Scenario("base")) // filter by account records, err := handler.GetResourcePage( @@ -155,7 +159,14 @@ func TestFeeBumpTransactionPage(t *testing.T) { test.ResetHorizonDB(t, tt.HorizonDB) q := &history.Q{tt.HorizonSession()} fixture := history.FeeBumpScenario(tt, q, true) - handler := GetTransactionsHandler{} + handler := GetTransactionsHandler{ + LedgerState: &ledger.State{}, + } + handler.LedgerState.SetHorizonStatus(ledger.HorizonStatus{ + HistoryLatest: fixture.Ledger.Sequence, + HistoryElder: fixture.Ledger.Sequence, + ExpHistoryLatest: uint32(fixture.Ledger.Sequence), + }) records, err := handler.GetResourcePage( httptest.NewRecorder(),