概念
genutil 包含多种创世相关的实用功能,可用于区块链应用,具体包括:
- 与创世交易相关的功能(gentx)
- 用于收集和创建 gentx 的命令
- 对 gentx 的
InitChain处理 - 创世文件创建
- 创世文件校验
- 创世文件迁移
- 与 CometBFT 相关的初始化
- 将应用创世数据转换为 CometBFT 创世数据
创世
Genutil 包含用于定义应用程序创世状态的数据结构。 应用程序创世由共识层创世(例如 CometBFT 创世)和应用相关的创世数据组成。package types
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"time"
cmtjson "github.com/cometbft/cometbft/libs/json"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
cmttypes "github.com/cometbft/cometbft/types"
cmttime "github.com/cometbft/cometbft/types/time"
"github.com/cosmos/cosmos-sdk/version"
)
const (
// MaxChainIDLen is the maximum length of a chain ID.
MaxChainIDLen = cmttypes.MaxChainIDLen
)
// AppGenesis defines the app's genesis.
type AppGenesis struct {
AppName string `json:"app_name"`
AppVersion string `json:"app_version"`
GenesisTime time.Time `json:"genesis_time"`
ChainID string `json:"chain_id"`
InitialHeight int64 `json:"initial_height"`
AppHash []byte `json:"app_hash"`
AppState json.RawMessage `json:"app_state,omitempty"`
Consensus *ConsensusGenesis `json:"consensus,omitempty"`
}
// NewAppGenesisWithVersion returns a new AppGenesis with the app name and app version already.
func NewAppGenesisWithVersion(chainID string, appState json.RawMessage) *AppGenesis {
return &AppGenesis{
AppName: version.AppName,
AppVersion: version.Version,
ChainID: chainID,
AppState: appState,
Consensus: &ConsensusGenesis{
Validators: nil,
},
}
}
// ValidateAndComplete performs validation and completes the AppGenesis.
func (ag *AppGenesis)
ValidateAndComplete()
error {
if ag.ChainID == "" {
return errors.New("genesis doc must include non-empty chain_id")
}
if len(ag.ChainID) > MaxChainIDLen {
return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", MaxChainIDLen)
}
if ag.InitialHeight < 0 {
return fmt.Errorf("initial_height cannot be negative (got %v)", ag.InitialHeight)
}
if ag.InitialHeight == 0 {
ag.InitialHeight = 1
}
if ag.GenesisTime.IsZero() {
ag.GenesisTime = cmttime.Now()
}
if err := ag.Consensus.ValidateAndComplete(); err != nil {
return err
}
return nil
}
// SaveAs is a utility method for saving AppGenesis as a JSON file.
func (ag *AppGenesis)
SaveAs(file string)
error {
appGenesisBytes, err := json.MarshalIndent(ag, "", "
")
if err != nil {
return err
}
return os.WriteFile(file, appGenesisBytes, 0o600)
}
// AppGenesisFromFile reads the AppGenesis from the provided file.
func AppGenesisFromFile(genFile string) (*AppGenesis, error) {
jsonBlob, err := os.ReadFile(genFile)
if err != nil {
return nil, fmt.Errorf("couldn't read AppGenesis file (%s): %w", genFile, err)
}
var appGenesis AppGenesis
if err := json.Unmarshal(jsonBlob, &appGenesis); err != nil {
// fallback to CometBFT genesis
var ctmGenesis cmttypes.GenesisDoc
if err2 := cmtjson.Unmarshal(jsonBlob, &ctmGenesis); err2 != nil {
return nil, fmt.Errorf("error unmarshalling AppGenesis at %s: %w\n failed fallback to CometBFT GenDoc: %w", genFile, err, err2)
}
appGenesis = AppGenesis{
AppName: version.AppName,
// AppVersion is not filled as we do not know it from a CometBFT genesis
GenesisTime: ctmGenesis.GenesisTime,
ChainID: ctmGenesis.ChainID,
InitialHeight: ctmGenesis.InitialHeight,
AppHash: ctmGenesis.AppHash,
AppState: ctmGenesis.AppState,
Consensus: &ConsensusGenesis{
Validators: ctmGenesis.Validators,
Params: ctmGenesis.ConsensusParams,
},
}
}
return &appGenesis, nil
}
// --------------------------
// CometBFT Genesis Handling
// --------------------------
// ToGenesisDoc converts the AppGenesis to a CometBFT GenesisDoc.
func (ag *AppGenesis)
ToGenesisDoc() (*cmttypes.GenesisDoc, error) {
return &cmttypes.GenesisDoc{
GenesisTime: ag.GenesisTime,
ChainID: ag.ChainID,
InitialHeight: ag.InitialHeight,
AppHash: ag.AppHash,
AppState: ag.AppState,
Validators: ag.Consensus.Validators,
ConsensusParams: ag.Consensus.Params,
}, nil
}
// ConsensusGenesis defines the consensus layer's genesis.
// TODO(@julienrbrt)
eventually abstract from CometBFT types
type ConsensusGenesis struct {
Validators []cmttypes.GenesisValidator `json:"validators,omitempty"`
Params *cmttypes.ConsensusParams `json:"params,omitempty"`
}
// NewConsensusGenesis returns a ConsensusGenesis with given values.
// It takes a proto consensus params so it can called from server export command.
func NewConsensusGenesis(params cmtproto.ConsensusParams, validators []cmttypes.GenesisValidator) *ConsensusGenesis {
return &ConsensusGenesis{
Params: &cmttypes.ConsensusParams{
Block: cmttypes.BlockParams{
MaxBytes: params.Block.MaxBytes,
MaxGas: params.Block.MaxGas,
},
Evidence: cmttypes.EvidenceParams{
MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks,
MaxAgeDuration: params.Evidence.MaxAgeDuration,
MaxBytes: params.Evidence.MaxBytes,
},
Validator: cmttypes.ValidatorParams{
PubKeyTypes: params.Validator.PubKeyTypes,
},
},
Validators: validators,
}
}
func (cs *ConsensusGenesis)
MarshalJSON() ([]byte, error) {
type Alias ConsensusGenesis
return cmtjson.Marshal(&Alias{
Validators: cs.Validators,
Params: cs.Params,
})
}
func (cs *ConsensusGenesis)
UnmarshalJSON(b []byte)
error {
type Alias ConsensusGenesis
result := Alias{
}
if err := cmtjson.Unmarshal(b, &result); err != nil {
return err
}
cs.Params = result.Params
cs.Validators = result.Validators
return nil
}
func (cs *ConsensusGenesis)
ValidateAndComplete()
error {
if cs == nil {
return fmt.Errorf("consensus genesis cannot be nil")
}
if cs.Params == nil {
cs.Params = cmttypes.DefaultConsensusParams()
}
else if err := cs.Params.ValidateBasic(); err != nil {
return err
}
for i, v := range cs.Validators {
if v.Power == 0 {
return fmt.Errorf("the genesis file cannot contain validators with no voting power: %v", v)
}
if len(v.Address) > 0 && !bytes.Equal(v.PubKey.Address(), v.Address) {
return fmt.Errorf("incorrect address for validator %v in the genesis file, should be %v", v, v.PubKey.Address())
}
if len(v.Address) == 0 {
cs.Validators[i].Address = v.PubKey.Address()
}
}
return nil
}
package types
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"time"
cmtjson "github.com/cometbft/cometbft/libs/json"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
cmttypes "github.com/cometbft/cometbft/types"
cmttime "github.com/cometbft/cometbft/types/time"
"github.com/cosmos/cosmos-sdk/version"
)
const (
// MaxChainIDLen is the maximum length of a chain ID.
MaxChainIDLen = cmttypes.MaxChainIDLen
)
// AppGenesis defines the app's genesis.
type AppGenesis struct {
AppName string `json:"app_name"`
AppVersion string `json:"app_version"`
GenesisTime time.Time `json:"genesis_time"`
ChainID string `json:"chain_id"`
InitialHeight int64 `json:"initial_height"`
AppHash []byte `json:"app_hash"`
AppState json.RawMessage `json:"app_state,omitempty"`
Consensus *ConsensusGenesis `json:"consensus,omitempty"`
}
// NewAppGenesisWithVersion returns a new AppGenesis with the app name and app version already.
func NewAppGenesisWithVersion(chainID string, appState json.RawMessage) *AppGenesis {
return &AppGenesis{
AppName: version.AppName,
AppVersion: version.Version,
ChainID: chainID,
AppState: appState,
Consensus: &ConsensusGenesis{
Validators: nil,
},
}
}
// ValidateAndComplete performs validation and completes the AppGenesis.
func (ag *AppGenesis)
ValidateAndComplete()
error {
if ag.ChainID == "" {
return errors.New("genesis doc must include non-empty chain_id")
}
if len(ag.ChainID) > MaxChainIDLen {
return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", MaxChainIDLen)
}
if ag.InitialHeight < 0 {
return fmt.Errorf("initial_height cannot be negative (got %v)", ag.InitialHeight)
}
if ag.InitialHeight == 0 {
ag.InitialHeight = 1
}
if ag.GenesisTime.IsZero() {
ag.GenesisTime = cmttime.Now()
}
if err := ag.Consensus.ValidateAndComplete(); err != nil {
return err
}
return nil
}
// SaveAs is a utility method for saving AppGenesis as a JSON file.
func (ag *AppGenesis)
SaveAs(file string)
error {
appGenesisBytes, err := json.MarshalIndent(ag, "", "
")
if err != nil {
return err
}
return os.WriteFile(file, appGenesisBytes, 0o600)
}
// AppGenesisFromFile reads the AppGenesis from the provided file.
func AppGenesisFromFile(genFile string) (*AppGenesis, error) {
jsonBlob, err := os.ReadFile(genFile)
if err != nil {
return nil, fmt.Errorf("couldn't read AppGenesis file (%s): %w", genFile, err)
}
var appGenesis AppGenesis
if err := json.Unmarshal(jsonBlob, &appGenesis); err != nil {
// fallback to CometBFT genesis
var ctmGenesis cmttypes.GenesisDoc
if err2 := cmtjson.Unmarshal(jsonBlob, &ctmGenesis); err2 != nil {
return nil, fmt.Errorf("error unmarshalling AppGenesis at %s: %w\n failed fallback to CometBFT GenDoc: %w", genFile, err, err2)
}
appGenesis = AppGenesis{
AppName: version.AppName,
// AppVersion is not filled as we do not know it from a CometBFT genesis
GenesisTime: ctmGenesis.GenesisTime,
ChainID: ctmGenesis.ChainID,
InitialHeight: ctmGenesis.InitialHeight,
AppHash: ctmGenesis.AppHash,
AppState: ctmGenesis.AppState,
Consensus: &ConsensusGenesis{
Validators: ctmGenesis.Validators,
Params: ctmGenesis.ConsensusParams,
},
}
}
return &appGenesis, nil
}
// --------------------------
// CometBFT Genesis Handling
// --------------------------
// ToGenesisDoc converts the AppGenesis to a CometBFT GenesisDoc.
func (ag *AppGenesis)
ToGenesisDoc() (*cmttypes.GenesisDoc, error) {
return &cmttypes.GenesisDoc{
GenesisTime: ag.GenesisTime,
ChainID: ag.ChainID,
InitialHeight: ag.InitialHeight,
AppHash: ag.AppHash,
AppState: ag.AppState,
Validators: ag.Consensus.Validators,
ConsensusParams: ag.Consensus.Params,
}, nil
}
// ConsensusGenesis defines the consensus layer's genesis.
// TODO(@julienrbrt)
eventually abstract from CometBFT types
type ConsensusGenesis struct {
Validators []cmttypes.GenesisValidator `json:"validators,omitempty"`
Params *cmttypes.ConsensusParams `json:"params,omitempty"`
}
// NewConsensusGenesis returns a ConsensusGenesis with given values.
// It takes a proto consensus params so it can called from server export command.
func NewConsensusGenesis(params cmtproto.ConsensusParams, validators []cmttypes.GenesisValidator) *ConsensusGenesis {
return &ConsensusGenesis{
Params: &cmttypes.ConsensusParams{
Block: cmttypes.BlockParams{
MaxBytes: params.Block.MaxBytes,
MaxGas: params.Block.MaxGas,
},
Evidence: cmttypes.EvidenceParams{
MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks,
MaxAgeDuration: params.Evidence.MaxAgeDuration,
MaxBytes: params.Evidence.MaxBytes,
},
Validator: cmttypes.ValidatorParams{
PubKeyTypes: params.Validator.PubKeyTypes,
},
},
Validators: validators,
}
}
func (cs *ConsensusGenesis)
MarshalJSON() ([]byte, error) {
type Alias ConsensusGenesis
return cmtjson.Marshal(&Alias{
Validators: cs.Validators,
Params: cs.Params,
})
}
func (cs *ConsensusGenesis)
UnmarshalJSON(b []byte)
error {
type Alias ConsensusGenesis
result := Alias{
}
if err := cmtjson.Unmarshal(b, &result); err != nil {
return err
}
cs.Params = result.Params
cs.Validators = result.Validators
return nil
}
func (cs *ConsensusGenesis)
ValidateAndComplete()
error {
if cs == nil {
return fmt.Errorf("共识创世配置不能为 nil")
}
if cs.Params == nil {
cs.Params = cmttypes.DefaultConsensusParams()
}
else if err := cs.Params.ValidateBasic(); err != nil {
return err
}
for i, v := range cs.Validators {
if v.Power == 0 {
return fmt.Errorf("创世文件不能包含没有投票权的验证者: %v", v)
}
if len(v.Address) > 0 && !bytes.Equal(v.PubKey.Address(), v.Address) {
return fmt.Errorf("创世文件中验证者 %v 的地址不正确,应为 %v", v, v.PubKey.Address())
}
if len(v.Address) == 0 {
cs.Validators[i].Address = v.PubKey.Address()
}
}
return nil
}
package server
import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"runtime/pprof"
"github.com/cometbft/cometbft/abci/server"
cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands"
cmtcfg "github.com/cometbft/cometbft/config"
"github.com/cometbft/cometbft/node"
"github.com/cometbft/cometbft/p2p"
pvm "github.com/cometbft/cometbft/privval"
"github.com/cometbft/cometbft/proxy"
"github.com/cometbft/cometbft/rpc/client/local"
cmttypes "github.com/cometbft/cometbft/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/hashicorp/go-metrics"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pruningtypes "cosmossdk.io/store/pruning/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server/api"
serverconfig "github.com/cosmos/cosmos-sdk/server/config"
servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
servercmtlog "github.com/cosmos/cosmos-sdk/server/log"
"github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/telemetry"
"github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/cosmos/cosmos-sdk/version"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
)
const (
// CometBFT 全节点启动标志
flagWithComet = "with-comet"
flagAddress = "address"
flagTransport = "transport"
flagTraceStore = "trace-store"
flagCPUProfile = "cpu-profile"
FlagMinGasPrices = "minimum-gas-prices"
FlagQueryGasLimit = "query-gas-limit"
FlagHaltHeight = "halt-height"
FlagHaltTime = "halt-time"
FlagInterBlockCache = "inter-block-cache"
FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades"
FlagTrace = "trace"
FlagInvCheckPeriod = "inv-check-period"
FlagPruning = "pruning"
FlagPruningKeepRecent = "pruning-keep-recent"
FlagPruningInterval = "pruning-interval"
FlagIndexEvents = "index-events"
FlagMinRetainBlocks = "min-retain-blocks"
FlagIAVLCacheSize = "iavl-cache-size"
FlagDisableIAVLFastNode = "iavl-disable-fastnode"
// 与状态同步相关的标志
FlagStateSyncSnapshotInterval = "state-sync.snapshot-interval"
FlagStateSyncSnapshotKeepRecent = "state-sync.snapshot-keep-recent"
// 与 API 相关的标志
FlagAPIEnable = "api.enable"
FlagAPISwagger = "api.swagger"
FlagAPIAddress = "api.address"
FlagAPIMaxOpenConnections = "api.max-open-connections"
FlagRPCReadTimeout = "api.rpc-read-timeout"
FlagRPCWriteTimeout = "api.rpc-write-timeout"
FlagRPCMaxBodyBytes = "api.rpc-max-body-bytes"
FlagAPIEnableUnsafeCORS = "api.enabled-unsafe-cors"
// 与 gRPC 相关的标志
flagGRPCOnly = "grpc-only"
flagGRPCEnable = "grpc.enable"
flagGRPCAddress = "grpc.address"
flagGRPCWebEnable = "grpc-web.enable"
// mempool 标志
FlagMempoolMaxTxs = "mempool.max-txs"
)
// StartCmdOptions 定义了可在 `StartCmdWithOptions` 中自定义的选项,
type StartCmdOptions struct {
// DBOpener 可用于自定义数据库打开方式,例如自定义数据库选项或支持不同的数据库后端,
// 默认使用内置的数据库打开器。
DBOpener func(rootDir string, backendType dbm.BackendType) (dbm.DB, error)
// PostSetup 可用于在同一个可取消上下文下设置额外服务,
// 它不会在独立模式下调用,只会在进程内模式下调用。
PostSetup func(svrCtx *Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group)
error
// AddFlags 为 start 命令添加自定义标志
AddFlags func(cmd *cobra.Command)
}
// StartCmd 运行传入的服务,可选择独立模式或与
// CometBFT 一起在进程内运行。
func StartCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command {
return StartCmdWithOptions(appCreator, defaultNodeHome, StartCmdOptions{
})
}
// StartCmdWithOptions 运行传入的服务,可选择独立模式或与
// CometBFT 一起在进程内运行。
func StartCmdWithOptions(appCreator types.AppCreator, defaultNodeHome string, opts StartCmdOptions) *cobra.Command {
if opts.DBOpener == nil {
opts.DBOpener = openDB
}
cmd := &cobra.Command{
Use: "start",
Short: "运行全节点",
Long: `在进程内或进程外结合 CometBFT 运行全节点应用。默认情况下,
应用会在进程内与 CometBFT 一起运行。
可以通过 '--pruning' 标志提供裁剪选项,或者改为同时使用 '--pruning-keep-recent' 和
'pruning-interval'。
'--pruning' 的可选值如下:
default: 保留最近 362880 个状态,每 10 个区块执行一次裁剪
nothing: 保存所有历史状态,不删除任何内容(即归档节点)
everything: 保留最近 2 个状态;每 10 个区块执行一次裁剪。
custom: 允许通过 'pruning-keep-recent' 和 'pruning-interval' 手动指定裁剪选项
节点停机配置通过两个标志提供:'--halt-height' 和 '--halt-time'。在
ABCI Commit 阶段,节点会检查当前区块高度是否大于或等于
halt-height,或当前区块时间是否大于或等于 halt-time。如果满足条件,
节点将尝试优雅关闭,并且该区块不会被提交。此外,节点
也无法继续提交后续区块。
为进行性能分析和基准测试,可以通过 '--cpu-profile' 标志启用 CPU 性能分析,
该标志接收生成的 pprof 文件路径。
节点可以以“仅查询”模式启动,此时仅通过 'grpc-only' 标志启用 gRPC 和 JSON HTTP
API 服务。在该模式下,会绕过 CometBFT,
可在链上升级完成后仍需执行旧版查询时使用。注意,启用该模式时,
gRPC 也会自动启用。
`,
PreRunE: func(cmd *cobra.Command, _ []string)
error {
serverCtx := GetServerContextFromCmd(cmd)
// 将标志绑定到 Context 的 Viper,以便应用构建时能够据此设置
// 相应选项。
if err := serverCtx.Viper.BindPFlags(cmd.Flags()); err != nil {
return err
}
_, err := GetPruningOptionsFromFlags(serverCtx.Viper)
return err
},
RunE: func(cmd *cobra.Command, _ []string)
error {
serverCtx := GetServerContextFromCmd(cmd)
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
withCMT, _ := cmd.Flags().GetBool(flagWithComet)
if !withCMT {
serverCtx.Logger.Info("在不使用 CometBFT 的情况下启动 ABCI")
}
return wrapCPUProfile(serverCtx, func()
error {
return start(serverCtx, clientCtx, appCreator, withCMT, opts)
})
},
}
cmd.Flags().String(flags.FlagHome, defaultNodeHome, "应用的主目录")
cmd.Flags().Bool(flagWithComet, true, "在进程内以嵌入方式运行带 CometBFT 的 ABCI 应用")
cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "监听地址")
cmd.Flags().String(flagTransport, "socket", "传输协议:socket、grpc")
cmd.Flags().String(flagTraceStore, "", "启用 KVStore 跟踪并输出到文件")
cmd.Flags().String(FlagMinGasPrices, "", "接受交易的最低 gas 价格;交易中的任意费用都必须满足该最小值(例如 0.01photino;0.0001stake)")
cmd.Flags().Uint64(FlagQueryGasLimit, 0, "REST/gRPC 查询可消耗的最大 gas。留空或设为 0 表示不受限制。")
cmd.Flags().IntSlice(FlagUnsafeSkipUpgrades, []int{
}, "跳过一组升级高度以继续使用旧二进制")
cmd.Flags().Uint64(FlagHaltHeight, 0, "在该区块高度优雅停止链并关闭节点")
cmd.Flags().Uint64(FlagHaltTime, 0, "用于优雅停止链并关闭节点的最小区块时间(Unix 秒)")
cmd.Flags().Bool(FlagInterBlockCache, true, "启用区块间缓存")
cmd.Flags().String(flagCPUProfile, "", "启用 CPU 性能分析并写入指定文件")
cmd.Flags().Bool(FlagTrace, false, "为 ABCI 日志中的错误提供完整堆栈跟踪")
cmd.Flags().String(FlagPruning, pruningtypes.PruningOptionDefault, "裁剪策略(default|nothing|everything|custom)")
cmd.Flags().Uint64(FlagPruningKeepRecent, 0, "磁盘上保留的最近高度数量(当 pruning 不是 'custom' 时忽略)")
cmd.Flags().Uint64(FlagPruningInterval, 0, "从磁盘删除已裁剪高度的高度间隔(当 pruning 不是 'custom' 时忽略)")
cmd.Flags().Uint(FlagInvCheckPeriod, 0, "每 N 个区块校验已注册的不变量")
cmd.Flags().Uint64(FlagMinRetainBlocks, 0, "在 ABCI 提交期间用于裁剪 CometBFT 区块的最小区块高度偏移")
cmd.Flags().Bool(FlagAPIEnable, false, "定义是否启用 API 服务器")
cmd.Flags().Bool(FlagAPISwagger, false, "定义是否自动注册 Swagger 文档(注意:还必须启用 API)")
cmd.Flags().String(FlagAPIAddress, serverconfig.DefaultAPIAddress, "API 服务器监听地址")
cmd.Flags().Uint(FlagAPIMaxOpenConnections, 1000, "定义最大打开连接数")
cmd.Flags().Uint(FlagRPCReadTimeout, 10, "定义 CometBFT RPC 读取超时(秒)")
cmd.Flags().Uint(FlagRPCWriteTimeout, 0, "定义 CometBFT RPC 写入超时(秒)")
cmd.Flags().Uint(FlagRPCMaxBodyBytes, 1000000, "定义 CometBFT 请求体的最大大小(字节)")
cmd.Flags().Bool(FlagAPIEnableUnsafeCORS, false, "定义是否启用 CORS(不安全,请自行承担风险)")
cmd.Flags().Bool(flagGRPCOnly, false, "以仅 gRPC 查询模式启动节点(不会启动 CometBFT 进程)")
cmd.Flags().Bool(flagGRPCEnable, true, "定义是否启用 gRPC 服务器")
cmd.Flags().String(flagGRPCAddress, serverconfig.DefaultGRPCAddress, "gRPC 服务器监听地址")
cmd.Flags().Bool(flagGRPCWebEnable, true, "定义是否启用 gRPC-Web 服务器。(注意:还必须启用 gRPC)")
cmd.Flags().Uint64(FlagStateSyncSnapshotInterval, 0, "状态同步快照间隔")
cmd.Flags().Uint32(FlagStateSyncSnapshotKeepRecent, 2, "保留的状态同步快照数量")
cmd.Flags().Bool(FlagDisableIAVLFastNode, false, "禁用 IAVL 树的快速节点")
cmd.Flags().Int(FlagMempoolMaxTxs, mempool.DefaultMaxTx, "设置应用侧 mempool 的 MaxTx 值")
// 支持旧标志名以向后兼容
cmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string)
pflag.NormalizedName {
if name == "with-tendermint" {
name = flagWithComet
}
return pflag.NormalizedName(name)
})
// 为所有 CometBFT 特定命令行选项添加支持
cmtcmd.AddNodeFlags(cmd)
if opts.AddFlags != nil {
opts.AddFlags(cmd)
}
return cmd
}
func start(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreator, withCmt bool, opts StartCmdOptions)
error {
svrCfg, err := getAndValidateConfig(svrCtx)
if err != nil {
return err
}
app, appCleanupFn, err := startApp(svrCtx, appCreator, opts)
if err != nil {
return err
}
defer appCleanupFn()
metrics, err := startTelemetry(svrCfg)
if err != nil {
return err
}
emitServerInfoMetrics()
if !withCmt {
return startStandAlone(svrCtx, app, opts)
}
return startInProcess(svrCtx, svrCfg, clientCtx, app, metrics, opts)
}
func startStandAlone(svrCtx *Context, app types.Application, opts StartCmdOptions)
error {
addr := svrCtx.Viper.GetString(flagAddress)
transport := svrCtx.Viper.GetString(flagTransport)
cmtApp := NewCometABCIWrapper(app)
svr, err := server.NewServer(addr, transport, cmtApp)
if err != nil {
return fmt.Errorf("error creating listener: %v", err)
}
svr.SetLogger(servercmtlog.CometLoggerWrapper{
Logger: svrCtx.Logger.With("module", "abci-server")
})
g, ctx := getCtx(svrCtx, false)
g.Go(func()
error {
if err := svr.Start(); err != nil {
svrCtx.Logger.Error("failed to start out-of-process ABCI server", "err", err)
return err
}
// Wait for the calling process to be canceled or close the provided context,
// so we can gracefully stop the ABCI server.
<-ctx.Done()
svrCtx.Logger.Info("stopping the ABCI server...")
return errors.Join(svr.Stop(), app.Close())
})
return g.Wait()
}
func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application,
metrics *telemetry.Metrics, opts StartCmdOptions,
)
error {
cmtCfg := svrCtx.Config
home := cmtCfg.RootDir
gRPCOnly := svrCtx.Viper.GetBool(flagGRPCOnly)
g, ctx := getCtx(svrCtx, true)
if gRPCOnly {
// TODO: Generalize logic so that gRPC only is really in startStandAlone
svrCtx.Logger.Info("starting node in gRPC only mode; CometBFT is disabled")
svrCfg.GRPC.Enable = true
}
else {
svrCtx.Logger.Info("starting node with ABCI CometBFT in-process")
tmNode, cleanupFn, err := startCmtNode(ctx, cmtCfg, app, svrCtx)
if err != nil {
return err
}
defer cleanupFn()
// Add the tx service to the gRPC router. We only need to register this
// service if API or gRPC is enabled, and avoid doing so in the general
// case, because it spawns a new local CometBFT RPC client.
if svrCfg.API.Enable || svrCfg.GRPC.Enable {
// Re-assign for making the client available below do not use := to avoid
// shadowing the clientCtx variable.
clientCtx = clientCtx.WithClient(local.New(tmNode))
app.RegisterTxService(clientCtx)
app.RegisterTendermintService(clientCtx)
app.RegisterNodeService(clientCtx, svrCfg)
}
}
grpcSrv, clientCtx, err := startGrpcServer(ctx, g, svrCfg.GRPC, clientCtx, svrCtx, app)
if err != nil {
return err
}
err = startAPIServer(ctx, g, cmtCfg, svrCfg, clientCtx, svrCtx, app, home, grpcSrv, metrics)
if err != nil {
return err
}
if opts.PostSetup != nil {
if err := opts.PostSetup(svrCtx, clientCtx, ctx, g); err != nil {
return err
}
}
// wait for signal capture and gracefully return
// we are guaranteed to be waiting for the "ListenForQuitSignals" goroutine.
return g.Wait()
}
// TODO: Move nodeKey into being created within the function.
func startCmtNode(
ctx context.Context,
cfg *cmtcfg.Config,
app types.Application,
svrCtx *Context,
) (tmNode *node.Node, cleanupFn func(), err error) {
nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
if err != nil {
return nil, cleanupFn, err
}
cmtApp := NewCometABCIWrapper(app)
tmNode, err = node.NewNodeWithContext(
ctx,
cfg,
pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()),
nodeKey,
proxy.NewLocalClientCreator(cmtApp),
getGenDocProvider(cfg),
cmtcfg.DefaultDBProvider,
node.DefaultMetricsProvider(cfg.Instrumentation),
servercmtlog.CometLoggerWrapper{
Logger: svrCtx.Logger
},
)
if err != nil {
return tmNode, cleanupFn, err
}
if err := tmNode.Start(); err != nil {
return tmNode, cleanupFn, err
}
cleanupFn = func() {
if tmNode != nil && tmNode.IsRunning() {
_ = tmNode.Stop()
_ = app.Close()
}
}
return tmNode, cleanupFn, nil
}
func getAndValidateConfig(svrCtx *Context) (serverconfig.Config, error) {
config, err := serverconfig.GetConfig(svrCtx.Viper)
if err != nil {
return config, err
}
if err := config.ValidateBasic(); err != nil {
return config, err
}
return config, nil
}
// returns a function which returns the genesis doc from the genesis file.
func getGenDocProvider(cfg *cmtcfg.Config)
func() (*cmttypes.GenesisDoc, error) {
return func() (*cmttypes.GenesisDoc, error) {
appGenesis, err := genutiltypes.AppGenesisFromFile(cfg.GenesisFile())
if err != nil {
return nil, err
}
return appGenesis.ToGenesisDoc()
}
}
func setupTraceWriter(svrCtx *Context) (traceWriter io.WriteCloser, cleanup func(), err error) {
// clean up the traceWriter when the server is shutting down
cleanup = func() {
}
traceWriterFile := svrCtx.Viper.GetString(flagTraceStore)
traceWriter, err = openTraceWriter(traceWriterFile)
if err != nil {
return traceWriter, cleanup, err
}
// if flagTraceStore is not used then traceWriter is nil
if traceWriter != nil {
cleanup = func() {
if err = traceWriter.Close(); err != nil {
svrCtx.Logger.Error("failed to close trace writer", "err", err)
}
}
}
return traceWriter, cleanup, nil
}
func startGrpcServer(
ctx context.Context,
g *errgroup.Group,
config serverconfig.GRPCConfig,
clientCtx client.Context,
svrCtx *Context,
app types.Application,
) (*grpc.Server, client.Context, error) {
if !config.Enable {
// return grpcServer as nil if gRPC is disabled
return nil, clientCtx, nil
}
_, port, err := net.SplitHostPort(config.Address)
if err != nil {
return nil, clientCtx, err
}
maxSendMsgSize := config.MaxSendMsgSize
if maxSendMsgSize == 0 {
maxSendMsgSize = serverconfig.DefaultGRPCMaxSendMsgSize
}
maxRecvMsgSize := config.MaxRecvMsgSize
if maxRecvMsgSize == 0 {
maxRecvMsgSize = serverconfig.DefaultGRPCMaxRecvMsgSize
}
grpcAddress := fmt.Sprintf("127.0.0.1:%s", port)
// if gRPC is enabled, configure gRPC client for gRPC gateway
grpcClient, err := grpc.Dial(
grpcAddress,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultCallOptions(
grpc.ForceCodec(codec.NewProtoCodec(clientCtx.InterfaceRegistry).GRPCCodec()),
grpc.MaxCallRecvMsgSize(maxRecvMsgSize),
grpc.MaxCallSendMsgSize(maxSendMsgSize),
),
)
if err != nil {
return nil, clientCtx, err
}
clientCtx = clientCtx.WithGRPCClient(grpcClient)
svrCtx.Logger.Debug("gRPC client assigned to client context", "target", grpcAddress)
grpcSrv, err := servergrpc.NewGRPCServer(clientCtx, app, config)
if err != nil {
return nil, clientCtx, err
}
// Start the gRPC server in a goroutine. Note, the provided ctx will ensure
// that the server is gracefully shut down.
g.Go(func()
error {
return servergrpc.StartGRPCServer(ctx, svrCtx.Logger.With("module", "grpc-server"), config, grpcSrv)
})
return grpcSrv, clientCtx, nil
}
func startAPIServer(
ctx context.Context,
g *errgroup.Group,
cmtCfg *cmtcfg.Config,
svrCfg serverconfig.Config,
clientCtx client.Context,
svrCtx *Context,
app types.Application,
home string,
grpcSrv *grpc.Server,
metrics *telemetry.Metrics,
)
error {
if !svrCfg.API.Enable {
return nil
}
clientCtx = clientCtx.WithHomeDir(home)
apiSrv := api.New(clientCtx, svrCtx.Logger.With("module", "api-server"), grpcSrv)
app.RegisterAPIRoutes(apiSrv, svrCfg.API)
if svrCfg.Telemetry.Enabled {
apiSrv.SetTelemetry(metrics)
}
g.Go(func()
error {
return apiSrv.Start(ctx, svrCfg)
})
return nil
}
func startTelemetry(cfg serverconfig.Config) (*telemetry.Metrics, error) {
if !cfg.Telemetry.Enabled {
return nil, nil
}
return telemetry.New(cfg.Telemetry)
}
// wrapCPUProfile starts CPU profiling, if enabled, and executes the provided
// callbackFn in a separate goroutine, then will wait for that callback to
// return.
//
// NOTE: We expect the caller to handle graceful shutdown and signal handling.
func wrapCPUProfile(svrCtx *Context, callbackFn func()
error)
error {
if cpuProfile := svrCtx.Viper.GetString(flagCPUProfile); cpuProfile != "" {
f, err := os.Create(cpuProfile)
if err != nil {
return err
}
svrCtx.Logger.Info("starting CPU profiler", "profile", cpuProfile)
if err := pprof.StartCPUProfile(f); err != nil {
return err
}
defer func() {
svrCtx.Logger.Info("stopping CPU profiler", "profile", cpuProfile)
pprof.StopCPUProfile()
if err := f.Close(); err != nil {
svrCtx.Logger.Info("failed to close cpu-profile file", "profile", cpuProfile, "err", err.Error())
}
}()
}
return callbackFn()
}
// emitServerInfoMetrics emits server info related metrics using application telemetry.
func emitServerInfoMetrics() {
var ls []metrics.Label
versionInfo := version.NewInfo()
if len(versionInfo.GoVersion) > 0 {
ls = append(ls, telemetry.NewLabel("go", versionInfo.GoVersion))
}
if len(versionInfo.CosmosSdkVersion) > 0 {
ls = append(ls, telemetry.NewLabel("version", versionInfo.CosmosSdkVersion))
}
if len(ls) == 0 {
return
}
telemetry.SetGaugeWithLabels([]string{"server", "info"
}, 1, ls)
}
func getCtx(svrCtx *Context, block bool) (*errgroup.Group, context.Context) {
ctx, cancelFn := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
// listen for quit signals so the calling parent process can gracefully exit
ListenForQuitSignals(g, block, cancelFn, svrCtx.Logger)
return g, ctx
}
func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions) (app types.Application, cleanupFn func(), err error) {
traceWriter, traceCleanupFn, err := setupTraceWriter(svrCtx)
if err != nil {
return app, traceCleanupFn, err
}
home := svrCtx.Config.RootDir
db, err := opts.DBOpener(home, GetAppDBBackend(svrCtx.Viper))
if err != nil {
return app, traceCleanupFn, err
}
app = appCreator(svrCtx.Logger, db, traceWriter, svrCtx.Viper)
cleanupFn = func() {
traceCleanupFn()
if localErr := app.Close(); localErr != nil {
svrCtx.Logger.Error(localErr.Error())
}
}
return app, cleanupFn, nil
}
客户端
CLI
genutil 命令位于 genesis 子命令下。
add-genesis-account
向genesis.json 添加创世账户。更多信息见此处。
collect-gentxs
收集创世交易并输出一个genesis.json 文件。
simd genesis collect-gentxs
genesis.json 文件,其中包含所有验证者的数据(我们有时将其称为“超级创世文件”,以区别于单验证者创世文件)。
gentx
生成一笔携带自委托的创世交易。simd genesis gentx [key_name] [amount] --chain-id [chain-id]
amount 至少应为 1000000000stake。
如果提供的数量过多或过少,启动节点时将会报错。
migrate
将创世文件迁移到指定的目标(SDK)版本。simd genesis migrate [target-version]
migrate 命令是可扩展的,并接受一个 MigrationMap。该映射用于将目标版本对应到创世迁移函数。
当不使用默认的 MigrationMap 时,仍建议调用与你的链所使用 SDK 版本对应的默认 MigrationMap,并在其前后追加你自己的创世迁移。validate-genesis
验证位于默认位置或作为参数传入的位置上的创世文件。simd genesis validate-genesis
validate-genesis 仅会验证创世文件对于当前应用二进制文件是否有效。若要验证来自该应用先前版本的创世文件,请使用 migrate 命令先将创世文件迁移到当前版本。Concepts
Thegenutil package contains a variety of genesis utility functionalities for usage within a blockchain application. Namely:
- Genesis transactions related (gentx)
- Commands for collection and creation of gentxs
InitChainprocessing of gentxs- Genesis file creation
- Genesis file validation
- Genesis file migration
- CometBFT related initialization
- Translation of an app genesis to a CometBFT genesis
Genesis
Genutil contains the data structure that defines an application genesis. An application genesis consist of a consensus genesis (g.e. CometBFT genesis) and application related genesis data.package types
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"time"
cmtjson "github.com/cometbft/cometbft/libs/json"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
cmttypes "github.com/cometbft/cometbft/types"
cmttime "github.com/cometbft/cometbft/types/time"
"github.com/cosmos/cosmos-sdk/version"
)
const (
// MaxChainIDLen is the maximum length of a chain ID.
MaxChainIDLen = cmttypes.MaxChainIDLen
)
// AppGenesis defines the app's genesis.
type AppGenesis struct {
AppName string `json:"app_name"`
AppVersion string `json:"app_version"`
GenesisTime time.Time `json:"genesis_time"`
ChainID string `json:"chain_id"`
InitialHeight int64 `json:"initial_height"`
AppHash []byte `json:"app_hash"`
AppState json.RawMessage `json:"app_state,omitempty"`
Consensus *ConsensusGenesis `json:"consensus,omitempty"`
}
// NewAppGenesisWithVersion returns a new AppGenesis with the app name and app version already.
func NewAppGenesisWithVersion(chainID string, appState json.RawMessage) *AppGenesis {
return &AppGenesis{
AppName: version.AppName,
AppVersion: version.Version,
ChainID: chainID,
AppState: appState,
Consensus: &ConsensusGenesis{
Validators: nil,
},
}
}
// ValidateAndComplete performs validation and completes the AppGenesis.
func (ag *AppGenesis)
ValidateAndComplete()
error {
if ag.ChainID == "" {
return errors.New("genesis doc must include non-empty chain_id")
}
if len(ag.ChainID) > MaxChainIDLen {
return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", MaxChainIDLen)
}
if ag.InitialHeight < 0 {
return fmt.Errorf("initial_height cannot be negative (got %v)", ag.InitialHeight)
}
if ag.InitialHeight == 0 {
ag.InitialHeight = 1
}
if ag.GenesisTime.IsZero() {
ag.GenesisTime = cmttime.Now()
}
if err := ag.Consensus.ValidateAndComplete(); err != nil {
return err
}
return nil
}
// SaveAs is a utility method for saving AppGenesis as a JSON file.
func (ag *AppGenesis)
SaveAs(file string)
error {
appGenesisBytes, err := json.MarshalIndent(ag, "", "
")
if err != nil {
return err
}
return os.WriteFile(file, appGenesisBytes, 0o600)
}
// AppGenesisFromFile reads the AppGenesis from the provided file.
func AppGenesisFromFile(genFile string) (*AppGenesis, error) {
jsonBlob, err := os.ReadFile(genFile)
if err != nil {
return nil, fmt.Errorf("couldn't read AppGenesis file (%s): %w", genFile, err)
}
var appGenesis AppGenesis
if err := json.Unmarshal(jsonBlob, &appGenesis); err != nil {
// fallback to CometBFT genesis
var ctmGenesis cmttypes.GenesisDoc
if err2 := cmtjson.Unmarshal(jsonBlob, &ctmGenesis); err2 != nil {
return nil, fmt.Errorf("error unmarshalling AppGenesis at %s: %w\n failed fallback to CometBFT GenDoc: %w", genFile, err, err2)
}
appGenesis = AppGenesis{
AppName: version.AppName,
// AppVersion is not filled as we do not know it from a CometBFT genesis
GenesisTime: ctmGenesis.GenesisTime,
ChainID: ctmGenesis.ChainID,
InitialHeight: ctmGenesis.InitialHeight,
AppHash: ctmGenesis.AppHash,
AppState: ctmGenesis.AppState,
Consensus: &ConsensusGenesis{
Validators: ctmGenesis.Validators,
Params: ctmGenesis.ConsensusParams,
},
}
}
return &appGenesis, nil
}
// --------------------------
// CometBFT Genesis Handling
// --------------------------
// ToGenesisDoc converts the AppGenesis to a CometBFT GenesisDoc.
func (ag *AppGenesis)
ToGenesisDoc() (*cmttypes.GenesisDoc, error) {
return &cmttypes.GenesisDoc{
GenesisTime: ag.GenesisTime,
ChainID: ag.ChainID,
InitialHeight: ag.InitialHeight,
AppHash: ag.AppHash,
AppState: ag.AppState,
Validators: ag.Consensus.Validators,
ConsensusParams: ag.Consensus.Params,
}, nil
}
// ConsensusGenesis defines the consensus layer's genesis.
// TODO(@julienrbrt)
eventually abstract from CometBFT types
type ConsensusGenesis struct {
Validators []cmttypes.GenesisValidator `json:"validators,omitempty"`
Params *cmttypes.ConsensusParams `json:"params,omitempty"`
}
// NewConsensusGenesis returns a ConsensusGenesis with given values.
// It takes a proto consensus params so it can called from server export command.
func NewConsensusGenesis(params cmtproto.ConsensusParams, validators []cmttypes.GenesisValidator) *ConsensusGenesis {
return &ConsensusGenesis{
Params: &cmttypes.ConsensusParams{
Block: cmttypes.BlockParams{
MaxBytes: params.Block.MaxBytes,
MaxGas: params.Block.MaxGas,
},
Evidence: cmttypes.EvidenceParams{
MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks,
MaxAgeDuration: params.Evidence.MaxAgeDuration,
MaxBytes: params.Evidence.MaxBytes,
},
Validator: cmttypes.ValidatorParams{
PubKeyTypes: params.Validator.PubKeyTypes,
},
},
Validators: validators,
}
}
func (cs *ConsensusGenesis)
MarshalJSON() ([]byte, error) {
type Alias ConsensusGenesis
return cmtjson.Marshal(&Alias{
Validators: cs.Validators,
Params: cs.Params,
})
}
func (cs *ConsensusGenesis)
UnmarshalJSON(b []byte)
error {
type Alias ConsensusGenesis
result := Alias{
}
if err := cmtjson.Unmarshal(b, &result); err != nil {
return err
}
cs.Params = result.Params
cs.Validators = result.Validators
return nil
}
func (cs *ConsensusGenesis)
ValidateAndComplete()
error {
if cs == nil {
return fmt.Errorf("consensus genesis cannot be nil")
}
if cs.Params == nil {
cs.Params = cmttypes.DefaultConsensusParams()
}
else if err := cs.Params.ValidateBasic(); err != nil {
return err
}
for i, v := range cs.Validators {
if v.Power == 0 {
return fmt.Errorf("the genesis file cannot contain validators with no voting power: %v", v)
}
if len(v.Address) > 0 && !bytes.Equal(v.PubKey.Address(), v.Address) {
return fmt.Errorf("incorrect address for validator %v in the genesis file, should be %v", v, v.PubKey.Address())
}
if len(v.Address) == 0 {
cs.Validators[i].Address = v.PubKey.Address()
}
}
return nil
}
package types
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"time"
cmtjson "github.com/cometbft/cometbft/libs/json"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
cmttypes "github.com/cometbft/cometbft/types"
cmttime "github.com/cometbft/cometbft/types/time"
"github.com/cosmos/cosmos-sdk/version"
)
const (
// MaxChainIDLen is the maximum length of a chain ID.
MaxChainIDLen = cmttypes.MaxChainIDLen
)
// AppGenesis defines the app's genesis.
type AppGenesis struct {
AppName string `json:"app_name"`
AppVersion string `json:"app_version"`
GenesisTime time.Time `json:"genesis_time"`
ChainID string `json:"chain_id"`
InitialHeight int64 `json:"initial_height"`
AppHash []byte `json:"app_hash"`
AppState json.RawMessage `json:"app_state,omitempty"`
Consensus *ConsensusGenesis `json:"consensus,omitempty"`
}
// NewAppGenesisWithVersion returns a new AppGenesis with the app name and app version already.
func NewAppGenesisWithVersion(chainID string, appState json.RawMessage) *AppGenesis {
return &AppGenesis{
AppName: version.AppName,
AppVersion: version.Version,
ChainID: chainID,
AppState: appState,
Consensus: &ConsensusGenesis{
Validators: nil,
},
}
}
// ValidateAndComplete performs validation and completes the AppGenesis.
func (ag *AppGenesis)
ValidateAndComplete()
error {
if ag.ChainID == "" {
return errors.New("genesis doc must include non-empty chain_id")
}
if len(ag.ChainID) > MaxChainIDLen {
return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", MaxChainIDLen)
}
if ag.InitialHeight < 0 {
return fmt.Errorf("initial_height cannot be negative (got %v)", ag.InitialHeight)
}
if ag.InitialHeight == 0 {
ag.InitialHeight = 1
}
if ag.GenesisTime.IsZero() {
ag.GenesisTime = cmttime.Now()
}
if err := ag.Consensus.ValidateAndComplete(); err != nil {
return err
}
return nil
}
// SaveAs is a utility method for saving AppGenesis as a JSON file.
func (ag *AppGenesis)
SaveAs(file string)
error {
appGenesisBytes, err := json.MarshalIndent(ag, "", "
")
if err != nil {
return err
}
return os.WriteFile(file, appGenesisBytes, 0o600)
}
// AppGenesisFromFile reads the AppGenesis from the provided file.
func AppGenesisFromFile(genFile string) (*AppGenesis, error) {
jsonBlob, err := os.ReadFile(genFile)
if err != nil {
return nil, fmt.Errorf("couldn't read AppGenesis file (%s): %w", genFile, err)
}
var appGenesis AppGenesis
if err := json.Unmarshal(jsonBlob, &appGenesis); err != nil {
// fallback to CometBFT genesis
var ctmGenesis cmttypes.GenesisDoc
if err2 := cmtjson.Unmarshal(jsonBlob, &ctmGenesis); err2 != nil {
return nil, fmt.Errorf("error unmarshalling AppGenesis at %s: %w\n failed fallback to CometBFT GenDoc: %w", genFile, err, err2)
}
appGenesis = AppGenesis{
AppName: version.AppName,
// AppVersion is not filled as we do not know it from a CometBFT genesis
GenesisTime: ctmGenesis.GenesisTime,
ChainID: ctmGenesis.ChainID,
InitialHeight: ctmGenesis.InitialHeight,
AppHash: ctmGenesis.AppHash,
AppState: ctmGenesis.AppState,
Consensus: &ConsensusGenesis{
Validators: ctmGenesis.Validators,
Params: ctmGenesis.ConsensusParams,
},
}
}
return &appGenesis, nil
}
// --------------------------
// CometBFT Genesis Handling
// --------------------------
// ToGenesisDoc converts the AppGenesis to a CometBFT GenesisDoc.
func (ag *AppGenesis)
ToGenesisDoc() (*cmttypes.GenesisDoc, error) {
return &cmttypes.GenesisDoc{
GenesisTime: ag.GenesisTime,
ChainID: ag.ChainID,
InitialHeight: ag.InitialHeight,
AppHash: ag.AppHash,
AppState: ag.AppState,
Validators: ag.Consensus.Validators,
ConsensusParams: ag.Consensus.Params,
}, nil
}
// ConsensusGenesis defines the consensus layer's genesis.
// TODO(@julienrbrt)
eventually abstract from CometBFT types
type ConsensusGenesis struct {
Validators []cmttypes.GenesisValidator `json:"validators,omitempty"`
Params *cmttypes.ConsensusParams `json:"params,omitempty"`
}
// NewConsensusGenesis returns a ConsensusGenesis with given values.
// It takes a proto consensus params so it can called from server export command.
func NewConsensusGenesis(params cmtproto.ConsensusParams, validators []cmttypes.GenesisValidator) *ConsensusGenesis {
return &ConsensusGenesis{
Params: &cmttypes.ConsensusParams{
Block: cmttypes.BlockParams{
MaxBytes: params.Block.MaxBytes,
MaxGas: params.Block.MaxGas,
},
Evidence: cmttypes.EvidenceParams{
MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks,
MaxAgeDuration: params.Evidence.MaxAgeDuration,
MaxBytes: params.Evidence.MaxBytes,
},
Validator: cmttypes.ValidatorParams{
PubKeyTypes: params.Validator.PubKeyTypes,
},
},
Validators: validators,
}
}
func (cs *ConsensusGenesis)
MarshalJSON() ([]byte, error) {
type Alias ConsensusGenesis
return cmtjson.Marshal(&Alias{
Validators: cs.Validators,
Params: cs.Params,
})
}
func (cs *ConsensusGenesis)
UnmarshalJSON(b []byte)
error {
type Alias ConsensusGenesis
result := Alias{
}
if err := cmtjson.Unmarshal(b, &result); err != nil {
return err
}
cs.Params = result.Params
cs.Validators = result.Validators
return nil
}
func (cs *ConsensusGenesis)
ValidateAndComplete()
error {
if cs == nil {
return fmt.Errorf("consensus genesis cannot be nil")
}
if cs.Params == nil {
cs.Params = cmttypes.DefaultConsensusParams()
}
else if err := cs.Params.ValidateBasic(); err != nil {
return err
}
for i, v := range cs.Validators {
if v.Power == 0 {
return fmt.Errorf("the genesis file cannot contain validators with no voting power: %v", v)
}
if len(v.Address) > 0 && !bytes.Equal(v.PubKey.Address(), v.Address) {
return fmt.Errorf("incorrect address for validator %v in the genesis file, should be %v", v, v.PubKey.Address())
}
if len(v.Address) == 0 {
cs.Validators[i].Address = v.PubKey.Address()
}
}
return nil
}
package server
import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"runtime/pprof"
"github.com/cometbft/cometbft/abci/server"
cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands"
cmtcfg "github.com/cometbft/cometbft/config"
"github.com/cometbft/cometbft/node"
"github.com/cometbft/cometbft/p2p"
pvm "github.com/cometbft/cometbft/privval"
"github.com/cometbft/cometbft/proxy"
"github.com/cometbft/cometbft/rpc/client/local"
cmttypes "github.com/cometbft/cometbft/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/hashicorp/go-metrics"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pruningtypes "cosmossdk.io/store/pruning/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server/api"
serverconfig "github.com/cosmos/cosmos-sdk/server/config"
servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
servercmtlog "github.com/cosmos/cosmos-sdk/server/log"
"github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/telemetry"
"github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/cosmos/cosmos-sdk/version"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
)
const (
// CometBFT full-node start flags
flagWithComet = "with-comet"
flagAddress = "address"
flagTransport = "transport"
flagTraceStore = "trace-store"
flagCPUProfile = "cpu-profile"
FlagMinGasPrices = "minimum-gas-prices"
FlagQueryGasLimit = "query-gas-limit"
FlagHaltHeight = "halt-height"
FlagHaltTime = "halt-time"
FlagInterBlockCache = "inter-block-cache"
FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades"
FlagTrace = "trace"
FlagInvCheckPeriod = "inv-check-period"
FlagPruning = "pruning"
FlagPruningKeepRecent = "pruning-keep-recent"
FlagPruningInterval = "pruning-interval"
FlagIndexEvents = "index-events"
FlagMinRetainBlocks = "min-retain-blocks"
FlagIAVLCacheSize = "iavl-cache-size"
FlagDisableIAVLFastNode = "iavl-disable-fastnode"
// state sync-related flags
FlagStateSyncSnapshotInterval = "state-sync.snapshot-interval"
FlagStateSyncSnapshotKeepRecent = "state-sync.snapshot-keep-recent"
// api-related flags
FlagAPIEnable = "api.enable"
FlagAPISwagger = "api.swagger"
FlagAPIAddress = "api.address"
FlagAPIMaxOpenConnections = "api.max-open-connections"
FlagRPCReadTimeout = "api.rpc-read-timeout"
FlagRPCWriteTimeout = "api.rpc-write-timeout"
FlagRPCMaxBodyBytes = "api.rpc-max-body-bytes"
FlagAPIEnableUnsafeCORS = "api.enabled-unsafe-cors"
// gRPC-related flags
flagGRPCOnly = "grpc-only"
flagGRPCEnable = "grpc.enable"
flagGRPCAddress = "grpc.address"
flagGRPCWebEnable = "grpc-web.enable"
// mempool flags
FlagMempoolMaxTxs = "mempool.max-txs"
)
// StartCmdOptions defines options that can be customized in `StartCmdWithOptions`,
type StartCmdOptions struct {
// DBOpener can be used to customize db opening, for example customize db options or support different db backends,
// default to the builtin db opener.
DBOpener func(rootDir string, backendType dbm.BackendType) (dbm.DB, error)
// PostSetup can be used to setup extra services under the same cancellable context,
// it's not called in stand-alone mode, only for in-process mode.
PostSetup func(svrCtx *Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group)
error
// AddFlags add custom flags to start cmd
AddFlags func(cmd *cobra.Command)
}
// StartCmd runs the service passed in, either stand-alone or in-process with
// CometBFT.
func StartCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command {
return StartCmdWithOptions(appCreator, defaultNodeHome, StartCmdOptions{
})
}
// StartCmdWithOptions runs the service passed in, either stand-alone or in-process with
// CometBFT.
func StartCmdWithOptions(appCreator types.AppCreator, defaultNodeHome string, opts StartCmdOptions) *cobra.Command {
if opts.DBOpener == nil {
opts.DBOpener = openDB
}
cmd := &cobra.Command{
Use: "start",
Short: "Run the full node",
Long: `Run the full node application with CometBFT in or out of process. By
default, the application will run with CometBFT in process.
Pruning options can be provided via the '--pruning' flag or alternatively with '--pruning-keep-recent', and
'pruning-interval' together.
For '--pruning' the options are as follows:
default: the last 362880 states are kept, pruning at 10 block intervals
nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
everything: 2 latest states will be kept; pruning at 10 block intervals.
custom: allow pruning options to be manually specified through 'pruning-keep-recent', and 'pruning-interval'
Node halting configurations exist in the form of two flags: '--halt-height' and '--halt-time'. During
the ABCI Commit phase, the node will check if the current block height is greater than or equal to
the halt-height or if the current block time is greater than or equal to the halt-time. If so, the
node will attempt to gracefully shutdown and the block will not be committed. In addition, the node
will not be able to commit subsequent blocks.
For profiling and benchmarking purposes, CPU profiling can be enabled via the '--cpu-profile' flag
which accepts a path for the resulting pprof file.
The node may be started in a 'query only' mode where only the gRPC and JSON HTTP
API services are enabled via the 'grpc-only' flag. In this mode, CometBFT is
bypassed and can be used when legacy queries are needed after an on-chain upgrade
is performed. Note, when enabled, gRPC will also be automatically enabled.
`,
PreRunE: func(cmd *cobra.Command, _ []string)
error {
serverCtx := GetServerContextFromCmd(cmd)
// Bind flags to the Context's Viper so the app construction can set
// options accordingly.
if err := serverCtx.Viper.BindPFlags(cmd.Flags()); err != nil {
return err
}
_, err := GetPruningOptionsFromFlags(serverCtx.Viper)
return err
},
RunE: func(cmd *cobra.Command, _ []string)
error {
serverCtx := GetServerContextFromCmd(cmd)
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
withCMT, _ := cmd.Flags().GetBool(flagWithComet)
if !withCMT {
serverCtx.Logger.Info("starting ABCI without CometBFT")
}
return wrapCPUProfile(serverCtx, func()
error {
return start(serverCtx, clientCtx, appCreator, withCMT, opts)
})
},
}
cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
cmd.Flags().Bool(flagWithComet, true, "Run abci app embedded in-process with CometBFT")
cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address")
cmd.Flags().String(flagTransport, "socket", "Transport protocol: socket, grpc")
cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file")
cmd.Flags().String(FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)")
cmd.Flags().Uint64(FlagQueryGasLimit, 0, "Maximum gas a Rest/Grpc query can consume. Blank and 0 imply unbounded.")
cmd.Flags().IntSlice(FlagUnsafeSkipUpgrades, []int{
}, "Skip a set of upgrade heights to continue the old binary")
cmd.Flags().Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node")
cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds)
at which to gracefully halt the chain and shutdown the node")
cmd.Flags().Bool(FlagInterBlockCache, true, "Enable inter-block caching")
cmd.Flags().String(flagCPUProfile, "", "Enable CPU profiling and write to the provided file")
cmd.Flags().Bool(FlagTrace, false, "Provide full stack traces for errors in ABCI Log")
cmd.Flags().String(FlagPruning, pruningtypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)")
cmd.Flags().Uint64(FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')")
cmd.Flags().Uint64(FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')")
cmd.Flags().Uint(FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks")
cmd.Flags().Uint64(FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune CometBFT blocks")
cmd.Flags().Bool(FlagAPIEnable, false, "Define if the API server should be enabled")
cmd.Flags().Bool(FlagAPISwagger, false, "Define if swagger documentation should automatically be registered (Note: the API must also be enabled)")
cmd.Flags().String(FlagAPIAddress, serverconfig.DefaultAPIAddress, "the API server address to listen on")
cmd.Flags().Uint(FlagAPIMaxOpenConnections, 1000, "Define the number of maximum open connections")
cmd.Flags().Uint(FlagRPCReadTimeout, 10, "Define the CometBFT RPC read timeout (in seconds)")
cmd.Flags().Uint(FlagRPCWriteTimeout, 0, "Define the CometBFT RPC write timeout (in seconds)")
cmd.Flags().Uint(FlagRPCMaxBodyBytes, 1000000, "Define the CometBFT maximum request body (in bytes)")
cmd.Flags().Bool(FlagAPIEnableUnsafeCORS, false, "Define if CORS should be enabled (unsafe - use it at your own risk)")
cmd.Flags().Bool(flagGRPCOnly, false, "Start the node in gRPC query only mode (no CometBFT process is started)")
cmd.Flags().Bool(flagGRPCEnable, true, "Define if the gRPC server should be enabled")
cmd.Flags().String(flagGRPCAddress, serverconfig.DefaultGRPCAddress, "the gRPC server address to listen on")
cmd.Flags().Bool(flagGRPCWebEnable, true, "Define if the gRPC-Web server should be enabled. (Note: gRPC must also be enabled)")
cmd.Flags().Uint64(FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval")
cmd.Flags().Uint32(FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep")
cmd.Flags().Bool(FlagDisableIAVLFastNode, false, "Disable fast node for IAVL tree")
cmd.Flags().Int(FlagMempoolMaxTxs, mempool.DefaultMaxTx, "Sets MaxTx value for the app-side mempool")
// support old flags name for backwards compatibility
cmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string)
pflag.NormalizedName {
if name == "with-tendermint" {
name = flagWithComet
}
return pflag.NormalizedName(name)
})
// add support for all CometBFT-specific command line options
cmtcmd.AddNodeFlags(cmd)
if opts.AddFlags != nil {
opts.AddFlags(cmd)
}
return cmd
}
func start(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreator, withCmt bool, opts StartCmdOptions)
error {
svrCfg, err := getAndValidateConfig(svrCtx)
if err != nil {
return err
}
app, appCleanupFn, err := startApp(svrCtx, appCreator, opts)
if err != nil {
return err
}
defer appCleanupFn()
metrics, err := startTelemetry(svrCfg)
if err != nil {
return err
}
emitServerInfoMetrics()
if !withCmt {
return startStandAlone(svrCtx, app, opts)
}
return startInProcess(svrCtx, svrCfg, clientCtx, app, metrics, opts)
}
func startStandAlone(svrCtx *Context, app types.Application, opts StartCmdOptions)
error {
addr := svrCtx.Viper.GetString(flagAddress)
transport := svrCtx.Viper.GetString(flagTransport)
cmtApp := NewCometABCIWrapper(app)
svr, err := server.NewServer(addr, transport, cmtApp)
if err != nil {
return fmt.Errorf("error creating listener: %v", err)
}
svr.SetLogger(servercmtlog.CometLoggerWrapper{
Logger: svrCtx.Logger.With("module", "abci-server")
})
g, ctx := getCtx(svrCtx, false)
g.Go(func()
error {
if err := svr.Start(); err != nil {
svrCtx.Logger.Error("failed to start out-of-process ABCI server", "err", err)
return err
}
// Wait for the calling process to be canceled or close the provided context,
// so we can gracefully stop the ABCI server.
<-ctx.Done()
svrCtx.Logger.Info("stopping the ABCI server...")
return errors.Join(svr.Stop(), app.Close())
})
return g.Wait()
}
func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application,
metrics *telemetry.Metrics, opts StartCmdOptions,
)
error {
cmtCfg := svrCtx.Config
home := cmtCfg.RootDir
gRPCOnly := svrCtx.Viper.GetBool(flagGRPCOnly)
g, ctx := getCtx(svrCtx, true)
if gRPCOnly {
// TODO: Generalize logic so that gRPC only is really in startStandAlone
svrCtx.Logger.Info("starting node in gRPC only mode; CometBFT is disabled")
svrCfg.GRPC.Enable = true
}
else {
svrCtx.Logger.Info("starting node with ABCI CometBFT in-process")
tmNode, cleanupFn, err := startCmtNode(ctx, cmtCfg, app, svrCtx)
if err != nil {
return err
}
defer cleanupFn()
// Add the tx service to the gRPC router. We only need to register this
// service if API or gRPC is enabled, and avoid doing so in the general
// case, because it spawns a new local CometBFT RPC client.
if svrCfg.API.Enable || svrCfg.GRPC.Enable {
// Re-assign for making the client available below do not use := to avoid
// shadowing the clientCtx variable.
clientCtx = clientCtx.WithClient(local.New(tmNode))
app.RegisterTxService(clientCtx)
app.RegisterTendermintService(clientCtx)
app.RegisterNodeService(clientCtx, svrCfg)
}
}
grpcSrv, clientCtx, err := startGrpcServer(ctx, g, svrCfg.GRPC, clientCtx, svrCtx, app)
if err != nil {
return err
}
err = startAPIServer(ctx, g, cmtCfg, svrCfg, clientCtx, svrCtx, app, home, grpcSrv, metrics)
if err != nil {
return err
}
if opts.PostSetup != nil {
if err := opts.PostSetup(svrCtx, clientCtx, ctx, g); err != nil {
return err
}
}
// wait for signal capture and gracefully return
// we are guaranteed to be waiting for the "ListenForQuitSignals" goroutine.
return g.Wait()
}
// TODO: Move nodeKey into being created within the function.
func startCmtNode(
ctx context.Context,
cfg *cmtcfg.Config,
app types.Application,
svrCtx *Context,
) (tmNode *node.Node, cleanupFn func(), err error) {
nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
if err != nil {
return nil, cleanupFn, err
}
cmtApp := NewCometABCIWrapper(app)
tmNode, err = node.NewNodeWithContext(
ctx,
cfg,
pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()),
nodeKey,
proxy.NewLocalClientCreator(cmtApp),
getGenDocProvider(cfg),
cmtcfg.DefaultDBProvider,
node.DefaultMetricsProvider(cfg.Instrumentation),
servercmtlog.CometLoggerWrapper{
Logger: svrCtx.Logger
},
)
if err != nil {
return tmNode, cleanupFn, err
}
if err := tmNode.Start(); err != nil {
return tmNode, cleanupFn, err
}
cleanupFn = func() {
if tmNode != nil && tmNode.IsRunning() {
_ = tmNode.Stop()
_ = app.Close()
}
}
return tmNode, cleanupFn, nil
}
func getAndValidateConfig(svrCtx *Context) (serverconfig.Config, error) {
config, err := serverconfig.GetConfig(svrCtx.Viper)
if err != nil {
return config, err
}
if err := config.ValidateBasic(); err != nil {
return config, err
}
return config, nil
}
// returns a function which returns the genesis doc from the genesis file.
func getGenDocProvider(cfg *cmtcfg.Config)
func() (*cmttypes.GenesisDoc, error) {
return func() (*cmttypes.GenesisDoc, error) {
appGenesis, err := genutiltypes.AppGenesisFromFile(cfg.GenesisFile())
if err != nil {
return nil, err
}
return appGenesis.ToGenesisDoc()
}
}
func setupTraceWriter(svrCtx *Context) (traceWriter io.WriteCloser, cleanup func(), err error) {
// clean up the traceWriter when the server is shutting down
cleanup = func() {
}
traceWriterFile := svrCtx.Viper.GetString(flagTraceStore)
traceWriter, err = openTraceWriter(traceWriterFile)
if err != nil {
return traceWriter, cleanup, err
}
// if flagTraceStore is not used then traceWriter is nil
if traceWriter != nil {
cleanup = func() {
if err = traceWriter.Close(); err != nil {
svrCtx.Logger.Error("failed to close trace writer", "err", err)
}
}
}
return traceWriter, cleanup, nil
}
func startGrpcServer(
ctx context.Context,
g *errgroup.Group,
config serverconfig.GRPCConfig,
clientCtx client.Context,
svrCtx *Context,
app types.Application,
) (*grpc.Server, client.Context, error) {
if !config.Enable {
// return grpcServer as nil if gRPC is disabled
return nil, clientCtx, nil
}
_, port, err := net.SplitHostPort(config.Address)
if err != nil {
return nil, clientCtx, err
}
maxSendMsgSize := config.MaxSendMsgSize
if maxSendMsgSize == 0 {
maxSendMsgSize = serverconfig.DefaultGRPCMaxSendMsgSize
}
maxRecvMsgSize := config.MaxRecvMsgSize
if maxRecvMsgSize == 0 {
maxRecvMsgSize = serverconfig.DefaultGRPCMaxRecvMsgSize
}
grpcAddress := fmt.Sprintf("127.0.0.1:%s", port)
// if gRPC is enabled, configure gRPC client for gRPC gateway
grpcClient, err := grpc.Dial(
grpcAddress,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultCallOptions(
grpc.ForceCodec(codec.NewProtoCodec(clientCtx.InterfaceRegistry).GRPCCodec()),
grpc.MaxCallRecvMsgSize(maxRecvMsgSize),
grpc.MaxCallSendMsgSize(maxSendMsgSize),
),
)
if err != nil {
return nil, clientCtx, err
}
clientCtx = clientCtx.WithGRPCClient(grpcClient)
svrCtx.Logger.Debug("gRPC client assigned to client context", "target", grpcAddress)
grpcSrv, err := servergrpc.NewGRPCServer(clientCtx, app, config)
if err != nil {
return nil, clientCtx, err
}
// Start the gRPC server in a goroutine. Note, the provided ctx will ensure
// that the server is gracefully shut down.
g.Go(func()
error {
return servergrpc.StartGRPCServer(ctx, svrCtx.Logger.With("module", "grpc-server"), config, grpcSrv)
})
return grpcSrv, clientCtx, nil
}
func startAPIServer(
ctx context.Context,
g *errgroup.Group,
cmtCfg *cmtcfg.Config,
svrCfg serverconfig.Config,
clientCtx client.Context,
svrCtx *Context,
app types.Application,
home string,
grpcSrv *grpc.Server,
metrics *telemetry.Metrics,
)
error {
if !svrCfg.API.Enable {
return nil
}
clientCtx = clientCtx.WithHomeDir(home)
apiSrv := api.New(clientCtx, svrCtx.Logger.With("module", "api-server"), grpcSrv)
app.RegisterAPIRoutes(apiSrv, svrCfg.API)
if svrCfg.Telemetry.Enabled {
apiSrv.SetTelemetry(metrics)
}
g.Go(func()
error {
return apiSrv.Start(ctx, svrCfg)
})
return nil
}
func startTelemetry(cfg serverconfig.Config) (*telemetry.Metrics, error) {
if !cfg.Telemetry.Enabled {
return nil, nil
}
return telemetry.New(cfg.Telemetry)
}
// wrapCPUProfile starts CPU profiling, if enabled, and executes the provided
// callbackFn in a separate goroutine, then will wait for that callback to
// return.
//
// NOTE: We expect the caller to handle graceful shutdown and signal handling.
func wrapCPUProfile(svrCtx *Context, callbackFn func()
error)
error {
if cpuProfile := svrCtx.Viper.GetString(flagCPUProfile); cpuProfile != "" {
f, err := os.Create(cpuProfile)
if err != nil {
return err
}
svrCtx.Logger.Info("starting CPU profiler", "profile", cpuProfile)
if err := pprof.StartCPUProfile(f); err != nil {
return err
}
defer func() {
svrCtx.Logger.Info("stopping CPU profiler", "profile", cpuProfile)
pprof.StopCPUProfile()
if err := f.Close(); err != nil {
svrCtx.Logger.Info("failed to close cpu-profile file", "profile", cpuProfile, "err", err.Error())
}
}()
}
return callbackFn()
}
// emitServerInfoMetrics emits server info related metrics using application telemetry.
func emitServerInfoMetrics() {
var ls []metrics.Label
versionInfo := version.NewInfo()
if len(versionInfo.GoVersion) > 0 {
ls = append(ls, telemetry.NewLabel("go", versionInfo.GoVersion))
}
if len(versionInfo.CosmosSdkVersion) > 0 {
ls = append(ls, telemetry.NewLabel("version", versionInfo.CosmosSdkVersion))
}
if len(ls) == 0 {
return
}
telemetry.SetGaugeWithLabels([]string{"server", "info"
}, 1, ls)
}
func getCtx(svrCtx *Context, block bool) (*errgroup.Group, context.Context) {
ctx, cancelFn := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
// listen for quit signals so the calling parent process can gracefully exit
ListenForQuitSignals(g, block, cancelFn, svrCtx.Logger)
return g, ctx
}
func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions) (app types.Application, cleanupFn func(), err error) {
traceWriter, traceCleanupFn, err := setupTraceWriter(svrCtx)
if err != nil {
return app, traceCleanupFn, err
}
home := svrCtx.Config.RootDir
db, err := opts.DBOpener(home, GetAppDBBackend(svrCtx.Viper))
if err != nil {
return app, traceCleanupFn, err
}
app = appCreator(svrCtx.Logger, db, traceWriter, svrCtx.Viper)
cleanupFn = func() {
traceCleanupFn()
if localErr := app.Close(); localErr != nil {
svrCtx.Logger.Error(localErr.Error())
}
}
return app, cleanupFn, nil
}
Client
CLI
The genutil commands are available under thegenesis subcommand.
add-genesis-account
Add a genesis account togenesis.json. Learn more here.
collect-gentxs
Collect genesis txs and output agenesis.json file.
simd genesis collect-gentxs
genesis.json file that includes data from all the validators (we sometimes call it the “super genesis file” to distinguish it from single-validator genesis files).
gentx
Generate a genesis tx carrying a self delegation.simd genesis gentx [key_name] [amount] --chain-id [chain-id]
amount should be at least 1000000000stake.
If you provide too much or too little, you will encounter an error when starting a node.
migrate
Migrate genesis to a specified target (SDK) version.simd genesis migrate [target-version]
The
migrate command is extensible and takes a MigrationMap. This map is a mapping of target versions to genesis migrations functions.
When not using the default MigrationMap, it is recommended to still call the default MigrationMap corresponding the SDK version of the chain and prepend/append your own genesis migrations.validate-genesis
Validates the genesis file at the default location or at the location passed as an argument.simd genesis validate-genesis
Validate genesis only validates if the genesis is valid at the current application binary. For validating a genesis from a previous version of the application, use the
migrate command to migrate the genesis to the current version.