Skip to content

Commit 7a4e899

Browse files
committed
feat: get blocks API handle
1 parent 0cad690 commit 7a4e899

File tree

6 files changed

+180
-70
lines changed

6 files changed

+180
-70
lines changed

cmd/api.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ func RunApi(cmd *cobra.Command, args []string) {
7373
// signature scoped queries
7474
root.GET("/transactions/:to/:signature", handlers.GetTransactionsByContractAndSignature)
7575
root.GET("/events/:contract/:signature", handlers.GetLogsByContractAndSignature)
76+
77+
// blocks table queries
78+
root.GET("/blocks", handlers.GetBlocks)
7679
}
7780

7881
r.GET("/health", func(c *gin.Context) {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package handlers
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
"github.com/rs/zerolog/log"
6+
"github.com/thirdweb-dev/indexer/api"
7+
"github.com/thirdweb-dev/indexer/internal/storage"
8+
)
9+
10+
// BlockModel represents a simplified Block structure for Swagger documentation
11+
type BlockModel struct {
12+
ChainId string `json:"chain_id"`
13+
Number string `json:"number"`
14+
Hash string `json:"hash"`
15+
ParentHash string `json:"parent_hash"`
16+
Timestamp uint64 `json:"timestamp"`
17+
Nonce string `json:"nonce"`
18+
Sha3Uncles string `json:"sha3_uncles"`
19+
LogsBloom string `json:"logs_bloom"`
20+
ReceiptsRoot string `json:"receipts_root"`
21+
Difficulty string `json:"difficulty"`
22+
TotalDifficulty string `json:"total_difficulty"`
23+
Size uint64 `json:"size"`
24+
ExtraData string `json:"extra_data"`
25+
GasLimit uint64 `json:"gas_limit"`
26+
GasUsed uint64 `json:"gas_used"`
27+
BaseFeePerGas string `json:"base_fee_per_gas"`
28+
WithdrawalsRoot string `json:"withdrawals_root"`
29+
}
30+
31+
// @Summary Get all blocks
32+
// @Description Retrieve all blocks
33+
// @Tags blocks
34+
// @Accept json
35+
// @Produce json
36+
// @Security BasicAuth
37+
// @Param chainId path string true "Chain ID"
38+
// @Param filter query string false "Filter parameters"
39+
// @Param group_by query string false "Field to group results by"
40+
// @Param sort_by query string false "Field to sort results by"
41+
// @Param sort_order query string false "Sort order (asc or desc)"
42+
// @Param page query int false "Page number for pagination"
43+
// @Param limit query int false "Number of items per page" default(5)
44+
// @Param aggregate query []string false "List of aggregate functions to apply"
45+
// @Success 200 {object} api.QueryResponse{data=[]BlockModel}
46+
// @Failure 400 {object} api.Error
47+
// @Failure 401 {object} api.Error
48+
// @Failure 500 {object} api.Error
49+
// @Router /{chainId}/blocks [get]
50+
func GetBlocks(c *gin.Context) {
51+
handleBlocksRequest(c)
52+
}
53+
54+
func handleBlocksRequest(c *gin.Context) {
55+
chainId, err := api.GetChainId(c)
56+
if err != nil {
57+
api.BadRequestErrorHandler(c, err)
58+
return
59+
}
60+
61+
queryParams, err := api.ParseQueryParams(c.Request)
62+
if err != nil {
63+
api.BadRequestErrorHandler(c, err)
64+
return
65+
}
66+
67+
mainStorage, err := getMainStorage()
68+
if err != nil {
69+
log.Error().Err(err).Msg("Error getting main storage")
70+
api.InternalErrorHandler(c)
71+
return
72+
}
73+
74+
// Prepare the QueryFilter
75+
qf := storage.QueryFilter{
76+
FilterParams: queryParams.FilterParams,
77+
ChainId: chainId,
78+
SortBy: queryParams.SortBy,
79+
SortOrder: queryParams.SortOrder,
80+
Page: queryParams.Page,
81+
Limit: queryParams.Limit,
82+
}
83+
84+
// Initialize the QueryResult
85+
queryResult := api.QueryResponse{
86+
Meta: api.Meta{
87+
ChainId: chainId.Uint64(),
88+
Page: queryParams.Page,
89+
Limit: queryParams.Limit,
90+
TotalItems: 0,
91+
TotalPages: 0, // TODO: Implement total pages count
92+
},
93+
Data: nil,
94+
Aggregations: nil,
95+
}
96+
97+
// If aggregates or groupings are specified, retrieve them
98+
if len(queryParams.Aggregates) > 0 || len(queryParams.GroupBy) > 0 {
99+
qf.Aggregates = queryParams.Aggregates
100+
qf.GroupBy = queryParams.GroupBy
101+
102+
aggregatesResult, err := mainStorage.GetAggregations("blocks", qf)
103+
if err != nil {
104+
log.Error().Err(err).Msg("Error querying aggregates")
105+
// TODO: might want to choose BadRequestError if it's due to not-allowed functions
106+
api.InternalErrorHandler(c)
107+
return
108+
}
109+
queryResult.Aggregations = aggregatesResult.Aggregates
110+
queryResult.Meta.TotalItems = len(aggregatesResult.Aggregates)
111+
} else {
112+
// Retrieve blocks data
113+
blocksResult, err := mainStorage.GetBlocks(qf)
114+
if err != nil {
115+
log.Error().Err(err).Msg("Error querying blocks")
116+
// TODO: might want to choose BadRequestError if it's due to not-allowed functions
117+
api.InternalErrorHandler(c)
118+
return
119+
}
120+
121+
queryResult.Data = blocksResult.Data
122+
queryResult.Meta.TotalItems = len(blocksResult.Data)
123+
}
124+
125+
sendJSONResponse(c, queryResult)
126+
}

internal/storage/clickhouse.go

Lines changed: 31 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -291,55 +291,9 @@ func (c *ClickHouseConnector) StoreBlockFailures(failures []common.BlockFailure)
291291
return batch.Send()
292292
}
293293

