You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
7.2 KiB

8 years ago
add support for block pruning via ABCI Commit response (#4588) * Added BlockStore.DeleteBlock() * Added initial block pruner prototype * wip * Added BlockStore.PruneBlocks() * Added consensus setting for block pruning * Added BlockStore base * Error on replay if base does not have blocks * Handle missing blocks when sending VoteSetMaj23Message * Error message tweak * Properly update blockstore state * Error message fix again * blockchain: ignore peer missing blocks * Added FIXME * Added test for block replay with truncated history * Handle peer base in blockchain reactor * Improved replay error handling * Added tests for Store.PruneBlocks() * Fix non-RPC handling of truncated block history * Panic on missing block meta in needProofBlock() * Updated changelog * Handle truncated block history in RPC layer * Added info about earliest block in /status RPC * Reorder height and base in blockchain reactor messages * Updated changelog * Fix tests * Appease linter * Minor review fixes * Non-empty BlockStores should always have base > 0 * Update code to assume base > 0 invariant * Added blockstore tests for pruning to 0 * Make sure we don't prune below the current base * Added BlockStore.Size() * config: added retain_blocks recommendations * Update v1 blockchain reactor to handle blockstore base * Added state database pruning * Propagate errors on missing validator sets * Comment tweaks * Improved error message Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * use ABCI field ResponseCommit.retain_height instead of retain-blocks config option * remove State.RetainHeight, return value instead * fix minor issues * rename pruneHeights() to pruneBlocks() * noop to fix GitHub borkage Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
5 years ago
add support for block pruning via ABCI Commit response (#4588) * Added BlockStore.DeleteBlock() * Added initial block pruner prototype * wip * Added BlockStore.PruneBlocks() * Added consensus setting for block pruning * Added BlockStore base * Error on replay if base does not have blocks * Handle missing blocks when sending VoteSetMaj23Message * Error message tweak * Properly update blockstore state * Error message fix again * blockchain: ignore peer missing blocks * Added FIXME * Added test for block replay with truncated history * Handle peer base in blockchain reactor * Improved replay error handling * Added tests for Store.PruneBlocks() * Fix non-RPC handling of truncated block history * Panic on missing block meta in needProofBlock() * Updated changelog * Handle truncated block history in RPC layer * Added info about earliest block in /status RPC * Reorder height and base in blockchain reactor messages * Updated changelog * Fix tests * Appease linter * Minor review fixes * Non-empty BlockStores should always have base > 0 * Update code to assume base > 0 invariant * Added blockstore tests for pruning to 0 * Make sure we don't prune below the current base * Added BlockStore.Size() * config: added retain_blocks recommendations * Update v1 blockchain reactor to handle blockstore base * Added state database pruning * Propagate errors on missing validator sets * Comment tweaks * Improved error message Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * use ABCI field ResponseCommit.retain_height instead of retain-blocks config option * remove State.RetainHeight, return value instead * fix minor issues * rename pruneHeights() to pruneBlocks() * noop to fix GitHub borkage Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
5 years ago
add support for block pruning via ABCI Commit response (#4588) * Added BlockStore.DeleteBlock() * Added initial block pruner prototype * wip * Added BlockStore.PruneBlocks() * Added consensus setting for block pruning * Added BlockStore base * Error on replay if base does not have blocks * Handle missing blocks when sending VoteSetMaj23Message * Error message tweak * Properly update blockstore state * Error message fix again * blockchain: ignore peer missing blocks * Added FIXME * Added test for block replay with truncated history * Handle peer base in blockchain reactor * Improved replay error handling * Added tests for Store.PruneBlocks() * Fix non-RPC handling of truncated block history * Panic on missing block meta in needProofBlock() * Updated changelog * Handle truncated block history in RPC layer * Added info about earliest block in /status RPC * Reorder height and base in blockchain reactor messages * Updated changelog * Fix tests * Appease linter * Minor review fixes * Non-empty BlockStores should always have base > 0 * Update code to assume base > 0 invariant * Added blockstore tests for pruning to 0 * Make sure we don't prune below the current base * Added BlockStore.Size() * config: added retain_blocks recommendations * Update v1 blockchain reactor to handle blockstore base * Added state database pruning * Propagate errors on missing validator sets * Comment tweaks * Improved error message Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * use ABCI field ResponseCommit.retain_height instead of retain-blocks config option * remove State.RetainHeight, return value instead * fix minor issues * rename pruneHeights() to pruneBlocks() * noop to fix GitHub borkage Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
5 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
7 years ago
  1. package core
  2. import (
  3. "fmt"
  4. "sort"
  5. tmmath "github.com/tendermint/tendermint/libs/math"
  6. tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
  7. ctypes "github.com/tendermint/tendermint/rpc/core/types"
  8. rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
  9. "github.com/tendermint/tendermint/state/indexer"
  10. "github.com/tendermint/tendermint/types"
  11. )
  12. // BlockchainInfo gets block headers for minHeight <= height <= maxHeight.
  13. //
  14. // If maxHeight does not yet exist, blocks up to the current height will be
  15. // returned. If minHeight does not exist (due to pruning), earliest existing
  16. // height will be used.
  17. //
  18. // At most 20 items will be returned. Block headers are returned in descending
  19. // order (highest first).
  20. //
  21. // More: https://docs.tendermint.com/master/rpc/#/Info/blockchain
  22. func (env *Environment) BlockchainInfo(
  23. ctx *rpctypes.Context,
  24. minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
  25. const limit int64 = 20
  26. var err error
  27. minHeight, maxHeight, err = filterMinMax(
  28. env.BlockStore.Base(),
  29. env.BlockStore.Height(),
  30. minHeight,
  31. maxHeight,
  32. limit)
  33. if err != nil {
  34. return nil, err
  35. }
  36. env.Logger.Debug("BlockchainInfo", "maxHeight", maxHeight, "minHeight", minHeight)
  37. blockMetas := make([]*types.BlockMeta, 0, maxHeight-minHeight+1)
  38. for height := maxHeight; height >= minHeight; height-- {
  39. blockMeta := env.BlockStore.LoadBlockMeta(height)
  40. if blockMeta != nil {
  41. blockMetas = append(blockMetas, blockMeta)
  42. }
  43. }
  44. return &ctypes.ResultBlockchainInfo{
  45. LastHeight: env.BlockStore.Height(),
  46. BlockMetas: blockMetas}, nil
  47. }
  48. // error if either min or max are negative or min > max
  49. // if 0, use blockstore base for min, latest block height for max
  50. // enforce limit.
  51. func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) {
  52. // filter negatives
  53. if min < 0 || max < 0 {
  54. return min, max, ctypes.ErrZeroOrNegativeHeight
  55. }
  56. // adjust for default values
  57. if min == 0 {
  58. min = 1
  59. }
  60. if max == 0 {
  61. max = height
  62. }
  63. // limit max to the height
  64. max = tmmath.MinInt64(height, max)
  65. // limit min to the base
  66. min = tmmath.MaxInt64(base, min)
  67. // limit min to within `limit` of max
  68. // so the total number of blocks returned will be `limit`
  69. min = tmmath.MaxInt64(min, max-limit+1)
  70. if min > max {
  71. return min, max, fmt.Errorf("%w: min height %d can't be greater than max height %d",
  72. ctypes.ErrInvalidRequest, min, max)
  73. }
  74. return min, max, nil
  75. }
  76. // Block gets block at a given height.
  77. // If no height is provided, it will fetch the latest block.
  78. // More: https://docs.tendermint.com/master/rpc/#/Info/block
  79. func (env *Environment) Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) {
  80. height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
  81. if err != nil {
  82. return nil, err
  83. }
  84. block := env.BlockStore.LoadBlock(height)
  85. blockMeta := env.BlockStore.LoadBlockMeta(height)
  86. if blockMeta == nil {
  87. return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: block}, nil
  88. }
  89. return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
  90. }
  91. // BlockByHash gets block by hash.
  92. // More: https://docs.tendermint.com/master/rpc/#/Info/block_by_hash
  93. func (env *Environment) BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error) {
  94. block := env.BlockStore.LoadBlockByHash(hash)
  95. if block == nil {
  96. return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
  97. }
  98. // If block is not nil, then blockMeta can't be nil.
  99. blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
  100. return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
  101. }
  102. // Commit gets block commit at a given height.
  103. // If no height is provided, it will fetch the commit for the latest block.
  104. // More: https://docs.tendermint.com/master/rpc/#/Info/commit
  105. func (env *Environment) Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) {
  106. height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
  107. if err != nil {
  108. return nil, err
  109. }
  110. blockMeta := env.BlockStore.LoadBlockMeta(height)
  111. if blockMeta == nil {
  112. return nil, nil
  113. }
  114. header := blockMeta.Header
  115. // If the next block has not been committed yet,
  116. // use a non-canonical commit
  117. if height == env.BlockStore.Height() {
  118. commit := env.BlockStore.LoadSeenCommit(height)
  119. return ctypes.NewResultCommit(&header, commit, false), nil
  120. }
  121. // Return the canonical commit (comes from the block at height+1)
  122. commit := env.BlockStore.LoadBlockCommit(height)
  123. return ctypes.NewResultCommit(&header, commit, true), nil
  124. }
  125. // BlockResults gets ABCIResults at a given height.
  126. // If no height is provided, it will fetch results for the latest block.
  127. //
  128. // Results are for the height of the block containing the txs.
  129. // Thus response.results.deliver_tx[5] is the results of executing
  130. // getBlock(h).Txs[5]
  131. // More: https://docs.tendermint.com/master/rpc/#/Info/block_results
  132. func (env *Environment) BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockResults, error) {
  133. height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
  134. if err != nil {
  135. return nil, err
  136. }
  137. results, err := env.StateStore.LoadABCIResponses(height)
  138. if err != nil {
  139. return nil, err
  140. }
  141. return &ctypes.ResultBlockResults{
  142. Height: height,
  143. TxsResults: results.DeliverTxs,
  144. BeginBlockEvents: results.BeginBlock.Events,
  145. EndBlockEvents: results.EndBlock.Events,
  146. ValidatorUpdates: results.EndBlock.ValidatorUpdates,
  147. ConsensusParamUpdates: results.EndBlock.ConsensusParamUpdates,
  148. }, nil
  149. }
  150. // BlockSearch searches for a paginated set of blocks matching BeginBlock and
  151. // EndBlock event search criteria.
  152. func (env *Environment) BlockSearch(
  153. ctx *rpctypes.Context,
  154. query string,
  155. pagePtr, perPagePtr *int,
  156. orderBy string,
  157. ) (*ctypes.ResultBlockSearch, error) {
  158. if !indexer.KVSinkEnabled(env.EventSinks) {
  159. return nil, fmt.Errorf("block searching is disabled due to no kvEventSink")
  160. }
  161. q, err := tmquery.New(query)
  162. if err != nil {
  163. return nil, err
  164. }
  165. var kvsink indexer.EventSink
  166. for _, sink := range env.EventSinks {
  167. if sink.Type() == indexer.KV {
  168. kvsink = sink
  169. }
  170. }
  171. results, err := kvsink.SearchBlockEvents(ctx.Context(), q)
  172. if err != nil {
  173. return nil, err
  174. }
  175. // sort results (must be done before pagination)
  176. switch orderBy {
  177. case "desc", "":
  178. sort.Slice(results, func(i, j int) bool { return results[i] > results[j] })
  179. case "asc":
  180. sort.Slice(results, func(i, j int) bool { return results[i] < results[j] })
  181. default:
  182. return nil, fmt.Errorf("expected order_by to be either `asc` or `desc` or empty: %w", ctypes.ErrInvalidRequest)
  183. }
  184. // paginate results
  185. totalCount := len(results)
  186. perPage := env.validatePerPage(perPagePtr)
  187. page, err := validatePage(pagePtr, perPage, totalCount)
  188. if err != nil {
  189. return nil, err
  190. }
  191. skipCount := validateSkipCount(page, perPage)
  192. pageSize := tmmath.MinInt(perPage, totalCount-skipCount)
  193. apiResults := make([]*ctypes.ResultBlock, 0, pageSize)
  194. for i := skipCount; i < skipCount+pageSize; i++ {
  195. block := env.BlockStore.LoadBlock(results[i])
  196. if block != nil {
  197. blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
  198. if blockMeta != nil {
  199. apiResults = append(apiResults, &ctypes.ResultBlock{
  200. Block: block,
  201. BlockID: blockMeta.BlockID,
  202. })
  203. }
  204. }
  205. }
  206. return &ctypes.ResultBlockSearch{Blocks: apiResults, TotalCount: totalCount}, nil
  207. }