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"
|
|
"github.com/tendermint/tendermint/proxy"
|
|
sm "github.com/tendermint/tendermint/state"
|
|
"github.com/tendermint/tendermint/state/indexer"
|
|
"github.com/tendermint/tendermint/state/txindex"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
const (
|
|
// see README
|
|
defaultPerPage = 30
|
|
maxPerPage = 100
|
|
|
|
// 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 (
|
|
// set by Node
|
|
env *Environment
|
|
)
|
|
|
|
// SetEnvironment sets up the given Environment.
|
|
// It will race if multiple Node call SetEnvironment.
|
|
func SetEnvironment(e *Environment) {
|
|
env = e
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// These interfaces are used by RPC and must be thread safe
|
|
|
|
type Consensus interface {
|
|
GetState() sm.State
|
|
GetValidators() (int64, []*types.Validator)
|
|
GetLastHeight() int64
|
|
GetRoundStateJSON() ([]byte, error)
|
|
GetRoundStateSimpleJSON() ([]byte, error)
|
|
}
|
|
|
|
type transport interface {
|
|
Listeners() []string
|
|
IsListening() bool
|
|
NodeInfo() p2p.NodeInfo
|
|
}
|
|
|
|
type peers interface {
|
|
AddPersistentPeers([]string) error
|
|
AddUnconditionalPeerIDs([]string) error
|
|
AddPrivatePeerIDs([]string) error
|
|
DialPeersAsync([]string) error
|
|
Peers() p2p.IPeerSet
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// Environment contains objects and interfaces used by the RPC. It is expected
|
|
// to be setup once during startup.
|
|
type Environment struct {
|
|
// external, thread safe interfaces
|
|
ProxyAppQuery proxy.AppConnQuery
|
|
ProxyAppMempool proxy.AppConnMempool
|
|
|
|
// interfaces defined in types and above
|
|
StateStore sm.Store
|
|
BlockStore sm.BlockStore
|
|
EvidencePool sm.EvidencePool
|
|
ConsensusState Consensus
|
|
P2PPeers peers
|
|
P2PTransport transport
|
|
|
|
// objects
|
|
PubKey crypto.PubKey
|
|
GenDoc *types.GenesisDoc // cache the genesis structure
|
|
TxIndexer txindex.TxIndexer
|
|
BlockIndexer indexer.BlockIndexer
|
|
ConsensusReactor *consensus.Reactor
|
|
EventBus *types.EventBus // thread safe
|
|
Mempool mempl.Mempool
|
|
|
|
Logger log.Logger
|
|
|
|
Config cfg.RPCConfig
|
|
|
|
// cache of chunked genesis data.
|
|
genChunks []string
|
|
}
|
|
|
|
//----------------------------------------------
|
|
|
|
func validatePage(pagePtr *int, perPage, totalCount int) (int, error) {
|
|
if perPage < 1 {
|
|
panic(fmt.Sprintf("zero or negative perPage: %d", perPage))
|
|
}
|
|
|
|
if pagePtr == nil { // no page parameter
|
|
return 1, nil
|
|
}
|
|
|
|
pages := ((totalCount - 1) / perPage) + 1
|
|
if pages == 0 {
|
|
pages = 1 // one page (even if it's empty)
|
|
}
|
|
page := *pagePtr
|
|
if page <= 0 || page > pages {
|
|
return 1, fmt.Errorf("page should be within [1, %d] range, given %d", pages, page)
|
|
}
|
|
|
|
return page, nil
|
|
}
|
|
|
|
func validatePerPage(perPagePtr *int) int {
|
|
if perPagePtr == nil { // no per_page parameter
|
|
return defaultPerPage
|
|
}
|
|
|
|
perPage := *perPagePtr
|
|
if perPage < 1 {
|
|
return defaultPerPage
|
|
} else if perPage > maxPerPage {
|
|
return maxPerPage
|
|
}
|
|
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 {
|
|
return 0
|
|
}
|
|
|
|
return skipCount
|
|
}
|
|
|
|
// latestHeight can be either latest committed or uncommitted (+1) height.
|
|
func getHeight(latestHeight int64, heightPtr *int64) (int64, error) {
|
|
if heightPtr != nil {
|
|
height := *heightPtr
|
|
if height <= 0 {
|
|
return 0, fmt.Errorf("height must be greater than 0, but got %d", height)
|
|
}
|
|
if height > latestHeight {
|
|
return 0, fmt.Errorf("height %d must be less than or equal to the current blockchain height %d",
|
|
height, latestHeight)
|
|
}
|
|
base := env.BlockStore.Base()
|
|
if height < base {
|
|
return 0, fmt.Errorf("height %d is not available, lowest height is %d",
|
|
height, base)
|
|
}
|
|
return height, nil
|
|
}
|
|
return latestHeight, nil
|
|
}
|
|
|
|
func latestUncommittedHeight() int64 {
|
|
nodeIsSyncing := env.ConsensusReactor.WaitSync()
|
|
if nodeIsSyncing {
|
|
return env.BlockStore.Height()
|
|
}
|
|
return env.BlockStore.Height() + 1
|
|
}
|