294-
func (c *ClickHouseConnector) GetBlocks(qf QueryFilter) (blocks []common.Block, err error) {
294+
func (c *ClickHouseConnector) GetBlocks(qf QueryFilter) (QueryResult[common.Block], error) {
295295
columns := "chain_id, number, hash, parent_hash, timestamp, nonce, sha3_uncles, logs_bloom, receipts_root, difficulty, total_difficulty, size, extra_data, gas_limit, gas_used, transaction_count, base_fee_per_gas, withdrawals_root"
296-
query := fmt.Sprintf("SELECT %s FROM %s.blocks WHERE number IN (%s) AND is_deleted = 0",
297-
columns, c.cfg.Database, getBlockNumbersStringArray(qf.BlockNumbers))
298-
299-
if qf.ChainId.Sign() > 0 {
300-
query += fmt.Sprintf(" AND chain_id = %s", qf.ChainId.String())
301-
}
302-
303-
query += getLimitClause(int(qf.Limit))
304-
305-
if err := common.ValidateQuery(query); err != nil {
306-
return nil, err
307-
}
308-
rows, err := c.conn.Query(context.Background(), query)
309-
if err != nil {
310-
return nil, err
311-
}
312-
defer rows.Close()
313-
314-
for rows.Next() {
315-
var block common.Block
316-
err := rows.Scan(
317-
&block.ChainId,
318-
&block.Number,
319-
&block.Hash,
320-
&block.ParentHash,
321-
&block.Timestamp,
322-
&block.Nonce,
323-
&block.Sha3Uncles,
324-
&block.LogsBloom,
325-
&block.ReceiptsRoot,
326-
&block.Difficulty,
327-
&block.TotalDifficulty,
328-
&block.Size,
329-
&block.ExtraData,
330-
&block.GasLimit,
331-
&block.GasUsed,
332-
&block.TransactionCount,
333-
&block.BaseFeePerGas,
334-
&block.WithdrawalsRoot,
335-
)
336-
if err != nil {
337-
zLog.Error().Err(err).Msg("Error scanning block")
338-
return nil, err
339-
}
340-
blocks = append(blocks, block)
341-
}
342-
return blocks, nil
296+
return executeQuery[common.Block](c, "blocks", columns, qf, scanBlock)
343297
}
344298

345299
func (c *ClickHouseConnector) GetTransactions(qf QueryFilter) (QueryResult[common.Transaction], error) {
@@ -613,6 +567,35 @@ func scanLog(rows driver.Rows) (common.Log, error) {
613567
return log, nil
614568
}
615569

570+
func scanBlock(rows driver.Rows) (common.Block, error) {
571+
var block common.Block
572+
err := rows.Scan(
573+
&block.ChainId,
574+
&block.Number,
575+
&block.Hash,
576+
&block.ParentHash,
577+
&block.Timestamp,
578+
&block.Nonce,
579+
&block.Sha3Uncles,
580+
&block.LogsBloom,
581+
&block.ReceiptsRoot,
582+
&block.Difficulty,
583+
&block.TotalDifficulty,
584+
&block.Size,
585+
&block.ExtraData,
586+
&block.GasLimit,
587+
&block.GasUsed,
588+
&block.TransactionCount,
589+
&block.BaseFeePerGas,
590+
&block.WithdrawalsRoot,
591+
)
592+
if err != nil {
593+
return common.Block{}, fmt.Errorf("error scanning block: %w", err)
594+
}
595+
596+
return block, nil
597+
}
598+
616599
func (c *ClickHouseConnector) GetMaxBlockNumber(chainId *big.Int) (maxBlockNumber *big.Int, err error) {
617600
query := fmt.Sprintf("SELECT number FROM %s.blocks WHERE is_deleted = 0", c.cfg.Database)
618601
if chainId.Sign() > 0 {

internal/storage/connector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ type IStagingStorage interface {
5353
type IMainStorage interface {
5454
InsertBlockData(data *[]common.BlockData) error
5555

56-
GetBlocks(qf QueryFilter) (blocks []common.Block, err error)
56+
GetBlocks(qf QueryFilter) (blocks QueryResult[common.Block], err error)
5757
GetTransactions(qf QueryFilter) (transactions QueryResult[common.Transaction], err error)
5858
GetLogs(qf QueryFilter) (logs QueryResult[common.Log], err error)
5959
GetAggregations(table string, qf QueryFilter) (QueryResult[interface{}], error)

internal/storage/memory.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func (m *MemoryConnector) insertBlocks(blocks *[]common.Block) error {
8585
return nil
8686
}
8787

88-
func (m *MemoryConnector) GetBlocks(qf QueryFilter) ([]common.Block, error) {
88+
func (m *MemoryConnector) GetBlocks(qf QueryFilter) (QueryResult[common.Block], error) {
8989
blocks := []common.Block{}
9090
limit := getLimit(qf)
9191
blockNumbersToCheck := getBlockNumbersToCheck(qf)
@@ -100,13 +100,13 @@ func (m *MemoryConnector) GetBlocks(qf QueryFilter) ([]common.Block, error) {
100100
block := common.Block{}
101101
err := json.Unmarshal([]byte(value), &block)
102102
if err != nil {
103-
return nil, err
103+
return QueryResult[common.Block]{}, err
104104
}
105105
blocks = append(blocks, block)
106106
}
107107
}
108108
}
109-
return blocks, nil
109+
return QueryResult[common.Block]{Data: blocks}, nil
110110
}
111111

112112
func (m *MemoryConnector) insertTransactions(txs *[]common.Transaction) error {
@@ -120,7 +120,7 @@ func (m *MemoryConnector) insertTransactions(txs *[]common.Transaction) error {
120120
return nil
121121
}
122122

123-
func (m *MemoryConnector) GetTransactions(qf QueryFilter) ([]common.Transaction, error) {
123+
func (m *MemoryConnector) GetTransactions(qf QueryFilter) (QueryResult[common.Transaction], error) {
124124
txs := []common.Transaction{}
125125
limit := getLimit(qf)
126126
blockNumbersToCheck := getBlockNumbersToCheck(qf)
@@ -134,13 +134,13 @@ func (m *MemoryConnector) GetTransactions(qf QueryFilter) ([]common.Transaction,
134134
tx := common.Transaction{}
135135
err := json.Unmarshal([]byte(value), &tx)
136136
if err != nil {
137-
return nil, err
137+
return QueryResult[common.Transaction]{}, err
138138
}
139139
txs = append(txs, tx)
140140
}
141141
}
142142
}
143-
return txs, nil
143+
return QueryResult[common.Transaction]{Data: txs}, nil
144144
}
145145

146146
func (m *MemoryConnector) insertLogs(logs *[]common.Log) error {
@@ -154,7 +154,7 @@ func (m *MemoryConnector) insertLogs(logs *[]common.Log) error {
154154
return nil
155155
}
156156

157-
func (m *MemoryConnector) GetLogs(qf QueryFilter) ([]common.Log, error) {
157+
func (m *MemoryConnector) GetLogs(qf QueryFilter) (QueryResult[common.Log], error) {
158158
logs := []common.Log{}
159159
limit := getLimit(qf)
160160
blockNumbersToCheck := getBlockNumbersToCheck(qf)
@@ -168,13 +168,13 @@ func (m *MemoryConnector) GetLogs(qf QueryFilter) ([]common.Log, error) {
168168
log := common.Log{}
169169
err := json.Unmarshal([]byte(value), &log)
170170
if err != nil {
171-
return nil, err
171+
return QueryResult[common.Log]{}, err
172172
}
173173
logs = append(logs, log)
174174
}
175175
}
176176
}
177-
return logs, nil
177+
return QueryResult[common.Log]{Data: logs}, nil
178178
}
179179

180180
func (m *MemoryConnector) GetMaxBlockNumber(chainId *big.Int) (*big.Int, error) {
@@ -314,7 +314,7 @@ func (m *MemoryConnector) insertTraces(traces *[]common.Trace) error {
314314
return nil
315315
}
316316

317-
func (m *MemoryConnector) GetTraces(qf QueryFilter) ([]common.Trace, error) {
317+
func (m *MemoryConnector) GetTraces(qf QueryFilter) (QueryResult[common.Trace], error) {
318318
traces := []common.Trace{}
319319
limit := getLimit(qf)
320320
blockNumbersToCheck := getBlockNumbersToCheck(qf)
@@ -328,13 +328,13 @@ func (m *MemoryConnector) GetTraces(qf QueryFilter) ([]common.Trace, error) {
328328
trace := common.Trace{}
329329
err := json.Unmarshal([]byte(value), &trace)
330330
if err != nil {
331-
return nil, err
331+
return QueryResult[common.Trace]{}, err
332332
}
333333
traces = append(traces, trace)
334334
}
335335
}
336336
}
337-
return traces, nil
337+
return QueryResult[common.Trace]{Data: traces}, nil
338338
}
339339

340340
func traceAddressToString(traceAddress []uint64) string {

test/mocks/MockIMainStorage.go

Lines changed: 7 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)