diff --git a/api/api.go b/api/api.go index 6dcacc5f6..494eb68db 100644 --- a/api/api.go +++ b/api/api.go @@ -80,6 +80,9 @@ const ( ParamStartDateBefore = "startDateBefore" ParamEndDateAfter = "endDateAfter" ParamEndDateBefore = "endDateBefore" + ParamChainId = "chainId" + ParamHash = "hash" + ParamProposerAddress = "proposerAddress" ) var ( diff --git a/api/api_types.go b/api/api_types.go index d948b7207..1a8e2f3e0 100644 --- a/api/api_types.go +++ b/api/api_types.go @@ -54,6 +54,14 @@ type TransactionParams struct { Type string `json:"type,omitempty"` } +// BlockParams allows the client to filter blocks +type BlockParams struct { + PaginationParams + ChainID string `json:"chainId,omitempty"` + Hash string `json:"hash,omitempty"` + ProposerAddress string `json:"proposerAddress,omitempty"` +} + // FeesParams allows the client to filter fees type FeesParams struct { PaginationParams @@ -267,8 +275,8 @@ type TransactionReference struct { // TransactionsList is used to return a paginated list to the client type TransactionsList struct { - Transactions []*indexertypes.Transaction `json:"transactions"` - Pagination *Pagination `json:"pagination"` + Transactions []*indexertypes.TransactionMetadata `json:"transactions"` + Pagination *Pagination `json:"pagination"` } // FeesList is used to return a paginated list to the client @@ -438,4 +446,11 @@ func CensusTypeToOrigin(ctype CensusTypeDescription) (models.CensusOrigin, []byt type Block struct { comettypes.Block `json:",inline"` Hash types.HexBytes `json:"hash" ` + TxCount int64 `json:"txCount"` +} + +// BlockList is used to return a paginated list to the client +type BlockList struct { + Blocks []*indexertypes.Block `json:"blocks"` + Pagination *Pagination `json:"pagination"` } diff --git a/api/chain.go b/api/chain.go index 85c88801c..603035bc3 100644 --- a/api/chain.go +++ b/api/chain.go @@ -13,7 +13,6 @@ import ( "go.vocdoni.io/dvote/crypto/zk/circuit" "go.vocdoni.io/dvote/httprouter" "go.vocdoni.io/dvote/httprouter/apirest" - "go.vocdoni.io/dvote/types" "go.vocdoni.io/dvote/util" "go.vocdoni.io/dvote/vochain" "go.vocdoni.io/dvote/vochain/genesis" @@ -166,7 +165,7 @@ func (a *API) enableChainHandlers() error { "/chain/blocks/{height}", "GET", apirest.MethodAccessTypePublic, - a.chainBlockHandler, + a.chainBlockByHeightHandler, ); err != nil { return err } @@ -178,6 +177,14 @@ func (a *API) enableChainHandlers() error { ); err != nil { return err } + if err := a.Endpoint.RegisterMethod( + "/chain/blocks", + "GET", + apirest.MethodAccessTypePublic, + a.chainBlockListHandler, + ); err != nil { + return err + } if err := a.Endpoint.RegisterMethod( "/chain/organizations/filter/page/{page}", "POST", @@ -621,7 +628,7 @@ func (a *API) chainTxRefByHashHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCo if err != nil { return err } - ref, err := a.indexer.GetTxHashReference(hash) + ref, err := a.indexer.GetTxMetadataByHash(hash) if err != nil { if errors.Is(err, indexer.ErrTransactionNotFound) { return ErrTransactionNotFound @@ -665,7 +672,7 @@ func (a *API) chainTxHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) er return ErrVochainGetTxFailed.WithErr(err) } - ref, err := a.indexer.GetTxReferenceByBlockHeightAndBlockIndex(height, index) + ref, err := a.indexer.GetTxByBlockHeightAndBlockIndex(height, index) if err != nil { if errors.Is(err, indexer.ErrTransactionNotFound) { return ErrTransactionNotFound @@ -686,8 +693,8 @@ func (a *API) chainTxHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) er // chainTxRefByIndexHandler // -// @Summary Transaction by index -// @Description Get transaction by its index. This is not transaction reference (hash), and neither the block height and block index. The transaction index is an incremental counter for each transaction. You could use the transaction `block` and `index` to retrieve full info using [transaction by block and index](transaction-by-block-index). +// @Summary Transaction metadata (by db index) +// @Description Get transaction by its internal index. This is not the transaction hash, and neither the block height and block index. The transaction index is an incremental counter for each transaction. You could use the transaction `block` and `index` to retrieve full info using [transaction by block and index](transaction-by-block-index). // @Tags Chain // @Accept json // @Produce json @@ -700,7 +707,7 @@ func (a *API) chainTxRefByIndexHandler(_ *apirest.APIdata, ctx *httprouter.HTTPC if err != nil { return err } - ref, err := a.indexer.GetTransaction(index) + ref, err := a.indexer.GetTxMetadataByID(index) if err != nil { if errors.Is(err, indexer.ErrTransactionNotFound) { return ErrTransactionNotFound @@ -856,7 +863,7 @@ func (a *API) chainValidatorsHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCon return ctx.Send(data, apirest.HTTPstatusOK) } -// chainBlockHandler +// chainBlockByHeightHandler // // @Summary Get block (by height) // @Description Returns the full block information at the given height @@ -866,23 +873,36 @@ func (a *API) chainValidatorsHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCon // @Param height path int true "Block height" // @Success 200 {object} api.Block // @Router /chain/blocks/{height} [get] -func (a *API) chainBlockHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { +func (a *API) chainBlockByHeightHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { height, err := strconv.ParseUint(ctx.URLParam(ParamHeight), 10, 64) if err != nil { return err } - tmblock := a.vocapp.GetBlockByHeight(int64(height)) - if tmblock == nil { - return ErrBlockNotFound + idxblock, err := a.indexer.BlockByHeight(int64(height)) + if err != nil { + if errors.Is(err, indexer.ErrBlockNotFound) { + return ErrBlockNotFound + } + return ErrBlockNotFound.WithErr(err) + } + txcount, err := a.indexer.CountTransactionsByHeight(int64(height)) + if err != nil { + return ErrIndexerQueryFailed.WithErr(err) } block := &Block{ Block: comettypes.Block{ - Header: tmblock.Header, - Data: tmblock.Data, - Evidence: tmblock.Evidence, - LastCommit: tmblock.LastCommit, + Header: comettypes.Header{ + ChainID: idxblock.ChainID, + Height: idxblock.Height, + Time: idxblock.Time, + ProposerAddress: []byte(idxblock.ProposerAddress), + LastBlockID: comettypes.BlockID{ + Hash: []byte(idxblock.LastBlockHash), + }, + }, }, - Hash: types.HexBytes(tmblock.Hash()), + Hash: idxblock.Hash, + TxCount: txcount, } data, err := json.Marshal(block) if err != nil { @@ -906,18 +926,31 @@ func (a *API) chainBlockByHashHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCo if err != nil { return err } - tmblock := a.vocapp.GetBlockByHash(hash) - if tmblock == nil { - return ErrBlockNotFound + idxblock, err := a.indexer.BlockByHash(hash) + if err != nil { + if errors.Is(err, indexer.ErrBlockNotFound) { + return ErrBlockNotFound + } + return ErrBlockNotFound.WithErr(err) + } + txcount, err := a.indexer.CountTransactionsByHeight(idxblock.Height) + if err != nil { + return ErrIndexerQueryFailed.WithErr(err) } block := &Block{ Block: comettypes.Block{ - Header: tmblock.Header, - Data: tmblock.Data, - Evidence: tmblock.Evidence, - LastCommit: tmblock.LastCommit, + Header: comettypes.Header{ + ChainID: idxblock.ChainID, + Height: idxblock.Height, + Time: idxblock.Time, + ProposerAddress: []byte(idxblock.ProposerAddress), + LastBlockID: comettypes.BlockID{ + Hash: []byte(idxblock.LastBlockHash), + }, + }, }, - Hash: types.HexBytes(tmblock.Hash()), + Hash: idxblock.Hash, + TxCount: txcount, } data, err := json.Marshal(block) if err != nil { @@ -926,6 +959,63 @@ func (a *API) chainBlockByHashHandler(_ *apirest.APIdata, ctx *httprouter.HTTPCo return ctx.Send(convertKeysToCamel(data), apirest.HTTPstatusOK) } +// chainBlockListHandler +// +// @Summary List all blocks +// @Description Returns the list of blocks, ordered by descending height. +// @Tags Chain +// @Accept json +// @Produce json +// @Param page query number false "Page" +// @Param limit query number false "Items per page" +// @Param chainId query string false "Filter by exact chainId" +// @Param hash query string false "Filter by partial hash" +// @Param proposerAddress query string false "Filter by exact proposerAddress" +// @Success 200 {object} BlockList +// @Router /chain/blocks [get] +func (a *API) chainBlockListHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error { + params, err := parseBlockParams( + ctx.QueryParam(ParamPage), + ctx.QueryParam(ParamLimit), + ctx.QueryParam(ParamChainId), + ctx.QueryParam(ParamHash), + ctx.QueryParam(ParamProposerAddress), + ) + if err != nil { + return err + } + + return a.sendBlockList(ctx, params) +} + +// sendBlockList produces a filtered, paginated BlockList, +// and sends it marshalled over ctx.Send +// +// Errors returned are always of type APIerror. +func (a *API) sendBlockList(ctx *httprouter.HTTPContext, params *BlockParams) error { + blocks, total, err := a.indexer.SearchBlocks( + params.Limit, + params.Page*params.Limit, + params.ChainID, + params.Hash, + params.ProposerAddress, + ) + if err != nil { + return ErrIndexerQueryFailed.WithErr(err) + } + + pagination, err := calculatePagination(params.Page, params.Limit, total) + if err != nil { + return err + } + + list := &BlockList{ + Blocks: blocks, + Pagination: pagination, + } + return marshalAndSend(ctx, list) +} + // chainTransactionCountHandler // // @Summary Transactions count @@ -1239,3 +1329,18 @@ func parseTransactionParams(paramPage, paramLimit, paramHeight, paramType string Type: paramType, }, nil } + +// parseBlockParams returns an BlockParams filled with the passed params +func parseBlockParams(paramPage, paramLimit, paramChainId, paramHash, paramProposerAddress string) (*BlockParams, error) { + pagination, err := parsePaginationParams(paramPage, paramLimit) + if err != nil { + return nil, err + } + + return &BlockParams{ + PaginationParams: pagination, + ChainID: paramChainId, + Hash: util.TrimHex(paramHash), + ProposerAddress: util.TrimHex(paramProposerAddress), + }, nil +} diff --git a/test/api_test.go b/test/api_test.go index 80e4f3699..267895636 100644 --- a/test/api_test.go +++ b/test/api_test.go @@ -461,6 +461,41 @@ func TestAPIAccountTokentxs(t *testing.T) { qt.Assert(t, gotAcct1.Balance, qt.Equals, initBalance+amountAcc2toAcct1-amountAcc1toAcct2-uint64(txBasePrice)) } +func TestAPIBlocks(t *testing.T) { + server := testcommon.APIserver{} + server.Start(t, + api.ChainHandler, + api.CensusHandler, + api.VoteHandler, + api.AccountHandler, + api.ElectionHandler, + api.WalletHandler, + ) + token1 := uuid.New() + c := testutil.NewTestHTTPclient(t, server.ListenAddr, &token1) + + // Block 1 + server.VochainAPP.AdvanceTestBlock() + waitUntilHeight(t, c, 1) + + // create a new account + initBalance := uint64(80) + _ = createAccount(t, c, server, initBalance) + + // Block 2 + server.VochainAPP.AdvanceTestBlock() + waitUntilHeight(t, c, 2) + + // check the txCount + resp, code := c.Request("GET", nil, "chain", "blocks", "1") + qt.Assert(t, code, qt.Equals, 200, qt.Commentf("response: %s", resp)) + + block := api.Block{} + err := json.Unmarshal(resp, &block) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, block.TxCount, qt.Equals, int64(1)) +} + func runAPIElectionCostWithParams(t *testing.T, electionParams electionprice.ElectionParameters, startBlock uint32, initialBalance, diff --git a/vochain/app.go b/vochain/app.go index 7e0107537..f6691be61 100644 --- a/vochain/app.go +++ b/vochain/app.go @@ -290,10 +290,6 @@ func (app *BaseApplication) beginBlock(t time.Time, height uint32) { app.State.SetHeight(height) go app.State.CachePurge(height) - app.State.OnBeginBlock(vstate.BeginBlock{ - Height: int64(height), - Time: t, - }) } // endBlock is called at the end of every block. diff --git a/vochain/appsetup.go b/vochain/appsetup.go index fb52f66ed..6726b8b21 100644 --- a/vochain/appsetup.go +++ b/vochain/appsetup.go @@ -25,9 +25,6 @@ func (app *BaseApplication) SetNode(vochaincfg *config.VochainCfg) error { if app.Node, err = newTendermint(app, vochaincfg); err != nil { return fmt.Errorf("could not set tendermint node service: %s", err) } - if vochaincfg.IsSeedNode { - return nil - } // Note that cometcli.New logs any error rather than returning it. app.NodeClient = cometcli.New(app.Node) return nil diff --git a/vochain/indexer/bench_test.go b/vochain/indexer/bench_test.go index 631e83205..f73645bd9 100644 --- a/vochain/indexer/bench_test.go +++ b/vochain/indexer/bench_test.go @@ -85,6 +85,7 @@ func BenchmarkIndexer(b *testing.B) { tx := &vochaintx.Tx{ TxID: rnd.Random32(), TxModelType: "vote", + Tx: &models.Tx{Payload: &models.Tx_Vote{}}, } idx.OnNewTx(tx, height, txBlockIndex) curTxs = append(curTxs, tx) @@ -112,7 +113,7 @@ func BenchmarkIndexer(b *testing.B) { qt.Check(b, bytes.Equal(voteRef.Meta.TxHash, tx.TxID[:]), qt.IsTrue) } - txRef, err := idx.GetTxHashReference(tx.TxID[:]) + txRef, err := idx.GetTxMetadataByHash(tx.TxID[:]) qt.Check(b, err, qt.IsNil) if err == nil { qt.Check(b, txRef.BlockHeight, qt.Equals, vote.Height) @@ -138,7 +139,11 @@ func BenchmarkFetchTx(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { for j := 0; j < numTxs; j++ { - idx.OnNewTx(&vochaintx.Tx{TxID: util.Random32()}, uint32(i), int32(j)) + idx.OnNewTx(&vochaintx.Tx{ + TxID: util.Random32(), + TxModelType: "vote", + Tx: &models.Tx{Payload: &models.Tx_Vote{}}, + }, uint32(i), int32(j)) } err := idx.Commit(uint32(i)) qt.Assert(b, err, qt.IsNil) @@ -147,14 +152,14 @@ func BenchmarkFetchTx(b *testing.B) { startTime := time.Now() for j := 0; j < numTxs; j++ { - _, err = idx.GetTransaction(uint64((i * numTxs) + j + 1)) + _, err = idx.GetTxMetadataByID(uint64((i * numTxs) + j + 1)) qt.Assert(b, err, qt.IsNil) } log.Infof("fetched %d transactions (out of %d total) by index, took %s", numTxs, (i+1)*numTxs, time.Since(startTime)) startTime = time.Now() for j := 0; j < numTxs; j++ { - _, err = idx.GetTxHashReference([]byte(fmt.Sprintf("hash%d%d", i, j))) + _, err = idx.GetTxMetadataByHash([]byte(fmt.Sprintf("hash%d%d", i, j))) qt.Assert(b, err, qt.IsNil) } log.Infof("fetched %d transactions (out of %d total) by hash, took %s", diff --git a/vochain/indexer/block.go b/vochain/indexer/block.go index e40a5fdfb..8592712e6 100644 --- a/vochain/indexer/block.go +++ b/vochain/indexer/block.go @@ -7,35 +7,79 @@ import ( "fmt" "time" - "go.vocdoni.io/dvote/log" indexerdb "go.vocdoni.io/dvote/vochain/indexer/db" - "go.vocdoni.io/dvote/vochain/state" + "go.vocdoni.io/dvote/vochain/indexer/indexertypes" ) // ErrBlockNotFound is returned if the block is not found in the indexer database. var ErrBlockNotFound = fmt.Errorf("block not found") -func (idx *Indexer) OnBeginBlock(bb state.BeginBlock) { - idx.blockMu.Lock() - defer idx.blockMu.Unlock() - queries := idx.blockTxQueries() - if _, err := queries.CreateBlock(context.TODO(), indexerdb.CreateBlockParams{ - Height: bb.Height, - Time: bb.Time, - DataHash: nonNullBytes(bb.DataHash), - }); err != nil { - log.Errorw(err, "cannot index new block") +// BlockTimestamp returns the timestamp of the block at the given height +func (idx *Indexer) BlockTimestamp(height int64) (time.Time, error) { + block, err := idx.BlockByHeight(height) + if err != nil { + return time.Time{}, err } + return block.Time, nil } -// BlockTimestamp returns the timestamp of the block at the given height -func (idx *Indexer) BlockTimestamp(height int64) (time.Time, error) { - block, err := idx.readOnlyQuery.GetBlock(context.TODO(), height) +// BlockByHeight returns the available information of the block at the given height +func (idx *Indexer) BlockByHeight(height int64) (*indexertypes.Block, error) { + block, err := idx.readOnlyQuery.GetBlockByHeight(context.TODO(), height) if err != nil { if errors.Is(err, sql.ErrNoRows) { - return time.Time{}, ErrBlockNotFound + return nil, ErrBlockNotFound } - return time.Time{}, err + return nil, err } - return block.Time, nil + return indexertypes.BlockFromDB(&block), nil +} + +// BlockByHash returns the available information of the block with the given hash +func (idx *Indexer) BlockByHash(hash []byte) (*indexertypes.Block, error) { + block, err := idx.readOnlyQuery.GetBlockByHash(context.TODO(), hash) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrBlockNotFound + } + return nil, err + } + return indexertypes.BlockFromDB(&block), nil +} + +// SearchBlocks returns the list of blocks indexed. +// chainID, hash, proposerAddress are optional, if declared as zero-value will be ignored. +// The first one returned is the newest, so they are in descending order. +func (idx *Indexer) SearchBlocks(limit, offset int, chainID, hash, proposerAddress string) ([]*indexertypes.Block, uint64, error) { + if offset < 0 { + return nil, 0, fmt.Errorf("invalid value: offset cannot be %d", offset) + } + if limit <= 0 { + return nil, 0, fmt.Errorf("invalid value: limit cannot be %d", limit) + } + results, err := idx.readOnlyQuery.SearchBlocks(context.TODO(), indexerdb.SearchBlocksParams{ + Limit: int64(limit), + Offset: int64(offset), + ChainID: chainID, + HashSubstr: hash, + ProposerAddress: proposerAddress, + }) + if err != nil { + return nil, 0, err + } + list := []*indexertypes.Block{} + for _, row := range results { + list = append(list, &indexertypes.Block{ + ChainID: row.ChainID, + Height: row.Height, + Time: row.Time, + Hash: row.Hash, + ProposerAddress: row.ProposerAddress, + LastBlockHash: row.LastBlockHash, + }) + } + if len(results) == 0 { + return list, 0, nil + } + return list, uint64(results[0].TotalCount), nil } diff --git a/vochain/indexer/db/blocks.sql.go b/vochain/indexer/db/blocks.sql.go index ffa3fa896..f9cb75888 100644 --- a/vochain/indexer/db/blocks.sql.go +++ b/vochain/indexer/db/blocks.sql.go @@ -13,31 +13,142 @@ import ( const createBlock = `-- name: CreateBlock :execresult INSERT INTO blocks( - height, time, data_hash + chain_id, height, time, hash, proposer_address, last_block_hash ) VALUES ( - ?, ?, ? + ?, ?, ?, ?, ?, ? ) ` type CreateBlockParams struct { - Height int64 - Time time.Time - DataHash []byte + ChainID string + Height int64 + Time time.Time + Hash []byte + ProposerAddress []byte + LastBlockHash []byte } func (q *Queries) CreateBlock(ctx context.Context, arg CreateBlockParams) (sql.Result, error) { - return q.exec(ctx, q.createBlockStmt, createBlock, arg.Height, arg.Time, arg.DataHash) + return q.exec(ctx, q.createBlockStmt, createBlock, + arg.ChainID, + arg.Height, + arg.Time, + arg.Hash, + arg.ProposerAddress, + arg.LastBlockHash, + ) } -const getBlock = `-- name: GetBlock :one -SELECT height, time, data_hash FROM blocks +const getBlockByHash = `-- name: GetBlockByHash :one +SELECT height, time, chain_id, hash, proposer_address, last_block_hash FROM blocks +WHERE hash = ? +LIMIT 1 +` + +func (q *Queries) GetBlockByHash(ctx context.Context, hash []byte) (Block, error) { + row := q.queryRow(ctx, q.getBlockByHashStmt, getBlockByHash, hash) + var i Block + err := row.Scan( + &i.Height, + &i.Time, + &i.ChainID, + &i.Hash, + &i.ProposerAddress, + &i.LastBlockHash, + ) + return i, err +} + +const getBlockByHeight = `-- name: GetBlockByHeight :one +SELECT height, time, chain_id, hash, proposer_address, last_block_hash FROM blocks WHERE height = ? LIMIT 1 ` -func (q *Queries) GetBlock(ctx context.Context, height int64) (Block, error) { - row := q.queryRow(ctx, q.getBlockStmt, getBlock, height) +func (q *Queries) GetBlockByHeight(ctx context.Context, height int64) (Block, error) { + row := q.queryRow(ctx, q.getBlockByHeightStmt, getBlockByHeight, height) var i Block - err := row.Scan(&i.Height, &i.Time, &i.DataHash) + err := row.Scan( + &i.Height, + &i.Time, + &i.ChainID, + &i.Hash, + &i.ProposerAddress, + &i.LastBlockHash, + ) return i, err } + +const searchBlocks = `-- name: SearchBlocks :many +SELECT height, time, chain_id, hash, proposer_address, last_block_hash, COUNT(*) OVER() AS total_count +FROM blocks +WHERE ( + (?1 = '' OR chain_id = ?1) + AND LENGTH(?2) <= 64 -- if passed arg is longer, then just abort the query + AND ( + ?2 = '' + OR (LENGTH(?2) = 64 AND LOWER(HEX(hash)) = LOWER(?2)) + OR (LENGTH(?2) < 64 AND INSTR(LOWER(HEX(hash)), LOWER(?2)) > 0) + -- TODO: consider keeping an hash_hex column for faster searches + ) + AND (?3 = 0 OR LOWER(HEX(proposer_address)) = LOWER(?3)) +) +ORDER BY height DESC +LIMIT ?5 +OFFSET ?4 +` + +type SearchBlocksParams struct { + ChainID interface{} + HashSubstr interface{} + ProposerAddress interface{} + Offset int64 + Limit int64 +} + +type SearchBlocksRow struct { + Height int64 + Time time.Time + ChainID string + Hash []byte + ProposerAddress []byte + LastBlockHash []byte + TotalCount int64 +} + +func (q *Queries) SearchBlocks(ctx context.Context, arg SearchBlocksParams) ([]SearchBlocksRow, error) { + rows, err := q.query(ctx, q.searchBlocksStmt, searchBlocks, + arg.ChainID, + arg.HashSubstr, + arg.ProposerAddress, + arg.Offset, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SearchBlocksRow + for rows.Next() { + var i SearchBlocksRow + if err := rows.Scan( + &i.Height, + &i.Time, + &i.ChainID, + &i.Hash, + &i.ProposerAddress, + &i.LastBlockHash, + &i.TotalCount, + ); 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/db/db.go b/vochain/indexer/db/db.go index 3695e8b73..76442c287 100644 --- a/vochain/indexer/db/db.go +++ b/vochain/indexer/db/db.go @@ -36,6 +36,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.countTransactionsStmt, err = db.PrepareContext(ctx, countTransactions); err != nil { return nil, fmt.Errorf("error preparing query CountTransactions: %w", err) } + if q.countTransactionsByHeightStmt, err = db.PrepareContext(ctx, countTransactionsByHeight); err != nil { + return nil, fmt.Errorf("error preparing query CountTransactionsByHeight: %w", err) + } if q.countVotesStmt, err = db.PrepareContext(ctx, countVotes); err != nil { return nil, fmt.Errorf("error preparing query CountVotes: %w", err) } @@ -60,8 +63,11 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.createVoteStmt, err = db.PrepareContext(ctx, createVote); err != nil { return nil, fmt.Errorf("error preparing query CreateVote: %w", err) } - if q.getBlockStmt, err = db.PrepareContext(ctx, getBlock); err != nil { - return nil, fmt.Errorf("error preparing query GetBlock: %w", err) + if q.getBlockByHashStmt, err = db.PrepareContext(ctx, getBlockByHash); err != nil { + return nil, fmt.Errorf("error preparing query GetBlockByHash: %w", err) + } + if q.getBlockByHeightStmt, err = db.PrepareContext(ctx, getBlockByHeight); err != nil { + return nil, fmt.Errorf("error preparing query GetBlockByHeight: %w", err) } if q.getEntityCountStmt, err = db.PrepareContext(ctx, getEntityCount); err != nil { return nil, fmt.Errorf("error preparing query GetEntityCount: %w", err) @@ -96,6 +102,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.searchAccountsStmt, err = db.PrepareContext(ctx, searchAccounts); err != nil { return nil, fmt.Errorf("error preparing query SearchAccounts: %w", err) } + if q.searchBlocksStmt, err = db.PrepareContext(ctx, searchBlocks); err != nil { + return nil, fmt.Errorf("error preparing query SearchBlocks: %w", err) + } if q.searchEntitiesStmt, err = db.PrepareContext(ctx, searchEntities); err != nil { return nil, fmt.Errorf("error preparing query SearchEntities: %w", err) } @@ -157,6 +166,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing countTransactionsStmt: %w", cerr) } } + if q.countTransactionsByHeightStmt != nil { + if cerr := q.countTransactionsByHeightStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing countTransactionsByHeightStmt: %w", cerr) + } + } if q.countVotesStmt != nil { if cerr := q.countVotesStmt.Close(); cerr != nil { err = fmt.Errorf("error closing countVotesStmt: %w", cerr) @@ -197,9 +211,14 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing createVoteStmt: %w", cerr) } } - if q.getBlockStmt != nil { - if cerr := q.getBlockStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getBlockStmt: %w", cerr) + if q.getBlockByHashStmt != nil { + if cerr := q.getBlockByHashStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getBlockByHashStmt: %w", cerr) + } + } + if q.getBlockByHeightStmt != nil { + if cerr := q.getBlockByHeightStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getBlockByHeightStmt: %w", cerr) } } if q.getEntityCountStmt != nil { @@ -257,6 +276,11 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing searchAccountsStmt: %w", cerr) } } + if q.searchBlocksStmt != nil { + if cerr := q.searchBlocksStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing searchBlocksStmt: %w", cerr) + } + } if q.searchEntitiesStmt != nil { if cerr := q.searchEntitiesStmt.Close(); cerr != nil { err = fmt.Errorf("error closing searchEntitiesStmt: %w", cerr) @@ -360,6 +384,7 @@ type Queries struct { countAccountsStmt *sql.Stmt countTokenTransfersByAccountStmt *sql.Stmt countTransactionsStmt *sql.Stmt + countTransactionsByHeightStmt *sql.Stmt countVotesStmt *sql.Stmt createAccountStmt *sql.Stmt createBlockStmt *sql.Stmt @@ -368,7 +393,8 @@ type Queries struct { createTokenTransferStmt *sql.Stmt createTransactionStmt *sql.Stmt createVoteStmt *sql.Stmt - getBlockStmt *sql.Stmt + getBlockByHashStmt *sql.Stmt + getBlockByHeightStmt *sql.Stmt getEntityCountStmt *sql.Stmt getProcessStmt *sql.Stmt getProcessCountStmt *sql.Stmt @@ -380,6 +406,7 @@ type Queries struct { getTxReferenceByBlockHeightAndBlockIndexStmt *sql.Stmt getVoteStmt *sql.Stmt searchAccountsStmt *sql.Stmt + searchBlocksStmt *sql.Stmt searchEntitiesStmt *sql.Stmt searchProcessesStmt *sql.Stmt searchTokenFeesStmt *sql.Stmt @@ -402,6 +429,7 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { countAccountsStmt: q.countAccountsStmt, countTokenTransfersByAccountStmt: q.countTokenTransfersByAccountStmt, countTransactionsStmt: q.countTransactionsStmt, + countTransactionsByHeightStmt: q.countTransactionsByHeightStmt, countVotesStmt: q.countVotesStmt, createAccountStmt: q.createAccountStmt, createBlockStmt: q.createBlockStmt, @@ -410,7 +438,8 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { createTokenTransferStmt: q.createTokenTransferStmt, createTransactionStmt: q.createTransactionStmt, createVoteStmt: q.createVoteStmt, - getBlockStmt: q.getBlockStmt, + getBlockByHashStmt: q.getBlockByHashStmt, + getBlockByHeightStmt: q.getBlockByHeightStmt, getEntityCountStmt: q.getEntityCountStmt, getProcessStmt: q.getProcessStmt, getProcessCountStmt: q.getProcessCountStmt, @@ -422,6 +451,7 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { getTxReferenceByBlockHeightAndBlockIndexStmt: q.getTxReferenceByBlockHeightAndBlockIndexStmt, getVoteStmt: q.getVoteStmt, searchAccountsStmt: q.searchAccountsStmt, + searchBlocksStmt: q.searchBlocksStmt, searchEntitiesStmt: q.searchEntitiesStmt, searchProcessesStmt: q.searchProcessesStmt, searchTokenFeesStmt: q.searchTokenFeesStmt, diff --git a/vochain/indexer/db/models.go b/vochain/indexer/db/models.go index 08a6e0e2b..061d2182f 100644 --- a/vochain/indexer/db/models.go +++ b/vochain/indexer/db/models.go @@ -11,9 +11,12 @@ import ( ) type Block struct { - Height int64 - Time time.Time - DataHash []byte + Height int64 + Time time.Time + ChainID string + Hash []byte + ProposerAddress []byte + LastBlockHash []byte } type Process struct { @@ -62,4 +65,5 @@ type Transaction struct { BlockHeight int64 BlockIndex int64 Type string + RawTx []byte } diff --git a/vochain/indexer/db/transactions.sql.go b/vochain/indexer/db/transactions.sql.go index 2d06f135a..96104b209 100644 --- a/vochain/indexer/db/transactions.sql.go +++ b/vochain/indexer/db/transactions.sql.go @@ -23,11 +23,23 @@ func (q *Queries) CountTransactions(ctx context.Context) (int64, error) { return count, err } +const countTransactionsByHeight = `-- name: CountTransactionsByHeight :one +SELECT COUNT(*) FROM transactions +WHERE block_height = ? +` + +func (q *Queries) CountTransactionsByHeight(ctx context.Context, blockHeight int64) (int64, error) { + row := q.queryRow(ctx, q.countTransactionsByHeightStmt, countTransactionsByHeight, blockHeight) + var count int64 + err := row.Scan(&count) + return count, err +} + const createTransaction = `-- name: CreateTransaction :execresult INSERT INTO transactions ( - hash, block_height, block_index, type + hash, block_height, block_index, type, raw_tx ) VALUES ( - ?, ?, ?, ? + ?, ?, ?, ?, ? ) ` @@ -36,6 +48,7 @@ type CreateTransactionParams struct { BlockHeight int64 BlockIndex int64 Type string + RawTx []byte } func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (sql.Result, error) { @@ -44,11 +57,12 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa arg.BlockHeight, arg.BlockIndex, arg.Type, + arg.RawTx, ) } const getTransaction = `-- name: GetTransaction :one -SELECT id, hash, block_height, block_index, type FROM transactions +SELECT id, hash, block_height, block_index, type, raw_tx FROM transactions WHERE id = ? LIMIT 1 ` @@ -62,12 +76,13 @@ func (q *Queries) GetTransaction(ctx context.Context, id int64) (Transaction, er &i.BlockHeight, &i.BlockIndex, &i.Type, + &i.RawTx, ) return i, err } const getTransactionByHash = `-- name: GetTransactionByHash :one -SELECT id, hash, block_height, block_index, type FROM transactions +SELECT id, hash, block_height, block_index, type, raw_tx FROM transactions WHERE hash = ? LIMIT 1 ` @@ -81,12 +96,13 @@ func (q *Queries) GetTransactionByHash(ctx context.Context, hash types.Hash) (Tr &i.BlockHeight, &i.BlockIndex, &i.Type, + &i.RawTx, ) return i, err } const getTxReferenceByBlockHeightAndBlockIndex = `-- name: GetTxReferenceByBlockHeightAndBlockIndex :one -SELECT id, hash, block_height, block_index, type FROM transactions +SELECT id, hash, block_height, block_index, type, raw_tx FROM transactions WHERE block_height = ? AND block_index = ? LIMIT 1 ` @@ -105,20 +121,21 @@ func (q *Queries) GetTxReferenceByBlockHeightAndBlockIndex(ctx context.Context, &i.BlockHeight, &i.BlockIndex, &i.Type, + &i.RawTx, ) return i, err } const searchTransactions = `-- name: SearchTransactions :many WITH results AS ( - SELECT id, hash, block_height, block_index, type + SELECT id, hash, block_height, block_index, type, raw_tx FROM transactions WHERE ( (?3 = 0 OR block_height = ?3) AND (?4 = '' OR LOWER(type) = LOWER(?4)) ) ) -SELECT id, hash, block_height, block_index, type, COUNT(*) OVER() AS total_count +SELECT id, hash, block_height, block_index, type, raw_tx, COUNT(*) OVER() AS total_count FROM results ORDER BY id DESC LIMIT ?2 @@ -138,6 +155,7 @@ type SearchTransactionsRow struct { BlockHeight int64 BlockIndex int64 Type string + RawTx []byte TotalCount int64 } @@ -161,6 +179,7 @@ func (q *Queries) SearchTransactions(ctx context.Context, arg SearchTransactions &i.BlockHeight, &i.BlockIndex, &i.Type, + &i.RawTx, &i.TotalCount, ); err != nil { return nil, err diff --git a/vochain/indexer/indexer.go b/vochain/indexer/indexer.go index 43b59323e..d66ce1f07 100644 --- a/vochain/indexer/indexer.go +++ b/vochain/indexer/indexer.go @@ -414,6 +414,20 @@ func (idx *Indexer) Commit(height uint32) error { queries := idx.blockTxQueries() ctx := context.TODO() + // index the new block + if b := idx.App.GetBlockByHeight(int64(height)); b != nil { + if _, err := queries.CreateBlock(context.TODO(), indexerdb.CreateBlockParams{ + ChainID: b.ChainID, + Height: b.Height, + Time: b.Time, + Hash: nonNullBytes(b.Hash()), + ProposerAddress: nonNullBytes(b.ProposerAddress), + LastBlockHash: nonNullBytes(b.LastBlockID.Hash), + }); err != nil { + log.Errorw(err, "cannot index new block") + } + } + for _, pidStr := range updateProcs { pid := types.ProcessID(pidStr) if err := idx.updateProcess(ctx, queries, pid); err != nil { diff --git a/vochain/indexer/indexer_test.go b/vochain/indexer/indexer_test.go index fad19d248..9d475ffdc 100644 --- a/vochain/indexer/indexer_test.go +++ b/vochain/indexer/indexer_test.go @@ -1392,6 +1392,7 @@ func TestTxIndexer(t *testing.T) { idx.OnNewTx(&vochaintx.Tx{ TxID: getTxID(i, j), TxModelType: "setAccount", + Tx: &models.Tx{Payload: &models.Tx_SetAccount{}}, }, uint32(i), int32(j)) } } @@ -1404,7 +1405,7 @@ func TestTxIndexer(t *testing.T) { for i := 0; i < totalBlocks; i++ { for j := 0; j < txsPerBlock; j++ { - ref, err := idx.GetTransaction(uint64(i*txsPerBlock + j + 1)) + ref, err := idx.GetTxMetadataByID(uint64(i*txsPerBlock + j + 1)) qt.Assert(t, err, qt.IsNil) qt.Assert(t, ref.BlockHeight, qt.Equals, uint32(i)) qt.Assert(t, ref.TxBlockIndex, qt.Equals, int32(j)) @@ -1412,7 +1413,7 @@ func TestTxIndexer(t *testing.T) { h := make([]byte, 32) id := getTxID(i, j) copy(h, id[:]) - hashRef, err := idx.GetTxHashReference(h) + hashRef, err := idx.GetTxMetadataByHash(h) qt.Assert(t, err, qt.IsNil) qt.Assert(t, hashRef.BlockHeight, qt.Equals, uint32(i)) qt.Assert(t, hashRef.TxBlockIndex, qt.Equals, int32(j)) diff --git a/vochain/indexer/indexertypes/block.go b/vochain/indexer/indexertypes/block.go new file mode 100644 index 000000000..9864f4101 --- /dev/null +++ b/vochain/indexer/indexertypes/block.go @@ -0,0 +1,32 @@ +package indexertypes + +import ( + "time" + + "go.vocdoni.io/dvote/types" + indexerdb "go.vocdoni.io/dvote/vochain/indexer/db" +) + +// Block represents a block handled by the Vochain. +// The indexer Block data type is different from the vochain state data type +// since it is optimized for querying purposes and not for keeping a shared consensus state. +type Block struct { + ChainID string `json:"chainId"` + Height int64 `json:"height"` + Time time.Time `json:"time"` + Hash types.HexBytes `json:"hash"` + ProposerAddress types.HexBytes `json:"proposer"` + LastBlockHash types.HexBytes `json:"lastBlockHash"` +} + +// BlockFromDB converts the indexerdb.Block into a Block +func BlockFromDB(dbblock *indexerdb.Block) *Block { + return &Block{ + ChainID: dbblock.ChainID, + Height: dbblock.Height, + Time: dbblock.Time, + Hash: nonEmptyBytes(dbblock.Hash), + ProposerAddress: nonEmptyBytes(dbblock.ProposerAddress), + LastBlockHash: nonEmptyBytes(dbblock.LastBlockHash), + } +} diff --git a/vochain/indexer/indexertypes/types.go b/vochain/indexer/indexertypes/types.go index d0d2b29aa..c448fa245 100644 --- a/vochain/indexer/indexertypes/types.go +++ b/vochain/indexer/indexertypes/types.go @@ -8,6 +8,7 @@ import ( "go.vocdoni.io/dvote/types" indexerdb "go.vocdoni.io/dvote/vochain/indexer/db" "go.vocdoni.io/dvote/vochain/results" + "go.vocdoni.io/dvote/vochain/transaction/vochaintx" "go.vocdoni.io/proto/build/go/models" "google.golang.org/protobuf/proto" ) @@ -176,16 +177,8 @@ type TxPackage struct { Signature types.HexBytes `json:"signature"` } -// TxMetadata contains tx information for the TransactionList api -type TxMetadata struct { - Type string `json:"type"` - BlockHeight uint32 `json:"blockHeight,omitempty"` - Index int32 `json:"index"` - Hash types.HexBytes `json:"hash"` -} - -// Transaction holds the db reference for a single transaction -type Transaction struct { +// TransactionMetadata contains tx information for the TransactionList api +type TransactionMetadata struct { Index uint64 `json:"transactionNumber" format:"int64" example:"944"` Hash types.HexBytes `json:"transactionHash" swaggertype:"string" example:"75e8f822f5dd13973ac5158d600f0a2a5fea4bfefce9712ab5195bf17884cfad"` BlockHeight uint32 `json:"blockHeight" format:"int32" example:"64924"` @@ -193,8 +186,18 @@ type Transaction struct { TxType string `json:"transactionType" enums:"vote,newProcess,admin,setProcess,registerKey,mintTokens,sendTokens,setTransactionCosts,setAccount,collectFaucet,setKeykeeper" example:"Vote"` } -func TransactionFromDB(dbtx *indexerdb.Transaction) *Transaction { - return &Transaction{ +func TransactionMetadataFromDB(dbtx *indexerdb.Transaction) *TransactionMetadata { + return &TransactionMetadata{ + Index: uint64(dbtx.ID), + Hash: dbtx.Hash, + BlockHeight: uint32(dbtx.BlockHeight), + TxBlockIndex: int32(dbtx.BlockIndex), + TxType: dbtx.Type, + } +} + +func TransactionMetadataFromDBRow(dbtx *indexerdb.SearchTransactionsRow) *TransactionMetadata { + return &TransactionMetadata{ Index: uint64(dbtx.ID), Hash: dbtx.Hash, BlockHeight: uint32(dbtx.BlockHeight), @@ -203,6 +206,24 @@ func TransactionFromDB(dbtx *indexerdb.Transaction) *Transaction { } } +// Transaction holds the db reference for a single transaction +type Transaction struct { + *TransactionMetadata + RawTx types.HexBytes `json:"rawTx,omitempty"` + Tx *vochaintx.Tx +} + +func TransactionFromDB(dbtx *indexerdb.Transaction, chainID string) *Transaction { + tx := &Transaction{ + TransactionMetadata: TransactionMetadataFromDB(dbtx), + RawTx: dbtx.RawTx, + } + if err := tx.Tx.Unmarshal(tx.RawTx, chainID); err != nil { + log.Errorw(err, "couldn't unmarshal rawTx from indexer") + } + return tx +} + // TokenTransferMeta contains the information of a token transfer and some extra useful information. // The types are compatible with the SQL defined schema. type TokenTransferMeta struct { diff --git a/vochain/indexer/migrations/0013_alter_columns_table_blocks.sql b/vochain/indexer/migrations/0013_alter_columns_table_blocks.sql new file mode 100644 index 000000000..c83c32c06 --- /dev/null +++ b/vochain/indexer/migrations/0013_alter_columns_table_blocks.sql @@ -0,0 +1,13 @@ +-- +goose Up +ALTER TABLE blocks DROP COLUMN data_hash; +ALTER TABLE blocks ADD COLUMN chain_id TEXT NOT NULL DEFAULT ''; +ALTER TABLE blocks ADD COLUMN hash BLOB NOT NULL DEFAULT x''; +ALTER TABLE blocks ADD COLUMN proposer_address BLOB NOT NULL DEFAULT x''; +ALTER TABLE blocks ADD COLUMN last_block_hash BLOB NOT NULL DEFAULT x''; + +-- +goose Down +ALTER TABLE blocks ADD COLUMN data_hash BLOB NOT NULL; +ALTER TABLE blocks DROP COLUMN chain_id; +ALTER TABLE blocks DROP COLUMN hash; +ALTER TABLE blocks DROP COLUMN proposer_address; +ALTER TABLE blocks DROP COLUMN last_block_hash; diff --git a/vochain/indexer/migrations/0014_alter_columns_table_transactions.sql b/vochain/indexer/migrations/0014_alter_columns_table_transactions.sql new file mode 100644 index 000000000..b1302265c --- /dev/null +++ b/vochain/indexer/migrations/0014_alter_columns_table_transactions.sql @@ -0,0 +1,5 @@ +-- +goose Up +ALTER TABLE transactions ADD COLUMN raw_tx BLOB NOT NULL DEFAULT x''; + +-- +goose Down +ALTER TABLE transactions DROP COLUMN raw_tx; diff --git a/vochain/indexer/queries/blocks.sql b/vochain/indexer/queries/blocks.sql index 577e875b5..efa342bc6 100644 --- a/vochain/indexer/queries/blocks.sql +++ b/vochain/indexer/queries/blocks.sql @@ -1,11 +1,34 @@ -- name: CreateBlock :execresult INSERT INTO blocks( - height, time, data_hash + chain_id, height, time, hash, proposer_address, last_block_hash ) VALUES ( - ?, ?, ? + ?, ?, ?, ?, ?, ? ); --- name: GetBlock :one +-- name: GetBlockByHeight :one SELECT * FROM blocks WHERE height = ? LIMIT 1; + +-- name: GetBlockByHash :one +SELECT * FROM blocks +WHERE hash = ? +LIMIT 1; + +-- name: SearchBlocks :many +SELECT *, COUNT(*) OVER() AS total_count +FROM blocks +WHERE ( + (sqlc.arg(chain_id) = '' OR chain_id = sqlc.arg(chain_id)) + AND LENGTH(sqlc.arg(hash_substr)) <= 64 -- if passed arg is longer, then just abort the query + AND ( + sqlc.arg(hash_substr) = '' + OR (LENGTH(sqlc.arg(hash_substr)) = 64 AND LOWER(HEX(hash)) = LOWER(sqlc.arg(hash_substr))) + OR (LENGTH(sqlc.arg(hash_substr)) < 64 AND INSTR(LOWER(HEX(hash)), LOWER(sqlc.arg(hash_substr))) > 0) + -- TODO: consider keeping an hash_hex column for faster searches + ) + AND (sqlc.arg(proposer_address) = 0 OR LOWER(HEX(proposer_address)) = LOWER(sqlc.arg(proposer_address))) +) +ORDER BY height DESC +LIMIT sqlc.arg(limit) +OFFSET sqlc.arg(offset); diff --git a/vochain/indexer/queries/transactions.sql b/vochain/indexer/queries/transactions.sql index 0e625a197..c2e1bf2ef 100644 --- a/vochain/indexer/queries/transactions.sql +++ b/vochain/indexer/queries/transactions.sql @@ -1,8 +1,8 @@ -- name: CreateTransaction :execresult INSERT INTO transactions ( - hash, block_height, block_index, type + hash, block_height, block_index, type, raw_tx ) VALUES ( - ?, ?, ?, ? + ?, ?, ?, ?, ? ); -- name: GetTransaction :one @@ -18,6 +18,10 @@ LIMIT 1; -- name: CountTransactions :one SELECT COUNT(*) FROM transactions; +-- name: CountTransactionsByHeight :one +SELECT COUNT(*) FROM transactions +WHERE block_height = ?; + -- name: GetTxReferenceByBlockHeightAndBlockIndex :one SELECT * FROM transactions WHERE block_height = ? AND block_index = ? diff --git a/vochain/indexer/transaction.go b/vochain/indexer/transaction.go index 4b8c9fe21..ac10cb886 100644 --- a/vochain/indexer/transaction.go +++ b/vochain/indexer/transaction.go @@ -11,6 +11,7 @@ import ( indexerdb "go.vocdoni.io/dvote/vochain/indexer/db" "go.vocdoni.io/dvote/vochain/indexer/indexertypes" "go.vocdoni.io/dvote/vochain/transaction/vochaintx" + "google.golang.org/protobuf/proto" ) // ErrTransactionNotFound is returned if the transaction is not found. @@ -22,8 +23,13 @@ func (idx *Indexer) CountTotalTransactions() (uint64, error) { return uint64(count), err } -// GetTransaction fetches the txReference for the given tx height -func (idx *Indexer) GetTransaction(id uint64) (*indexertypes.Transaction, error) { +// CountTransactionsByHeight returns the number of transactions indexed for a given height +func (idx *Indexer) CountTransactionsByHeight(height int64) (int64, error) { + return idx.readOnlyQuery.CountTransactionsByHeight(context.TODO(), height) +} + +// GetTxMetadataByID fetches the tx metadata for the given tx height +func (idx *Indexer) GetTxMetadataByID(id uint64) (*indexertypes.TransactionMetadata, error) { sqlTxRef, err := idx.readOnlyQuery.GetTransaction(context.TODO(), int64(id)) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -31,40 +37,44 @@ func (idx *Indexer) GetTransaction(id uint64) (*indexertypes.Transaction, error) } return nil, fmt.Errorf("tx with id %d not found: %v", id, err) } - return indexertypes.TransactionFromDB(&sqlTxRef), nil + return indexertypes.TransactionMetadataFromDB(&sqlTxRef), nil } -// GetTxReferenceByBlockHeightAndBlockIndex fetches the txReference for the given tx height and block tx index -func (idx *Indexer) GetTxReferenceByBlockHeightAndBlockIndex(blockHeight, blockIndex int64) (*indexertypes.Transaction, error) { - sqlTxRef, err := idx.readOnlyQuery.GetTxReferenceByBlockHeightAndBlockIndex(context.TODO(), indexerdb.GetTxReferenceByBlockHeightAndBlockIndexParams{ - BlockHeight: blockHeight, - BlockIndex: blockIndex, - }) +// GetTxMetadataByHash fetches the tx metadata for the given tx hash +func (idx *Indexer) GetTxMetadataByHash(hash types.HexBytes) (*indexertypes.TransactionMetadata, error) { + sqlTxRef, err := idx.readOnlyQuery.GetTransactionByHash(context.TODO(), hash) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrTransactionNotFound } - return nil, fmt.Errorf("tx at block %d and index %d not found: %v", blockHeight, blockIndex, err) + return nil, fmt.Errorf("tx hash %x not found: %v", hash, err) } - return indexertypes.TransactionFromDB(&sqlTxRef), nil + return indexertypes.TransactionMetadataFromDB(&sqlTxRef), nil } -// GetTxHashReference fetches the txReference for the given tx hash -func (idx *Indexer) GetTxHashReference(hash types.HexBytes) (*indexertypes.Transaction, error) { - sqlTxRef, err := idx.readOnlyQuery.GetTransactionByHash(context.TODO(), hash) +// GetTxByBlockHeightAndBlockIndex fetches the txReference for the given tx height and block tx index +func (idx *Indexer) GetTxByBlockHeightAndBlockIndex(blockHeight, blockIndex int64) (*indexertypes.Transaction, error) { + sqlTxRef, err := idx.readOnlyQuery.GetTxReferenceByBlockHeightAndBlockIndex(context.TODO(), indexerdb.GetTxReferenceByBlockHeightAndBlockIndexParams{ + BlockHeight: blockHeight, + BlockIndex: blockIndex, + }) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrTransactionNotFound } - return nil, fmt.Errorf("tx hash %x not found: %v", hash, err) + return nil, fmt.Errorf("tx at block %d and index %d not found: %v", blockHeight, blockIndex, err) } - return indexertypes.TransactionFromDB(&sqlTxRef), nil + block, err := idx.readOnlyQuery.GetBlockByHeight(context.TODO(), blockHeight) + if err != nil { + return nil, fmt.Errorf("block %d not found: %w", blockHeight, err) + } + return indexertypes.TransactionFromDB(&sqlTxRef, block.ChainID), nil } // SearchTransactions returns the list of transactions indexed. // height and txType are optional, if declared as zero-value will be ignored. // The first one returned is the newest, so they are in descending order. -func (idx *Indexer) SearchTransactions(limit, offset int, blockHeight uint64, txType string) ([]*indexertypes.Transaction, uint64, error) { +func (idx *Indexer) SearchTransactions(limit, offset int, blockHeight uint64, txType string) ([]*indexertypes.TransactionMetadata, uint64, error) { if offset < 0 { return nil, 0, fmt.Errorf("invalid value: offset cannot be %d", offset) } @@ -80,9 +90,9 @@ func (idx *Indexer) SearchTransactions(limit, offset int, blockHeight uint64, tx if err != nil { return nil, 0, err } - list := []*indexertypes.Transaction{} + list := []*indexertypes.TransactionMetadata{} for _, row := range results { - list = append(list, &indexertypes.Transaction{ + list = append(list, &indexertypes.TransactionMetadata{ Index: uint64(row.ID), Hash: row.Hash, BlockHeight: uint32(row.BlockHeight), @@ -99,12 +109,20 @@ func (idx *Indexer) SearchTransactions(limit, offset int, blockHeight uint64, tx func (idx *Indexer) OnNewTx(tx *vochaintx.Tx, blockHeight uint32, txIndex int32) { idx.blockMu.Lock() defer idx.blockMu.Unlock() + + rawtx, err := proto.Marshal(tx.Tx) + if err != nil { + log.Errorw(err, "indexer cannot marshal new transaction") + return + } + queries := idx.blockTxQueries() if _, err := queries.CreateTransaction(context.TODO(), indexerdb.CreateTransactionParams{ Hash: tx.TxID[:], BlockHeight: int64(blockHeight), BlockIndex: int64(txIndex), Type: tx.TxModelType, + RawTx: rawtx, }); err != nil { log.Errorw(err, "cannot index new transaction") } diff --git a/vochain/keykeeper/keykeeper.go b/vochain/keykeeper/keykeeper.go index 7f46f654d..4a4425cd1 100644 --- a/vochain/keykeeper/keykeeper.go +++ b/vochain/keykeeper/keykeeper.go @@ -268,9 +268,6 @@ func (*KeyKeeper) OnVote(_ *state.Vote, _ int32) {} // OnNewTx is not used by the KeyKeeper func (*KeyKeeper) OnNewTx(_ *vochaintx.Tx, _ uint32, _ int32) {} -// OnBeginBlock is not used by the KeyKeeper -func (*KeyKeeper) OnBeginBlock(_ state.BeginBlock) {} - // OnCensusUpdate is not used by the KeyKeeper func (*KeyKeeper) OnCensusUpdate(_, _ []byte, _ string, _ uint64) {} diff --git a/vochain/offchaindatahandler/offchaindatahandler.go b/vochain/offchaindatahandler/offchaindatahandler.go index d6e97bd9b..4bc3be6b4 100644 --- a/vochain/offchaindatahandler/offchaindatahandler.go +++ b/vochain/offchaindatahandler/offchaindatahandler.go @@ -166,7 +166,6 @@ func (d *OffChainDataHandler) OnSetAccount(_ []byte, account *state.Account) { func (*OffChainDataHandler) OnCancel(_ []byte, _ int32) {} func (*OffChainDataHandler) OnVote(_ *state.Vote, _ int32) {} func (*OffChainDataHandler) OnNewTx(_ *vochaintx.Tx, _ uint32, _ int32) {} -func (*OffChainDataHandler) OnBeginBlock(state.BeginBlock) {} func (*OffChainDataHandler) OnProcessKeys(_ []byte, _ string, _ int32) {} func (*OffChainDataHandler) OnRevealKeys(_ []byte, _ string, _ int32) {} func (*OffChainDataHandler) OnProcessStatusChange(_ []byte, _ models.ProcessStatus, _ int32) {} diff --git a/vochain/state/eventlistener.go b/vochain/state/eventlistener.go index 1c2d05281..7b599702b 100644 --- a/vochain/state/eventlistener.go +++ b/vochain/state/eventlistener.go @@ -1,8 +1,6 @@ package state import ( - "time" - "go.vocdoni.io/dvote/vochain/transaction/vochaintx" "go.vocdoni.io/proto/build/go/models" ) @@ -32,7 +30,6 @@ type EventListener interface { OnSpendTokens(addr []byte, txType models.TxType, cost uint64, reference string) OnCensusUpdate(pid, censusRoot []byte, censusURI string, censusSize uint64) Commit(height uint32) (err error) - OnBeginBlock(BeginBlock) Rollback() } @@ -46,15 +43,3 @@ func (v *State) AddEventListener(l EventListener) { func (v *State) CleanEventListeners() { v.eventListeners = nil } - -type BeginBlock struct { - Height int64 - Time time.Time - DataHash []byte -} - -func (v *State) OnBeginBlock(bb BeginBlock) { - for _, l := range v.eventListeners { - l.OnBeginBlock(bb) - } -} diff --git a/vochain/state/state_test.go b/vochain/state/state_test.go index e2b76c685..b36dc7722 100644 --- a/vochain/state/state_test.go +++ b/vochain/state/state_test.go @@ -182,7 +182,6 @@ type Listener struct { func (*Listener) OnVote(_ *Vote, _ int32) {} func (*Listener) OnNewTx(_ *vochaintx.Tx, _ uint32, _ int32) {} -func (*Listener) OnBeginBlock(BeginBlock) {} func (*Listener) OnProcess(_ *models.Process, _ int32) {} func (*Listener) OnProcessStatusChange(_ []byte, _ models.ProcessStatus, _ int32) {} func (*Listener) OnProcessDurationChange(_ []byte, _ uint32, _ int32) {}