diff --git a/api/accounts.go b/api/accounts.go index 910bb08f1..dd0be0d6f 100644 --- a/api/accounts.go +++ b/api/accounts.go @@ -83,6 +83,14 @@ func (a *API) enableAccountHandlers() error { ); err != nil { return err } + if err := a.endpoint.RegisterMethod( + "/accounts/{accountID}/fees/page/{page}", + "GET", + apirest.MethodAccessTypePublic, + a.tokenFeesHandler, + ); err != nil { + return err + } return nil } @@ -421,3 +429,50 @@ func (a *API) tokenTransfersHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCont } return ctx.Send(data, apirest.HTTPstatusOK) } + +// tokenFeesHandler +// +// @Summary List account token fees +// @Description Returns the token fees for an account. A spending is an amount of tokens burnt from one account for executing transactions. +// @Tags Accounts +// @Accept json +// @Produce json +// @Param accountID path string true "Specific accountID" +// @Param page path string true "Paginator page" +// @Success 200 {object} object{fees=[]indexertypes.TokenFeeMeta} +// @Router /accounts/{accountID}/fees/page/{page} [get] +func (a *API) tokenFeesHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + accountID, err := hex.DecodeString(util.TrimHex(ctx.URLParam("accountID"))) + if err != nil || accountID == nil { + return ErrCantParseAccountID.Withf("%q", ctx.URLParam("accountID")) + } + acc, err := a.vocapp.State.GetAccount(common.BytesToAddress(accountID), true) + if acc == nil { + return ErrAccountNotFound + } + if err != nil { + return err + } + page := 0 + if ctx.URLParam("page") != "" { + page, err = strconv.Atoi(ctx.URLParam("page")) + if err != nil { + return ErrCantParsePageNumber + } + } + page = page * MaxPageSize + + fees, err := a.indexer.GetTokenFeesByFromAccount(accountID, int32(page), MaxPageSize) + if err != nil { + return ErrCantFetchTokenTransfers.WithErr(err) + } + data, err := json.Marshal( + struct { + Fees []*indexertypes.TokenFeeMeta `json:"fees"` + }{Fees: fees}, + ) + if err != nil { + return ErrMarshalingServerJSONFailed.WithErr(err) + } + return ctx.Send(data, apirest.HTTPstatusOK) +} diff --git a/api/chain.go b/api/chain.go index 2faa482a2..942df9f8e 100644 --- a/api/chain.go +++ b/api/chain.go @@ -178,6 +178,30 @@ func (a *API) enableChainHandlers() error { ); err != nil { return err } + if err := a.endpoint.RegisterMethod( + "/chain/fees/page/{page}", + "GET", + apirest.MethodAccessTypePublic, + a.chainListFeesHandler, + ); err != nil { + return err + } + if err := a.endpoint.RegisterMethod( + "/chain/fees/reference/{reference}/page/{page}", + "GET", + apirest.MethodAccessTypePublic, + a.chainListFeesByReferenceHandler, + ); err != nil { + return err + } + if err := a.endpoint.RegisterMethod( + "/chain/fees/type/{type}/page/{page}", + "GET", + apirest.MethodAccessTypePublic, + a.chainListFeesByTypeHandler, + ); err != nil { + return err + } return nil } @@ -846,3 +870,123 @@ func (a *API) chainTxCountHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContex return ctx.Send(data, apirest.HTTPstatusOK) } + +// chainListFeesHandler +// +// @Summary List all token fees +// @Description Returns the token fees list ordered by date. A spending is an amount of tokens burnt from one account for executing transactions. +// @Tags Accounts +// @Accept json +// @Produce json +// @Param page path string true "Paginator page" +// @Success 200 {object} object{fees=[]indexertypes.TokenFeeMeta} +// @Router /chain/fees/page/{page} [get] +func (a *API) chainListFeesHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + var err error + page := 0 + if ctx.URLParam("page") != "" { + page, err = strconv.Atoi(ctx.URLParam("page")) + if err != nil { + return ErrCantParsePageNumber + } + } + page = page * MaxPageSize + + fees, err := a.indexer.GetTokenFees(int32(page), MaxPageSize) + if err != nil { + return ErrCantFetchTokenTransfers.WithErr(err) + } + data, err := json.Marshal( + struct { + Fees []*indexertypes.TokenFeeMeta `json:"fees"` + }{Fees: fees}, + ) + if err != nil { + return ErrMarshalingServerJSONFailed.WithErr(err) + } + return ctx.Send(data, apirest.HTTPstatusOK) +} + +// chainListFeesByReferenceHandler +// +// @Summary List all token fees by reference +// @Description Returns the token fees list filtered by reference and ordered by date. A spending is an amount of tokens burnt from one account for executing transactions. +// @Tags Accounts +// @Accept json +// @Produce json +// @Param reference path string true "Reference filter" +// @Param page path string true "Paginator page" +// @Success 200 {object} object{fees=[]indexertypes.TokenFeeMeta} +// @Router /chain/fees/reference/{reference}/page/{page} [get] +func (a *API) chainListFeesByReferenceHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + var err error + page := 0 + if ctx.URLParam("page") != "" { + page, err = strconv.Atoi(ctx.URLParam("page")) + if err != nil { + return ErrCantParsePageNumber + } + } + page = page * MaxPageSize + + reference := ctx.URLParam("reference") + if reference == "" { + return ErrMissingParameter + } + + fees, err := a.indexer.GetTokenFeesByReference(reference, int32(page), MaxPageSize) + if err != nil { + return ErrCantFetchTokenTransfers.WithErr(err) + } + data, err := json.Marshal( + struct { + Fees []*indexertypes.TokenFeeMeta `json:"fees"` + }{Fees: fees}, + ) + if err != nil { + return ErrMarshalingServerJSONFailed.WithErr(err) + } + return ctx.Send(data, apirest.HTTPstatusOK) +} + +// chainListFeesByTypeHandler +// +// @Summary List all token fees by type +// @Description Returns the token fees list filtered by type and ordered by date. A spending is an amount of tokens burnt from one account for executing transactions. +// @Tags Accounts +// @Accept json +// @Produce json +// @Param type path string true "Type filter" +// @Param page path string true "Paginator page" +// @Success 200 {object} object{fees=[]indexertypes.TokenFeeMeta} +// @Router /chain/fees/type/{type}/page/{page} [get] +func (a *API) chainListFeesByTypeHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + var err error + page := 0 + if ctx.URLParam("page") != "" { + page, err = strconv.Atoi(ctx.URLParam("page")) + if err != nil { + return ErrCantParsePageNumber + } + } + page = page * MaxPageSize + + typeFilter := ctx.URLParam("type") + if typeFilter == "" { + return ErrMissingParameter + } + + fees, err := a.indexer.GetTokenFeesByType(typeFilter, int32(page), MaxPageSize) + if err != nil { + return ErrCantFetchTokenTransfers.WithErr(err) + } + data, err := json.Marshal( + struct { + Fees []*indexertypes.TokenFeeMeta `json:"fees"` + }{Fees: fees}, + ) + if err != nil { + return ErrMarshalingServerJSONFailed.WithErr(err) + } + return ctx.Send(data, apirest.HTTPstatusOK) +} diff --git a/vochain/indexer/db/db.go b/vochain/indexer/db/db.go index e6dd028ef..31818c0db 100644 --- a/vochain/indexer/db/db.go +++ b/vochain/indexer/db/db.go @@ -39,6 +39,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.createProcessStmt, err = db.PrepareContext(ctx, createProcess); err != nil { return nil, fmt.Errorf("error preparing query CreateProcess: %w", err) } + if q.createTokenFeeStmt, err = db.PrepareContext(ctx, createTokenFee); err != nil { + return nil, fmt.Errorf("error preparing query CreateTokenFee: %w", err) + } if q.createTokenTransferStmt, err = db.PrepareContext(ctx, createTokenTransfer); err != nil { return nil, fmt.Errorf("error preparing query CreateTokenTransfer: %w", err) } @@ -69,6 +72,18 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getProcessStatusStmt, err = db.PrepareContext(ctx, getProcessStatus); err != nil { return nil, fmt.Errorf("error preparing query GetProcessStatus: %w", err) } + if q.getTokenFeesStmt, err = db.PrepareContext(ctx, getTokenFees); err != nil { + return nil, fmt.Errorf("error preparing query GetTokenFees: %w", err) + } + if q.getTokenFeesByFromAccountStmt, err = db.PrepareContext(ctx, getTokenFeesByFromAccount); err != nil { + return nil, fmt.Errorf("error preparing query GetTokenFeesByFromAccount: %w", err) + } + if q.getTokenFeesByReferenceStmt, err = db.PrepareContext(ctx, getTokenFeesByReference); err != nil { + return nil, fmt.Errorf("error preparing query GetTokenFeesByReference: %w", err) + } + if q.getTokenFeesByTxTypeStmt, err = db.PrepareContext(ctx, getTokenFeesByTxType); err != nil { + return nil, fmt.Errorf("error preparing query GetTokenFeesByTxType: %w", err) + } if q.getTokenTransferStmt, err = db.PrepareContext(ctx, getTokenTransfer); err != nil { return nil, fmt.Errorf("error preparing query GetTokenTransfer: %w", err) } @@ -144,6 +159,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing createProcessStmt: %w", cerr) } } + if q.createTokenFeeStmt != nil { + if cerr := q.createTokenFeeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createTokenFeeStmt: %w", cerr) + } + } if q.createTokenTransferStmt != nil { if cerr := q.createTokenTransferStmt.Close(); cerr != nil { err = fmt.Errorf("error closing createTokenTransferStmt: %w", cerr) @@ -194,6 +214,26 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getProcessStatusStmt: %w", cerr) } } + if q.getTokenFeesStmt != nil { + if cerr := q.getTokenFeesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getTokenFeesStmt: %w", cerr) + } + } + if q.getTokenFeesByFromAccountStmt != nil { + if cerr := q.getTokenFeesByFromAccountStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getTokenFeesByFromAccountStmt: %w", cerr) + } + } + if q.getTokenFeesByReferenceStmt != nil { + if cerr := q.getTokenFeesByReferenceStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getTokenFeesByReferenceStmt: %w", cerr) + } + } + if q.getTokenFeesByTxTypeStmt != nil { + if cerr := q.getTokenFeesByTxTypeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getTokenFeesByTxTypeStmt: %w", cerr) + } + } if q.getTokenTransferStmt != nil { if cerr := q.getTokenTransferStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getTokenTransferStmt: %w", cerr) @@ -313,6 +353,7 @@ type Queries struct { countVotesByProcessIDStmt *sql.Stmt createBlockStmt *sql.Stmt createProcessStmt *sql.Stmt + createTokenFeeStmt *sql.Stmt createTokenTransferStmt *sql.Stmt createTransactionStmt *sql.Stmt createVoteStmt *sql.Stmt @@ -323,6 +364,10 @@ type Queries struct { getProcessCountStmt *sql.Stmt getProcessIDsByFinalResultsStmt *sql.Stmt getProcessStatusStmt *sql.Stmt + getTokenFeesStmt *sql.Stmt + getTokenFeesByFromAccountStmt *sql.Stmt + getTokenFeesByReferenceStmt *sql.Stmt + getTokenFeesByTxTypeStmt *sql.Stmt getTokenTransferStmt *sql.Stmt getTokenTransfersByFromAccountStmt *sql.Stmt getTransactionStmt *sql.Stmt @@ -349,6 +394,7 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { countVotesByProcessIDStmt: q.countVotesByProcessIDStmt, createBlockStmt: q.createBlockStmt, createProcessStmt: q.createProcessStmt, + createTokenFeeStmt: q.createTokenFeeStmt, createTokenTransferStmt: q.createTokenTransferStmt, createTransactionStmt: q.createTransactionStmt, createVoteStmt: q.createVoteStmt, @@ -359,6 +405,10 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { getProcessCountStmt: q.getProcessCountStmt, getProcessIDsByFinalResultsStmt: q.getProcessIDsByFinalResultsStmt, getProcessStatusStmt: q.getProcessStatusStmt, + getTokenFeesStmt: q.getTokenFeesStmt, + getTokenFeesByFromAccountStmt: q.getTokenFeesByFromAccountStmt, + getTokenFeesByReferenceStmt: q.getTokenFeesByReferenceStmt, + getTokenFeesByTxTypeStmt: q.getTokenFeesByTxTypeStmt, getTokenTransferStmt: q.getTokenTransferStmt, getTokenTransfersByFromAccountStmt: q.getTokenTransfersByFromAccountStmt, getTransactionStmt: q.getTransactionStmt, diff --git a/vochain/indexer/db/models.go b/vochain/indexer/db/models.go index bee918f2e..be49ad590 100644 --- a/vochain/indexer/db/models.go +++ b/vochain/indexer/db/models.go @@ -46,13 +46,13 @@ type Process struct { SourceNetworkID int64 } -type TokenSpending struct { +type TokenFee struct { ID int64 BlockHeight int64 FromAccount []byte - Reference []byte + Reference string Cost int64 - Txtype string + TxType string SpendTime time.Time } diff --git a/vochain/indexer/db/token_fees.sql.go b/vochain/indexer/db/token_fees.sql.go new file mode 100644 index 000000000..0f42e0b80 --- /dev/null +++ b/vochain/indexer/db/token_fees.sql.go @@ -0,0 +1,226 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.21.0 +// source: token_fees.sql + +package indexerdb + +import ( + "context" + "database/sql" + "time" +) + +const createTokenFee = `-- name: CreateTokenFee :execresult +INSERT INTO token_fees ( + from_account, block_height, reference, + cost, tx_type, spend_time +) VALUES ( + ?, ?, ?, + ?, ?, ? +) +` + +type CreateTokenFeeParams struct { + FromAccount []byte + BlockHeight int64 + Reference string + Cost int64 + TxType string + SpendTime time.Time +} + +func (q *Queries) CreateTokenFee(ctx context.Context, arg CreateTokenFeeParams) (sql.Result, error) { + return q.exec(ctx, q.createTokenFeeStmt, createTokenFee, + arg.FromAccount, + arg.BlockHeight, + arg.Reference, + arg.Cost, + arg.TxType, + arg.SpendTime, + ) +} + +const getTokenFees = `-- name: GetTokenFees :many +SELECT id, block_height, from_account, reference, cost, tx_type, spend_time FROM token_fees +ORDER BY spend_time DESC +LIMIT ?2 +OFFSET ?1 +` + +type GetTokenFeesParams struct { + Offset int64 + Limit int64 +} + +func (q *Queries) GetTokenFees(ctx context.Context, arg GetTokenFeesParams) ([]TokenFee, error) { + rows, err := q.query(ctx, q.getTokenFeesStmt, getTokenFees, arg.Offset, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []TokenFee + for rows.Next() { + var i TokenFee + if err := rows.Scan( + &i.ID, + &i.BlockHeight, + &i.FromAccount, + &i.Reference, + &i.Cost, + &i.TxType, + &i.SpendTime, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getTokenFeesByFromAccount = `-- name: GetTokenFeesByFromAccount :many +; + +SELECT id, block_height, from_account, reference, cost, tx_type, spend_time FROM token_fees +WHERE from_account = ?1 +ORDER BY spend_time DESC +LIMIT ?3 +OFFSET ?2 +` + +type GetTokenFeesByFromAccountParams struct { + FromAccount []byte + Offset int64 + Limit int64 +} + +func (q *Queries) GetTokenFeesByFromAccount(ctx context.Context, arg GetTokenFeesByFromAccountParams) ([]TokenFee, error) { + rows, err := q.query(ctx, q.getTokenFeesByFromAccountStmt, getTokenFeesByFromAccount, arg.FromAccount, arg.Offset, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []TokenFee + for rows.Next() { + var i TokenFee + if err := rows.Scan( + &i.ID, + &i.BlockHeight, + &i.FromAccount, + &i.Reference, + &i.Cost, + &i.TxType, + &i.SpendTime, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getTokenFeesByReference = `-- name: GetTokenFeesByReference :many +; + +SELECT id, block_height, from_account, reference, cost, tx_type, spend_time FROM token_fees +WHERE reference = ?1 +ORDER BY spend_time DESC +LIMIT ?3 +OFFSET ?2 +` + +type GetTokenFeesByReferenceParams struct { + Reference string + Offset int64 + Limit int64 +} + +func (q *Queries) GetTokenFeesByReference(ctx context.Context, arg GetTokenFeesByReferenceParams) ([]TokenFee, error) { + rows, err := q.query(ctx, q.getTokenFeesByReferenceStmt, getTokenFeesByReference, arg.Reference, arg.Offset, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []TokenFee + for rows.Next() { + var i TokenFee + if err := rows.Scan( + &i.ID, + &i.BlockHeight, + &i.FromAccount, + &i.Reference, + &i.Cost, + &i.TxType, + &i.SpendTime, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getTokenFeesByTxType = `-- name: GetTokenFeesByTxType :many +; + +SELECT id, block_height, from_account, reference, cost, tx_type, spend_time FROM token_fees +WHERE tx_type = ?1 +ORDER BY spend_time DESC +LIMIT ?3 +OFFSET ?2 +` + +type GetTokenFeesByTxTypeParams struct { + TxType string + Offset int64 + Limit int64 +} + +func (q *Queries) GetTokenFeesByTxType(ctx context.Context, arg GetTokenFeesByTxTypeParams) ([]TokenFee, error) { + rows, err := q.query(ctx, q.getTokenFeesByTxTypeStmt, getTokenFeesByTxType, arg.TxType, arg.Offset, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []TokenFee + for rows.Next() { + var i TokenFee + if err := rows.Scan( + &i.ID, + &i.BlockHeight, + &i.FromAccount, + &i.Reference, + &i.Cost, + &i.TxType, + &i.SpendTime, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/vochain/indexer/indexer.go b/vochain/indexer/indexer.go index 02a0d7346..dde848075 100644 --- a/vochain/indexer/indexer.go +++ b/vochain/indexer/indexer.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "sort" + "strings" "sync" "time" @@ -493,8 +494,7 @@ func (idx *Indexer) OnProcessKeys(pid []byte, _ string, _ int32) { } // OnProcessStatusChange adds the process to blockUpdateProcs and, if ended, the resultsPool -func (idx *Indexer) OnProcessStatusChange(pid []byte, _ models.ProcessStatus, - _ int32) { +func (idx *Indexer) OnProcessStatusChange(pid []byte, _ models.ProcessStatus, _ int32) { idx.blockMu.Lock() defer idx.blockMu.Unlock() idx.blockUpdateProcs[string(pid)] = true @@ -540,6 +540,7 @@ func (idx *Indexer) OnProcessesStart(pids [][]byte) { func (*Indexer) OnSetAccount(_ []byte, _ *state.Account) {} func (idx *Indexer) OnTransferTokens(tx *vochaintx.TokenTransfer) { + t := time.Now() idx.blockMu.Lock() defer idx.blockMu.Unlock() queries := idx.blockTxQueries() @@ -549,7 +550,7 @@ func (idx *Indexer) OnTransferTokens(tx *vochaintx.TokenTransfer) { FromAccount: tx.FromAddress.Bytes(), ToAccount: tx.ToAddress.Bytes(), Amount: int64(tx.Amount), - TransferTime: time.Now(), + TransferTime: t, }); err != nil { log.Errorw(err, "cannot index new transaction") } @@ -588,7 +589,119 @@ func (idx *Indexer) GetTokenTransfersByFromAccount(from []byte, offset, maxItems return tt, nil } -// OnSpendTokens does nothing -func (idx *Indexer) OnSpendTokens(address []byte, txType models.TxType, cost uint64, reference []byte) { - // TODO: fill the table token_spendings +// OnSpendTokens indexes a token spending event. +func (idx *Indexer) OnSpendTokens(address []byte, txType models.TxType, cost uint64, reference string) { + t := time.Now() + idx.blockMu.Lock() + defer idx.blockMu.Unlock() + queries := idx.blockTxQueries() + if _, err := queries.CreateTokenFee(context.TODO(), indexerdb.CreateTokenFeeParams{ + FromAccount: address, + TxType: strings.ToLower(txType.String()), + Cost: int64(cost), + Reference: reference, + SpendTime: t, + BlockHeight: int64(idx.App.Height()), + }); err != nil { + log.Errorw(err, "cannot index new token spending") + } +} + +// GetTokenFeesByFromAccount returns all the token transfers made from a given account +// from the database, ordered by timestamp and paginated by maxItems and offset +func (idx *Indexer) GetTokenFeesByFromAccount(from []byte, offset, maxItems int32) ([]*indexertypes.TokenFeeMeta, error) { + ttFromDB, err := idx.readOnlyQuery.GetTokenFeesByFromAccount(context.TODO(), indexerdb.GetTokenFeesByFromAccountParams{ + FromAccount: from, + Limit: int64(maxItems), + Offset: int64(offset), + }) + if err != nil { + return nil, err + } + tt := []*indexertypes.TokenFeeMeta{} + for _, t := range ttFromDB { + tt = append(tt, &indexertypes.TokenFeeMeta{ + Cost: uint64(t.Cost), + From: t.FromAccount, + TxType: t.TxType, + Height: uint64(t.BlockHeight), + Reference: string(t.Reference), + Timestamp: t.SpendTime, + }) + } + return tt, nil +} + +// GetTokenFees returns all the token transfers from the database, ordered +// by timestamp and paginated by maxItems and offset +func (idx *Indexer) GetTokenFees(offset, maxItems int32) ([]*indexertypes.TokenFeeMeta, error) { + ttFromDB, err := idx.readOnlyQuery.GetTokenFees(context.TODO(), indexerdb.GetTokenFeesParams{ + Limit: int64(maxItems), + Offset: int64(offset), + }) + if err != nil { + return nil, err + } + tt := []*indexertypes.TokenFeeMeta{} + for _, t := range ttFromDB { + tt = append(tt, &indexertypes.TokenFeeMeta{ + Cost: uint64(t.Cost), + From: t.FromAccount, + TxType: t.TxType, + Height: uint64(t.BlockHeight), + Reference: string(t.Reference), + Timestamp: t.SpendTime, + }) + } + return tt, nil +} + +// GetTokenFeesByReference returns all the token fees associated with a given reference +// from the database, ordered by timestamp and paginated by maxItems and offset +func (idx *Indexer) GetTokenFeesByReference(reference string, offset, maxItems int32) ([]*indexertypes.TokenFeeMeta, error) { + ttFromDB, err := idx.readOnlyQuery.GetTokenFeesByReference(context.TODO(), indexerdb.GetTokenFeesByReferenceParams{ + Reference: reference, + Limit: int64(maxItems), + Offset: int64(offset), + }) + if err != nil { + return nil, err + } + tt := []*indexertypes.TokenFeeMeta{} + for _, t := range ttFromDB { + tt = append(tt, &indexertypes.TokenFeeMeta{ + Cost: uint64(t.Cost), + From: t.FromAccount, + TxType: t.TxType, + Height: uint64(t.BlockHeight), + Reference: string(t.Reference), + Timestamp: t.SpendTime, + }) + } + return tt, nil +} + +// GetTokenFeesByType returns all the token fees associated with a given transaction type +// from the database, ordered by timestamp and paginated by maxItems and offset +func (idx *Indexer) GetTokenFeesByType(txType string, offset, maxItems int32) ([]*indexertypes.TokenFeeMeta, error) { + ttFromDB, err := idx.readOnlyQuery.GetTokenFeesByTxType(context.TODO(), indexerdb.GetTokenFeesByTxTypeParams{ + TxType: txType, + Limit: int64(maxItems), + Offset: int64(offset), + }) + if err != nil { + return nil, err + } + tt := []*indexertypes.TokenFeeMeta{} + for _, t := range ttFromDB { + tt = append(tt, &indexertypes.TokenFeeMeta{ + Cost: uint64(t.Cost), + From: t.FromAccount, + TxType: t.TxType, + Height: uint64(t.BlockHeight), + Reference: string(t.Reference), + Timestamp: t.SpendTime, + }) + } + return tt, nil } diff --git a/vochain/indexer/indexertypes/types.go b/vochain/indexer/indexertypes/types.go index 89884b3eb..c2bc60086 100644 --- a/vochain/indexer/indexertypes/types.go +++ b/vochain/indexer/indexertypes/types.go @@ -210,3 +210,14 @@ type TokenTransferMeta struct { Timestamp time.Time `json:"timestamp"` To types.AccountID `json:"to"` } + +// TokenFeeMeta contains the information of a token fees and some extra useful information. +// The types are compatible with the SQL defined schema. +type TokenFeeMeta struct { + Cost uint64 `json:"cost"` + From types.AccountID `json:"from"` + Height uint64 `json:"height"` + Reference string `json:"reference"` + Timestamp time.Time `json:"timestamp"` + TxType string `json:"txType"` +} diff --git a/vochain/indexer/migrations/0007_create_table_token_fee.sql b/vochain/indexer/migrations/0007_create_table_token_fee.sql new file mode 100644 index 000000000..46b9a318c --- /dev/null +++ b/vochain/indexer/migrations/0007_create_table_token_fee.sql @@ -0,0 +1,26 @@ +-- +goose Up +CREATE TABLE token_fees ( + id INTEGER NOT NULL PRIMARY KEY, + block_height INTEGER NOT NULL, + from_account BLOB NOT NULL, + reference TEXT NOT NULL, + cost INTEGER NOT NULL, + tx_type TEXT NOT NULL, + spend_time DATETIME NOT NULL +); + +CREATE INDEX index_from_account_token_fees +ON token_fees(from_account); + +CREATE INDEX index_tx_type_token_fees +ON token_fees(tx_type); + +CREATE INDEX index_tx_reference_fees +ON token_fees(reference); + +-- +goose Down +DROP TABLE token_fees + +DROP INDEX index_tx_type_token_fees +DROP INDEX index_from_account_token_fees +DROP INDEX index_tx_reference_fees diff --git a/vochain/indexer/migrations/0007_create_table_token_spending.sql b/vochain/indexer/migrations/0007_create_table_token_spending.sql deleted file mode 100644 index b63f6265c..000000000 --- a/vochain/indexer/migrations/0007_create_table_token_spending.sql +++ /dev/null @@ -1,22 +0,0 @@ --- +goose Up -CREATE TABLE token_spendings ( - id INTEGER NOT NULL PRIMARY KEY, - block_height INTEGER NOT NULL, - from_account BLOB NOT NULL, - reference BLOB NOT NULL, - cost INTEGER NOT NULL, - txType TEXT NOT NULL, - spend_time DATETIME NOT NULL -); - -CREATE INDEX index_from_account_token_spendings -ON token_spendings(from_account); - -CREATE INDEX index_txType_token_spendings -ON token_spendings(txType); - --- +goose Down -DROP TABLE token_spendings - -DROP INDEX index_txType_token_spendings -DROP INDEX index_from_account_token_spendings diff --git a/vochain/indexer/queries/token_fees.sql b/vochain/indexer/queries/token_fees.sql new file mode 100644 index 000000000..f4876c973 --- /dev/null +++ b/vochain/indexer/queries/token_fees.sql @@ -0,0 +1,39 @@ +-- name: CreateTokenFee :execresult +INSERT INTO token_fees ( + from_account, block_height, reference, + cost, tx_type, spend_time +) VALUES ( + ?, ?, ?, + ?, ?, ? +); + +-- name: GetTokenFees :many +SELECT * FROM token_fees +ORDER BY spend_time DESC +LIMIT sqlc.arg(limit) +OFFSET sqlc.arg(offset) +; + +-- name: GetTokenFeesByFromAccount :many +SELECT * FROM token_fees +WHERE from_account = sqlc.arg(from_account) +ORDER BY spend_time DESC +LIMIT sqlc.arg(limit) +OFFSET sqlc.arg(offset) +; + +-- name: GetTokenFeesByTxType :many +SELECT * FROM token_fees +WHERE tx_type = sqlc.arg(tx_type) +ORDER BY spend_time DESC +LIMIT sqlc.arg(limit) +OFFSET sqlc.arg(offset) +; + +-- name: GetTokenFeesByReference :many +SELECT * FROM token_fees +WHERE reference = sqlc.arg(reference) +ORDER BY spend_time DESC +LIMIT sqlc.arg(limit) +OFFSET sqlc.arg(offset) +; diff --git a/vochain/keykeeper/keykeeper.go b/vochain/keykeeper/keykeeper.go index e2b7b2494..4ca7ae0b7 100644 --- a/vochain/keykeeper/keykeeper.go +++ b/vochain/keykeeper/keykeeper.go @@ -510,4 +510,4 @@ func (*KeyKeeper) OnSetAccount(_ []byte, _ *state.Account) {} func (*KeyKeeper) OnTransferTokens(_ *vochaintx.TokenTransfer) {} // OnSpendTokens does nothing -func (*KeyKeeper) OnSpendTokens(_ []byte, _ models.TxType, _ uint64, _ []byte) +func (*KeyKeeper) OnSpendTokens(_ []byte, _ models.TxType, _ uint64, _ string) {} diff --git a/vochain/offchaindatahandler/offchaindatahandler.go b/vochain/offchaindatahandler/offchaindatahandler.go index 52c35bdc3..00317b02e 100644 --- a/vochain/offchaindatahandler/offchaindatahandler.go +++ b/vochain/offchaindatahandler/offchaindatahandler.go @@ -166,4 +166,4 @@ func (*OffChainDataHandler) OnRevealKeys(_ []byte, _ string, _ int32) func (*OffChainDataHandler) OnProcessStatusChange(_ []byte, _ models.ProcessStatus, _ int32) {} func (*OffChainDataHandler) OnTransferTokens(_ *vochaintx.TokenTransfer) {} func (*OffChainDataHandler) OnProcessResults(_ []byte, _ *models.ProcessResult, _ int32) {} -func (*OffChainDataHandler) OnSpendTokens(_ []byte, _ models.TxType, _ uint64, _ []byte) {} +func (*OffChainDataHandler) OnSpendTokens(_ []byte, _ models.TxType, _ uint64, _ string) {} diff --git a/vochain/state/account.go b/vochain/state/account.go index 7e5cfe684..05ee56f95 100644 --- a/vochain/state/account.go +++ b/vochain/state/account.go @@ -226,7 +226,7 @@ func (v *State) SetAccount(accountAddress common.Address, account *Account) erro // BurnTxCostIncrementNonce reduces the transaction cost from the account balance and increments nonce. // If cost is set to 0, the cost is calculated from the tx type base cost. // Reference is optional and can be used to store a reference to the transaction that caused the burn. -func (v *State) BurnTxCostIncrementNonce(accountAddress common.Address, txType models.TxType, cost uint64, reference []byte) error { +func (v *State) BurnTxCostIncrementNonce(accountAddress common.Address, txType models.TxType, cost uint64, reference string) error { // get tx cost if cost == 0 { var err error diff --git a/vochain/state/eventlistener.go b/vochain/state/eventlistener.go index 924ceee11..dedcad2db 100644 --- a/vochain/state/eventlistener.go +++ b/vochain/state/eventlistener.go @@ -28,7 +28,7 @@ type EventListener interface { OnProcessesStart(pids [][]byte) OnSetAccount(addr []byte, account *Account) OnTransferTokens(tx *vochaintx.TokenTransfer) - OnSpendTokens(addr []byte, txType models.TxType, cost uint64, reference []byte) + OnSpendTokens(addr []byte, txType models.TxType, cost uint64, reference string) OnCensusUpdate(pid, censusRoot []byte, censusURI string) Commit(height uint32) (err error) OnBeginBlock(BeginBlock) diff --git a/vochain/state/state_test.go b/vochain/state/state_test.go index d35911487..2dffe7e06 100644 --- a/vochain/state/state_test.go +++ b/vochain/state/state_test.go @@ -186,7 +186,7 @@ func (*Listener) OnProcessResults(_ []byte, _ *models.ProcessResult, _ int32) func (*Listener) OnCensusUpdate(_, _ []byte, _ string) {} func (*Listener) OnSetAccount(_ []byte, _ *Account) {} func (*Listener) OnTransferTokens(_ *vochaintx.TokenTransfer) {} -func (*Listener) OnSpendTokens(_ []byte, _ models.TxType, _ uint64, _ []byte) {} +func (*Listener) OnSpendTokens(_ []byte, _ models.TxType, _ uint64, _ string) {} func (l *Listener) OnProcessesStart(pids [][]byte) { l.processStart = append(l.processStart, pids) } diff --git a/vochain/transaction/transaction.go b/vochain/transaction/transaction.go index 9074ecacb..795a6f8cb 100644 --- a/vochain/transaction/transaction.go +++ b/vochain/transaction/transaction.go @@ -1,6 +1,7 @@ package transaction import ( + "encoding/hex" "fmt" "github.com/ethereum/go-ethereum/common" @@ -139,7 +140,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa common.Address(txSender), models.TxType_NEW_PROCESS, t.txElectionCostFromProcess(p), - p.GetProcessId(), + hex.EncodeToString(p.GetProcessId()), ) } @@ -182,7 +183,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa default: return nil, fmt.Errorf("unknown set process tx type") } - return response, t.state.BurnTxCostIncrementNonce(common.Address(txSender), tx.Txtype, 0, tx.ProcessId) + return response, t.state.BurnTxCostIncrementNonce(common.Address(txSender), tx.Txtype, 0, hex.EncodeToString(tx.ProcessId)) } case *models.Tx_SetAccount: @@ -276,7 +277,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa txSenderAddress, models.TxType_SET_ACCOUNT_INFO_URI, 0, - []byte(tx.GetInfoURI()), + tx.GetInfoURI(), ); err != nil { return nil, fmt.Errorf("setAccountTx: burnCostIncrementNonce %w", err) } @@ -301,7 +302,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa txSenderAddress, models.TxType_ADD_DELEGATE_FOR_ACCOUNT, 0, - nil, + "", ); err != nil { return nil, fmt.Errorf("setAccountDelegateTx: burnTxCostIncrementNonce %w", err) } @@ -321,7 +322,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa txSenderAddress, models.TxType_DEL_DELEGATE_FOR_ACCOUNT, 0, - nil, + "", ); err != nil { return nil, fmt.Errorf("setAccountDelegate: burnTxCostIncrementNonce %w", err) } @@ -379,7 +380,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa if forCommit { tx := vtx.Tx.GetSendTokens() from, to := common.BytesToAddress(tx.From), common.BytesToAddress(tx.To) - err := t.state.BurnTxCostIncrementNonce(from, models.TxType_SEND_TOKENS, 0, to.Bytes()) + err := t.state.BurnTxCostIncrementNonce(from, models.TxType_SEND_TOKENS, 0, to.Hex()) if err != nil { return nil, fmt.Errorf("sendTokensTx: burnTxCostIncrementNonce %w", err) } @@ -405,7 +406,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa if err != nil { return nil, fmt.Errorf("collectFaucetTx: cannot get issuerAddress %w", err) } - if err := t.state.BurnTxCostIncrementNonce(issuerAddress, models.TxType_COLLECT_FAUCET, 0, nil); err != nil { + if err := t.state.BurnTxCostIncrementNonce(issuerAddress, models.TxType_COLLECT_FAUCET, 0, ""); err != nil { return nil, fmt.Errorf("collectFaucetTx: burnTxCost %w", err) } faucetPayload := &models.FaucetPayload{} @@ -443,7 +444,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa txAddress, models.TxType_SET_ACCOUNT_SIK, 0, - newSIK, + newSIK.String(), ); err != nil { return nil, fmt.Errorf("setSIKTx: burnTxCostIncrementNonce %w", err) } @@ -463,7 +464,7 @@ func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*Transa txAddress, models.TxType_DEL_ACCOUNT_SIK, 0, - nil, + "", ); err != nil { return nil, fmt.Errorf("delSIKTx: burnTxCostIncrementNonce %w", err) }