From 5bde6d9c4f585c9e2b7046133114a3bff84cd2fa Mon Sep 17 00:00:00 2001 From: Gui Iribarren Date: Tue, 20 Aug 2024 12:17:19 +0200 Subject: [PATCH] api: new endpoint /chain/blocks accepts QueryParams: * GET /chain/blocks * page * limit * chainId * hash * proposerAddress * api: add structs BlockList, BlockParams * api: add consts ParamChainId, ParamHash, ParamProposerAddress * indexer: add indexertypes.Block --- api/api.go | 3 + api/api_types.go | 14 +++++ api/chain.go | 80 +++++++++++++++++++++++++++ vochain/indexer/indexertypes/block.go | 20 +++++++ 4 files changed, 117 insertions(+) create mode 100644 vochain/indexer/indexertypes/block.go diff --git a/api/api.go b/api/api.go index 6dcacc5f6..33fcba409 100644 --- a/api/api.go +++ b/api/api.go @@ -71,6 +71,9 @@ const ( ParamWithResults = "withResults" ParamFinalResults = "finalResults" ParamManuallyEnded = "manuallyEnded" + ParamChainId = "chainId" + ParamHash = "hash" + ParamProposerAddress = "proposerAddress" ParamHeight = "height" ParamReference = "reference" ParamType = "type" diff --git a/api/api_types.go b/api/api_types.go index d948b7207..efb390503 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 @@ -439,3 +447,9 @@ type Block struct { comettypes.Block `json:",inline"` Hash types.HexBytes `json:"hash" ` } + +// 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 13fd80127..79f0405d8 100644 --- a/api/chain.go +++ b/api/chain.go @@ -178,6 +178,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", @@ -972,6 +980,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.BlockList( + 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 @@ -1320,3 +1385,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/vochain/indexer/indexertypes/block.go b/vochain/indexer/indexertypes/block.go new file mode 100644 index 000000000..4954dbc22 --- /dev/null +++ b/vochain/indexer/indexertypes/block.go @@ -0,0 +1,20 @@ +package indexertypes + +import ( + "time" + + "go.vocdoni.io/dvote/types" +) + +// 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"` + TxCount int64 `json:"txCount"` +}