From 3c9c7963b2ddb3907416d07345edf2fed1f1efad Mon Sep 17 00:00:00 2001 From: Artem Poltorzhitskiy Date: Sun, 31 Dec 2023 15:37:47 +0100 Subject: [PATCH] Feature: filters for address messages (#86) * Feature: filters for address messages * Fix: licensec --- cmd/api/handler/address.go | 28 ++++++++++------ cmd/api/handler/address_test.go | 25 +++++++++++---- cmd/api/handler/responses/message.go | 39 ++++++++++++++++------- cmd/api/handler/responses/tx.go | 24 +++++++++++++- cmd/api/init.go | 2 +- internal/storage/address.go | 7 ---- internal/storage/message.go | 18 ++++++++++- internal/storage/mock/address.go | 39 ----------------------- internal/storage/mock/message.go | 39 +++++++++++++++++++++++ internal/storage/postgres/message.go | 22 +++++++++++++ internal/storage/postgres/storage_test.go | 26 +++++++++++++-- test/data/msg_address.yml | 3 ++ 12 files changed, 192 insertions(+), 80 deletions(-) diff --git a/cmd/api/handler/address.go b/cmd/api/handler/address.go index 5746535f..aa05fd2b 100644 --- a/cmd/api/handler/address.go +++ b/cmd/api/handler/address.go @@ -18,6 +18,7 @@ type AddressHandler struct { address storage.IAddress txs storage.ITx blobLogs storage.IBlobLog + messages storage.IMessage state storage.IState indexerName string } @@ -26,6 +27,7 @@ func NewAddressHandler( address storage.IAddress, txs storage.ITx, blobLogs storage.IBlobLog, + messages storage.IMessage, state storage.IState, indexerName string, ) *AddressHandler { @@ -33,6 +35,7 @@ func NewAddressHandler( address: address, txs: txs, blobLogs: blobLogs, + messages: messages, state: state, indexerName: indexerName, } @@ -177,10 +180,11 @@ func (handler *AddressHandler) Transactions(c echo.Context) error { } type getAddressMessages struct { - Hash string `param:"hash" validate:"required,address"` - Limit uint64 `query:"limit" validate:"omitempty,min=1,max=100"` - Offset uint64 `query:"offset" validate:"omitempty,min=0"` - Sort string `query:"sort" validate:"omitempty,oneof=asc desc"` + Hash string `param:"hash" validate:"required,address"` + Limit uint64 `query:"limit" validate:"omitempty,min=1,max=100"` + Offset uint64 `query:"offset" validate:"omitempty,min=0"` + Sort string `query:"sort" validate:"omitempty,oneof=asc desc"` + MsgType StringArray `query:"msg_type" validate:"omitempty,dive,msg_type"` } func (p *getAddressMessages) SetDefault() { @@ -190,13 +194,17 @@ func (p *getAddressMessages) SetDefault() { if p.Sort == "" { p.Sort = asc } + if p.MsgType == nil { + p.MsgType = make(StringArray, 0) + } } func (p *getAddressMessages) ToFilters() storage.AddressMsgsFilter { return storage.AddressMsgsFilter{ - Limit: int(p.Limit), - Offset: int(p.Offset), - Sort: pgSort(p.Sort), + Limit: int(p.Limit), + Offset: int(p.Offset), + Sort: pgSort(p.Sort), + MessageTypes: p.MsgType, } } @@ -211,7 +219,7 @@ func (p *getAddressMessages) ToFilters() storage.AddressMsgsFilter { // @Param offset query integer false "Offset" minimum(1) // @Param sort query string false "Sort order" Enums(asc, desc) // @Produce json -// @Success 200 {array} responses.Message +// @Success 200 {array} responses.MessageForAddress // @Failure 400 {object} Error // @Failure 500 {object} Error // @Router /v1/address/{hash}/messages [get] @@ -234,12 +242,12 @@ func (handler *AddressHandler) Messages(c echo.Context) error { } filters := req.ToFilters() - msgs, err := handler.address.Messages(c.Request().Context(), address.Id, filters) + msgs, err := handler.messages.ByAddress(c.Request().Context(), address.Id, filters) if err != nil { return handleError(c, err, handler.address) } - response := make([]responses.Message, len(msgs)) + response := make([]responses.MessageForAddress, len(msgs)) for i := range msgs { response[i] = responses.NewMessageForAddress(msgs[i]) } diff --git a/cmd/api/handler/address_test.go b/cmd/api/handler/address_test.go index 121c6427..9889991f 100644 --- a/cmd/api/handler/address_test.go +++ b/cmd/api/handler/address_test.go @@ -43,6 +43,7 @@ type AddressTestSuite struct { address *mock.MockIAddress txs *mock.MockITx blobLogs *mock.MockIBlobLog + messages *mock.MockIMessage state *mock.MockIState echo *echo.Echo handler *AddressHandler @@ -57,8 +58,9 @@ func (s *AddressTestSuite) SetupSuite() { s.address = mock.NewMockIAddress(s.ctrl) s.txs = mock.NewMockITx(s.ctrl) s.blobLogs = mock.NewMockIBlobLog(s.ctrl) + s.messages = mock.NewMockIMessage(s.ctrl) s.state = mock.NewMockIState(s.ctrl) - s.handler = NewAddressHandler(s.address, s.txs, s.blobLogs, s.state, testIndexerName) + s.handler = NewAddressHandler(s.address, s.txs, s.blobLogs, s.messages, s.state, testIndexerName) } // TearDownSuite - @@ -254,13 +256,15 @@ func (s *AddressTestSuite) TestMessages() { Address: testAddress, }, nil) - s.address.EXPECT(). - Messages(gomock.Any(), uint64(1), gomock.Any()). - Return([]storage.MsgAddress{ + s.messages.EXPECT(). + ByAddress(gomock.Any(), uint64(1), gomock.Any()). + Return([]storage.AddressMessageWithTx{ { - AddressId: 1, - MsgId: 1, - Type: types.MsgAddressTypeDelegator, + MsgAddress: storage.MsgAddress{ + AddressId: 1, + MsgId: 1, + Type: types.MsgAddressTypeDelegator, + }, Msg: &storage.Message{ Id: 1, Height: 1000, @@ -269,6 +273,12 @@ func (s *AddressTestSuite) TestMessages() { TxId: 1, Data: nil, }, + Tx: &storage.Tx{ + Id: 1, + MessageTypes: types.NewMsgTypeBitMask(types.MsgWithdrawDelegatorReward), + MessagesCount: 1, + Status: types.StatusSuccess, + }, }, }, nil) @@ -285,6 +295,7 @@ func (s *AddressTestSuite) TestMessages() { s.Require().EqualValues(1000, msg.Height) s.Require().Equal(int64(0), msg.Position) s.Require().EqualValues(types.MsgWithdrawDelegatorReward, msg.Type) + s.Require().NotNil(msg.Tx) } func (s *AddressTestSuite) TestBlobs() { diff --git a/cmd/api/handler/responses/message.go b/cmd/api/handler/responses/message.go index 546ef24c..dd7b2f14 100644 --- a/cmd/api/handler/responses/message.go +++ b/cmd/api/handler/responses/message.go @@ -37,18 +37,6 @@ func NewMessage(msg storage.Message) Message { } } -func NewMessageForAddress(msg storage.MsgAddress) Message { - return Message{ - Id: msg.MsgId, - Height: msg.Msg.Height, - Time: msg.Msg.Time, - Position: msg.Msg.Position, - TxId: msg.Msg.TxId, - Type: msg.Msg.Type, - Data: msg.Msg.Data, - } -} - func NewMessageWithTx(msg storage.MessageWithTx) Message { message := Message{ Id: msg.Id, @@ -67,3 +55,30 @@ func NewMessageWithTx(msg storage.MessageWithTx) Message { return message } + +type MessageForAddress struct { + Id uint64 `example:"321" format:"int64" json:"id" swaggertype:"integer"` + Height pkgTypes.Level `example:"100" format:"int64" json:"height" swaggertype:"integer"` + Time time.Time `example:"2023-07-04T03:10:57+00:00" format:"date-time" json:"time" swaggertype:"string"` + Position int64 `example:"2" format:"int64" json:"position" swaggertype:"integer"` + TxId uint64 `example:"11" format:"int64" json:"tx_id,omitempty" swaggertype:"integer"` + + Type types.MsgType `example:"MsgCreatePeriodicVestingAccount" json:"type"` + + Data map[string]any `json:"data"` + Tx TxForAddress `json:"tx"` +} + +func NewMessageForAddress(msg storage.AddressMessageWithTx) MessageForAddress { + message := MessageForAddress{ + Id: msg.MsgId, + Height: msg.Msg.Height, + Time: msg.Msg.Time, + Position: msg.Msg.Position, + TxId: msg.Msg.TxId, + Type: msg.Msg.Type, + Data: msg.Msg.Data, + Tx: NewTxForAddress(msg.Tx), + } + return message +} diff --git a/cmd/api/handler/responses/tx.go b/cmd/api/handler/responses/tx.go index 520653c1..04bc534b 100644 --- a/cmd/api/handler/responses/tx.go +++ b/cmd/api/handler/responses/tx.go @@ -5,9 +5,10 @@ package responses import ( "encoding/hex" - pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" "time" + pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" + "github.com/celenium-io/celestia-indexer/internal/storage" "github.com/celenium-io/celestia-indexer/internal/storage/types" ) @@ -68,3 +69,24 @@ func NewTx(tx storage.Tx) Tx { func (Tx) SearchType() string { return "tx" } + +type TxForAddress struct { + MessagesCount int64 `example:"1" format:"int64" json:"messages_count" swaggertype:"integer"` + Hash string `example:"652452A670018D629CC116E510BA88C1CABE061336661B1F3D206D248BD558AF" format:"binary" json:"hash" swaggertype:"string"` + Fee string `example:"9348" format:"int64" json:"fee" swaggertype:"string"` + + MessageTypes []types.MsgType `example:"MsgSend,MsgUnjail" json:"message_types"` + Status types.Status `example:"success" json:"status"` +} + +func NewTxForAddress(tx *storage.Tx) TxForAddress { + result := TxForAddress{ + MessagesCount: tx.MessagesCount, + Fee: tx.Fee.String(), + Status: tx.Status, + Hash: hex.EncodeToString(tx.Hash), + MessageTypes: tx.MessageTypes.Names(), + } + + return result +} diff --git a/cmd/api/init.go b/cmd/api/init.go index d6ea8ac2..0a6dd9ca 100644 --- a/cmd/api/init.go +++ b/cmd/api/init.go @@ -261,7 +261,7 @@ func initHandlers(ctx context.Context, e *echo.Echo, cfg Config, db postgres.Sto searchHandler := handler.NewSearchHandler(db.Address, db.Blocks, db.Namespace, db.Tx) v1.GET("/search", searchHandler.Search) - addressHandlers := handler.NewAddressHandler(db.Address, db.Tx, db.BlobLogs, db.State, cfg.Indexer.Name) + addressHandlers := handler.NewAddressHandler(db.Address, db.Tx, db.BlobLogs, db.Message, db.State, cfg.Indexer.Name) addressesGroup := v1.Group("/address") { addressesGroup.GET("", addressHandlers.List) diff --git a/internal/storage/address.go b/internal/storage/address.go index a7a45686..62b0dfae 100644 --- a/internal/storage/address.go +++ b/internal/storage/address.go @@ -18,19 +18,12 @@ type AddressListFilter struct { Sort storage.SortOrder } -type AddressMsgsFilter struct { - Limit int - Offset int - Sort storage.SortOrder -} - //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed type IAddress interface { storage.Table[*Address] ByHash(ctx context.Context, hash []byte) (Address, error) ListWithBalance(ctx context.Context, filters AddressListFilter) ([]Address, error) - Messages(ctx context.Context, id uint64, filters AddressMsgsFilter) ([]MsgAddress, error) } // Address - diff --git a/internal/storage/message.go b/internal/storage/message.go index d424fe27..f85657bb 100644 --- a/internal/storage/message.go +++ b/internal/storage/message.go @@ -22,19 +22,35 @@ type MessageListWithTxFilters struct { MessageTypes []string } +type AddressMsgsFilter struct { + Limit int + Offset int + Sort storage.SortOrder + MessageTypes []string +} + type MessageWithTx struct { - bun.BaseModel `bun:"message,alias:message" comment:"Table with celestia messages."` + bun.BaseModel `bun:"message,alias:message"` Message Tx *Tx `bun:"rel:belongs-to"` } +type AddressMessageWithTx struct { + bun.BaseModel `bun:"message,alias:message"` + + MsgAddress + Msg *Message `bun:"rel:belongs-to,join:msg_id=id"` + Tx *Tx `bun:"rel:belongs-to,join:msg__tx_id=id"` +} + //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed type IMessage interface { storage.Table[*Message] ByTxId(ctx context.Context, txId uint64) ([]Message, error) ListWithTx(ctx context.Context, filters MessageListWithTxFilters) ([]MessageWithTx, error) + ByAddress(ctx context.Context, id uint64, filters AddressMsgsFilter) ([]AddressMessageWithTx, error) } // Message - diff --git a/internal/storage/mock/address.go b/internal/storage/mock/address.go index 8a6f7ff5..383f2b4b 100644 --- a/internal/storage/mock/address.go +++ b/internal/storage/mock/address.go @@ -315,45 +315,6 @@ func (c *IAddressListWithBalanceCall) DoAndReturn(f func(context.Context, storag return c } -// Messages mocks base method. -func (m *MockIAddress) Messages(ctx context.Context, id uint64, filters storage.AddressMsgsFilter) ([]storage.MsgAddress, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Messages", ctx, id, filters) - ret0, _ := ret[0].([]storage.MsgAddress) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Messages indicates an expected call of Messages. -func (mr *MockIAddressMockRecorder) Messages(ctx, id, filters any) *IAddressMessagesCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Messages", reflect.TypeOf((*MockIAddress)(nil).Messages), ctx, id, filters) - return &IAddressMessagesCall{Call: call} -} - -// IAddressMessagesCall wrap *gomock.Call -type IAddressMessagesCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *IAddressMessagesCall) Return(arg0 []storage.MsgAddress, arg1 error) *IAddressMessagesCall { - c.Call = c.Call.Return(arg0, arg1) - return c -} - -// Do rewrite *gomock.Call.Do -func (c *IAddressMessagesCall) Do(f func(context.Context, uint64, storage.AddressMsgsFilter) ([]storage.MsgAddress, error)) *IAddressMessagesCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *IAddressMessagesCall) DoAndReturn(f func(context.Context, uint64, storage.AddressMsgsFilter) ([]storage.MsgAddress, error)) *IAddressMessagesCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - // Save mocks base method. func (m_2 *MockIAddress) Save(ctx context.Context, m *storage.Address) error { m_2.ctrl.T.Helper() diff --git a/internal/storage/mock/message.go b/internal/storage/mock/message.go index 4681cd51..752d5e33 100644 --- a/internal/storage/mock/message.go +++ b/internal/storage/mock/message.go @@ -43,6 +43,45 @@ func (m *MockIMessage) EXPECT() *MockIMessageMockRecorder { return m.recorder } +// ByAddress mocks base method. +func (m *MockIMessage) ByAddress(ctx context.Context, id uint64, filters storage.AddressMsgsFilter) ([]storage.AddressMessageWithTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ByAddress", ctx, id, filters) + ret0, _ := ret[0].([]storage.AddressMessageWithTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ByAddress indicates an expected call of ByAddress. +func (mr *MockIMessageMockRecorder) ByAddress(ctx, id, filters any) *IMessageByAddressCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ByAddress", reflect.TypeOf((*MockIMessage)(nil).ByAddress), ctx, id, filters) + return &IMessageByAddressCall{Call: call} +} + +// IMessageByAddressCall wrap *gomock.Call +type IMessageByAddressCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *IMessageByAddressCall) Return(arg0 []storage.AddressMessageWithTx, arg1 error) *IMessageByAddressCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *IMessageByAddressCall) Do(f func(context.Context, uint64, storage.AddressMsgsFilter) ([]storage.AddressMessageWithTx, error)) *IMessageByAddressCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *IMessageByAddressCall) DoAndReturn(f func(context.Context, uint64, storage.AddressMsgsFilter) ([]storage.AddressMessageWithTx, error)) *IMessageByAddressCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // ByTxId mocks base method. func (m *MockIMessage) ByTxId(ctx context.Context, txId uint64) ([]storage.Message, error) { m.ctrl.T.Helper() diff --git a/internal/storage/postgres/message.go b/internal/storage/postgres/message.go index 2577eeed..f5b22888 100644 --- a/internal/storage/postgres/message.go +++ b/internal/storage/postgres/message.go @@ -9,6 +9,7 @@ import ( "github.com/celenium-io/celestia-indexer/internal/storage" "github.com/dipdup-net/go-lib/database" "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" + "github.com/uptrace/bun" ) // Message - @@ -38,3 +39,24 @@ func (m *Message) ListWithTx(ctx context.Context, filters storage.MessageListWit err = query.Relation("Tx").Scan(ctx) return } + +func (m *Message) ByAddress(ctx context.Context, addressId uint64, filters storage.AddressMsgsFilter) (msgs []storage.AddressMessageWithTx, err error) { + query := m.DB().NewSelect().Model((*storage.MsgAddress)(nil)). + Where("address_id = ?", addressId). + Offset(filters.Offset) + + query = limitScope(query, filters.Limit) + query = sortScope(query, "msg_id", filters.Sort) + + wrapQuery := m.DB().NewSelect().TableExpr("(?) as msg_address", query). + ColumnExpr(`msg_address.address_id, msg_address.msg_id, msg_address.type, msg.id AS msg__id, msg.height AS msg__height, msg.time AS msg__time, msg.position AS msg__position, msg.type AS msg__type, msg.tx_id AS msg__tx_id, msg.data AS msg__data`). + ColumnExpr("tx.messages_count as tx__messages_count, tx.fee as tx__fee, tx.status as tx__status, tx.hash as tx__hash, tx.message_types as tx__message_types"). + Join("left join message as msg on msg_address.msg_id = msg.id"). + Join("left join tx on tx.id = msg.tx_id") + if len(filters.MessageTypes) > 0 { + wrapQuery = wrapQuery.Where("msg.type IN (?)", bun.In(filters.MessageTypes)) + } + wrapQuery = sortScope(wrapQuery, "msg_id", filters.Sort) + err = wrapQuery.Scan(ctx, &msgs) + return +} diff --git a/internal/storage/postgres/storage_test.go b/internal/storage/postgres/storage_test.go index fbfde9cf..b612a31d 100644 --- a/internal/storage/postgres/storage_test.go +++ b/internal/storage/postgres/storage_test.go @@ -306,21 +306,43 @@ func (s *StorageTestSuite) TestAddressMessages() { ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) defer ctxCancel() - messages, err := s.storage.Address.Messages(ctx, 1, storage.AddressMsgsFilter{ + messages, err := s.storage.Message.ByAddress(ctx, 1, storage.AddressMsgsFilter{ Limit: 10, Offset: 0, Sort: sdk.SortOrderAsc, }) s.Require().NoError(err) - s.Require().Len(messages, 1) + s.Require().Len(messages, 2) + msg := messages[0].Msg + s.Require().EqualValues(1, msg.Id) + s.Require().EqualValues(1000, msg.Height) + s.Require().EqualValues(0, msg.Position) s.Require().EqualValues(types.MsgAddressTypeFromAddress, messages[0].Type) + s.Require().Equal(types.MsgWithdrawDelegatorReward, msg.Type) + s.Require().NotNil(messages[0].Tx) +} + +func (s *StorageTestSuite) TestAddressMessagesWithType() { + ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer ctxCancel() + + messages, err := s.storage.Message.ByAddress(ctx, 1, storage.AddressMsgsFilter{ + Limit: 10, + Offset: 0, + Sort: sdk.SortOrderAsc, + MessageTypes: []string{"MsgWithdrawDelegatorReward", "MsgDelegate"}, + }) + s.Require().NoError(err) + s.Require().Len(messages, 2) msg := messages[0].Msg s.Require().EqualValues(1, msg.Id) s.Require().EqualValues(1000, msg.Height) s.Require().EqualValues(0, msg.Position) + s.Require().EqualValues(types.MsgAddressTypeFromAddress, messages[0].Type) s.Require().Equal(types.MsgWithdrawDelegatorReward, msg.Type) + s.Require().NotNil(messages[0].Tx) } func (s *StorageTestSuite) TestEventByTxId() { diff --git a/test/data/msg_address.yml b/test/data/msg_address.yml index ef65b5f9..3dd77422 100644 --- a/test/data/msg_address.yml +++ b/test/data/msg_address.yml @@ -3,4 +3,7 @@ type: fromAddress - msg_id: 1 address_id: 2 + type: toAddress +- msg_id: 2 + address_id: 1 type: toAddress \ No newline at end of file