From 1925aa3c5009a95db219df04d23e818107e984ce Mon Sep 17 00:00:00 2001 From: floflo777 Date: Thu, 12 Mar 2026 00:18:50 +0100 Subject: [PATCH] fix: GetBlocks pagination off-by-one returning PerPage+1 results The GetBlocks callback was mutating page.PerPage inside the closure (page.PerPage += 1) to fetch one extra block for computing the Took duration metadata. This caused Load() to iterate PerPage+1 times instead of PerPage, corrupting Count and TotalPages in the response. Instead, create a separate PageParams with PerPage+1 before calling Load(), and use the original p.PerPage for the results guard and to restore correct pagination metadata afterwards. Fixes #197 --- store/indexer.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/store/indexer.go b/store/indexer.go index b6dd0b273..36070b034 100644 --- a/store/indexer.go +++ b/store/indexer.go @@ -3,6 +3,7 @@ package store import ( "bytes" "encoding/binary" + "math" "time" "golang.org/x/sync/errgroup" @@ -147,15 +148,19 @@ func (t *Indexer) GetBlockHeaderByHeight(height uint64) (*lib.BlockResult, lib.E // GetBlocks() returns a page of blocks based on the page parameters func (t *Indexer) GetBlocks(p lib.PageParams) (page *lib.Page, err lib.ErrorI) { - results, count, page := make(lib.BlockResults, 0), 0, lib.NewPage(p, lib.BlockResultsPageName) + results, count := make(lib.BlockResults, 0), 0 + // request 1 extra block so the callback can compute the "Took" duration + // for the last block in the page using the next block's timestamp + extra := lib.PageParams{PageNumber: p.PageNumber, PerPage: p.PerPage + 1} + page = lib.NewPage(extra, lib.BlockResultsPageName) err = page.Load(lib.JoinLenPrefix(blockHeightPrefix), true, &results, t.db, func(_, b []byte) lib.ErrorI { // get the block from the iterator value block, e := t.getBlock(b, true) if e != nil { return e } - // do not capture the 1 additional block that is needed for the metadata - if count < page.PerPage { + // do not capture the extra block that is only needed for metadata + if count < p.PerPage { results = append(results, block) } // calculate the time took using the N block and the N-1 block (next block aka blockHeight + 1) @@ -165,13 +170,14 @@ func (t *Indexer) GetBlocks(p lib.PageParams) (page *lib.Page, err lib.ErrorI) { blockTime := time.UnixMicro(int64(block.BlockHeader.Time)) nextBlkTime := time.UnixMicro(int64(nextBlock.BlockHeader.Time)) nextBlock.Meta.Took = uint64(nextBlkTime.Sub(blockTime).Milliseconds()) - } else { - page.PerPage += 1 // modify the perPage to get 1 additional block the block meta may be filled in } count++ return nil }) - page.PerPage = p.PerPage // reset the perPage + // restore the original page params so the response reflects what was requested + page.PerPage = p.PerPage + page.Count = len(results) + page.TotalPages = int(math.Ceil(float64(page.TotalCount) / float64(p.PerPage))) return }