Skip to content

Commit

Permalink
Merge branch 'feat/index-blocks' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
altergui committed Aug 20, 2024
2 parents 323d2c7 + 5f96e84 commit 9c94bbe
Show file tree
Hide file tree
Showing 25 changed files with 731 additions and 240 deletions.
3 changes: 3 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ const (
ParamWithResults = "withResults"
ParamFinalResults = "finalResults"
ParamManuallyEnded = "manuallyEnded"
ParamChainId = "chainId"
ParamHash = "hash"
ParamProposerAddress = "proposerAddress"
ParamHeight = "height"
ParamReference = "reference"
ParamType = "type"
Expand Down
25 changes: 20 additions & 5 deletions api/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -284,9 +292,9 @@ type TransfersList struct {
}

type GenericTransactionWithInfo struct {
TxContent json.RawMessage `json:"tx"`
TxInfo indexertypes.Transaction `json:"txInfo"`
Signature types.HexBytes `json:"signature"`
TxContent json.RawMessage `json:"tx"`
TxInfo *indexertypes.Transaction `json:"txInfo"`
Signature types.HexBytes `json:"signature"`
}

type ChainInfo struct {
Expand Down Expand Up @@ -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"`
}
170 changes: 133 additions & 37 deletions api/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ 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"
"go.vocdoni.io/dvote/vochain/indexer"
"go.vocdoni.io/dvote/vochain/state"
Expand Down Expand Up @@ -166,7 +164,7 @@ func (a *API) enableChainHandlers() error {
"/chain/blocks/{height}",
"GET",
apirest.MethodAccessTypePublic,
a.chainBlockHandler,
a.chainBlockByHeightHandler,
); err != nil {
return err
}
Expand All @@ -178,6 +176,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",
Expand Down Expand Up @@ -621,7 +627,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
Expand Down Expand Up @@ -657,25 +663,17 @@ func (a *API) chainTxHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) er
if err != nil {
return err
}
stx, err := a.vocapp.GetTx(uint32(height), int32(index))
if err != nil {
if errors.Is(err, vochain.ErrTransactionNotFound) {
return ErrTransactionNotFound
}
return ErrVochainGetTxFailed.WithErr(err)
}

ref, err := a.indexer.GetTxReferenceByBlockHeightAndBlockIndex(height, index)
ref, err := a.indexer.GetTransactionByHeightAndIndex(height, index)
if err != nil {
if errors.Is(err, indexer.ErrTransactionNotFound) {
return ErrTransactionNotFound
}
return ErrVochainGetTxFailed.WithErr(err)
}
tx := &GenericTransactionWithInfo{
TxContent: []byte(protoFormat(stx.Tx)),
Signature: stx.Signature,
TxInfo: *ref,
TxContent: []byte(protoFormat(ref.RawTx)),
TxInfo: ref,
Signature: ref.Signature,
}
data, err := json.Marshal(tx)
if err != nil {
Expand All @@ -686,8 +684,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
Expand All @@ -700,7 +698,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
Expand Down Expand Up @@ -856,7 +854,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
Expand All @@ -866,23 +864,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 {
Expand All @@ -906,18 +917,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 {
Expand All @@ -926,6 +950,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
Expand Down Expand Up @@ -1239,3 +1320,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
}
35 changes: 35 additions & 0 deletions test/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 0 additions & 4 deletions vochain/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 0 additions & 3 deletions vochain/appsetup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 9c94bbe

Please sign in to comment.