From bd514bce74628a1eeddb758d5522f11c3164ddc1 Mon Sep 17 00:00:00 2001 From: Artem Poltorzhitskiy Date: Sat, 17 Feb 2024 00:37:31 +0100 Subject: [PATCH] Fix: event requests (#141) --- cmd/api/handler/block.go | 16 ++++++++- cmd/api/handler/block_test.go | 10 ++++-- cmd/api/handler/tx.go | 8 ++++- cmd/api/handler/tx_test.go | 2 +- internal/storage/block.go | 1 + internal/storage/event.go | 10 ++++-- internal/storage/mock/block.go | 40 +++++++++++++++++++++ internal/storage/mock/event.go | 24 ++++++------- internal/storage/postgres/block.go | 10 ++++++ internal/storage/postgres/block_test.go | 12 +++++++ internal/storage/postgres/event.go | 30 +++++++++++----- internal/storage/postgres/event_test.go | 42 +++++++++++++++++++++++ internal/storage/postgres/storage_test.go | 26 -------------- 13 files changed, 178 insertions(+), 53 deletions(-) create mode 100644 internal/storage/postgres/event_test.go diff --git a/cmd/api/handler/block.go b/cmd/api/handler/block.go index 39741e39..e194fdfb 100644 --- a/cmd/api/handler/block.go +++ b/cmd/api/handler/block.go @@ -180,7 +180,21 @@ func (handler *BlockHandler) GetEvents(c echo.Context) error { } req.SetDefault() - events, err := handler.events.ByBlock(c.Request().Context(), req.Height, req.Limit, req.Offset) + blockTime, err := handler.block.Time(c.Request().Context(), req.Height) + if err != nil { + if handler.block.IsNoRows(err) { + return returnArray(c, []any{}) + } + return internalServerError(c, err) + } + + fltrs := storage.EventFilter{ + Limit: req.Limit, + Offset: req.Offset, + Time: blockTime.UTC(), + } + + events, err := handler.events.ByBlock(c.Request().Context(), req.Height, fltrs) if err != nil { return handleError(c, err, handler.block) } diff --git a/cmd/api/handler/block_test.go b/cmd/api/handler/block_test.go index 7f4062b3..ff563997 100644 --- a/cmd/api/handler/block_test.go +++ b/cmd/api/handler/block_test.go @@ -304,8 +304,13 @@ func (s *BlockTestSuite) TestGetEvents() { c.SetParamNames("height") c.SetParamValues("100") + s.blocks.EXPECT(). + Time(gomock.Any(), pkgTypes.Level(100)). + Return(testTime, nil). + Times(1) + s.events.EXPECT(). - ByBlock(gomock.Any(), pkgTypes.Level(100), 2, 0). + ByBlock(gomock.Any(), pkgTypes.Level(100), gomock.Any()). Return([]storage.Event{ { Id: 1, @@ -318,7 +323,8 @@ func (s *BlockTestSuite) TestGetEvents() { "test": "value", }, }, - }, nil) + }, nil). + Times(1) s.Require().NoError(s.handler.GetEvents(c)) s.Require().Equal(http.StatusOK, rec.Code) diff --git a/cmd/api/handler/tx.go b/cmd/api/handler/tx.go index f3d022a4..13dc9a6c 100644 --- a/cmd/api/handler/tx.go +++ b/cmd/api/handler/tx.go @@ -186,7 +186,13 @@ func (handler *TxHandler) GetEvents(c echo.Context) error { return handleError(c, err, handler.tx) } - events, err := handler.events.ByTxId(c.Request().Context(), tx.Id, req.Limit, req.Offset) + fltrs := storage.EventFilter{ + Limit: req.Limit, + Offset: req.Offset, + Time: tx.Time.UTC(), + } + + events, err := handler.events.ByTxId(c.Request().Context(), tx.Id, fltrs) if err != nil { return handleError(c, err, handler.tx) } diff --git a/cmd/api/handler/tx_test.go b/cmd/api/handler/tx_test.go index f464808b..6f2c00a7 100644 --- a/cmd/api/handler/tx_test.go +++ b/cmd/api/handler/tx_test.go @@ -421,7 +421,7 @@ func (s *TxTestSuite) TestGetEvents() { Return(testTx, nil) s.events.EXPECT(). - ByTxId(gomock.Any(), uint64(1), 2, 0). + ByTxId(gomock.Any(), uint64(1), gomock.Any()). Return([]storage.Event{ { Id: 1, diff --git a/internal/storage/block.go b/internal/storage/block.go index 7ee5b7f0..3a4c52cb 100644 --- a/internal/storage/block.go +++ b/internal/storage/block.go @@ -25,6 +25,7 @@ type IBlock interface { ByHash(ctx context.Context, hash []byte) (Block, error) ByProposer(ctx context.Context, proposerId uint64, limit, offset int) ([]Block, error) ListWithStats(ctx context.Context, limit, offset uint64, order storage.SortOrder) ([]*Block, error) + Time(ctx context.Context, height pkgTypes.Level) (time.Time, error) } // Block - diff --git a/internal/storage/event.go b/internal/storage/event.go index 83d5841f..5602939a 100644 --- a/internal/storage/event.go +++ b/internal/storage/event.go @@ -17,12 +17,18 @@ import ( var json = jsoniter.ConfigCompatibleWithStandardLibrary +type EventFilter struct { + Limit int + Offset int + Time time.Time +} + //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed type IEvent interface { storage.Table[*Event] - ByTxId(ctx context.Context, txId uint64, limit, offset int) ([]Event, error) - ByBlock(ctx context.Context, height pkgTypes.Level, limit, offset int) ([]Event, error) + ByTxId(ctx context.Context, txId uint64, fltrs EventFilter) ([]Event, error) + ByBlock(ctx context.Context, height pkgTypes.Level, fltrs EventFilter) ([]Event, error) } // Event - diff --git a/internal/storage/mock/block.go b/internal/storage/mock/block.go index ddf5634d..34ca7e54 100644 --- a/internal/storage/mock/block.go +++ b/internal/storage/mock/block.go @@ -14,6 +14,7 @@ package mock import ( context "context" reflect "reflect" + time "time" storage "github.com/celenium-io/celestia-indexer/internal/storage" types "github.com/celenium-io/celestia-indexer/pkg/types" @@ -549,6 +550,45 @@ func (c *IBlockSaveCall) DoAndReturn(f func(context.Context, *storage.Block) err return c } +// Time mocks base method. +func (m *MockIBlock) Time(ctx context.Context, height types.Level) (time.Time, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Time", ctx, height) + ret0, _ := ret[0].(time.Time) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Time indicates an expected call of Time. +func (mr *MockIBlockMockRecorder) Time(ctx, height any) *IBlockTimeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Time", reflect.TypeOf((*MockIBlock)(nil).Time), ctx, height) + return &IBlockTimeCall{Call: call} +} + +// IBlockTimeCall wrap *gomock.Call +type IBlockTimeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *IBlockTimeCall) Return(arg0 time.Time, arg1 error) *IBlockTimeCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *IBlockTimeCall) Do(f func(context.Context, types.Level) (time.Time, error)) *IBlockTimeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *IBlockTimeCall) DoAndReturn(f func(context.Context, types.Level) (time.Time, error)) *IBlockTimeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // Update mocks base method. func (m_2 *MockIBlock) Update(ctx context.Context, m *storage.Block) error { m_2.ctrl.T.Helper() diff --git a/internal/storage/mock/event.go b/internal/storage/mock/event.go index 8e596ad5..53d1c6dd 100644 --- a/internal/storage/mock/event.go +++ b/internal/storage/mock/event.go @@ -45,18 +45,18 @@ func (m *MockIEvent) EXPECT() *MockIEventMockRecorder { } // ByBlock mocks base method. -func (m *MockIEvent) ByBlock(ctx context.Context, height types.Level, limit, offset int) ([]storage.Event, error) { +func (m *MockIEvent) ByBlock(ctx context.Context, height types.Level, fltrs storage.EventFilter) ([]storage.Event, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ByBlock", ctx, height, limit, offset) + ret := m.ctrl.Call(m, "ByBlock", ctx, height, fltrs) ret0, _ := ret[0].([]storage.Event) ret1, _ := ret[1].(error) return ret0, ret1 } // ByBlock indicates an expected call of ByBlock. -func (mr *MockIEventMockRecorder) ByBlock(ctx, height, limit, offset any) *IEventByBlockCall { +func (mr *MockIEventMockRecorder) ByBlock(ctx, height, fltrs any) *IEventByBlockCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ByBlock", reflect.TypeOf((*MockIEvent)(nil).ByBlock), ctx, height, limit, offset) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ByBlock", reflect.TypeOf((*MockIEvent)(nil).ByBlock), ctx, height, fltrs) return &IEventByBlockCall{Call: call} } @@ -72,30 +72,30 @@ func (c *IEventByBlockCall) Return(arg0 []storage.Event, arg1 error) *IEventByBl } // Do rewrite *gomock.Call.Do -func (c *IEventByBlockCall) Do(f func(context.Context, types.Level, int, int) ([]storage.Event, error)) *IEventByBlockCall { +func (c *IEventByBlockCall) Do(f func(context.Context, types.Level, storage.EventFilter) ([]storage.Event, error)) *IEventByBlockCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *IEventByBlockCall) DoAndReturn(f func(context.Context, types.Level, int, int) ([]storage.Event, error)) *IEventByBlockCall { +func (c *IEventByBlockCall) DoAndReturn(f func(context.Context, types.Level, storage.EventFilter) ([]storage.Event, error)) *IEventByBlockCall { c.Call = c.Call.DoAndReturn(f) return c } // ByTxId mocks base method. -func (m *MockIEvent) ByTxId(ctx context.Context, txId uint64, limit, offset int) ([]storage.Event, error) { +func (m *MockIEvent) ByTxId(ctx context.Context, txId uint64, fltrs storage.EventFilter) ([]storage.Event, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ByTxId", ctx, txId, limit, offset) + ret := m.ctrl.Call(m, "ByTxId", ctx, txId, fltrs) ret0, _ := ret[0].([]storage.Event) ret1, _ := ret[1].(error) return ret0, ret1 } // ByTxId indicates an expected call of ByTxId. -func (mr *MockIEventMockRecorder) ByTxId(ctx, txId, limit, offset any) *IEventByTxIdCall { +func (mr *MockIEventMockRecorder) ByTxId(ctx, txId, fltrs any) *IEventByTxIdCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ByTxId", reflect.TypeOf((*MockIEvent)(nil).ByTxId), ctx, txId, limit, offset) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ByTxId", reflect.TypeOf((*MockIEvent)(nil).ByTxId), ctx, txId, fltrs) return &IEventByTxIdCall{Call: call} } @@ -111,13 +111,13 @@ func (c *IEventByTxIdCall) Return(arg0 []storage.Event, arg1 error) *IEventByTxI } // Do rewrite *gomock.Call.Do -func (c *IEventByTxIdCall) Do(f func(context.Context, uint64, int, int) ([]storage.Event, error)) *IEventByTxIdCall { +func (c *IEventByTxIdCall) Do(f func(context.Context, uint64, storage.EventFilter) ([]storage.Event, error)) *IEventByTxIdCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *IEventByTxIdCall) DoAndReturn(f func(context.Context, uint64, int, int) ([]storage.Event, error)) *IEventByTxIdCall { +func (c *IEventByTxIdCall) DoAndReturn(f func(context.Context, uint64, storage.EventFilter) ([]storage.Event, error)) *IEventByTxIdCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/internal/storage/postgres/block.go b/internal/storage/postgres/block.go index 610fb4a0..905c8134 100644 --- a/internal/storage/postgres/block.go +++ b/internal/storage/postgres/block.go @@ -5,10 +5,12 @@ package postgres import ( "context" + "time" "github.com/celenium-io/celestia-indexer/internal/storage" storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" "github.com/celenium-io/celestia-indexer/pkg/types" + pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" "github.com/dipdup-net/go-lib/database" sdk "github.com/dipdup-net/indexer-sdk/pkg/storage" "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" @@ -243,3 +245,11 @@ func (b *Blocks) ByProposer(ctx context.Context, proposerId uint64, limit, offse err = query.Scan(ctx) return } + +func (b *Blocks) Time(ctx context.Context, height pkgTypes.Level) (response time.Time, err error) { + err = b.DB().NewSelect().Model((*storage.Block)(nil)). + Column("time"). + Where("height = ?", height). + Scan(ctx, &response) + return +} diff --git a/internal/storage/postgres/block_test.go b/internal/storage/postgres/block_test.go index f688374a..c6a36618 100644 --- a/internal/storage/postgres/block_test.go +++ b/internal/storage/postgres/block_test.go @@ -222,3 +222,15 @@ func (s *StorageTestSuite) TestBlockByProposer() { s.Require().EqualValues(11000, block.Stats.BlockTime) s.Require().EqualValues(4, block.Stats.BlobsCount) } + +func (s *StorageTestSuite) TestBlockTime() { + ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer ctxCancel() + + expected, err := time.Parse(time.RFC3339, "2023-07-04T03:10:57Z") + s.Require().NoError(err) + + blockTime, err := s.storage.Blocks.Time(ctx, 1000) + s.Require().NoError(err) + s.Require().Equal(expected.UTC(), blockTime.UTC()) +} diff --git a/internal/storage/postgres/event.go b/internal/storage/postgres/event.go index 892267a0..d6747ebe 100644 --- a/internal/storage/postgres/event.go +++ b/internal/storage/postgres/event.go @@ -5,11 +5,13 @@ package postgres import ( "context" + "time" pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" "github.com/celenium-io/celestia-indexer/internal/storage" "github.com/dipdup-net/go-lib/database" + sdk "github.com/dipdup-net/indexer-sdk/pkg/storage" "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" ) @@ -26,13 +28,19 @@ func NewEvent(db *database.Bun) *Event { } // ByTxId - -func (e *Event) ByTxId(ctx context.Context, txId uint64, limit, offset int) (events []storage.Event, err error) { +func (e *Event) ByTxId(ctx context.Context, txId uint64, fltrs storage.EventFilter) (events []storage.Event, err error) { query := e.DB().NewSelect().Model(&events). Where("tx_id = ?", txId) - query = limitScope(query, limit) + query = limitScope(query, fltrs.Limit) + query = sortScope(query, "id", sdk.SortOrderAsc) - if offset > 0 { - query = query.Offset(offset) + if fltrs.Offset > 0 { + query = query.Offset(fltrs.Offset) + } + if !fltrs.Time.IsZero() { + query = query. + Where("time >= ?", fltrs.Time). + Where("time < ?", fltrs.Time.Add(time.Second)) } err = query.Scan(ctx) @@ -40,15 +48,21 @@ func (e *Event) ByTxId(ctx context.Context, txId uint64, limit, offset int) (eve } // ByBlock - -func (e *Event) ByBlock(ctx context.Context, height pkgTypes.Level, limit, offset int) (events []storage.Event, err error) { +func (e *Event) ByBlock(ctx context.Context, height pkgTypes.Level, fltrs storage.EventFilter) (events []storage.Event, err error) { query := e.DB().NewSelect().Model(&events). Where("height = ?", height). Where("tx_id IS NULL") - query = limitScope(query, limit) + query = limitScope(query, fltrs.Limit) + query = sortScope(query, "id", sdk.SortOrderAsc) - if offset > 0 { - query = query.Offset(offset) + if fltrs.Offset > 0 { + query = query.Offset(fltrs.Offset) + } + if !fltrs.Time.IsZero() { + query = query. + Where("time >= ?", fltrs.Time). + Where("time < ?", fltrs.Time.Add(time.Second)) } err = query.Scan(ctx) return diff --git a/internal/storage/postgres/event_test.go b/internal/storage/postgres/event_test.go new file mode 100644 index 00000000..6a0903ac --- /dev/null +++ b/internal/storage/postgres/event_test.go @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 PK Lab AG +// SPDX-License-Identifier: MIT + +package postgres + +import ( + "context" + "time" + + "github.com/celenium-io/celestia-indexer/internal/storage" + "github.com/celenium-io/celestia-indexer/internal/storage/types" +) + +func (s *StorageTestSuite) TestEventByTxId() { + ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer ctxCancel() + + events, err := s.storage.Event.ByTxId(ctx, 1, storage.EventFilter{ + Limit: 10, + }) + s.Require().NoError(err) + s.Require().Len(events, 1) + s.Require().EqualValues(2, events[0].Id) + s.Require().EqualValues(1000, events[0].Height) + s.Require().EqualValues(1, events[0].Position) + s.Require().Equal(types.EventTypeMint, events[0].Type) +} + +func (s *StorageTestSuite) TestEventByBlock() { + ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer ctxCancel() + + events, err := s.storage.Event.ByBlock(ctx, 1000, storage.EventFilter{ + Limit: 2, + }) + s.Require().NoError(err) + s.Require().Len(events, 1) + s.Require().EqualValues(1, events[0].Id) + s.Require().EqualValues(1000, events[0].Height) + s.Require().EqualValues(0, events[0].Position) + s.Require().Equal(types.EventTypeBurn, events[0].Type) +} diff --git a/internal/storage/postgres/storage_test.go b/internal/storage/postgres/storage_test.go index d459d80f..0f056812 100644 --- a/internal/storage/postgres/storage_test.go +++ b/internal/storage/postgres/storage_test.go @@ -101,32 +101,6 @@ func (s *StorageTestSuite) TestStateGetByNameFailed() { s.Require().Error(err) } -func (s *StorageTestSuite) TestEventByTxId() { - ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) - defer ctxCancel() - - events, err := s.storage.Event.ByTxId(ctx, 1, 10, 0) - s.Require().NoError(err) - s.Require().Len(events, 1) - s.Require().EqualValues(2, events[0].Id) - s.Require().EqualValues(1000, events[0].Height) - s.Require().EqualValues(1, events[0].Position) - s.Require().Equal(types.EventTypeMint, events[0].Type) -} - -func (s *StorageTestSuite) TestEventByBlock() { - ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) - defer ctxCancel() - - events, err := s.storage.Event.ByBlock(ctx, 1000, 2, 0) - s.Require().NoError(err) - s.Require().Len(events, 1) - s.Require().EqualValues(1, events[0].Id) - s.Require().EqualValues(1000, events[0].Height) - s.Require().EqualValues(0, events[0].Position) - s.Require().Equal(types.EventTypeBurn, events[0].Type) -} - func (s *StorageTestSuite) TestMessageByTxId() { ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) defer ctxCancel()