387 lines
11 KiB
Go
387 lines
11 KiB
Go
package cometbft
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
abciserver "github.com/cometbft/cometbft/abci/server"
|
|
abci "github.com/cometbft/cometbft/abci/types"
|
|
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"
|
|
gogoproto "github.com/cosmos/gogoproto/proto"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
"google.golang.org/grpc"
|
|
|
|
appmodulev2 "cosmossdk.io/core/appmodule/v2"
|
|
"cosmossdk.io/core/server"
|
|
"cosmossdk.io/core/transaction"
|
|
"cosmossdk.io/log"
|
|
"cosmossdk.io/schema/appdata"
|
|
"cosmossdk.io/schema/decoding"
|
|
"cosmossdk.io/schema/indexer"
|
|
serverv2 "cosmossdk.io/server/v2"
|
|
"cosmossdk.io/server/v2/appmanager"
|
|
cometlog "cosmossdk.io/server/v2/cometbft/log"
|
|
"cosmossdk.io/server/v2/cometbft/mempool"
|
|
"cosmossdk.io/server/v2/cometbft/oe"
|
|
"cosmossdk.io/server/v2/cometbft/types"
|
|
"cosmossdk.io/store/v2/snapshots"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
|
)
|
|
|
|
const ServerName = "comet"
|
|
|
|
var (
|
|
_ serverv2.ServerComponent[transaction.Tx] = (*CometBFTServer[transaction.Tx])(nil)
|
|
_ serverv2.HasCLICommands = (*CometBFTServer[transaction.Tx])(nil)
|
|
_ serverv2.HasStartFlags = (*CometBFTServer[transaction.Tx])(nil)
|
|
)
|
|
|
|
type CometBFTServer[T transaction.Tx] struct {
|
|
Node *node.Node
|
|
Consensus abci.Application
|
|
|
|
logger log.Logger
|
|
serverOptions ServerOptions[T]
|
|
config Config
|
|
cfgOptions []CfgOption
|
|
|
|
app appmanager.AppManager[T]
|
|
txCodec transaction.Codec[T]
|
|
store types.Store
|
|
}
|
|
|
|
func New[T transaction.Tx](
|
|
logger log.Logger,
|
|
appName string,
|
|
store types.Store,
|
|
app appmanager.AppManager[T],
|
|
appCodec codec.Codec,
|
|
txCodec transaction.Codec[T],
|
|
queryHandlers map[string]appmodulev2.Handler,
|
|
decoderResolver decoding.DecoderResolver,
|
|
serverOptions ServerOptions[T],
|
|
cfg server.ConfigMap,
|
|
cfgOptions ...CfgOption,
|
|
) (*CometBFTServer[T], error) {
|
|
srv := &CometBFTServer[T]{
|
|
serverOptions: serverOptions,
|
|
cfgOptions: cfgOptions,
|
|
app: app,
|
|
txCodec: txCodec,
|
|
store: store,
|
|
}
|
|
srv.logger = logger.With(log.ModuleKey, srv.Name())
|
|
|
|
home, _ := cfg[serverv2.FlagHome].(string)
|
|
|
|
// get configs (app.toml + config.toml) from viper
|
|
appTomlConfig := srv.Config().(*AppTomlConfig)
|
|
configTomlConfig := cmtcfg.DefaultConfig().SetRoot(home)
|
|
if len(cfg) > 0 {
|
|
if err := serverv2.UnmarshalSubConfig(cfg, srv.Name(), &appTomlConfig); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
|
}
|
|
|
|
if err := serverv2.UnmarshalSubConfig(cfg, "", &configTomlConfig); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
|
}
|
|
}
|
|
|
|
srv.config = Config{
|
|
ConfigTomlConfig: configTomlConfig,
|
|
AppTomlConfig: appTomlConfig,
|
|
}
|
|
|
|
chainID, _ := cfg[FlagChainID].(string)
|
|
if chainID == "" {
|
|
// fallback to genesis chain-id
|
|
reader, err := os.Open(srv.config.ConfigTomlConfig.GenesisFile())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open genesis file: %w", err)
|
|
}
|
|
defer reader.Close()
|
|
|
|
chainID, err = genutiltypes.ParseChainIDFromGenesis(reader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse chain-id from genesis file: %w", err)
|
|
}
|
|
}
|
|
|
|
indexedABCIEvents := make(map[string]struct{}, len(srv.config.AppTomlConfig.IndexABCIEvents))
|
|
for _, e := range srv.config.AppTomlConfig.IndexABCIEvents {
|
|
indexedABCIEvents[e] = struct{}{}
|
|
}
|
|
|
|
sc := store.GetStateCommitment().(snapshots.CommitSnapshotter)
|
|
|
|
snapshotStore, err := GetSnapshotStore(srv.config.ConfigTomlConfig.RootDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// initialize the indexer
|
|
var listener *appdata.Listener
|
|
if indexerCfg := srv.config.AppTomlConfig.Indexer; len(indexerCfg.Target) > 0 {
|
|
indexingTarget, err := indexer.StartIndexing(indexer.IndexingOptions{
|
|
Config: indexerCfg,
|
|
Resolver: decoderResolver,
|
|
Logger: logger.With(log.ModuleKey, "indexer"),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to start indexing: %w", err)
|
|
}
|
|
|
|
listener = &indexingTarget.Listener
|
|
}
|
|
|
|
// snapshot manager
|
|
snapshotManager := snapshots.NewManager(
|
|
snapshotStore,
|
|
srv.serverOptions.SnapshotOptions(cfg),
|
|
sc,
|
|
nil, // extensions snapshotter registered below
|
|
logger,
|
|
)
|
|
if exts := serverOptions.SnapshotExtensions; len(exts) > 0 {
|
|
if err := snapshotManager.RegisterExtensions(serverOptions.SnapshotExtensions...); err != nil {
|
|
return nil, fmt.Errorf("failed to register snapshot extensions: %w", err)
|
|
}
|
|
}
|
|
|
|
c := &consensus[T]{
|
|
appName: appName,
|
|
version: getCometBFTServerVersion(),
|
|
app: app,
|
|
cfg: srv.config,
|
|
store: store,
|
|
logger: logger,
|
|
txCodec: txCodec,
|
|
appCodec: appCodec,
|
|
listener: listener,
|
|
snapshotManager: snapshotManager,
|
|
streamingManager: srv.serverOptions.StreamingManager,
|
|
mempool: srv.serverOptions.Mempool(cfg),
|
|
lastCommittedHeight: atomic.Int64{},
|
|
prepareProposalHandler: srv.serverOptions.PrepareProposalHandler,
|
|
processProposalHandler: srv.serverOptions.ProcessProposalHandler,
|
|
verifyVoteExt: srv.serverOptions.VerifyVoteExtensionHandler,
|
|
checkTxHandler: srv.serverOptions.CheckTxHandler,
|
|
extendVote: srv.serverOptions.ExtendVoteHandler,
|
|
chainID: chainID,
|
|
indexedABCIEvents: indexedABCIEvents,
|
|
initialHeight: 0,
|
|
queryHandlersMap: queryHandlers,
|
|
getProtoRegistry: sync.OnceValues(gogoproto.MergedRegistry),
|
|
addrPeerFilter: srv.serverOptions.AddrPeerFilter,
|
|
idPeerFilter: srv.serverOptions.IdPeerFilter,
|
|
}
|
|
|
|
c.optimisticExec = oe.NewOptimisticExecution(
|
|
logger,
|
|
c.internalFinalizeBlock,
|
|
)
|
|
|
|
srv.Consensus = c
|
|
|
|
return srv, nil
|
|
}
|
|
|
|
// NewWithConfigOptions creates a new CometBFT server with the provided config options.
|
|
// It is *not* a fully functional server (since it has been created without dependencies)
|
|
// The returned server should only be used to get and set configuration.
|
|
func NewWithConfigOptions[T transaction.Tx](opts ...CfgOption) *CometBFTServer[T] {
|
|
return &CometBFTServer[T]{
|
|
cfgOptions: opts,
|
|
}
|
|
}
|
|
|
|
func (s *CometBFTServer[T]) Name() string {
|
|
return ServerName
|
|
}
|
|
|
|
func (s *CometBFTServer[T]) Start(ctx context.Context) error {
|
|
wrappedLogger := cometlog.CometLoggerWrapper{Logger: s.logger}
|
|
if s.config.AppTomlConfig.Standalone {
|
|
svr, err := abciserver.NewServer(s.config.AppTomlConfig.Address, s.config.AppTomlConfig.Transport, s.Consensus)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating listener: %w", err)
|
|
}
|
|
|
|
svr.SetLogger(wrappedLogger)
|
|
|
|
return svr.Start()
|
|
}
|
|
|
|
nodeKey, err := p2p.LoadOrGenNodeKey(s.config.ConfigTomlConfig.NodeKeyFile())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pv, err := pvm.LoadOrGenFilePV(
|
|
s.config.ConfigTomlConfig.PrivValidatorKeyFile(),
|
|
s.config.ConfigTomlConfig.PrivValidatorStateFile(),
|
|
s.serverOptions.KeygenF,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.Node, err = node.NewNode(
|
|
ctx,
|
|
s.config.ConfigTomlConfig,
|
|
pv,
|
|
nodeKey,
|
|
proxy.NewConsensusSyncLocalClientCreator(s.Consensus),
|
|
getGenDocProvider(s.config.ConfigTomlConfig),
|
|
cmtcfg.DefaultDBProvider,
|
|
node.DefaultMetricsProvider(s.config.ConfigTomlConfig.Instrumentation),
|
|
wrappedLogger,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.logger.Info("starting consensus server")
|
|
return s.Node.Start()
|
|
}
|
|
|
|
func (s *CometBFTServer[T]) Stop(context.Context) error {
|
|
if s.Node != nil && s.Node.IsRunning() {
|
|
s.logger.Info("stopping consensus server")
|
|
return s.Node.Stop()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// returns a function which returns the genesis doc from the genesis file.
|
|
func getGenDocProvider(cfg *cmtcfg.Config) func() (node.ChecksummedGenesisDoc, error) {
|
|
return func() (node.ChecksummedGenesisDoc, error) {
|
|
appGenesis, err := genutiltypes.AppGenesisFromFile(cfg.GenesisFile())
|
|
if err != nil {
|
|
return node.ChecksummedGenesisDoc{
|
|
Sha256Checksum: []byte{},
|
|
}, err
|
|
}
|
|
|
|
gen, err := appGenesis.ToGenesisDoc()
|
|
if err != nil {
|
|
return node.ChecksummedGenesisDoc{
|
|
Sha256Checksum: []byte{},
|
|
}, err
|
|
}
|
|
genbz, err := gen.AppState.MarshalJSON()
|
|
if err != nil {
|
|
return node.ChecksummedGenesisDoc{
|
|
Sha256Checksum: []byte{},
|
|
}, err
|
|
}
|
|
|
|
bz, err := json.Marshal(genbz)
|
|
if err != nil {
|
|
return node.ChecksummedGenesisDoc{
|
|
Sha256Checksum: []byte{},
|
|
}, err
|
|
}
|
|
sum := sha256.Sum256(bz)
|
|
|
|
return node.ChecksummedGenesisDoc{
|
|
GenesisDoc: gen,
|
|
Sha256Checksum: sum[:],
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func (s *CometBFTServer[T]) StartCmdFlags() *pflag.FlagSet {
|
|
flags := pflag.NewFlagSet(s.Name(), pflag.ExitOnError)
|
|
|
|
flags.String(FlagAddress, "tcp://127.0.0.1:26658", "Listen address")
|
|
flags.String(FlagTransport, "socket", "Transport protocol: socket, grpc")
|
|
flags.Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node")
|
|
flags.Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node")
|
|
flags.Bool(FlagTrace, false, "Provide full stack traces for errors in ABCI Log")
|
|
flags.Bool(Standalone, false, "Run app without CometBFT")
|
|
flags.Int(FlagMempoolMaxTxs, mempool.DefaultMaxTx, "Sets MaxTx value for the app-side mempool")
|
|
|
|
// add comet flags, we use an empty command to avoid duplicating CometBFT's AddNodeFlags.
|
|
// we can then merge the flag sets.
|
|
emptyCmd := &cobra.Command{}
|
|
cmtcmd.AddNodeFlags(emptyCmd)
|
|
flags.AddFlagSet(emptyCmd.Flags())
|
|
|
|
return flags
|
|
}
|
|
|
|
func (s *CometBFTServer[T]) CLICommands() serverv2.CLIConfig {
|
|
return serverv2.CLIConfig{
|
|
Commands: []*cobra.Command{
|
|
StatusCommand(),
|
|
ShowNodeIDCmd(),
|
|
ShowValidatorCmd(),
|
|
ShowAddressCmd(),
|
|
VersionCmd(),
|
|
s.BootstrapStateCmd(),
|
|
cmtcmd.ResetAllCmd,
|
|
cmtcmd.ResetStateCmd,
|
|
},
|
|
Queries: []*cobra.Command{
|
|
QueryBlockCmd(),
|
|
QueryBlocksCmd(),
|
|
QueryBlockResultsCmd(),
|
|
},
|
|
}
|
|
}
|
|
|
|
// CometBFT is a special server, it has config in config.toml and app.toml
|
|
|
|
// Config returns the (app.toml) server configuration.
|
|
func (s *CometBFTServer[T]) Config() any {
|
|
if s.config.AppTomlConfig == nil || s.config.AppTomlConfig.Address == "" {
|
|
cfg := &Config{AppTomlConfig: DefaultAppTomlConfig()}
|
|
// overwrite the default config with the provided options
|
|
for _, opt := range s.cfgOptions {
|
|
opt(cfg)
|
|
}
|
|
|
|
return cfg.AppTomlConfig
|
|
}
|
|
|
|
return s.config.AppTomlConfig
|
|
}
|
|
|
|
// WriteCustomConfigAt writes the default cometbft config.toml
|
|
func (s *CometBFTServer[T]) WriteCustomConfigAt(configPath string) error {
|
|
cfg := &Config{ConfigTomlConfig: cmtcfg.DefaultConfig()}
|
|
for _, opt := range s.cfgOptions {
|
|
opt(cfg)
|
|
}
|
|
|
|
cmtcfg.WriteConfigFile(filepath.Join(configPath, "config.toml"), cfg.ConfigTomlConfig)
|
|
return nil
|
|
}
|
|
|
|
// gRPCServiceRegistrar returns a function that registers the CometBFT gRPC service
|
|
// Those services are defined for backward compatibility.
|
|
// Eventually, they will be removed in favor of the new gRPC services.
|
|
func (s *CometBFTServer[T]) GRPCServiceRegistrar(
|
|
clientCtx client.Context,
|
|
cfg server.ConfigMap,
|
|
) func(srv *grpc.Server) error {
|
|
return gRPCServiceRegistrar[T](clientCtx, cfg, s.Config().(*AppTomlConfig), s.txCodec, s.Consensus, s.app)
|
|
}
|