rpc: add chunked rpc interface (backport #6445) (#6717)

* rpc: add chunked rpc interface (#6445)

(cherry picked from commit d9134063e7)

# Conflicts:
#	light/proxy/routes.go
#	node/node.go
#	rpc/core/net.go
#	rpc/core/routes.go

* fix conflicts

Co-authored-by: Sam Kleinman <garen@tychoish.com>
Co-authored-by: marbar3778 <marbar3778@yahoo.com>
This commit is contained in:
mergify[bot]
2021-07-14 09:22:53 +00:00
committed by GitHub
parent 2c2f511f24
commit da9eefd111
12 changed files with 157 additions and 1 deletions

View File

@@ -1,12 +1,14 @@
package core
import (
"encoding/base64"
"fmt"
"time"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/consensus"
"github.com/tendermint/tendermint/crypto"
tmjson "github.com/tendermint/tendermint/libs/json"
"github.com/tendermint/tendermint/libs/log"
mempl "github.com/tendermint/tendermint/mempool"
"github.com/tendermint/tendermint/p2p"
@@ -25,6 +27,10 @@ const (
// SubscribeTimeout is the maximum time we wait to subscribe for an event.
// must be less than the server's write timeout (see rpcserver.DefaultConfig)
SubscribeTimeout = 5 * time.Second
// genesisChunkSize is the maximum size, in bytes, of each
// chunk in the genesis structure for the chunked API
genesisChunkSize = 16 * 1024 * 1024 // 16
)
var (
@@ -91,6 +97,9 @@ type Environment struct {
Logger log.Logger
Config cfg.RPCConfig
// cache of chunked genesis data.
genChunks []string
}
//----------------------------------------------
@@ -130,6 +139,35 @@ func validatePerPage(perPagePtr *int) int {
return perPage
}
// InitGenesisChunks configures the environment and should be called on service
// startup.
func InitGenesisChunks() error {
if env.genChunks != nil {
return nil
}
if env.GenDoc == nil {
return nil
}
data, err := tmjson.Marshal(env.GenDoc)
if err != nil {
return err
}
for i := 0; i < len(data); i += genesisChunkSize {
end := i + genesisChunkSize
if end > len(data) {
end = len(data)
}
env.genChunks = append(env.genChunks, base64.StdEncoding.EncodeToString(data[i:end]))
}
return nil
}
func validateSkipCount(page, perPage int) int {
skipCount := (page - 1) * perPage
if skipCount < 0 {

View File

@@ -94,9 +94,35 @@ func UnsafeDialPeers(ctx *rpctypes.Context, peers []string, persistent, uncondit
// Genesis returns genesis file.
// More: https://docs.tendermint.com/master/rpc/#/Info/genesis
func Genesis(ctx *rpctypes.Context) (*ctypes.ResultGenesis, error) {
if len(env.genChunks) > 1 {
return nil, errors.New("genesis response is large, please use the genesis_chunked API instead")
}
return &ctypes.ResultGenesis{Genesis: env.GenDoc}, nil
}
func GenesisChunked(ctx *rpctypes.Context, chunk uint) (*ctypes.ResultGenesisChunk, error) {
if env.genChunks == nil {
return nil, fmt.Errorf("service configuration error, genesis chunks are not initialized")
}
if len(env.genChunks) == 0 {
return nil, fmt.Errorf("service configuration error, there are no chunks")
}
id := int(chunk)
if id > len(env.genChunks)-1 {
return nil, fmt.Errorf("there are %d chunks, %d is invalid", len(env.genChunks)-1, id)
}
return &ctypes.ResultGenesisChunk{
TotalChunks: len(env.genChunks),
ChunkNumber: id,
Data: env.genChunks[id],
}, nil
}
func getIDs(peers []string) ([]string, error) {
ids := make([]string, 0, len(peers))

View File

@@ -19,6 +19,7 @@ var Routes = map[string]*rpc.RPCFunc{
"net_info": rpc.NewRPCFunc(NetInfo, ""),
"blockchain": rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"),
"genesis": rpc.NewRPCFunc(Genesis, ""),
"genesis_chunked": rpc.NewRPCFunc(GenesisChunked, "chunk"),
"block": rpc.NewRPCFunc(Block, "height"),
"block_by_hash": rpc.NewRPCFunc(BlockByHash, "hash"),
"block_results": rpc.NewRPCFunc(BlockResults, "height"),

View File

@@ -23,6 +23,16 @@ type ResultGenesis struct {
Genesis *types.GenesisDoc `json:"genesis"`
}
// ResultGenesisChunk is the output format for the chunked/paginated
// interface. These chunks are produced by converting the genesis
// document to JSON and then splitting the resulting payload into
// 16 megabyte blocks and then base64 encoding each block.
type ResultGenesisChunk struct {
ChunkNumber int `json:"chunk"`
TotalChunks int `json:"total"`
Data string `json:"data"`
}
// Single block (with meta)
type ResultBlock struct {
BlockID types.BlockID `json:"block_id"`