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.

262 lines
7.9 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. "github.com/tendermint/tendermint/libs/bytes"
  6. tmmath "github.com/tendermint/tendermint/libs/math"
  7. tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
  8. ctypes "github.com/tendermint/tendermint/rpc/core/types"
  9. rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
  10. "github.com/tendermint/tendermint/state/indexer"
  11. "github.com/tendermint/tendermint/types"
  12. )
  13. // BlockchainInfo gets block headers for minHeight <= height <= maxHeight.
  14. //
  15. // If maxHeight does not yet exist, blocks up to the current height will be
  16. // returned. If minHeight does not exist (due to pruning), earliest existing
  17. // height will be used.
  18. //
  19. // At most 20 items will be returned. Block headers are returned in descending
  20. // order (highest first).
  21. //
  22. // More: https://docs.tendermint.com/master/rpc/#/Info/blockchain
  23. func (env *Environment) BlockchainInfo(
  24. ctx *rpctypes.Context,
  25. minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
  26. const limit int64 = 20
  27. var err error
  28. minHeight, maxHeight, err = filterMinMax(
  29. env.BlockStore.Base(),
  30. env.BlockStore.Height(),
  31. minHeight,
  32. maxHeight,
  33. limit)
  34. if err != nil {
  35. return nil, err
  36. }
  37. env.Logger.Debug("BlockchainInfo", "maxHeight", maxHeight, "minHeight", minHeight)
  38. blockMetas := make([]*types.BlockMeta, 0, maxHeight-minHeight+1)
  39. for height := maxHeight; height >= minHeight; height-- {
  40. blockMeta := env.BlockStore.LoadBlockMeta(height)
  41. if blockMeta != nil {
  42. blockMetas = append(blockMetas, blockMeta)
  43. }
  44. }
  45. return &ctypes.ResultBlockchainInfo{
  46. LastHeight: env.BlockStore.Height(),
  47. BlockMetas: blockMetas}, nil
  48. }
  49. // error if either min or max are negative or min > max
  50. // if 0, use blockstore base for min, latest block height for max
  51. // enforce limit.
  52. func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) {
  53. // filter negatives
  54. if min < 0 || max < 0 {
  55. return min, max, ctypes.ErrZeroOrNegativeHeight
  56. }
  57. // adjust for default values
  58. if min == 0 {
  59. min = 1
  60. }
  61. if max == 0 {
  62. max = height
  63. }
  64. // limit max to the height
  65. max = tmmath.MinInt64(height, max)
  66. // limit min to the base
  67. min = tmmath.MaxInt64(base, min)
  68. // limit min to within `limit` of max
  69. // so the total number of blocks returned will be `limit`
  70. min = tmmath.MaxInt64(min, max-limit+1)
  71. if min > max {
  72. return min, max, fmt.Errorf("%w: min height %d can't be greater than max height %d",
  73. ctypes.ErrInvalidRequest, min, max)
  74. }
  75. return min, max, nil
  76. }
  77. // Block gets block at a given height.
  78. // If no height is provided, it will fetch the latest block.
  79. // More: https://docs.tendermint.com/master/rpc/#/Info/block
  80. func (env *Environment) Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) {
  81. height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
  82. if err != nil {
  83. return nil, err
  84. }
  85. blockMeta := env.BlockStore.LoadBlockMeta(height)
  86. if blockMeta == nil {
  87. return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
  88. }
  89. block := env.BlockStore.LoadBlock(height)
  90. return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
  91. }
  92. // BlockByHash gets block by hash.
  93. // More: https://docs.tendermint.com/master/rpc/#/Info/block_by_hash
  94. func (env *Environment) BlockByHash(ctx *rpctypes.Context, hash bytes.HexBytes) (*ctypes.ResultBlock, error) {
  95. // N.B. The hash parameter is HexBytes so that the reflective parameter
  96. // decoding logic in the HTTP service will correctly translate from JSON.
  97. // See https://github.com/tendermint/tendermint/issues/6802 for context.
  98. block := env.BlockStore.LoadBlockByHash(hash)
  99. if block == nil {
  100. return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
  101. }
  102. // If block is not nil, then blockMeta can't be nil.
  103. blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
  104. return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil
  105. }
  106. // Commit gets block commit at a given height.
  107. // If no height is provided, it will fetch the commit for the latest block.
  108. // More: https://docs.tendermint.com/master/rpc/#/Info/commit
  109. func (env *Environment) Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) {
  110. height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
  111. if err != nil {
  112. return nil, err
  113. }
  114. blockMeta := env.BlockStore.LoadBlockMeta(height)
  115. if blockMeta == nil {
  116. return nil, nil
  117. }
  118. header := blockMeta.Header
  119. // If the next block has not been committed yet,
  120. // use a non-canonical commit
  121. if height == env.BlockStore.Height() {
  122. commit := env.BlockStore.LoadSeenCommit()
  123. // NOTE: we can't yet ensure atomicity of operations in asserting
  124. // whether this is the latest height and retrieving the seen commit
  125. if commit != nil && commit.Height == height {
  126. return ctypes.NewResultCommit(&header, commit, false), nil
  127. }
  128. }
  129. // Return the canonical commit (comes from the block at height+1)
  130. commit := env.BlockStore.LoadBlockCommit(height)
  131. if commit == nil {
  132. return nil, nil
  133. }
  134. return ctypes.NewResultCommit(&header, commit, true), nil
  135. }
  136. // BlockResults gets ABCIResults at a given height.
  137. // If no height is provided, it will fetch results for the latest block.
  138. //
  139. // Results are for the height of the block containing the txs.
  140. // Thus response.results.deliver_tx[5] is the results of executing
  141. // getBlock(h).Txs[5]
  142. // More: https://docs.tendermint.com/master/rpc/#/Info/block_results
  143. func (env *Environment) BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockResults, error) {
  144. height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
  145. if err != nil {
  146. return nil, err
  147. }
  148. results, err := env.StateStore.LoadABCIResponses(height)
  149. if err != nil {
  150. return nil, err
  151. }
  152. var totalGasUsed int64
  153. for _, tx := range results.GetDeliverTxs() {
  154. totalGasUsed += tx.GetGasUsed()
  155. }
  156. return &ctypes.ResultBlockResults{
  157. Height: height,
  158. TxsResults: results.DeliverTxs,
  159. TotalGasUsed: totalGasUsed,
  160. BeginBlockEvents: results.BeginBlock.Events,
  161. EndBlockEvents: results.EndBlock.Events,
  162. ValidatorUpdates: results.EndBlock.ValidatorUpdates,
  163. ConsensusParamUpdates: results.EndBlock.ConsensusParamUpdates,
  164. }, nil
  165. }
  166. // BlockSearch searches for a paginated set of blocks matching BeginBlock and
  167. // EndBlock event search criteria.
  168. func (env *Environment) BlockSearch(
  169. ctx *rpctypes.Context,
  170. query string,
  171. pagePtr, perPagePtr *int,
  172. orderBy string,
  173. ) (*ctypes.ResultBlockSearch, error) {
  174. if !indexer.KVSinkEnabled(env.EventSinks) {
  175. return nil, fmt.Errorf("block searching is disabled due to no kvEventSink")
  176. }
  177. q, err := tmquery.New(query)
  178. if err != nil {
  179. return nil, err
  180. }
  181. var kvsink indexer.EventSink
  182. for _, sink := range env.EventSinks {
  183. if sink.Type() == indexer.KV {
  184. kvsink = sink
  185. }
  186. }
  187. results, err := kvsink.SearchBlockEvents(ctx.Context(), q)
  188. if err != nil {
  189. return nil, err
  190. }
  191. // sort results (must be done before pagination)
  192. switch orderBy {
  193. case "desc", "":
  194. sort.Slice(results, func(i, j int) bool { return results[i] > results[j] })
  195. case "asc":
  196. sort.Slice(results, func(i, j int) bool { return results[i] < results[j] })
  197. default:
  198. return nil, fmt.Errorf("expected order_by to be either `asc` or `desc` or empty: %w", ctypes.ErrInvalidRequest)
  199. }
  200. // paginate results
  201. totalCount := len(results)
  202. perPage := env.validatePerPage(perPagePtr)
  203. page, err := validatePage(pagePtr, perPage, totalCount)
  204. if err != nil {
  205. return nil, err
  206. }
  207. skipCount := validateSkipCount(page, perPage)
  208. pageSize := tmmath.MinInt(perPage, totalCount-skipCount)
  209. apiResults := make([]*ctypes.ResultBlock, 0, pageSize)
  210. for i := skipCount; i < skipCount+pageSize; i++ {
  211. block := env.BlockStore.LoadBlock(results[i])
  212. if block != nil {
  213. blockMeta := env.BlockStore.LoadBlockMeta(block.Height)
  214. if blockMeta != nil {
  215. apiResults = append(apiResults, &ctypes.ResultBlock{
  216. Block: block,
  217. BlockID: blockMeta.BlockID,
  218. })
  219. }
  220. }
  221. }
  222. return &ctypes.ResultBlockSearch{Blocks: apiResults, TotalCount: totalCount}, nil
  223. }