diff --git a/api/types.go b/api/types.go index 0bc2f53ad2..873c20be6e 100644 --- a/api/types.go +++ b/api/types.go @@ -459,6 +459,10 @@ func (a BalanceHistories) SortAndAggregate(groupByTime uint32) BalanceHistories return bhs } +type BlockHeight struct { + Height uint32 `json:"height"` +} + // Blocks is list of blocks with paging information type Blocks struct { Paging diff --git a/api/worker.go b/api/worker.go index 448885aa8a..9787b1038f 100644 --- a/api/worker.go +++ b/api/worker.go @@ -911,6 +911,14 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn } func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) { + if itemsOnPage == 0 { + totalPages := count - 1 + return Paging{ + ItemsOnPage: itemsOnPage, + Page: page + 1, + TotalPages: totalPages, + }, 0, 0, page + } from := page * itemsOnPage totalPages := (count - 1) / itemsOnPage if totalPages < 0 { @@ -2203,6 +2211,34 @@ func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) { if err != nil { return nil, errors.Annotatef(err, "GetBestBlock") } + if (txsOnPage == 0) { + pg, _, _, _ := computePaging(txCount, page, txsOnPage) + addresses := w.newAddressesMapForAliases() + + txs := make([]*Tx, 0) + + return &Block{ + Paging: pg, + BlockInfo: BlockInfo{ + Hash: bi.Hash, + Prev: bi.Prev, + Next: bi.Next, + Height: bi.Height, + Confirmations: bi.Confirmations, + Size: bi.Size, + Time: bi.Time, + Bits: bi.Bits, + Difficulty: string(bi.Difficulty), + MerkleRoot: bi.MerkleRoot, + Nonce: string(bi.Nonce), + Txids: bi.Txids, + Version: bi.Version, + }, + TxCount: txCount, + Transactions: txs, + AddressAliases: w.getAddressAliases(addresses), + }, nil + } pg, from, to, page := computePaging(txCount, page, txsOnPage) txs := make([]*Tx, to-from) txi := 0 diff --git a/docs/api.md b/docs/api.md index 69ac457940..737f0c06a1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -603,9 +603,14 @@ Response: Returns information about block with transactions, subject to paging. ``` -GET /api/v2/block/ +GET /api/v2/block/[?page=&pageSize=] ``` +The optional query parameters: + +- _page_: specifies page of returned transactions, starting from 1. If out of range, Blockbook returns the closest possible page. +- _pageSize_: number of transactions returned by call (default and maximum 1000) + Response: ```javascript diff --git a/server/public.go b/server/public.go index d21da713f2..434cebb44b 100644 --- a/server/public.go +++ b/server/public.go @@ -32,6 +32,7 @@ const txsOnPage = 25 const blocksOnPage = 50 const mempoolTxsOnPage = 50 const txsInAPI = 1000 +const blocksInAPI = 100 const secondaryCoinCookieName = "secondary_coin" @@ -196,6 +197,7 @@ func (s *PublicServer) ConnectFullPublicInterface() { serveMux.HandleFunc(path+"api/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiDefault)) serveMux.HandleFunc(path+"api/balancehistory/", s.jsonHandler(s.apiBalanceHistory, apiDefault)) // v2 format + serveMux.HandleFunc(path+"api/v2/block-height/", s.jsonHandler(s.apiBlockHeight, apiV2)) serveMux.HandleFunc(path+"api/v2/block-index/", s.jsonHandler(s.apiBlockIndex, apiV2)) serveMux.HandleFunc(path+"api/v2/block-filters/", s.jsonHandler(s.apiBlockFilters, apiV2)) serveMux.HandleFunc(path+"api/v2/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiV2)) @@ -204,6 +206,7 @@ func (s *PublicServer) ConnectFullPublicInterface() { serveMux.HandleFunc(path+"api/v2/xpub/", s.jsonHandler(s.apiXpub, apiV2)) serveMux.HandleFunc(path+"api/v2/utxo/", s.jsonHandler(s.apiUtxo, apiV2)) serveMux.HandleFunc(path+"api/v2/block/", s.jsonHandler(s.apiBlock, apiV2)) + serveMux.HandleFunc(path+"api/v2/blocks/", s.jsonHandler(s.apiBlocks, apiV2)) serveMux.HandleFunc(path+"api/v2/rawblock/", s.jsonHandler(s.apiBlockRaw, apiDefault)) serveMux.HandleFunc(path+"api/v2/sendtx/", s.jsonHandler(s.apiSendTx, apiV2)) serveMux.HandleFunc(path+"api/v2/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV2)) @@ -928,6 +931,18 @@ func (s *PublicServer) getAddressQueryParams(r *http.Request, accountDetails api }, filterParam, gap } +func (s *PublicServer) getBlockQueryParams(r *http.Request, maxPageSize int) (int, int) { + page, ec := strconv.Atoi(r.URL.Query().Get("page")) + if ec != nil { + page = 0 + } + pageSize, ec := strconv.Atoi(r.URL.Query().Get("pageSize")) + if ec != nil || pageSize > maxPageSize { + pageSize = maxPageSize + } + return page, pageSize +} + func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) { var addressParam string i := strings.LastIndexByte(r.URL.Path, '/') @@ -1484,16 +1499,26 @@ func (s *PublicServer) apiBalanceHistory(r *http.Request, apiVersion int) (inter return history, err } +func (s *PublicServer) apiBlockHeight(r *http.Request, apiVersion int) (interface{}, error) { + s.metrics.ExplorerViews.With(common.Labels{"action": "api-block-height"}).Inc() + bestBlock, _, err := s.db.GetBestBlock() + if err == nil { + blockHeight := &api.BlockHeight{ + Height: bestBlock, + } + return blockHeight, err + } + return nil, err +} + func (s *PublicServer) apiBlock(r *http.Request, apiVersion int) (interface{}, error) { var block *api.Block var err error s.metrics.ExplorerViews.With(common.Labels{"action": "api-block"}).Inc() + if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { - page, ec := strconv.Atoi(r.URL.Query().Get("page")) - if ec != nil { - page = 0 - } - block, err = s.api.GetBlock(r.URL.Path[i+1:], page, txsInAPI) + page, pageSize := s.getBlockQueryParams(r, txsInAPI) + block, err = s.api.GetBlock(r.URL.Path[i+1:], page, pageSize) if err == nil && apiVersion == apiV1 { return s.api.BlockToV1(block), nil } @@ -1501,6 +1526,41 @@ func (s *PublicServer) apiBlock(r *http.Request, apiVersion int) (interface{}, e return block, err } +func (s *PublicServer) apiBlocks(r *http.Request, apiVersion int) (interface{}, error) { + var blocks []*api.Block + + s.metrics.ExplorerViews.With(common.Labels{"action": "api-blocks"}).Inc() + + page, pageSize := s.getBlockQueryParams(r, blocksInAPI) + + blocks = make([]*api.Block, 0, pageSize) + + bestBlock, _, err := s.db.GetBestBlock() + + if err != nil { + return blocks, err + } + + for i := 0; i < pageSize; i++ { + blockNumber := bestBlock - uint32(page * pageSize + i) + + // We have no more blocks, return as is + if blockNumber < 0 { + return blocks, nil + } + + block, err := s.api.GetBlock(strconv.FormatUint(uint64(blockNumber), 10), 1, 0) + + if err != nil { + return blocks, err + } + + blocks = append(blocks, block) + } + + return blocks, err +} + func (s *PublicServer) apiBlockRaw(r *http.Request, apiVersion int) (interface{}, error) { var block *api.BlockRaw var err error